ssh/tailssh: check if user matching autogroup:nonroot is root

Add a check to ensure that the user being matched to an
autogroup:nonroot rule is in fact a non-root user on the system.

Updates https://github.com/tailscale/corp/issues/43245

Signed-off-by: Mario Minardi <mario@tailscale.com>
This commit is contained in:
Mario Minardi
2026-07-27 17:18:34 -06:00
committed by Mario Minardi
parent f3ec43d7dd
commit e48e7b730a
5 changed files with 166 additions and 13 deletions
@@ -698,6 +698,15 @@ func (s *Server) SetMasqueradeAddresses(pairs []MasqueradePair) {
s.updateLocked("SetMasqueradeAddresses", s.nodeIDsLocked(0))
}
// SetSSHPolicy sets the SSH policy sent in MapResponses and notifies all
// connected nodes so they pick up the change.
func (s *Server) SetSSHPolicy(policy *tailcfg.SSHPolicy) {
s.mu.Lock()
defer s.mu.Unlock()
s.SSHPolicy = policy
s.updateLocked("SetSSHPolicy", s.nodeIDsLocked(0))
}
// SetNodeCapMap overrides the capability map the specified client receives.
func (s *Server) SetNodeCapMap(nodeKey key.NodePublic, capMap tailcfg.NodeCapMap) {
s.mu.Lock()
+34
View File
@@ -12,6 +12,8 @@ import (
"time"
"github.com/creachadair/mds/shell"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/tstest/natlab/vmtest"
"tailscale.com/tstest/natlab/vnet"
)
@@ -37,6 +39,8 @@ func TestTailscaleSSH_Ubuntu(t *testing.T) {
if out, code := testSuite.ssh(t, "nosuchuser", "true"); code == 0 {
t.Errorf("ubuntu nonexistent user: ssh succeeded, want failure:\n%s", out)
}
testSuite.checkAutogroupNonroot(t, "ubuntu")
}
// TestTailscaleSSH_FreeBSD exercises the Tailscale SSH server ("tailscale up
@@ -60,6 +64,8 @@ func TestTailscaleSSH_FreeBSD(t *testing.T) {
if out, code := testSuite.ssh(t, "nosuchuser", "true"); code == 0 {
t.Errorf("freebsd nonexistent user: ssh succeeded, want failure:\n%s", out)
}
testSuite.checkAutogroupNonroot(t, "freebsd")
}
// TestTailscaleSSH_Gokrazy exercises the gokrazy-specific cases in the
@@ -157,6 +163,34 @@ func (st *sshTest) check(t *testing.T, desc, user string, cmd, want string) {
}
}
// checkAutogroupNonroot adjusts the server's SSH policy to be equivalent to "autogroup:nonroot"
// and verifies that attempting to SSH as root fails.
func (st *sshTest) checkAutogroupNonroot(t *testing.T, name string) {
t.Helper()
orignalSSHPolicy := st.env.ControlServer().SSHPolicy
defer func() {
st.env.ControlServer().SetSSHPolicy(orignalSSHPolicy)
}()
st.env.ControlServer().SetSSHPolicy(&tailcfg.SSHPolicy{
Rules: []*tailcfg.SSHRule{{
Principals: []*tailcfg.SSHPrincipal{{Any: true}},
SSHUsers: map[string]string{"*": "="},
Action: &tailcfg.SSHAction{Accept: true},
}},
})
if err := tstest.WaitFor(30*time.Second, func() error {
_, code := st.ssh(t, "root", "true")
if code == 0 {
return fmt.Errorf("root SSH still succeeds")
}
return nil
}); err != nil {
t.Fatalf("%s: root SSH still succeeds after policy update to autogroup:nonroot-only; policy may not have propagated", name)
}
}
func newTestSuite(t *testing.T, serverName string, serverOS vmtest.OSImage) *sshTest {
t.Helper()
env := vmtest.New(t)
+3 -2
View File
@@ -1779,8 +1779,9 @@ func (e *Env) initVnet() {
e.server.ControlServer().SSHPolicy = &tailcfg.SSHPolicy{
Rules: []*tailcfg.SSHRule{{
Principals: []*tailcfg.SSHPrincipal{{Any: true}},
SSHUsers: map[string]string{"*": "="},
Action: &tailcfg.SSHAction{Accept: true},
// Allow permissive login + root login by default
SSHUsers: map[string]string{"*": "=", "root": "root"},
Action: &tailcfg.SSHAction{Accept: true},
}},
}
break