130 lines
4.8 KiB
TypeScript
130 lines
4.8 KiB
TypeScript
import {
|
|
existsSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, test } from "vitest";
|
|
|
|
import { WorkspaceSecretStore } from "../src/workspaces/secret-store.js";
|
|
|
|
const roots: string[] = [];
|
|
|
|
function fixture() {
|
|
const root = mkdtempSync(join(tmpdir(), "thoth-workspace-secret-store-"));
|
|
const runtimeRoot = mkdtempSync(join(tmpdir(), "thoth-workspace-secret-runtime-"));
|
|
roots.push(root, runtimeRoot);
|
|
return {
|
|
root,
|
|
runtimeRoot,
|
|
store: new WorkspaceSecretStore({
|
|
root,
|
|
runtimeRoot,
|
|
installationId: "installation-test",
|
|
}),
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("WorkspaceSecretStore", () => {
|
|
test("reuses stable materialized paths and removes them after the last lease", () => {
|
|
const { store } = fixture();
|
|
store.put("north-star", "dwh.password", "correct horse battery staple");
|
|
|
|
const first = store.materialize("north-star", ["dwh.password"]);
|
|
const second = store.materialize("north-star", ["dwh.password"]);
|
|
const firstPath = first.files.get("dwh.password")!;
|
|
const secondPath = second.files.get("dwh.password")!;
|
|
|
|
expect(secondPath).toBe(firstPath);
|
|
expect(readFileSync(firstPath, "utf8")).toBe("correct horse battery staple");
|
|
first.release();
|
|
expect(readFileSync(secondPath, "utf8")).toBe("correct horse battery staple");
|
|
second.release();
|
|
expect(existsSync(secondPath)).toBe(false);
|
|
});
|
|
|
|
test("persists ciphertext and exposes status without exposing plaintext", () => {
|
|
const { root, store } = fixture();
|
|
const secret = "correct horse battery staple";
|
|
|
|
store.put("psd-clinical", "dwh.password", secret);
|
|
|
|
expect(store.has("psd-clinical", "dwh.password")).toBe(true);
|
|
expect(store.configured("psd-clinical")).toEqual(["dwh.password"]);
|
|
const vault = readFileSync(join(root, "vault.json"), "utf8");
|
|
expect(vault).not.toContain(secret);
|
|
expect(statSync(join(root, "vault.json")).mode & 0o777).toBe(0o600);
|
|
expect(statSync(join(root, "master.key")).mode & 0o777).toBe(0o600);
|
|
});
|
|
|
|
test("blind replacement changes the materialized value and forget removes it", () => {
|
|
const { store } = fixture();
|
|
store.put("psd-clinical", "dwh.password", "old-value");
|
|
store.put("psd-clinical", "dwh.password", "new-value");
|
|
|
|
const lease = store.materialize("psd-clinical", ["dwh.password"]);
|
|
const path = lease.files.get("dwh.password");
|
|
expect(path).toBeDefined();
|
|
expect(readFileSync(path!, "utf8")).toBe("new-value");
|
|
expect(statSync(path!).mode & 0o777).toBe(0o400);
|
|
lease.release();
|
|
expect(existsSync(path!)).toBe(false);
|
|
|
|
store.forget("psd-clinical", "dwh.password");
|
|
expect(store.has("psd-clinical", "dwh.password")).toBe(false);
|
|
});
|
|
|
|
test("materializes only requested secrets and cleans the whole lease directory", () => {
|
|
const { runtimeRoot, store } = fixture();
|
|
store.putMany("psd-clinical", {
|
|
"dwh.password": "warehouse-password",
|
|
"evidence.api_key": "evidence-key",
|
|
});
|
|
|
|
const lease = store.materialize("psd-clinical", ["evidence.api_key"]);
|
|
expect([...lease.files.keys()]).toEqual(["evidence.api_key"]);
|
|
expect(readFileSync(lease.files.get("evidence.api_key")!, "utf8")).toBe("evidence-key");
|
|
expect(statSync(runtimeRoot).mode & 0o777).toBe(0o700);
|
|
const directory = join(lease.files.get("evidence.api_key")!, "..");
|
|
lease.release();
|
|
expect(existsSync(directory)).toBe(false);
|
|
});
|
|
|
|
test("fails closed with a sanitized error when the encrypted vault is tampered", () => {
|
|
const { root, store } = fixture();
|
|
const secret = "must-never-appear-in-errors";
|
|
store.put("psd-clinical", "dwh.password", secret);
|
|
|
|
const path = join(root, "vault.json");
|
|
const document = JSON.parse(readFileSync(path, "utf8")) as {
|
|
entries: Record<string, { ciphertext: string }>;
|
|
};
|
|
const record = Object.values(document.entries)[0]!;
|
|
record.ciphertext = Buffer.from("tampered").toString("base64");
|
|
writeFileSync(path, JSON.stringify(document), { mode: 0o600 });
|
|
|
|
expect(() => store.materialize("psd-clinical", ["dwh.password"]))
|
|
.toThrow("Workspace secret store is unavailable.");
|
|
try {
|
|
store.materialize("psd-clinical", ["dwh.password"]);
|
|
} catch (error) {
|
|
expect(String(error)).not.toContain(secret);
|
|
}
|
|
});
|
|
|
|
test("rejects invalid identifiers and oversized values", () => {
|
|
const { store } = fixture();
|
|
expect(() => store.put("../workspace", "dwh.password", "secret")).toThrow();
|
|
expect(() => store.put("psd-clinical", "../password", "secret")).toThrow();
|
|
expect(() => store.put("psd-clinical", "dwh.password", "x".repeat(65_537))).toThrow();
|
|
});
|
|
});
|