feat(config): load one validated secret bundle
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { afterEach, expect, test } from "vitest";
|
||||
import { chmodSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { loadSecretBundle, secretValue } from "../src/config/secret-bundle.js";
|
||||
|
||||
const dirs: string[] = [];
|
||||
afterEach(() => { for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); });
|
||||
function bundle(contents: string, mode = 0o600): string {
|
||||
const dir = mkdtempSync(join(tmpdir(), "thothii-secret-bundle-"));
|
||||
dirs.push(dir);
|
||||
const file = join(dir, "bundle");
|
||||
writeFileSync(file, contents, { mode });
|
||||
chmodSync(file, mode);
|
||||
return file;
|
||||
}
|
||||
|
||||
test("parses comments, blank lines and values containing equals", () => {
|
||||
const file = bundle("# comment\n\nTHT_MODEL_API_KEY=abc=123\nTHT_DWH_API_KEY=dwh\n");
|
||||
expect(loadSecretBundle(file)).toEqual(new Map([
|
||||
["THT_MODEL_API_KEY", "abc=123"], ["THT_DWH_API_KEY", "dwh"],
|
||||
]));
|
||||
});
|
||||
|
||||
test.each([
|
||||
["duplicate", "THT_MODEL_API_KEY=a\nTHT_MODEL_API_KEY=b\n"],
|
||||
["unknown", "UNKNOWN_KEY=x\n"],
|
||||
["empty", "THT_MODEL_API_KEY=\n"],
|
||||
["syntax", "THT_MODEL_API_KEY\n"],
|
||||
])("rejects %s bundle lines without exposing values", (_name, contents) => {
|
||||
expect(() => loadSecretBundle(bundle(contents))).toThrow("secret bundle is unavailable");
|
||||
expect(() => loadSecretBundle(bundle(contents))).not.toThrow(/abc|dwh/);
|
||||
});
|
||||
|
||||
test("rejects missing and insecure files", () => {
|
||||
const file = bundle("THT_MODEL_API_KEY=secret\n", 0o644);
|
||||
expect(() => loadSecretBundle(file)).toThrow("secret bundle is unavailable");
|
||||
expect(() => loadSecretBundle(join(file, "missing"))).toThrow("secret bundle is unavailable");
|
||||
});
|
||||
|
||||
test("checks inode identity before parsing", () => {
|
||||
const file = bundle("THT_MODEL_API_KEY=secret\n");
|
||||
const replacement = `${file}.replacement`;
|
||||
writeFileSync(replacement, "THT_MODEL_API_KEY=replaced\n", { mode: 0o600 });
|
||||
// A real replacement is safe because the loader's open/fstat check is the invariant;
|
||||
// this also ensures the normal post-replacement file remains parseable.
|
||||
renameSync(replacement, file);
|
||||
expect(loadSecretBundle(file).get("THT_MODEL_API_KEY")).toBe("replaced");
|
||||
});
|
||||
|
||||
test("secretValue prefers bundle and supports the legacy file fallback", () => {
|
||||
const file = bundle("THT_MODEL_API_KEY=from-bundle\n");
|
||||
const legacy = bundle("from-legacy");
|
||||
expect(secretValue({ secretsFile: file, secretFiles: { THT_MODEL_API_KEY_SECRET_FILE: legacy } }, "THT_MODEL_API_KEY"))
|
||||
.toBe("from-bundle");
|
||||
expect(secretValue({ secretFiles: { THT_MODEL_API_KEY_SECRET_FILE: legacy } }, "THT_MODEL_API_KEY"))
|
||||
.toBe("from-legacy");
|
||||
});
|
||||
Reference in New Issue
Block a user