SSH host key policy
The measured problem, the two policies, and how rotation is reconciled before the dial.
The problem, measured
On our own fleet, one Linux runner regenerates /etc/ssh/ssh_host_*_key four seconds
after every boot (cloud-init present) — while other machines in the same fleet never
rotate at all. Every dial to the rebooted machine failed until someone re-pinned by hand,
and re-pinning by hand is precisely the habit that makes a real key substitution
invisible.
Blanket StrictHostKeyChecking=no is not the answer: it also admits a brand-new host
answering on a reused address, which is the one case the guard exists for.
Keys
In the file:
<id> is the machine id from the registry — the same string used as the Host stanza name
in ~/.saphan/ssh/config. Resolution order: per machine → [fleet] → built-in
refuse.
Behaviour
| Policy | Known machine, key unchanged | Known machine, key changed | Unknown machine |
|---|---|---|---|
refuse (default) | dial | refuse, fail loud, event recorded | refuse |
accept_rotation | dial | re-pin, dial, event recorded | refuse — this is not a rotation |
Four constraints, each derived from measurement rather than taste:
accept_rotationis not blind trust-on-first-use. It applies only to a rotation on a machine already pinned. First contact with an unknown host is refused under both policies — the answer to an unknown host is to admit the machine.- Accepting always leaves a trace. The record receives host, old fingerprints, new fingerprints, timestamp, and the policy that allowed it. A silent acceptance belongs to the same class as a skipped test counted as passed.
- Per-machine scope is required, not decorative. Rotation is routine on some machines
in a fleet and unheard of on others; a single global
accept_rotationwould disarm the guard exactly where rotation is not normal. - The capability registry is pinned to the machine's device key, not to its SSH host key. Otherwise every reboot would invalidate that machine's measured confinement capability and probe history — and measurement after a real reboot showed those values are unaffected by rotation.
How it is enforced
ssh itself stays strict under both policies. The generated ~/.saphan/ssh/config
emits, for every machine stanza:
Two consequences worth stating plainly:
- The fleet has its own pin store. It never inherits, and never edits, your personal
~/.ssh/known_hosts. accept_rotationis never implemented by relaxing ssh. Rotation is reconciled before the dial: the live keys are compared against the pin, the policy is applied, a permitted re-pin is deliberate, and the outcome is typed —match·rotated-accepted·rotated-refused·unknown-host. Four distinct states of the world; a caller never has to infer one from another.
Direction of travel
SSH host certificates signed by a fleet CA. With those, rotation stops being an event
for ssh at all and ssh_host_key_policy remains only for machines outside the CA. That is
the option that removes the problem rather than managing it, and it is separate, planned
work.