Skip to content

feat(secrets): remove an age recipient and re-key, the other half of add-recipient (#294) - #658

Merged
Gerrrt merged 1 commit into
mainfrom
gerrrt/replace-second-recipient-294
Sep 23, 2026
Merged

Gerrrt merged 1 commit into
mainfrom
gerrrt/replace-second-recipient-294

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 23, 2026

Copy link
Copy Markdown
Owner

#294's second key, age1cutv5…, was never kept. Its replacement, age19mkg76v0…, has been a recipient since 6de8509 (2026-09-22). Taking the lost key out means editing .sops.yaml and re-keying in one step, and there was no tool for it. add-recipient.sh calls hand-editing a trap.

What this adds

scripts/remove-recipient.sh, with make secrets-remove-recipient PUBKEY=age1…, mirrors add-recipient.sh:

  1. It checks the key is in both the ciphertext and .sops.yaml. If only one lists it, it stops and says so.
  2. It backs up .sops.yaml, removes the line, and runs sops updatekeys. Then it checks the ciphertext: the removed key must be gone and every other recipient must still be there. If not, it rolls back. (updatekeys exits 0 when you answer "no" at its prompt.)
  3. It rewrites key-recipients.prom, which drops the removed key's row and keeps every other key's proof.

Refusals: each one is a way a removal would lose the recovery it's meant to protect.

  • It won't remove this host's own key.
  • It won't leave fewer than two recipients (ADR-0024).
  • It won't leave only recipients that key-recipients.prom has never recorded as proved.

The .sops.yaml edit handles removing the last key of a list: the new last line loses its trailing comma, which sops would otherwise read as an empty recipient. Six fixtures under --self-test cover it (middle, last, first, absent, only key in its rule, and one key in two rules), and self-tests.sh discovers them, so they run in CI. Running the edit against a copy of the live .sops.yaml removes exactly the age1cutv5… line.

back-up-the-age-key.md now describes a replacement as add, then remove, then prove.

After merge, on prometheus (main checkout, where the live key is)

make secrets-remove-recipient PUBKEY=age1cutv5qvkwxxy9n5clznnvm2hgseeuw9ecswgnspkmxs6d69hrpqqx2wxp4
git add .sops.yaml secrets/observability.sops.yaml

Commit and open a PR with those two files. The refusal about unproven keys won't trigger: age1yrdu996… was proved on 2026-09-03.

Then #294's last step is proving age19mkg76v0…, not age1cutv5…. That happens at the visit, together with #573's offsite copy: make secrets-verify-backup KEY=<path> should print -- recipient age19mkg76v0… and an ok line.

Removal doesn't reach git history. Every old ciphertext still opens to age1cutv5…. The key was lost, not exposed, so no credential rotation is needed. If that ever changes, the runbook's "Revocation is still rotation" section applies.

Checks

--self-test (6 fixtures), shellcheck, editorconfig and check_docs.py all pass. Two local failures aren't from this PR: shellcheck 0.11's SC2329 in check_hardened_boot.sh, and the /tmp fixture in backup-offsite.sh on a host where /tmp is a tmpfs.

Refs #294

🤖 Generated with Claude Code

…add-recipient (#294)

#294's second key, age1cutv5..., was never kept. Its replacement,
age19mkg76v0..., has been a recipient since 6de8509, and nothing could
take the lost key out without hand-editing .sops.yaml. add-recipient.sh
calls that a trap.

scripts/remove-recipient.sh, with `make secrets-remove-recipient
PUBKEY=`, mirrors add-recipient.sh. It checks the key is in both the
ciphertext and the policy, backs up .sops.yaml, removes the line, runs
`sops updatekeys`, and then checks the ciphertext rather than the exit
status. If the re-key did not happen it rolls back. Removing the last
key of a list also drops the trailing comma a hand-edit would leave,
which sops reads as an empty recipient.

It refuses three ways a removal loses the recovery it protects: taking
out this host's own key, leaving fewer than two recipients (ADR-0024),
and leaving only recipients key-recipients.prom has never recorded as
proved.

Six fixtures cover the .sops.yaml edit: middle, last, first, absent,
only-in-rule, and one key in two rules. Running the edit against a copy
of the live .sops.yaml removes exactly the age1cutv5... line.

back-up-the-age-key.md now describes a replacement as add, then remove,
then prove.

Refs #294

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gerrrt
Gerrrt merged commit 36f7948 into main Sep 23, 2026
4 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/replace-second-recipient-294 branch September 23, 2026 20:07
Gerrrt added a commit that referenced this pull request Sep 23, 2026
#652 landed ADR-0052, so with this branch's ADR-0053 the repository has 53.
The changelog conflict keeps both entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant