feat: harden self-managed reconciliation

This commit is contained in:
2026-09-05 10:17:55 +00:00 Unverified
parent 8e9d207915
commit 321f4e807a
40 changed files with 4977 additions and 404 deletions
+105
View File
@@ -0,0 +1,105 @@
import { createHash, randomUUID } from "node:crypto";
import {
chmod,
mkdir,
readFile,
realpath,
rename,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
export const TRUST_SCHEMA_VERSION = 1;
export type TrustIdentity = { project: string; fingerprint: string };
export type LocalTrustRecord = TrustIdentity;
type TrustFile = { version: number; records: LocalTrustRecord[] };
export function getTrustPath(): string {
return join(
process.env.XDG_CONFIG_HOME ?? join(process.env.HOME ?? "/tmp", ".config"),
"kuber",
"trust.json",
);
}
export async function resolveTrustIdentity(
project: string,
cwd = process.cwd(),
): Promise<TrustIdentity> {
const path = await realpath(cwd);
return {
project,
fingerprint: createHash("sha256").update(path).digest("hex"),
};
}
function validRecord(value: unknown): value is LocalTrustRecord {
return (
!!value &&
typeof value === "object" &&
typeof (value as Record<string, unknown>).project === "string" &&
typeof (value as Record<string, unknown>).fingerprint === "string" &&
/^[a-f0-9]{64}$/.test(
(value as Record<string, unknown>).fingerprint as string,
)
);
}
export async function readTrust(): Promise<LocalTrustRecord[]> {
try {
const value: unknown = JSON.parse(await readFile(getTrustPath(), "utf8"));
if (
!value ||
typeof value !== "object" ||
(value as TrustFile).version !== TRUST_SCHEMA_VERSION ||
!Array.isArray((value as TrustFile).records)
)
return [];
return (value as TrustFile).records.filter(validRecord);
} catch {
return [];
}
}
export async function writeTrust(records: LocalTrustRecord[]): Promise<void> {
const path = getTrustPath();
const directory = path.slice(0, path.lastIndexOf("/"));
await mkdir(directory, { recursive: true, mode: 0o700 });
await chmod(directory, 0o700);
const temporary = `${path}.${randomUUID()}.tmp`;
await writeFile(
temporary,
JSON.stringify({ version: TRUST_SCHEMA_VERSION, records }, null, 2) + "\n",
{ flag: "wx", mode: 0o600 },
);
await rename(temporary, path);
await chmod(path, 0o600);
}
export async function updateTrust(
update: (records: LocalTrustRecord[]) => LocalTrustRecord[],
): Promise<void> {
await writeTrust(update(await readTrust()));
}
export function trustHeaders(identity: TrustIdentity): Record<string, string> {
return {
"x-kuber-trust-project": identity.project,
"x-kuber-trust-fingerprint": identity.fingerprint,
};
}
export async function requireLocalTrust(
identity: TrustIdentity,
): Promise<LocalTrustRecord> {
const record = (await readTrust()).find(
(candidate) =>
candidate.project === identity.project &&
candidate.fingerprint === identity.fingerprint,
);
if (record) return record;
throw new Error(
"TRUST_REQUIRED: this directory is not trusted for this namespace. Run kuber trust before kuber up.",
);
}