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:
@@ -44,16 +44,17 @@ type fakeKeyGen struct{ priv, pub string }
|
||||
func (f *fakeKeyGen) Generate() (string, string, error) { return f.priv, f.pub, nil }
|
||||
|
||||
type fakeInstaller struct {
|
||||
err error
|
||||
calls int
|
||||
gotPub string
|
||||
gotPriv string
|
||||
gotPw string
|
||||
err error
|
||||
calls int
|
||||
gotPub string
|
||||
gotPriv string
|
||||
gotPw string
|
||||
gotKnownHost string
|
||||
}
|
||||
|
||||
func (f *fakeInstaller) Install(_ context.Context, _, _ string, _ int, password, privPEM, pub string) error {
|
||||
func (f *fakeInstaller) Install(_ context.Context, _, _ string, _ int, password, privPEM, pub, knownHosts string) error {
|
||||
f.calls++
|
||||
f.gotPub, f.gotPriv, f.gotPw = pub, privPEM, password
|
||||
f.gotPub, f.gotPriv, f.gotPw, f.gotKnownHost = pub, privPEM, password, knownHosts
|
||||
return f.err
|
||||
}
|
||||
|
||||
@@ -108,6 +109,9 @@ func TestBridge_AppliesEndToEnd(t *testing.T) {
|
||||
if inst.calls != 1 || inst.gotPw != "the-transient-pw" || inst.gotPub == "" {
|
||||
t.Fatalf("installer not called with pw+pub: %+v", inst)
|
||||
}
|
||||
if inst.gotKnownHost != "[h]:23 ssh-ed25519 AAAAKEY" {
|
||||
t.Fatalf("installer must receive the scanner-verified known_hosts to pin (no TOFU), got %q", inst.gotKnownHost)
|
||||
}
|
||||
if en.calls != 1 || en.gotHost != "h" || en.gotKnownHost != "[h]:23 ssh-ed25519 AAAAKEY" || en.gotPriv != "PRIVPEM" {
|
||||
t.Fatalf("enabler not called with the pinned known_hosts + key: %+v", en)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user