From 2ae670eb71b4d45236866c211677ab6d9081cd0c Mon Sep 17 00:00:00 2001 From: Maisem Ali Date: Thu, 1 Jun 2023 16:13:18 -0700 Subject: [PATCH] ssh/tailssh: work around lack of scontext in SELinux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trying to SSH when SELinux is enforced results in errors like: ``` ➜ ~ ssh ec2-user@ Last login: Thu Jun 1 22:51:44 from ec2-user: no shell: Permission denied Connection to closed. ``` while the `/var/log/audit/audit.log` has ``` type=AVC msg=audit(1685661291.067:465): avc: denied { transition } for pid=5296 comm="login" path="/usr/bin/bash" dev="nvme0n1p1" ino=2564 scontext=system_u:system_r:unconfined_service_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0 tclass=process permissive=0 ``` The right fix here would be to somehow install the appropriate context when tailscale is installed on host, but until we figure out a way to do that stop using the `login` cmd in these situations. Updates #4908 Signed-off-by: Maisem Ali --- hostinfo/hostinfo.go | 11 +++++++++++ ipn/ipnlocal/local.go | 14 ++++---------- ssh/tailssh/incubator.go | 17 +++++++++++++---- 3 files changed, 28 insertions(+), 14 deletions(-) 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 @@ package hostinfo 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 @@ import ( "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 @@ import ( "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) }