harden offsite apply-bridge: pin verified host key on the install/verify sessions (no TOFU)

The SSHCopyIDInstaller used StrictHostKeyChecking=accept-new on the ssh-copy-id
and sftp-verify connections, so even though the bridge verifies the box host-key
fingerprint against the hub descriptor BEFORE installing, the actual install
connection was not pinned to that verified key — a MITM could substitute a
different key in the gap between the scan and the install (TOCTOU).

Now the bridge threads the scanner-verified known_hosts line into KeyInstaller,
which writes it to a temp known_hosts and connects with StrictHostKeyChecking=yes
+ UserKnownHostsFile — the install/verify sessions refuse any key but the one the
bridge already matched. Empty known_hosts now refuses to install.

Test asserts the installer receives the pinned known_hosts; red-proofed by passing
an empty line (the pre-fix TOFU shape) → test fails. Addresses the security-review
"host-key TOFU after verify" finding on internal/offsiteapply/seams.go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-09 19:23:39 +02:00
parent aa61fb3411
commit 9a34887acc
5 changed files with 42 additions and 18 deletions
+3 -1
View File
@@ -13,7 +13,9 @@ SLICE 3; soft-quota = SLICE 4.)
→ nothing persisted, retried next cycle). Flow: **scan + VERIFY the box host key against `host_fingerprint`
(no blind TOFU)** → generate the controller keypair → **consume the one-time password**
(`POST /api/v1/offsite/consume-password/{id}`, Bearer APIKey, single-use, never logged) → install the
pubkey (`sshpass -e ssh-copy-id -p 23 -s -f`) + verify key auth → configure the offbox target →
pubkey (`sshpass -e ssh-copy-id -p 23 -s -f`, **pinning the scanner-verified `known_hosts` with
`StrictHostKeyChecking=yes` — no `accept-new`/TOFU on the install or verify session**, so a MITM cannot
substitute a key in the gap between the scan and the install) + verify key auth → configure the offbox target →
`EscrowState="pending"` (fork-4 enable path via `Manager.ApplyOffsiteTarget`) → persist the marker LAST.
Seams (consume/scan/keygen/install/enable) so unit tests fake all I/O. A consumed-but-failed install logs a
loud "password is spent — reset on the hub" signal.