feat(admin): build out the Encryption & keys page #350

Merged
Cordy merged 19 commits from enc-page into main 2026-08-28 00:54:30 +00:00
Owner

The page carrying the strongest claim in the product — Swisscairn holds nothing and can decrypt nothing — rendered three unlabelled Label: value lines. It was never in any admin slice, so it also never appeared on the deferred list; it fell through the gap between "done" and "known missing".

Backend

GET /api/v1/admin/encryption reports custody, algorithm, storage location, and the public half of the recovery and retained identities.

encryptionPayload is a pure function, so the assertion that matters — that no secret key material can reach the wire — has a total surface to inspect without an HTTP harness. main.go resolves the public halves at boot, where the secret ones already are, so nothing downstream ever holds one.

encrypt.RecipientString is new: age.Recipient is an interface with no String method, so rendering the public half needs the flavour switch, and it belongs beside the one that already exists for the secret half.

Frontend

Custody — who can decrypt this: the sovereignty claim, algorithm with the FIPS 203 rationale, custody mode (all three render), storage location, scope, and in-transit.

Recovery — what is possible without them, plus three additions the mockup review asked for:

  • a Retained identity row, so the single legacy slot is visible on the page rather than folded into a doc;
  • a warning against deleting it — that failure is silent, and surfaces during an incident;
  • an offline verification block, so the Custody claim can be checked rather than believed.

To rotate states that rotation is not retroactive, rather than implying it with advice.

Rotation stays absent by design, consistent with the existing copy: the page says where the procedure lives instead of offering a control that could lock an operator out of their own files.

Verification

go build ./..., go vet, go test ./internal/api/... ./internal/storage/encrypt/... all pass. All 56 new i18n keys carry four languages, and every t() call in the renderer resolves — a missing key would otherwise render its own name.

Follow-ups, deliberately not here

  • The warning box reuses .notebox with no danger styling; adding a variant means new CSS across five themes and belongs with design.
  • encRotateHint names the handbook path rather than linking, because the docs site is still preview-gated (#26 / #109). Worth converting to a link when it goes live.
  • Four files unrelated to this change are gofmt-dirty on main: internal/config/groups_test.go, internal/peering/client.go, internal/peering/sender.go, internal/storage/scope/scope_appowned_test.go.

Related: #348, #349.

The page carrying the strongest claim in the product — *Swisscairn holds nothing and can decrypt nothing* — rendered three unlabelled `Label: value` lines. It was never in any admin slice, so it also never appeared on the deferred list; it fell through the gap between "done" and "known missing". ## Backend `GET /api/v1/admin/encryption` reports custody, algorithm, storage location, and the **public** half of the recovery and retained identities. `encryptionPayload` is a pure function, so the assertion that matters — that no secret key material can reach the wire — has a total surface to inspect without an HTTP harness. `main.go` resolves the public halves at boot, where the secret ones already are, so nothing downstream ever holds one. `encrypt.RecipientString` is new: `age.Recipient` is an interface with no `String` method, so rendering the public half needs the flavour switch, and it belongs beside the one that already exists for the secret half. ## Frontend **Custody** — who can decrypt this: the sovereignty claim, algorithm with the FIPS 203 rationale, custody mode (all three render), storage location, scope, and in-transit. **Recovery** — what is possible without them, plus three additions the mockup review asked for: - a `Retained identity` row, so the single legacy slot is visible on the page rather than folded into a doc; - a warning against deleting it — that failure is silent, and surfaces during an incident; - an offline verification block, so the Custody claim can be checked rather than believed. `To rotate` states that rotation is not retroactive, rather than implying it with advice. Rotation stays absent by design, consistent with the existing copy: the page says where the procedure lives instead of offering a control that could lock an operator out of their own files. ## Verification `go build ./...`, `go vet`, `go test ./internal/api/... ./internal/storage/encrypt/...` all pass. All 56 new i18n keys carry four languages, and every `t()` call in the renderer resolves — a missing key would otherwise render its own name. ## Follow-ups, deliberately not here - The warning box reuses `.notebox` with no danger styling; adding a variant means new CSS across five themes and belongs with design. - `encRotateHint` names the handbook path rather than linking, because the docs site is still preview-gated (#26 / #109). Worth converting to a link when it goes live. - Four files unrelated to this change are `gofmt`-dirty on `main`: `internal/config/groups_test.go`, `internal/peering/client.go`, `internal/peering/sender.go`, `internal/storage/scope/scope_appowned_test.go`. Related: #348, #349.
GET /api/v1/admin/encryption reports custody, algorithm, storage
location, and the PUBLIC half of the recovery and retained identities.

The payload builder is a pure function so the assertion that matters --
that no secret key material can reach the wire -- has a total surface to
inspect without an HTTP harness. main.go resolves the public halves at
boot, where the secret ones already are, so nothing downstream ever
holds one.

encrypt.RecipientString is new: age.Recipient is an interface with no
String method, so rendering the public half needs the flavor switch,
and it belongs beside the one for the secret half rather than copied
into every caller.

Also corrects a config comment that still claimed age is not
post-quantum, which #28 made false.
feat(admin): build out the Encryption & keys page
All checks were successful
ci / test-and-build (pull_request) Successful in 43s
f7a43d8a50
Custody answers who can decrypt this; Recovery answers what is possible
without them. Replaces a placeholder that printed three unlabelled lines
on the page carrying the strongest claim in the product.

Recovery gains the retained-key row, so the one legacy slot and its
consequences are visible rather than folded into a doc; a warning against
deleting it, because that failure is silent until an incident; and an
offline verification block, so the sovereignty claim on Custody can be
checked rather than believed.

Rotation stays absent by design. The page states where the procedure
lives instead of offering a control that could lock an operator out.
Cordy merged commit 054f6f4d55 into main 2026-08-28 00:54:30 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#350
No description provided.