From 8bc4c09a46dd0ea5c2710738d9177bc4cb4dc0df Mon Sep 17 00:00:00 2001 From: Mario Minardi Date: Mon, 13 Jul 2026 21:09:26 -0600 Subject: [PATCH] tstest/natlab: add FreeBSD test case for SSH test Add logic to generateFreeBSDUserData to allow for an SSH connection as root for FreeBSD. Add FreeBSD test cases to ssh_test that exercise the same paths as the existing Ubuntu tests but for BSD. Updates https://github.com/tailscale/corp/issues/44813 Updates https://github.com/tailscale/tailscale/issues/13038 Signed-off-by: Mario Minardi --- tstest/natlab/vmtest/cloudinit.go | 9 +++++++++ tstest/natlab/vmtest/ssh_test.go | 21 ++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tstest/natlab/vmtest/cloudinit.go b/tstest/natlab/vmtest/cloudinit.go index 7d460e1ba..ad226ee89 100644 --- a/tstest/natlab/vmtest/cloudinit.go +++ b/tstest/natlab/vmtest/cloudinit.go @@ -157,6 +157,15 @@ func (e *Env) generateFreeBSDUserData(n *Node) string { ud.WriteString("runcmd:\n") + // Enable root SSH login for debugging via the debug NIC. + ud.WriteString(" - \"echo 'PermitRootLogin yes' >>/etc/ssh/sshd_config\"\n") + ud.WriteString(" - \"echo 'PubkeyAuthentication yes' >>/etc/ssh/sshd_config\"\n") + // Also inject the host's SSH key if available. + if pubkey, err := os.ReadFile("/tmp/vmtest_key.pub"); err == nil { + ud.WriteString(" - \"mkdir -p /root/.ssh && chmod 700 /root/.ssh\"\n") + fmt.Fprintf(&ud, " - \"echo '%s' >/root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys\"\n", strings.TrimSpace(string(pubkey))) + } + // /usr/local/bin may not exist on a fresh FreeBSD cloud image (it's // created when the first package is installed). ud.WriteString(" - \"mkdir -p /usr/local/bin\"\n") diff --git a/tstest/natlab/vmtest/ssh_test.go b/tstest/natlab/vmtest/ssh_test.go index 2020de9c4..2e876a93e 100644 --- a/tstest/natlab/vmtest/ssh_test.go +++ b/tstest/natlab/vmtest/ssh_test.go @@ -17,11 +17,11 @@ ) // TestTailscaleSSH exercises the Tailscale SSH server ("tailscale up --ssh", -// not a system sshd) on both Ubuntu and gokrazy nodes, with an Ubuntu node +// not a system sshd) on Ubuntu, FreeBSD, and gokrazy nodes, with an Ubuntu node // as the SSH client. // -// On Ubuntu it tests logging in as both root and a non-root user, which -// exercises the incubator's su-based login path. +// On Ubuntu and FreeBSD it tests logging in as both root and a non-root user, +// which exercises the incubator's su-based login path. // // On gokrazy it exercises the gokrazy special cases in the SSH code: // util/osuser hard-codes the login shell to /tmp/serial-busybox/ash and @@ -33,14 +33,20 @@ func TestTailscaleSSH(t *testing.T) { client := sshTestNode(env, "client", vmtest.Ubuntu2404) ubuntu := sshTestNode(env, "ubuntu", vmtest.Ubuntu2404, vmtest.TailscaleSSH()) gokrazy := sshTestNode(env, "gokrazy", vmtest.Gokrazy, vmtest.TailscaleSSH()) + freebsd := sshTestNode(env, "freebsd", vmtest.FreeBSD150, vmtest.TailscaleSSH()) env.Start() if out, err := env.SSHExec(ubuntu, "useradd -m -s /bin/bash tsuser"); err != nil { t.Fatalf("useradd: %v\n%s", err, out) } + if out, err := env.SSHExec(freebsd, "pw useradd tsuser -m"); err != nil { + t.Fatalf("pw useradd: %v\n%s", err, out) + } + ubuntuIP := tailscaleIP4(t, env, ubuntu) gokrazyIP := tailscaleIP4(t, env, gokrazy) + freebsdIP := tailscaleIP4(t, env, freebsd) // ssh runs cmd as user on the node at ip over Tailscale SSH, from the // client node, returning the combined output and the ssh client's exit @@ -93,6 +99,7 @@ func TestTailscaleSSH(t *testing.T) { } waitSSH("ubuntu", "root", ubuntuIP) waitSSH("gokrazy", "root", gokrazyIP) + waitSSH("freebsd", "root", freebsdIP) check := func(desc, user string, ip netip.Addr, cmd, want string) { t.Helper() @@ -109,11 +116,19 @@ func TestTailscaleSSH(t *testing.T) { check("ubuntu root login", "root", ubuntuIP, "id -un && pwd", "root\n/root") check("ubuntu non-root login", "tsuser", ubuntuIP, "id -un && pwd", "tsuser\n/home/tsuser") + check("freebsd root login", "root", freebsdIP, "id -un && pwd", "root\n/root") + check("freebsd non-root login", "tsuser", freebsdIP, "id -un && pwd", "tsuser\n/home/tsuser") + // A nonexistent user is rejected on Ubuntu... if out, code := ssh("nosuchuser", ubuntuIP, "true"); code == 0 { t.Errorf("ubuntu nonexistent user: ssh succeeded, want failure:\n%s", out) } + // and rejected on FreeBSD... + if out, code := ssh("nosuchuser", freebsdIP, "true"); code == 0 { + t.Errorf("freebsd nonexistent user: ssh succeeded, want failure:\n%s", out) + } + // ... but on gokrazy any username works and becomes root, via the // synthesized-user fallback in util/osuser (uid 0, home dir "/"). // $0 is the login shell the session ran the command with, which on