From 1a82826757e9593cafc9432e0be816adfa10c088 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 14 Jun 2026 02:42:01 +0000 Subject: [PATCH] refactor: consolidate sub2api secret material helpers --- scripts/src/platform-infra.ts | 115 +++++++++------------------------- 1 file changed, 29 insertions(+), 86 deletions(-) diff --git a/scripts/src/platform-infra.ts b/scripts/src/platform-infra.ts index 78de7688..fc7eff63 100644 --- a/scripts/src/platform-infra.ts +++ b/scripts/src/platform-infra.ts @@ -6,6 +6,8 @@ import { rootPath } from "./config"; import { startJob } from "./jobs"; import type { RenderedCliResult } from "./output"; import { pk01CaddyMergeManagedBlocksPython, renderCaddyManagedBlock, renderSimpleReverseProxyCaddySiteBlock } from "./pk01-caddy"; +import { prepareFrpcSecret } from "./platform-infra-public-service"; +import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets"; import { runSshCommandCapture, type SshCaptureResult } from "./ssh"; const defaultTargetId = "D601"; @@ -15,7 +17,6 @@ const fieldManager = "unidesk-platform-infra"; const manifestPath = rootPath("src", "components", "platform-infra", "sub2api", "sub2api.k8s.yaml"); const configPath = rootPath("config", "platform-infra", "sub2api.yaml"); const codexPoolConfigPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml"); -const repoRoot = rootPath(); const secretName = "sub2api-secrets"; const sub2apiCaddyManagedMarker = "sub2api"; const requiredSecretKeys = ["POSTGRES_PASSWORD", "ADMIN_PASSWORD", "JWT_SECRET", "TOTP_ENCRYPTION_KEY"] as const; @@ -1890,11 +1891,12 @@ function escapeRegExp(value: string): string { function prepareExternalActiveSecret(sub2api: Sub2ApiConfig): ExternalActiveSecretMaterial { const database = sub2api.runtime.database; const root = secretRoot(sub2api); - const dbSourcePath = join(root, database.sourceRef); - if (!existsSync(dbSourcePath)) { - throw new Error(`external-active requires ${redactRepoPath(dbSourcePath)}; run platform-db postgres apply/status first to materialize the PK01 DB credential source`); - } - const dbValues = parseEnvFile(readFileSync(dbSourcePath, "utf8")); + const dbSource = readEnvSourceFile({ + root, + sourceRef: database.sourceRef, + missingMessage: (sourcePath) => `external-active requires ${redactRepoPath(sourcePath)}; run platform-db postgres apply/status first to materialize the PK01 DB credential source`, + }); + const dbValues = dbSource.values; const dbUser = requiredEnvValue(dbValues, database.sourceKeys.user, database.sourceRef); const dbPassword = requiredEnvValue(dbValues, database.sourceKeys.password, database.sourceRef); const dbName = requiredEnvValue(dbValues, database.sourceKeys.dbName, database.sourceRef); @@ -1925,11 +1927,11 @@ function prepareExternalActiveSecret(sub2api: Sub2ApiConfig): ExternalActiveSecr if (action !== "none") writeEnvFile(appSourcePath, { ...existing, ...values }); return { sourceRef: database.sourceRef, - sourcePath: redactRepoPath(dbSourcePath), + sourcePath: dbSource.sourcePathRedacted, appSourceRef: sub2api.runtime.secrets.appSourceRef, appSourcePath: redactRepoPath(appSourcePath), action, - fingerprint: fingerprintValues(values, [...requiredSecretKeys]), + fingerprint: fingerprintSecretValues(values, [...requiredSecretKeys]), values, }; } @@ -1937,41 +1939,28 @@ function prepareExternalActiveSecret(sub2api: Sub2ApiConfig): ExternalActiveSecr function preparePublicExposureSecret(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): PublicExposureSecretMaterial | null { const exposure = target.publicExposure; if (exposure === null || !exposure.enabled) return null; - const sourcePath = join(secretRoot(sub2api), exposure.frpc.tokenSourceRef); - if (!existsSync(sourcePath)) throw new Error(`publicExposure requires ${redactRepoPath(sourcePath)} with ${exposure.frpc.tokenSourceKey}; copy the PK01 frps token into the local secret source first`); - const values = parseEnvFile(readFileSync(sourcePath, "utf8")); - const token = requiredEnvValue(values, exposure.frpc.tokenSourceKey, exposure.frpc.tokenSourceRef); - const frpcToml = [ - `serverAddr = "${exposure.frpc.serverAddr}"`, - `serverPort = ${exposure.frpc.serverPort}`, - `loginFailExit = true`, - `auth.token = "${escapeTomlString(token)}"`, - "", - "[[proxies]]", - `name = "${exposure.frpc.proxyName}"`, - `type = "tcp"`, - `localIP = "${exposure.frpc.localIP}"`, - `localPort = ${exposure.frpc.localPort}`, - `remotePort = ${exposure.frpc.remotePort}`, - "", - ].join("\n"); - return { - sourceRef: exposure.frpc.tokenSourceRef, - sourcePath: redactRepoPath(sourcePath), - secretName: exposure.frpc.secretName, - secretKey: exposure.frpc.secretKey, - fingerprint: fingerprintValues({ token, frpcToml }, ["token", "frpcToml"]), - frpcToml, - valuesPrinted: false, - }; + return prepareFrpcSecret({ + secretRoot: secretRoot(sub2api), + exposure, + sourcePathRedactor: redactRepoPath, + parseEnvFile, + requiredEnvValue, + readTextFile: (sourcePath) => { + if (!existsSync(sourcePath)) throw new Error(`publicExposure requires ${redactRepoPath(sourcePath)} with ${exposure.frpc.tokenSourceKey}; copy the PK01 frps token into the local secret source first`); + return readTextFile(sourcePath); + }, + }); } function prepareEgressProxySecret(sub2api: Sub2ApiConfig, target: Sub2ApiTargetConfig): EgressProxySecretMaterial | null { const proxy = target.egressProxy; if (proxy === null || !proxy.enabled) return null; - const sourcePath = join(secretRoot(sub2api), proxy.sourceRef); - if (!existsSync(sourcePath)) throw new Error(`egressProxy requires ${redactRepoPath(sourcePath)} with ${proxy.sourceKey}; materialize the master VPN subscription source first`); - const values = parseEnvFile(readFileSync(sourcePath, "utf8")); + const source = readEnvSourceFile({ + root: secretRoot(sub2api), + sourceRef: proxy.sourceRef, + missingMessage: (sourcePath) => `egressProxy requires ${redactRepoPath(sourcePath)} with ${proxy.sourceKey}; materialize the master VPN subscription source first`, + }); + const values = source.values; const subscriptionUrl = requiredEnvValue(values, proxy.sourceKey, proxy.sourceRef); const subscription = fetchSubscription(subscriptionUrl); const decoded = decodeSubscription(subscription.body); @@ -1982,10 +1971,10 @@ function prepareEgressProxySecret(sub2api: Sub2ApiConfig, target: Sub2ApiTargetC const noProxy = proxy.noProxy.join(","); return { sourceRef: proxy.sourceRef, - sourcePath: redactRepoPath(sourcePath), + sourcePath: source.sourcePathRedacted, secretName: proxy.secretName, secretKey: proxy.secretKey, - fingerprint: fingerprintValues({ subscription: subscription.body, configJson }, ["subscription", "configJson"]), + fingerprint: fingerprintSecretValues({ subscription: subscription.body, configJson }, ["subscription", "configJson"]), subscriptionBytes: Buffer.byteLength(subscription.body, "utf8"), selectedOutbound: selected.kind, configJson, @@ -2151,10 +2140,6 @@ function redactSubscriptionUri(uri: string): string { return uri.replace(/\/\/[^@]+@/u, "//@").replace(/password=[^&#]+/giu, "password=").replace(/pbk=[^&#]+/giu, "pbk="); } -function escapeTomlString(value: string): string { - return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\""); -} - async function applyPk01PublicExposure(config: UniDeskConfig, target: Sub2ApiTargetConfig): Promise> { const exposure = target.publicExposure; if (exposure === null || !exposure.enabled) return { ok: true, action: "not-enabled" }; @@ -2641,33 +2626,6 @@ function secretRoot(sub2api: Sub2ApiConfig): string { return isAbsolute(root) ? root : rootPath(root); } -function requiredEnvValue(values: Record, key: string, sourceRef: string): string { - const value = values[key]; - if (value === undefined || value.length === 0) throw new Error(`${sourceRef} is missing required key ${key}`); - return value; -} - -function parseEnvFile(text: string): Record { - const result: Record = {}; - for (const rawLine of text.split(/\r?\n/u)) { - const line = rawLine.trim(); - if (line.length === 0 || line.startsWith("#")) continue; - const eq = line.indexOf("="); - if (eq <= 0) continue; - const key = line.slice(0, eq).trim(); - if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue; - result[key] = unquoteEnvValue(line.slice(eq + 1).trim()); - } - return result; -} - -function unquoteEnvValue(value: string): string { - if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) { - return value.slice(1, -1); - } - return value; -} - function writeEnvFile(path: string, values: Record): void { mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); const lines = Object.keys(values) @@ -2682,21 +2640,6 @@ function quoteEnv(value: string): string { return `'${value.replaceAll("'", "'\"'\"'")}'`; } -function fingerprintValues(values: Record, keys: string[]): string { - const hash = createHash("sha256"); - for (const key of keys) { - hash.update(key); - hash.update("\0"); - hash.update(values[key] ?? ""); - hash.update("\0"); - } - return `sha256:${hash.digest("hex")}`; -} - -function redactRepoPath(path: string): string { - return path.startsWith(`${repoRoot}/`) ? path.slice(repoRoot.length + 1) : path; -} - function dryRunScript(yaml: string, target: Sub2ApiTargetConfig): string { const encoded = Buffer.from(yaml, "utf8").toString("base64"); return `