diff --git a/.changes/v1.16/UPGRADE NOTES-20260330-145227.yaml b/.changes/v1.16/UPGRADE NOTES-20260330-145227.yaml new file mode 100644 index 0000000000..3e6f41ce88 --- /dev/null +++ b/.changes/v1.16/UPGRADE NOTES-20260330-145227.yaml @@ -0,0 +1,5 @@ +kind: UPGRADE NOTES +body: Provisioner bastion_host_key is now correctly applied. Existing usage of bastion_host_key should verify the configured key is correct. +time: 2026-03-30T14:52:27.996705-04:00 +custom: + Issue: "38318" diff --git a/internal/communicator/ssh/communicator_test.go b/internal/communicator/ssh/communicator_test.go index f94eb05698..da42a07c6d 100644 --- a/internal/communicator/ssh/communicator_test.go +++ b/internal/communicator/ssh/communicator_test.go @@ -103,6 +103,19 @@ func newMockLineServer(t *testing.T, signer ssh.Signer, pubKey string) string { } t.Log("Accepted channel") + go func() { + buf := make([]byte, 64) + n, _ := channel.Read(buf) + if n > 0 { + // this unusual test server ends up here when we're trying + // to handshake through a bastion instance. It's the only + // test that uses this path, and only if the test wasn't + // working, so just close the channel and let it fail. + t.Logf("unexpected test server read: %q, closing channel\n", buf[:n]) + channel.Close() + } + }() + go func(in <-chan *ssh.Request) { defer channel.Close() for req := range in { @@ -901,3 +914,41 @@ func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh. return nil, fmt.Errorf("public key rejected") } } + +func TestBastionHostKey(t *testing.T) { + bastionAddr := newMockLineServer(t, nil, testClientPublicKey) + bastionHost, p, _ := net.SplitHostPort(bastionAddr) + bastionPort, _ := strconv.Atoi(p) + + // there doesn't need to be a real end server, we should abort before + // initiating the second connection because BastionHostKey is wrong for + // testServerPrivateKey + connInfo := &connectionInfo{ + User: "none", + Password: "none", + Host: "127.0.0.1", + Port: uint16(9999), + Timeout: "1s", + + BastionUser: "user", + BastionPassword: "pass", + BastionHost: bastionHost, + BastionHostKey: testClientPublicKey, + BastionPort: uint16(bastionPort), + } + + cfg, err := prepareSSHConfig(connInfo) + if err != nil { + t.Fatal(err) + } + + c := &Communicator{ + connInfo: connInfo, + config: cfg, + } + + _, err = c.newSession() + if err == nil || !strings.Contains(err.Error(), "Error connecting to bastion: ssh: handshake failed: knownhosts: key mismatch") { + t.Fatalf("expected host key mismatch, got error:%v", err) + } +} diff --git a/internal/communicator/ssh/provisioner.go b/internal/communicator/ssh/provisioner.go index 44d75006cd..aa014aa3f0 100644 --- a/internal/communicator/ssh/provisioner.go +++ b/internal/communicator/ssh/provisioner.go @@ -297,7 +297,7 @@ func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) { host: bastionHost, privateKey: connInfo.BastionPrivateKey, password: connInfo.BastionPassword, - hostKey: connInfo.HostKey, + hostKey: connInfo.BastionHostKey, certificate: connInfo.BastionCertificate, sshAgent: sshAgent, })