a73x

docs/cert-rotation.md

Ref:   Size: 2.6 KiB   History

# Server certificate rotation

The server's QUIC identity is a self-signed ECDSA cert generated on first
start (`transport.GenerateServerCert`, 2-year validity) and persisted next to
the database (`server.crt` / `server.key`). Agents trust it by **fingerprint
pin** carried in the join blob—not by CA path and **not by expiry**: an
expired cert keeps working for already-enrolled agents. Rotation is therefore
never an emergency at `NotAfter`; it is hygiene that bounds how long a stolen
`server.key` stays useful.

The server logs its cert fingerprint at every startup. It warns, at startup
and daily thereafter, when the cert is within 90 days of expiry
(`transport.CertRenewalDue`).

## Why rotate

- `server.key` compromise: anyone holding it can impersonate the server to
  every agent pinning that cert's fingerprint, until every agent re-pins.
- The validity period (2 years) is the *scheduled* cadence; rotate immediately
  on suspected key exposure.

## Ceremony

Rotating the cert changes its fingerprint, which invalidates every agent's
pin. Each agent must re-enroll to pick up the new fingerprint.

1. Stop the server.
2. Move the old cert+key aside: `mv server.crt server.crt.old && mv
   server.key server.key.old` (in the DB directory).
3. Start the server—it generates and persists a fresh 2-year cert and logs
   the new fingerprint.
4. For each host: mint a join token (`POST /api/v1/enroll-tokens`), copy the
   `join` blob, run `eitri-agent join <blob>` on the host, **then restart the
   agent daemon** (e.g. `systemctl restart eitri-agent`). `join` only rewrites
   the on-disk identity; the running daemon holds its identity in memory and
   keeps pinning the old fingerprint until restarted. Running VMs are
   untouched—they survive agent restarts by design, and the agent's
   reconcile state is independent of its server identity.
5. Delete `server.crt.old` / `server.key.old` once every host has reconnected
   (watch `GET /api/v1/hosts` for `online: true`).

Until a host re-enrolls, its agent logs `server cert pin mismatch` and backs
off—VMs keep running, but the host is dark to the control plane. Rotate
during a window where that is acceptable, host by host.

## Out of scope (future)

- Overlap rotation (serving old+new certs simultaneously)—needs dual-cert
  listener support.
- Pushing new pins over the existing authenticated channel (would remove the
  re-enroll requirement).

## Related

- [credential-revocation.md](credential-revocation.md)—revoking a leaked host
  credential (generation bump), the complementary emergency lever
- [connecting](connecting.md)—the SSH CAs, which are separate from the
  QUIC identity rotated here