diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 9ca8418ef..2280c6a5a 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -7,8 +7,10 @@ import ( "bufio" + "bytes" "io" "os" + "os/exec" "runtime" "runtime/debug" "strings" @@ -434,3 +436,12 @@ func etcAptSourceFileIsDisabled(r io.Reader) bool { } return disabled } + +// IsSELinuxEnforcing reports whether SELinux is in "Enforcing" mode. +func IsSELinuxEnforcing() bool { + if runtime.GOOS != "linux" { + return false + } + out, _ := exec.Command("getenforce").Output() + return string(bytes.TrimSpace(out)) == "Enforcing" +} diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index efa4088ec..330f2a8c2 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -4,7 +4,6 @@ package ipnlocal import ( - "bytes" "context" "encoding/base64" "encoding/json" @@ -18,7 +17,6 @@ "net/netip" "net/url" "os" - "os/exec" "os/user" "path/filepath" "runtime" @@ -2583,7 +2581,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error { if distro.Get() == distro.QNAP && !envknob.UseWIPCode() { return errors.New("The Tailscale SSH server does not run on QNAP.") } - checkSELinux() + b.updateSELinuxHealthWarning() // otherwise okay case "darwin": // okay only in tailscaled mode for now. @@ -4705,12 +4703,8 @@ func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) { var warnSSHSELinux = health.NewWarnable() -func checkSELinux() { - if runtime.GOOS != "linux" { - return - } - out, _ := exec.Command("getenforce").Output() - if string(bytes.TrimSpace(out)) == "Enforcing" { +func (b *LocalBackend) updateSELinuxHealthWarning() { + if hostinfo.IsSELinuxEnforcing() { warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux")) } else { warnSSHSELinux.Set(nil) @@ -4722,7 +4716,7 @@ func (b *LocalBackend) handleSSHConn(c net.Conn) (err error) { if err != nil { return err } - checkSELinux() + b.updateSELinuxHealthWarning() return s.HandleSSHConn(c) } diff --git a/ssh/tailssh/incubator.go b/ssh/tailssh/incubator.go index a0e79011e..4de3e2b88 100644 --- a/ssh/tailssh/incubator.go +++ b/ssh/tailssh/incubator.go @@ -34,6 +34,7 @@ "golang.org/x/exp/slices" "golang.org/x/sys/unix" "tailscale.com/cmd/tailscaled/childproc" + "tailscale.com/hostinfo" "tailscale.com/tempfork/gliderlabs/ssh" "tailscale.com/types/logger" "tailscale.com/version/distro" @@ -120,10 +121,18 @@ func (ss *sshSession) newIncubatorCommand() (cmd *exec.Cmd) { if isShell { incubatorArgs = append(incubatorArgs, "--shell") } - if isShell || runtime.GOOS == "darwin" { - // Only the macOS version of the login command supports executing a - // command, all other versions only support launching a shell - // without taking any arguments. + // Only the macOS version of the login command supports executing a + // command, all other versions only support launching a shell + // without taking any arguments. + shouldUseLoginCmd := isShell || runtime.GOOS == "darwin" + if hostinfo.IsSELinuxEnforcing() { + // If we're running on a SELinux-enabled system, the login + // command will be unable to set the correct context for the + // shell. Fall back to using the incubator to launch the shell. + // See http://github.com/tailscale/tailscale/issues/4908. + shouldUseLoginCmd = false + } + if shouldUseLoginCmd { if lp, err := exec.LookPath("login"); err == nil { incubatorArgs = append(incubatorArgs, "--login-cmd="+lp) }