test(proxmox,cli): end-to-end trust-surface integration tests (T02.10, REQ-058, REQ-059)

---ci---
project: orca
phase: 2
milestone: v0.8
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-04 12:04:24 +00:00
parent 2dcb14377a
commit d66b3b9a0a
+262
View File
@@ -784,3 +784,265 @@ func TestResetHostKey_EmptyHostErrors(t *testing.T) {
t.Error("expected error for empty host, got nil")
}
}
// bootstrapE2ESetup wires the real dialer against a fake SSH server so
// the full HostKeyCallback path (pinned or TOFU) runs end-to-end through
// BootstrapProxmox. Returns the host, port, and server (for fingerprint
// computation). The known_hosts file is created empty in the temp
// ORCA_HOME.
func bootstrapE2ESetup(t *testing.T) (srv *fakeSSHServer, host, port string) {
t.Helper()
srv = newFakeSSHServer(t)
t.Cleanup(srv.close)
home := t.TempDir()
t.Setenv("ORCA_HOME", home)
if err := os.WriteFile(filepath.Join(home, "known_hosts"), []byte{}, 0o600); err != nil {
t.Fatalf("create known_hosts: %v", err)
}
orig := sshDialer
t.Cleanup(func() { sshDialer = orig })
origRunner := sessionRunner
t.Cleanup(func() { sessionRunner = origRunner })
sessionRunner = nil
sshDialer = defaultSSHDialer{}
host, port, _ = net.SplitHostPort(srv.addr())
return srv, host, port
}
// TestBootstrapE2E_PinnedFingerprintCorrect verifies that
// --host-key-fingerprint with the correct pin (T02.10 case 1) succeeds
// end-to-end and Result.HostKeyFingerprint equals the pinned value.
func TestBootstrapE2E_PinnedFingerprintCorrect(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
hostKey := srv.hostPublicKey()
if hostKey == nil {
t.Fatal("server host key is nil")
}
pin := security.SSHFingerprintSHA256(hostKey)
portNum, _ := strconv.Atoi(port)
result, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
HostKeyFingerprint: pin,
})
if err != nil {
t.Fatalf("BootstrapProxmox with correct pin: %v", err)
}
if result.HostKeyFingerprint != pin {
t.Errorf("Result.HostKeyFingerprint = %q, want %q (pinned value)",
result.HostKeyFingerprint, pin)
}
}
// TestBootstrapE2E_PinnedFingerprintWrong verifies that
// --host-key-fingerprint with a wrong pin (T02.10 case 2) fails fast
// with the REQ-058 mismatch error, before any SSH session commands run.
func TestBootstrapE2E_PinnedFingerprintWrong(t *testing.T) {
_, host, port := bootstrapE2ESetup(t)
portNum, _ := strconv.Atoi(port)
wrong := "SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
_, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
HostKeyFingerprint: wrong,
})
if err == nil {
t.Fatal("expected error for wrong pin, got nil")
}
if !strings.Contains(err.Error(), "REQ-058") {
t.Errorf("error should mention REQ-058, got: %v", err)
}
}
// TestBootstrapE2E_TOFUFirstConnectCapturesKey verifies that with no
// --host-key-fingerprint on a first connect (empty known_hosts) (T02.10
// case 3) the TOFU callback captures the key, writes known_hosts, and
// bootstrap succeeds — exercised end-to-end through BootstrapProxmox.
func TestBootstrapE2E_TOFUFirstConnectCapturesKey(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
hostKey := srv.hostPublicKey()
if hostKey == nil {
t.Fatal("server host key is nil")
}
portNum, _ := strconv.Atoi(port)
home := os.Getenv("ORCA_HOME")
knownHostsPath := filepath.Join(home, "known_hosts")
before, _ := os.ReadFile(knownHostsPath)
if len(before) != 0 {
t.Fatalf("precondition: known_hosts not empty: %q", before)
}
result, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
})
if err != nil {
t.Fatalf("BootstrapProxmox first connect: %v", err)
}
data, err := os.ReadFile(knownHostsPath)
if err != nil {
t.Fatalf("read known_hosts: %v", err)
}
if len(data) == 0 {
t.Fatal("known_hosts empty — TOFU did not capture the key end-to-end")
}
expectedFP := security.SSHFingerprintSHA256(hostKey)
if result.HostKeyFingerprint != expectedFP {
t.Errorf("Result.HostKeyFingerprint = %q, want %q", result.HostKeyFingerprint, expectedFP)
}
}
// TestBootstrapE2E_TOFUSecondConnectMatches verifies that a second
// connect (known_hosts already has the key from the first connect)
// (T02.10 case 4) matches and succeeds end-to-end.
func TestBootstrapE2E_TOFUSecondConnectMatches(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
portNum, _ := strconv.Atoi(port)
for i := 0; i < 2; i++ {
sessionRunner = nil
if _, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
}); err != nil {
t.Fatalf("bootstrap run %d: %v", i+1, err)
}
}
_ = srv
}
// TestBootstrapE2E_TOFUMismatchFails verifies that when known_hosts has
// a different key (T02.10 case 5) the second connect fails with a
// mismatch (MITM detection) — end-to-end through BootstrapProxmox.
func TestBootstrapE2E_TOFUMismatchFails(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
hostKey := srv.hostPublicKey()
if hostKey == nil {
t.Fatal("server host key is nil")
}
portNum, _ := strconv.Atoi(port)
home := os.Getenv("ORCA_HOME")
knownHostsPath := filepath.Join(home, "known_hosts")
altPub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("ed25519 gen: %v", err)
}
altKey, err := ssh.NewPublicKey(altPub)
if err != nil {
t.Fatalf("new pub: %v", err)
}
addr := host + ":" + port
altLine := knownhosts.Line([]string{knownhosts.Normalize(addr)}, altKey)
if err := os.WriteFile(knownHostsPath, []byte(altLine+"\n"), 0o600); err != nil {
t.Fatalf("write known_hosts: %v", err)
}
_, err = BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
})
if err == nil {
t.Fatal("expected MITM/mismatch error, got nil")
}
if !strings.Contains(err.Error(), "ssh dial") {
t.Errorf("error should mention ssh dial, got: %v", err)
}
}
// TestBootstrapE2E_PrePopulatedKnownHostsMatches verifies the v0.6→v0.8
// migration path (T02.10 case 7): a known_hosts entry written by a prior
// join (simulating a v0.6 install) is matched on second-connect without
// re-capture, end-to-end through BootstrapProxmox.
func TestBootstrapE2E_PrePopulatedKnownHostsMatches(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
hostKey := srv.hostPublicKey()
if hostKey == nil {
t.Fatal("server host key is nil")
}
portNum, _ := strconv.Atoi(port)
home := os.Getenv("ORCA_HOME")
knownHostsPath := filepath.Join(home, "known_hosts")
addr := host + ":" + port
preLine := knownhosts.Line([]string{knownhosts.Normalize(addr)}, hostKey)
if err := os.WriteFile(knownHostsPath, []byte(preLine+"\n"), 0o600); err != nil {
t.Fatalf("write known_hosts: %v", err)
}
result, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
})
if err != nil {
t.Fatalf("BootstrapProxmox on pre-populated known_hosts: %v", err)
}
expectedFP := security.SSHFingerprintSHA256(hostKey)
if result.HostKeyFingerprint != expectedFP {
t.Errorf("Result.HostKeyFingerprint = %q, want %q", result.HostKeyFingerprint, expectedFP)
}
}
// TestBootstrapE2E_KeyResetThenRePin verifies T02.10 case 6: after
// ResetHostKey removes the known_hosts entry, the next BootstrapProxmox
// connect re-pins the key via TOFU and succeeds end-to-end. The reset
// target is the known_hosts entry key (host:port, normalized), which
// matches how the cli resolves the host from a proxmox node's address
// for non-default ports.
func TestBootstrapE2E_KeyResetThenRePin(t *testing.T) {
srv, host, port := bootstrapE2ESetup(t)
portNum, _ := strconv.Atoi(port)
home := os.Getenv("ORCA_HOME")
knownHostsPath := filepath.Join(home, "known_hosts")
addr := host + ":" + port
// First connect: TOFU captures + writes known_hosts.
sessionRunner = nil
if _, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
}); err != nil {
t.Fatalf("first bootstrap: %v", err)
}
before, _ := os.ReadFile(knownHostsPath)
if len(before) == 0 {
t.Fatal("precondition: known_hosts empty after first connect")
}
// Reset: known_hosts entry removed. Pass the full addr (host:port)
// so Normalize produces the same bracketed form the TOFU callback
// wrote for a non-default port.
if err := ResetHostKey(addr); err != nil {
t.Fatalf("ResetHostKey: %v", err)
}
after, _ := os.ReadFile(knownHostsPath)
if strings.Contains(string(after), knownhosts.Normalize(addr)) {
t.Fatalf("known_hosts still contains host after reset: %q", after)
}
// Next connect re-pins via TOFU + succeeds.
sessionRunner = nil
if _, err := BootstrapProxmox(t.Context(), Options{
Host: host,
Password: "pw",
SSHPort: portNum,
}); err != nil {
t.Fatalf("re-pin bootstrap after reset: %v", err)
}
rePinned, _ := os.ReadFile(knownHostsPath)
if !strings.Contains(string(rePinned), knownhosts.Normalize(addr)) {
t.Fatalf("known_hosts not re-populated on next connect: %q", rePinned)
}
_ = srv
}