refactor: share platform secret source helpers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { UniDeskConfig } from "./config";
|
||||
import { rootPath } from "./config";
|
||||
import { startJob } from "./jobs";
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
capture,
|
||||
compactCapture,
|
||||
dryRunManifestScript,
|
||||
fingerprintValues,
|
||||
parseJsonOutput,
|
||||
prepareFrpcSecret,
|
||||
publicHttpProbe,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
type PublicServiceExposure,
|
||||
type PublicServiceTarget,
|
||||
} from "./platform-infra-public-service";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets";
|
||||
|
||||
const configFile = rootPath("config", "platform-infra", "n8n.yaml");
|
||||
const configLabel = "config/platform-infra/n8n.yaml";
|
||||
@@ -817,28 +817,34 @@ PY
|
||||
|
||||
function prepareSecretMaterial(n8n: N8nConfig): SecretMaterial {
|
||||
const root = secretRoot(n8n);
|
||||
const dbSourcePath = join(root, n8n.runtime.database.sourceRef);
|
||||
if (!existsSync(dbSourcePath)) throw new Error(`n8n database source ${redactRepoPath(dbSourcePath)} is missing; run platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm first`);
|
||||
const dbValues = parseEnvFile(readTextFile(dbSourcePath));
|
||||
const dbSource = readEnvSourceFile({
|
||||
root,
|
||||
sourceRef: n8n.runtime.database.sourceRef,
|
||||
missingMessage: (sourcePath) => `n8n database source ${redactRepoPath(sourcePath)} is missing; run platform-db postgres apply --config config/platform-db/postgres-pk01.yaml --confirm first`,
|
||||
});
|
||||
const dbValues = dbSource.values;
|
||||
const dbUser = requiredEnvValue(dbValues, n8n.runtime.database.sourceKeys.user, n8n.runtime.database.sourceRef);
|
||||
const dbPassword = requiredEnvValue(dbValues, n8n.runtime.database.sourceKeys.password, n8n.runtime.database.sourceRef);
|
||||
const dbName = requiredEnvValue(dbValues, n8n.runtime.database.sourceKeys.dbName, n8n.runtime.database.sourceRef);
|
||||
if (dbUser !== n8n.runtime.database.user) throw new Error(`${n8n.runtime.database.sourceRef}.${n8n.runtime.database.sourceKeys.user} does not match n8n runtime.database.user`);
|
||||
if (dbName !== n8n.runtime.database.dbName) throw new Error(`${n8n.runtime.database.sourceRef}.${n8n.runtime.database.sourceKeys.dbName} does not match n8n runtime.database.dbName`);
|
||||
const appSourcePath = join(root, n8n.runtime.secrets.appSourceRef);
|
||||
if (!existsSync(appSourcePath)) throw new Error(`n8n app secret source ${redactRepoPath(appSourcePath)} is missing; run bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope platform-infra --confirm first`);
|
||||
const appValues = parseEnvFile(readTextFile(appSourcePath));
|
||||
const appSource = readEnvSourceFile({
|
||||
root,
|
||||
sourceRef: n8n.runtime.secrets.appSourceRef,
|
||||
missingMessage: (sourcePath) => `n8n app secret source ${redactRepoPath(sourcePath)} is missing; run bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope platform-infra --confirm first`,
|
||||
});
|
||||
const appValues = appSource.values;
|
||||
const encryptionKeyName = n8n.runtime.secrets.encryptionKey;
|
||||
const encryptionKey = requiredEnvValue(appValues, encryptionKeyName, n8n.runtime.secrets.appSourceRef);
|
||||
const values = { dbUser, dbPassword, dbName, encryptionKey };
|
||||
return {
|
||||
dbSourceRef: n8n.runtime.database.sourceRef,
|
||||
dbSourcePath: redactRepoPath(dbSourcePath),
|
||||
dbSourcePath: dbSource.sourcePathRedacted,
|
||||
appSourceRef: n8n.runtime.secrets.appSourceRef,
|
||||
appSourcePath: redactRepoPath(appSourcePath),
|
||||
appSourcePath: appSource.sourcePathRedacted,
|
||||
action: "read",
|
||||
values,
|
||||
fingerprint: fingerprintValues({ dbPassword, encryptionKey: values.encryptionKey }, ["dbPassword", "encryptionKey"]),
|
||||
fingerprint: fingerprintSecretValues({ dbPassword, encryptionKey: values.encryptionKey }, ["dbPassword", "encryptionKey"]),
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
@@ -1038,41 +1044,6 @@ function secretRoot(n8n: N8nConfig): string {
|
||||
return isAbsolute(root) ? root : rootPath(root);
|
||||
}
|
||||
|
||||
function readTextFile(path: string): string {
|
||||
if (!existsSync(path)) throw new Error(`required secret source ${redactRepoPath(path)} is missing`);
|
||||
return readFileSync(path, "utf8");
|
||||
}
|
||||
|
||||
function parseEnvFile(text: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
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 requiredEnvValue(values: Record<string, string>, 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 redactRepoPath(path: string): string {
|
||||
const root = rootPath();
|
||||
return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
|
||||
}
|
||||
|
||||
function boolField(value: Record<string, unknown> | null, key: string, fallback: boolean): boolean {
|
||||
return typeof value?.[key] === "boolean" ? value[key] : fallback;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user