Repository navigation
fix(sensitive): declare trinity's age-key deadline as verify-key-backup-sensitive - #1501
Merged
Merged
Conversation
…up-sensitive trinity's key-recipients.prom has recorded both recipients as never proved since 2026-10-03, and SecretsKeyBackupUnproven could not fire for either: it joins against a verify-key-backup deadline that SENSITIVE_JOBS never declared. Declaring that name on trinity too would break ScheduledJobStale and ScheduledJobStaleCritical for the whole estate (on(homelab_job) becomes many-to-many, proved under promtool), so trinity's row has its own name. The two key-proof rules match either name, the stale rules exclude it, `make secrets-verify-backup STACK=sensitive` records it, and Glance's maintenance page judges it per recipient. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The verifier rejects valid sensitive backups before recording recipient proofs, preventing alert clearance, while the maintenance page can hide missing proofs and incorrectly report all jobs on schedule.
2 open issues
What changed in this PR
This PR adds a distinct age-key backup deadline for trinity, allowing sensitive-stack recipients to be monitored without conflicting with estate-wide job-name joins.
Changes:
- Declares
verify-key-backup-sensitiveand routes sensitive proof runs to that job name. - Updates alert rules, regression tests, and Glance’s per-recipient status handling.
- Documents verification and deployment steps, with a changelog entry distinguishing the change from deployment.
| File | Description |
|---|---|
stacks/sensitive/glance/pages/maintenance.yml |
Handles the sensitive job as a per-recipient proof. |
stacks/observability/prometheus/tests/backup.test.yaml |
Adds sensitive-recipient and missing-metric alert cases. |
stacks/observability/prometheus/rules/backup.rules.yaml |
Recognizes the sensitive deadline and updates remediation text. |
scripts/install-timers.sh |
Declares the sensitive stack’s ninety-day human-proof deadline. |
Makefile |
Selects the sensitive job name for STACK=sensitive. |
docs/runbooks/schedule-maintenance.md |
Documents the added deadline and expected alerts. |
docs/runbooks/back-up-the-age-key.md |
Adds verification instructions for trinity. |
docs/changelog.md |
Records the monitoring change and deployment requirement. |
Review effort: Balanced
Gerrrt
force-pushed
the
gerrrt/sensitive-verify-key-backup
branch
from
November 14, 2026 00:34
8e49661 to
140f8d3
Compare
Gerrrt
enabled auto-merge (squash)
November 14, 2026 06:39
Merge origin/main, which landed #1488's fallback for a host with no key-proof deadline. Keep both: the primary branch matches either declaration name, the fallback covers a host that declares neither. verify-key-backup.sh held every stack to the observability names, so a sensitive copy failed after decrypting and its per-recipient proof was never recorded. Other stacks are now held to their compose guards, as render-config.sh does. Glance's maintenance page counted nothing for a key-proof declaration with no proof series on its host; it now lists it as needing attention. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What changed
trinitynow declares the ninety-day age-key deadline inSENSITIVE_JOBS, asverify-key-backup-sensitive.SecretsKeyBackupUnprovenandSecretsKeyRecipientsUnrecordedmatch that name as well asverify-key-backup.ScheduledJobStaleandScheduledJobStaleCriticalexclude it, like the other human proofs.make secrets-verify-backup STACK=sensitiverecords it, and Glance's maintenance page judges it per recipient and lists a declaration with no proof series as needing attention.scripts/verify-key-backup.shheld every stack's decrypted file to the observability names (Grafana, Alertmanager, SNMP), so a sensitive copy failed after it had decrypted and its proof was never recorded. Other stacks are now held to their compose guards, asrender-config.shdoes. The observability list is unchanged.Main landed #1488 meanwhile, which holds a host with no deadline row to the estate's deadline through a fallback branch. The merge keeps both: the primary branch matches either declaration name, and the fallback stays for a host that declares neither.
Why
A read-only audit of
trinityon 2026-11-13 found both recipients in itskey-recipients.promat0(never proved) since 2026-10-03.SecretsKeyBackupUnprovencouldn't fire for either of them: it joins against ahomelab_job_max_age_seconds{homelab_job="verify-key-backup"}row thattrinitynever declared. A losttrinitykey makessecrets/sensitive.sops.yamland every sensitive backup set unreadable, and nothing was set up to warn.The obvious fix was to add a
verify-key-backuprow toSENSITIVE_JOBS. I tested that under promtool first, and it breaksScheduledJobStaleandScheduledJobStaleCriticalfor the whole estate: they joinon(homelab_job), and one name declared on two instances is many-to-many (found duplicate series for the match group {homelab_job="verify-key-backup"}). That is whyinstall-timers.shcheck 8 forbids a name in both tables. So trinity's row gets its own name, followingconverge-sensitive.Blast radius
The estate's backup alert rules on
prometheus, Glance's maintenance page ontrinity, andmake secrets-verify-backup/scripts/verify-key-backup.sh. WithoutSTACK=sensitive, the make target recordsverify-key-backupexactly as before.With #1488 deployed,
SecretsKeyBackupUnprovenalready fires for trinity's recipients. Once the timers are reinstalled ontrinity, these fire until each offline copy is proved:SecretsKeyBackupUnproven, one alert per recipient (two today);ScheduledJobNeverRanforverify-key-backup-sensitive.Both are correct: neither key has ever been proved.
secrets/*.sops.yamlVerification
promtool test rules backup.test.yaml(prom/prometheus v3.16.0, as pinned):SUCCESS. New cases:ScheduledJobStale;SecretsKeyBackupUnprovenwithstack: sensitiveandinstance: trinity, while the monitoring host's fresh proof stays quiet;SecretsKeyRecipientsUnrecorded.verify-key-backup.shagainst a throwaway fixture (a fresh age key,stacks/sensitive/compose.yaml, a file holding its 29 guarded names with dummy values): the new script decrypts, passes and records the recipient's proof; main's script fails with the observability names missing. The names in the realsecrets/sensitive.sops.yaml(key names only, not values) cover all 29.The same promtool run against a plain
verify-key-backuprow on two instances fails with the many-to-many error quoted above.scripts/install-timers.sh --check: check 8 passes, and the new row passes as "no timer, 7776000s deadline declared for the human". The installed-timer checks are skipped off-host, as designed.make -n secrets-verify-backup KEY=x STACK=sensitiveruns--job verify-key-backup-sensitive. WithoutSTACK, it runs--job verify-key-backup.make validate: exit 0, with 10 skips, none of them failures. yamllint, ruff (×2) and ansible-lint were unavailable locally. Four installed-timer and unit checks are off-host by design in a scratch clone. The/tmptmpfs refusal case and the observability installed-copy comparison need their own host. I ran yamllint on the three changed YAML files directly inpipelinecomponents/yamllint, and it is clean.check_docs.py:docs OK. CI runs the full set.make validatepasses (with the skips listed above)Deployed to the lab and confirmed working
Docs updated (
docs/, service README, ordocs/roadmap.md)After merge, on
trinity(converge doesn't reinstall timers):prometheusgets the rule change through its own converge. A changelog entry for the deploy comes when that has run.🤖 Generated with Claude Code