ssh/tailssh: accept passwords and public keys

Some clients don't request 'none' authentication. Instead, they immediately supply
a password or public key. This change allows them to do so, but ignores the supplied
credentials and authenticates using Tailscale instead.

Updates #14922

Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann
2025-02-10 11:43:08 -06:00
committed by Percy Wegmann
parent f2f7fd12eb
commit db231107a2
6 changed files with 289 additions and 108 deletions

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build integrationtest
// +build integrationtest
package tailssh
@ -410,6 +409,48 @@ func TestSSHAgentForwarding(t *testing.T) {
}
}
// TestIntegrationParamiko attempts to connect to Tailscale SSH using the
// paramiko Python library. This library does not request 'none' auth. This
// test ensures that Tailscale SSH can correctly handle clients that don't
// request 'none' auth and instead immediately authenticate with a public key
// or password.
func TestIntegrationParamiko(t *testing.T) {
debugTest.Store(true)
t.Cleanup(func() {
debugTest.Store(false)
})
addr := testServer(t, "testuser", true, false)
host, port, err := net.SplitHostPort(addr)
if err != nil {
t.Fatalf("Failed to split addr %q: %s", addr, err)
}
out, err := exec.Command("python3", "-c", fmt.Sprintf(`
import paramiko.client as pm
from paramiko.ecdsakey import ECDSAKey
client = pm.SSHClient()
client.set_missing_host_key_policy(pm.AutoAddPolicy)
client.connect('%s', port=%s, username='testuser', pkey=ECDSAKey.generate(), allow_agent=False, look_for_keys=False)
client.exec_command('pwd')
`, host, port)).CombinedOutput()
if err != nil {
t.Fatalf("failed to connect with Paramiko using public key auth: %s\n%q", err, string(out))
}
out, err = exec.Command("python3", "-c", fmt.Sprintf(`
import paramiko.client as pm
from paramiko.ecdsakey import ECDSAKey
client = pm.SSHClient()
client.set_missing_host_key_policy(pm.AutoAddPolicy)
client.connect('%s', port=%s, username='testuser', password='doesntmatter', allow_agent=False, look_for_keys=False)
client.exec_command('pwd')
`, host, port)).CombinedOutput()
if err != nil {
t.Fatalf("failed to connect with Paramiko using password auth: %s\n%q", err, string(out))
}
}
func fallbackToSUAvailable() bool {
if runtime.GOOS != "linux" {
return false