fix: 保留 registry bcrypt 字面量
This commit is contained in:
@@ -368,6 +368,8 @@ bun scripts/cli.ts hwlab nodes control-plane legacy-cicd --help
|
||||
- 公网 HTTPS 只允许鉴权 `GET`/`HEAD`,不得成为 push、cache export 或删除入口;
|
||||
- worker 保持既有镜像引用,由集群 owning YAML 渲染 endpoint 与 SecretRef;
|
||||
- registry 大流量不得经过 WireGuard/Flannel,也不得使用 CDN 或公共镜像站;
|
||||
- bcrypt hash 必须以 Compose-safe 字面量写入 Secret source,且
|
||||
public-edge 用脱敏 material fingerprint 驱动凭据变更后的 recreate;
|
||||
- 验收统一走 `platform-infra k3s-cluster registry-smoke`,不手工 patch Caddy 或 containerd。
|
||||
- worker 安装使用短启动并由 control-plane Node Ready 轮询收口,禁止让 `trans`/SSH 长挂等待 systemd notify。
|
||||
- worker 加入集群不改变 PaC consumer 的 source commit authority,也不授权人工 PipelineRun、Argo sync 或业务 rollout。
|
||||
|
||||
@@ -201,6 +201,9 @@
|
||||
- hostname、DNS、upstream、允许方法和鉴权 SecretRef 来自集群 owning YAML;
|
||||
- Caddy 只允许 `GET`、`HEAD`,其余方法返回拒绝;
|
||||
- Basic Auth 只消费 SecretRef 中的用户名与 bcrypt hash,不把值写入 Git、计划输出或 Caddyfile;
|
||||
- bcrypt hash 在 Compose `env_file` 中必须使用单引号字面量,防止 `$`
|
||||
被 Compose 插值;compose 只携带 Secret material 的 SHA-256 标记,
|
||||
使规范化或轮换触发受控 recreate,禁止手工重启容器;
|
||||
- 可信 TLS 由 public-edge 管理,镜像层不得经过 CDN 代理。
|
||||
- worker containerd 归集群 CLI 管理:
|
||||
- 既有 `127.0.0.1:5000/...` 镜像引用保持不变;
|
||||
|
||||
@@ -129,18 +129,34 @@ function registryCredentialInit(target: K3sClusterTarget, confirm: boolean): Rec
|
||||
throw new Error(`${target.secrets.sourceRef} contains a partial legacy artifact registry credential; refusing to migrate`);
|
||||
}
|
||||
if (present.length === keys.length) {
|
||||
if (!confirm || legacyPresent.length === 0) {
|
||||
const source = readFileSync(path, "utf8");
|
||||
const normalizedSource = normalizeRegistryCredentialSource(source, auth.passwordHashKey, values[auth.passwordHashKey]!);
|
||||
const normalizationNeeded = normalizedSource !== source;
|
||||
if (!confirm || (!normalizationNeeded && legacyPresent.length === 0)) {
|
||||
return {
|
||||
ok: true,
|
||||
mutation: false,
|
||||
mode: legacyPresent.length === 0 ? "already-present" : "dry-run-cleanup-legacy-source",
|
||||
mode: normalizationNeeded
|
||||
? "dry-run-normalize-compose-literal"
|
||||
: legacyPresent.length === 0 ? "already-present" : "dry-run-cleanup-legacy-source",
|
||||
targetId: target.id,
|
||||
registryAuth: registryAuthSummary(target),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
removeEnvKeys(target.secrets.sourceRef, clusterSource, keys);
|
||||
return { ok: true, mutation: true, mode: "cleaned-legacy-source", targetId: target.id, registryAuth: registryAuthSummary(target), valuesPrinted: false };
|
||||
if (normalizationNeeded) {
|
||||
writeFileSync(path, normalizedSource, { encoding: "utf8", mode: 0o600 });
|
||||
chmodSync(path, 0o600);
|
||||
}
|
||||
if (legacyPresent.length === keys.length) removeEnvKeys(target.secrets.sourceRef, clusterSource, keys);
|
||||
return {
|
||||
ok: true,
|
||||
mutation: true,
|
||||
mode: normalizationNeeded ? "normalized-compose-literal" : "cleaned-legacy-source",
|
||||
targetId: target.id,
|
||||
registryAuth: registryAuthSummary(target),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
if (present.length > 0) throw new Error(`${path} contains a partial artifact registry credential; refusing to overwrite`);
|
||||
if (!confirm) {
|
||||
@@ -157,7 +173,7 @@ function registryCredentialInit(target: K3sClusterTarget, confirm: boolean): Rec
|
||||
const content = [
|
||||
`${auth.usernameKey}=${credential.username}`,
|
||||
`${auth.passwordKey}=${credential.password}`,
|
||||
`${auth.passwordHashKey}=${credential.passwordHash}`,
|
||||
`${auth.passwordHashKey}='${credential.passwordHash}'`,
|
||||
"",
|
||||
].join("\n");
|
||||
if (existsSync(path)) appendFileSync(path, content, { encoding: "utf8", mode: 0o600 });
|
||||
@@ -167,6 +183,15 @@ function registryCredentialInit(target: K3sClusterTarget, confirm: boolean): Rec
|
||||
return { ok: true, mutation: true, targetId: target.id, registryAuth: registryAuthSummary(target), valuesPrinted: false };
|
||||
}
|
||||
|
||||
function normalizeRegistryCredentialSource(source: string, passwordHashKey: string, passwordHash: string): string {
|
||||
const lines = source.split(/\r?\n/u).map((line) => {
|
||||
const match = /^([A-Za-z_][A-Za-z0-9_]*)=/u.exec(line);
|
||||
return match?.[1] === passwordHashKey ? `${passwordHashKey}='${passwordHash}'` : line;
|
||||
});
|
||||
while (lines.at(-1) === "") lines.pop();
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
function removeEnvKeys(path: string, source: string, keys: string[]): void {
|
||||
const selected = new Set(keys);
|
||||
const retained = source.split(/\r?\n/u).filter((line) => {
|
||||
|
||||
@@ -707,7 +707,13 @@ export function renderPublicEdgeArtifacts(target: PublicEdgeTarget, sites: Resol
|
||||
const dnsServers = target.runtime.dnsServers.map((server) => ` - ${server}`).join("\n");
|
||||
const envFiles = [...new Set(sites.flatMap((site) => site.accessPolicy === undefined ? [] : [site.accessPolicy.basicAuth.sourceRef]))];
|
||||
const envFileBlock = envFiles.length === 0 ? "" : ` env_file:\n${envFiles.map((path) => ` - ${path}`).join("\n")}\n`;
|
||||
const compose = `services:\n caddy:\n image: ${target.runtime.image}\n container_name: ${target.runtime.containerName}\n network_mode: host\n restart: unless-stopped\n${envFileBlock} dns:\n${dnsServers}\n volumes:\n - ${target.runtime.caddyfilePath}:/etc/caddy/Caddyfile:ro\n - ${target.runtime.dataDir}:/data\n - ${target.runtime.configDir}:/config\n`;
|
||||
const authMaterialFingerprint = envFiles.length === 0
|
||||
? null
|
||||
: sha256Fingerprint(envFiles.map((path) => `${path}\n${readFileSync(path, "utf8")}`).join("\n"));
|
||||
const authFingerprintBlock = authMaterialFingerprint === null
|
||||
? ""
|
||||
: ` environment:\n UNIDESK_PUBLIC_EDGE_AUTH_MATERIAL_FINGERPRINT: ${authMaterialFingerprint}\n`;
|
||||
const compose = `services:\n caddy:\n image: ${target.runtime.image}\n container_name: ${target.runtime.containerName}\n network_mode: host\n restart: unless-stopped\n${envFileBlock}${authFingerprintBlock} dns:\n${dnsServers}\n volumes:\n - ${target.runtime.caddyfilePath}:/etc/caddy/Caddyfile:ro\n - ${target.runtime.dataDir}:/data\n - ${target.runtime.configDir}:/config\n`;
|
||||
return { caddyfile: `${global}\n${blocks}`, compose };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user