Skip to content

Back up certificates/ca-key.pem, and prove the copy #496

Description

@Gerrrt

certificates/ca-key.pem — the estate's root, the one that signs Grafana's
two leaves — is the only root secret in the estate with no custody story. The
age key has ADR-0024 and back-up-the-age-key.md: a second recipient off-host
and off-estate, proved on demand against the real ciphertext, nagged by
SecretsKeyBackupUnproven at ninety days. The tier's root goes to the same
offline medium by build-the-tier-ca.md. The estate's root has nothing — not
a copy, not a sentence.

Found while deciding #436, and recorded in
ADR-0043
decision 4 with the constraints below, so that this issue starts from them
rather than re-litigating them.

What the loss actually costs

Bounded, not fatal — and worth stating accurately, because the age key's
"unbounded downside" does not apply here. The root is pathlen:0, so there is
no intermediate to keep issuing; but there is a re-mint path, and it is
already written: make certs ARGS='--ca --force', two leaves reissued, one
scp -3 through the Mac for alexander's, and a re-trust everywhere the old
ca.pem was imported (successor-handover.md, ADR-0037's cost table).

Two things about that path are the real gap:

  • The trust-distribution list is unknown. ADR-0037: "the Mac's system
    store and Firefox's separate one, at least, and whatever else nobody wrote
    down." A re-trust of unknown size is the defect.
  • The deadline is not 825 days. It is the next time a leaf is needed —
    the next VLAN 30 service that wants TLS.

What the answer is not

  • Not SOPS-in-git. ADR-0037 rejected that for the tier's root: "a
    passphrase-encrypted CA private key in git is the exact artefact that was
    purged from this repository's history." This key is one of the ones purged.
  • Not a passphrase. gen-certs.sh closes that trade deliberately, in its
    header, and says how to reopen it if you disagree. This issue does not.

So the control is medium, not cryptography: one copy, on the offline medium
that already holds the age key's second copy and the tier's root, and nowhere
else. back-up-the-age-key.md's disqualifiers apply as written — not on the
monitoring host, not in a git tree, not in a synced folder.

What the proof would be

The age key's proof decrypts real ciphertext. A CA key's proof needs no
decryption at all: the public half of the backup against the public half of
ca.pem.

openssl rsa -in <backup> -pubout 2>/dev/null | sha256sum
openssl x509 -in certificates/ca.pem -pubkey -noout | sha256sum

Equal, or the copy is not this key. A sibling of scripts/verify-key-backup.sh
with the same refusal to run against the live key by device and inode, the
same "prints no secret" property, and a homelab_*_last_proof_timestamp_seconds
series so that a CaKeyBackupUnproven rule can nag the way ADR-0024's does.
One sentence from the ADR justifies the split: an age key has no re-mint path
and a CA does, which is why this key gets the age key's backup first and its
alerting second.

Order of work

  1. The copy, and the sentence in generate-certificates.md's file table that
    says it exists — plus successor-handover.md, where "both live only on
    prometheus" stops being true for one of the two.
  2. Write down where ca.pem is trusted, so the re-mint path has a list.
  3. The proof script and its series.
  4. The rule.

Refs #436, ADR-0043, ADR-0024, ADR-0037.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecurityseq/1Step 1 within its milestone; same number = can run in parallel

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions