feat: auto-accept only new SSH host keys using "-o StrictHostKeyChecking: accept-new" (#303)

* feat: add "StrictHostKeyChecking no" to ssh

Disable host key checking to prevent interaction with ssh.

Fixes: #297

Signed-off-by: Miek Gieben <miek@miek.nl>

* use accept-new instead of no

Signed-off-by: Miek Gieben <miek@miek.nl>

* fix tests

Signed-off-by: Miek Gieben <miek@miek.nl>

* Fix tests here as well

Signed-off-by: Miek Gieben <miek@miek.nl>

---------

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben
2026-04-14 18:00:59 +10:00
committed by GitHub
parent 9cfcf49d28
commit e8111a419f
4 changed files with 18 additions and 15 deletions
+1
View File
@@ -33,6 +33,7 @@ func NewSSHCLIRemote(user, host string, port int, keyPath string) *SSHCLIRemote
func (r *SSHCLIRemote) newSSHCommand(ctx context.Context, cmd string) *exec.Cmd {
args := []string{
"-o", "ConnectTimeout=5",
"-o", "StrictHostKeyChecking=accept-new",
// Disable pseudo-terminal allocation to prevent SSH from executing as a login shell.
"-T",
}
+8 -8
View File
@@ -24,7 +24,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
host: "example.com",
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "example.com", "whoami",
},
},
{
@@ -33,7 +33,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
host: "example.com",
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "root@example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "root@example.com", "whoami",
},
},
{
@@ -42,7 +42,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
port: 2222,
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-p", "2222", "example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "2222", "example.com", "whoami",
},
},
{
@@ -51,7 +51,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
keyPath: "/path/to/key",
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-i", "/path/to/key", "example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-i", "/path/to/key", "example.com", "whoami",
},
},
{
@@ -61,7 +61,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
port: 2222,
cmd: "ls -la",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-p", "2222", "ubuntu@192.168.1.10", "ls -la",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "2222", "ubuntu@192.168.1.10", "ls -la",
},
},
{
@@ -71,7 +71,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
keyPath: "/path/to/key",
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-i", "/path/to/key", "root@example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-i", "/path/to/key", "root@example.com", "whoami",
},
},
{
@@ -81,7 +81,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
keyPath: "~/.ssh/id_rsa",
cmd: "whoami",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-p", "22", "-i", "~/.ssh/id_rsa", "example.com", "whoami",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "22", "-i", "~/.ssh/id_rsa", "example.com", "whoami",
},
},
{
@@ -92,7 +92,7 @@ func TestSSHCLIRemote_newSSHCommand(t *testing.T) {
keyPath: "~/.ssh/id_rsa",
cmd: "sudo bash -c 'echo hello'",
expected: []string{
"ssh", "-o", "ConnectTimeout=5", "-T", "-p", "2222", "-i", "~/.ssh/id_rsa",
"ssh", "-o", "ConnectTimeout=5", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "2222", "-i", "~/.ssh/id_rsa",
"admin@server.local", "sudo bash -c 'echo hello'",
},
},
+2
View File
@@ -131,6 +131,8 @@ func (c *SSHCLIConnector) buildSSHArgs() []string {
// Disable interactive prompts (e.g., passphrase input) to prevent interference with the TUI.
// Authentication must succeed non-interactively via SSH agent or unencrypted key.
args = append(args, "-o", "BatchMode=yes")
// Disable host key checking for parity with go+ssh.
args = append(args, "-o", "StrictHostKeyChecking=accept-new")
// Disable pseudo-terminal allocation to prevent SSH from executing as a login shell.
args = append(args, "-T")
+7 -7
View File
@@ -23,7 +23,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
Host: "example.com",
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "root@example.com"},
},
{
name: "basic connection without control socket",
@@ -32,7 +32,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
Host: "example.com",
},
controlSockPath: "",
expected: []string{"-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "root@example.com"},
expected: []string{"-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "root@example.com"},
},
{
name: "with custom port",
@@ -42,7 +42,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
Port: 2222,
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "-p", "2222", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "2222", "root@example.com"},
},
{
name: "with identity file",
@@ -52,7 +52,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
KeyPath: "/path/to/key",
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "-i", "/path/to/key", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "-i", "/path/to/key", "root@example.com"},
},
{
name: "all options combined",
@@ -64,7 +64,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
SockPath: "/custom/path/uncloud.sock",
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "-p", "2222", "-i", "/path/to/key", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "2222", "-i", "/path/to/key", "root@example.com"},
},
{
name: "port 0 not included",
@@ -74,7 +74,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
Port: 0,
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "root@example.com"},
},
{
name: "port 22 included when explicit",
@@ -84,7 +84,7 @@ func TestSSHCLIConnector_buildSSHArgs(t *testing.T) {
Port: 22,
},
controlSockPath: "/tmp/test.sock",
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-T", "-p", "22", "root@example.com"},
expected: []string{"-o", "ControlMaster=auto", "-o", "ControlPath=/tmp/test.sock", "-o", "ControlPersist=10m", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-T", "-p", "22", "root@example.com"},
},
}