Key rotation: managed re-encryption (drain job + ledger + boot tripwire) #411
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: Cordy/Cairn#411
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements the drain half of ADR 0002 (
docs/adr/0002-key-rotation-drain.md, merged via PR #408). #348 shipped the verify half (coverage scan + Rotation tab); this issue makes the rotate→drain→verify→discard cycle complete without waiting on users to touch files.Drain job
key-drainaudit event per run, with counts.Locked-file deferral (decided 2026-09-03)
Never rewrite beneath a live lock — a stale re-encryption racing an Office save could clobber the newer content. Instead:
drain-locked-files-mockup.html): amber inline notice during the run ("N files are locked and in use — skipped, not stuck") with an expandable list showing path, lock type and holder; end-of-pass summary splits re-encrypted / deferred (locked) / failed; stepper sub-label counts only the true remainder; hint copy tells the operator not to break locks — deferral converges on its own (a save re-encrypts under the new key; the next pass catches the rest).Ownership guarantee (flagged 2026-09-03)
Safe by construction, pinned by test:
ownermeta, so no owner re-stamping can occur — there is no code path by which the drain could attribute a file to anyone.Safety pieces (per ADR 0002)
UI + docs
https://www.swisscairn.ch/docs/encryption#rotation)./… (2)— give the root group a legible label.Known cost
Drained objects get a new modified time → sync clients re-download the drained set once. Document it.
Testbeds
Both dogfoods are live drain candidates: cairn-enc has 3 classical retired-only objects (first-ever green discard within reach), cairn-openbao has 18 hybrid ones from the 2026-09 rotation.
Shipped in v0.6.117 (PR #412, merged; both dogfoods rolled and healthy).
Design decisions recorded 2026-09-03
TestDrainMovesRetiredOnly's sidecar assertion.auth.ContextWithUserfrom the/home/<user>/segment, so keycloak-profile and OpenBao custody re-resolve the owner's current recipient — and PQ upgrade-on-touch rides along, exactly as ADR 0002 promised.filelock.Covering, shown in the amber notice + expandable list from the approveddrain-locked-files-mockup.html; copy says outright that nothing is stuck and locks must not be broken.key-rotation.jsonbeside the other runtime state (statestore-bound on s3); scan-proven zero marks entries drained; an undrained key vanishing from config refuses boot;CAIRN_KEYDRAIN_FORCE=1is the documented escape hatch. Handbook section rewritten.Operator pass wanted: both dogfoods are live testbeds — cairn-enc has 3 classical retired-only objects (one Re-encrypt press away from the first-ever verified discard), cairn-openbao ~18 hybrid. Deploys wiped sessions; fresh login needed.
v0.6.118 (PR #413, dogfoods rolled): first real drain on cairn-enc surfaced two things.
/test/cairn-handoff.md,/cairn-fix-first.md,/sovereignty-check.txtare pre-per-user-homes relics at the backend root; per-user custody has no principal to resolve, so the encrypt layer refused. They now re-encrypt directly to the active deployment-level recipients (Drainer.Fallback): such objects open only via the retiring recovery key today, so this preserves exactly their current access, on the new key./homeno longer resolves its own name as a username, so per-user custody can never be asked to mint IdP keys for a principal that does not exist.Open observation:
/home/Mülltrennung.docxlists but does not open (not found) — the same object the scan counts as "skipped". Likely a unicode-normalisation ghost (NFD vs NFC umlaut in the S3 key). Not a drain problem; if it is not visible/deletable in the UI, it becomes its own issue against the s3 driver.v0.6.119 (PR #415, dogfoods rolled): a finished drain pass now hands straight to verification —
OnDone(on completion, not on pause) starts the coverage scan, so the Rotation card refreshes to the true remaining count instead of the stale pre-pass figure, and a zero result marks the ledger drained without any further click.Found on cairn-openbao: the pass moved all 18 objects, but the headline (fed by the last scan) kept saying “18 objects remaining”, and a second Re-encrypt press just reported 0/0/0 until a manual Rescan. Decision recorded: auto-verify instead of a “Continue” button — the flow self-advances and the ledger benefits as a side effect.
Dogfood state: both instances reached the green “Covers nothing” verdict — cairn-enc (3 classical drained; the #414 ghost counts as skipped) and cairn-openbao (18 hybrid drained). Both are cleared for the discard step: remove
recoveryIdentityLegacyfrom the untracked config Secret, restart, expect a clean boot with the ledger retiring the entry.v0.6.120 (PR #416, dogfoods rolled): the tripwire (and the ledger-open failure path) now cancels the heartbeat and releases the state-backend instance lock before exiting. Found live during cairn-enc's first discard: every refused boot stranded the lock, so the recovery boot (key restored or
CAIRN_KEYDRAIN_FORCE=1) sat in CrashLoopBackOff for the full 5-minute stale window.And for the record — the tripwire's first live catch was a genuine one: the cairn-enc discard was attempted without a post-drain verified scan (the drain had moved all 3 objects, but the last completed scan still said 3 covered; the operator's rescan that day was on cairn-openbao). Boot refused exactly as designed; recovery via the documented
CAIRN_KEYDRAIN_FORCE=1escape hatch; ledger entry dropped with the loud warning. v0.6.119's auto-verify makes this scenario much harder to reproduce, since a finished pass now runs the scan itself.Closing on operator sign-off: rotate → drain → verify → discard completed end-to-end on both dogfoods (cairn-enc: 3 classical objects incl. the first-ever tripwire catch and escape-hatch recovery; cairn-openbao: 18 hybrid objects through the clean happy path). Shipped across v0.6.117–120 with three dogfound fixes along the way. Follow-ups live in #414 (s3 driver unicode roundtrip).