mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-14 17:43:03 -04:00
tstest/natlab: split SSH test into separate tests
Split TestTailscaleSSH into separate tests per host OS being tested to allow for potentially running these tests in parallel on different machines. Updates https://github.com/tailscale/tailscale/issues/13038 Signed-off-by: Mario Minardi <mario@tailscale.com>
This commit is contained in:
@@ -16,37 +16,77 @@
|
||||
"tailscale.com/tstest/natlab/vnet"
|
||||
)
|
||||
|
||||
// TestTailscaleSSH exercises the Tailscale SSH server ("tailscale up --ssh",
|
||||
// not a system sshd) on Ubuntu, FreeBSD, and gokrazy nodes, with an Ubuntu node
|
||||
// as the SSH client.
|
||||
//
|
||||
// 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
|
||||
// falls back to a synthesized root user (uid 0, home dir "/") when user
|
||||
// lookup fails, since gokrazy has no user database; and the incubator's
|
||||
// findSU refuses to use su on gokrazy, so sessions are handled in-process.
|
||||
func TestTailscaleSSH(t *testing.T) {
|
||||
env := vmtest.New(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()
|
||||
// TestTailscaleSSH_Ubuntu exercises the Tailscale SSH server ("tailscale up
|
||||
// --ssh", not a system sshd) on an Ubuntu node, with an Ubuntu node as the SSH
|
||||
// client. It tests logging in as both root and a non-root user, which
|
||||
// exercises the incubator's su-based login path.
|
||||
func TestTailscaleSSH_Ubuntu(t *testing.T) {
|
||||
env, _, ubuntu, ssh, waitSSH, check := newTailscaleSSHTest(t, "ubuntu", vmtest.Ubuntu2404)
|
||||
|
||||
if out, err := env.SSHExec(ubuntu, "useradd -m -s /bin/bash tsuser"); err != nil {
|
||||
t.Fatalf("useradd: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
ubuntuIP := tailscaleIP4(t, env, ubuntu)
|
||||
waitSSH("ubuntu", "root", ubuntuIP)
|
||||
|
||||
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")
|
||||
|
||||
if out, code := ssh("nosuchuser", ubuntuIP, "true"); code == 0 {
|
||||
t.Errorf("ubuntu nonexistent user: ssh succeeded, want failure:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTailscaleSSH_FreeBSD exercises the Tailscale SSH server ("tailscale up
|
||||
// --ssh", not a system sshd) on a FreeBSD node, with an Ubuntu node as the SSH
|
||||
// client. It tests logging in as both root and a non-root user, which
|
||||
// exercises the incubator's su-based login path.
|
||||
func TestTailscaleSSH_FreeBSD(t *testing.T) {
|
||||
env, _, freebsd, ssh, waitSSH, check := newTailscaleSSHTest(t, "freebsd", vmtest.FreeBSD150)
|
||||
|
||||
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)
|
||||
waitSSH("freebsd", "root", freebsdIP)
|
||||
|
||||
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")
|
||||
|
||||
if out, code := ssh("nosuchuser", freebsdIP, "true"); code == 0 {
|
||||
t.Errorf("freebsd nonexistent user: ssh succeeded, want failure:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTailscaleSSH_Gokrazy exercises the gokrazy-specific cases in the
|
||||
// Tailscale SSH server ("tailscale up --ssh", not a system sshd), with an
|
||||
// Ubuntu node as the SSH client. util/osuser hard-codes the login shell to
|
||||
// /tmp/serial-busybox/ash and falls back to a synthesized root user (uid 0,
|
||||
// home dir "/") when user lookup fails, since gokrazy has no user database;
|
||||
// and the incubator's findSU refuses to use su on gokrazy, so sessions are
|
||||
// handled in-process.
|
||||
func TestTailscaleSSH_Gokrazy(t *testing.T) {
|
||||
env, _, gokrazy, _, waitSSH, check := newTailscaleSSHTest(t, "gokrazy", vmtest.Gokrazy)
|
||||
|
||||
gokrazyIP := tailscaleIP4(t, env, gokrazy)
|
||||
waitSSH("gokrazy", "root", gokrazyIP)
|
||||
|
||||
// $0 is the login shell the session ran the command with, which on gokrazy
|
||||
// is hard-coded rather than looked up with getent. These sessions also
|
||||
// implicitly exercise the incubator's su-less in-process path, since
|
||||
// gokrazy has no su.
|
||||
check("gokrazy root login shell", "root", gokrazyIP, "echo $0", "/tmp/serial-busybox/ash")
|
||||
check("gokrazy nonexistent user maps to root", "nosuchuser", gokrazyIP, "pwd", "/")
|
||||
}
|
||||
|
||||
func newTailscaleSSHTest(t *testing.T, serverName string, serverOS vmtest.OSImage) (*vmtest.Env, *vmtest.Node, *vmtest.Node, func(string, netip.Addr, string) (string, int), func(string, string, netip.Addr), func(string, string, netip.Addr, string, string)) {
|
||||
t.Helper()
|
||||
env := vmtest.New(t)
|
||||
client := sshTestNode(env, "client", vmtest.Ubuntu2404)
|
||||
server := sshTestNode(env, serverName, serverOS, vmtest.TailscaleSSH())
|
||||
env.Start()
|
||||
|
||||
// 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
|
||||
@@ -97,9 +137,6 @@ func TestTailscaleSSH(t *testing.T) {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
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()
|
||||
@@ -113,30 +150,7 @@ 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
|
||||
// gokrazy is hard-coded rather than looked up with getent. These
|
||||
// sessions also implicitly exercise the incubator's su-less
|
||||
// in-process path, since gokrazy has no su.
|
||||
check("gokrazy root login shell", "root", gokrazyIP, "echo $0", "/tmp/serial-busybox/ash")
|
||||
check("gokrazy nonexistent user maps to root", "nosuchuser", gokrazyIP, "pwd", "/")
|
||||
return env, client, server, ssh, waitSSH, check
|
||||
}
|
||||
|
||||
// sshTestNode adds a node named name running img behind its own
|
||||
|
||||
Reference in New Issue
Block a user