mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
Every configured key id now has a canary in encryption_key_canaries, checked at boot. A key that cannot open its canary, or existing data written before the canary existed, puts the process in a refusal state: every API route answers 503 encryption.key_mismatch, /api/health reports reason encryption_key_mismatch, the pages render one explanation, and neither the queue producer nor the worker starts. The process stays up so the reason is visible instead of hiding in a crash loop. The admin Encryption page gains a "Back up your encryption key" step: key id and fingerprint (never the key), where the key lives per platform, a check of a pasted copy that is compared and discarded, and a confirmation bound to the active key id and fingerprint. The overview gets a tile and admins get a dashboard banner while it is due. HEALTHLOG_PLATFORM picks the platform the step opens on. Adds the TrueNAS self-hosting page and key backup sections in the docs.
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { isKeyBackupDue, resolvePlatformHint } from "../key-backup";
|
|
|
|
const confirmed = (keyId: string, fingerprint: string) => ({
|
|
encryptionKeyBackupConfirmedAt: new Date("2026-10-03T10:00:00Z"),
|
|
encryptionKeyBackupConfirmedKeyId: keyId,
|
|
encryptionKeyBackupConfirmedFingerprint: fingerprint,
|
|
});
|
|
|
|
describe("key backup step", () => {
|
|
it("is due without any confirmation", () => {
|
|
expect(isKeyBackupDue(null, "v1", "abcdefabcdef")).toBe(true);
|
|
expect(
|
|
isKeyBackupDue(
|
|
{
|
|
encryptionKeyBackupConfirmedAt: null,
|
|
encryptionKeyBackupConfirmedKeyId: null,
|
|
encryptionKeyBackupConfirmedFingerprint: null,
|
|
},
|
|
"v1",
|
|
"abcdefabcdef",
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("is done only for the exact key that was confirmed", () => {
|
|
expect(
|
|
isKeyBackupDue(confirmed("v1", "abcdefabcdef"), "v1", "abcdefabcdef"),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("is due again after a rotation (new active id)", () => {
|
|
expect(
|
|
isKeyBackupDue(confirmed("v1", "abcdefabcdef"), "v2", "abcdefabcdef"),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("is due again after a re-keyed install (same id, other key)", () => {
|
|
expect(
|
|
isKeyBackupDue(confirmed("v1", "abcdefabcdef"), "v1", "123456123456"),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("reads the platform hint and falls back to compose", () => {
|
|
expect(resolvePlatformHint("truenas")).toBe("truenas");
|
|
expect(resolvePlatformHint(" Unraid ")).toBe("unraid");
|
|
expect(resolvePlatformHint(undefined)).toBe("compose");
|
|
expect(resolvePlatformHint("kubernetes")).toBe("compose");
|
|
});
|
|
});
|