ssh/tailssh: work around lack of scontext in SELinux

Trying to SSH when SELinux is enforced results in errors like:

```
➜  ~ ssh ec2-user@<ip>
Last login: Thu Jun  1 22:51:44 from <ip2>
ec2-user: no shell: Permission denied
Connection to <ip> 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 <maisem@tailscale.com>
This commit is contained in:
Maisem Ali 2023-06-01 16:13:18 -07:00 committed by Maisem Ali
parent 0ed088b47b
commit 2ae670eb71
3 changed files with 28 additions and 14 deletions

View File

@ -7,8 +7,10 @@
import ( import (
"bufio" "bufio"
"bytes"
"io" "io"
"os" "os"
"os/exec"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
@ -434,3 +436,12 @@ func etcAptSourceFileIsDisabled(r io.Reader) bool {
} }
return disabled 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"
}

View File

@ -4,7 +4,6 @@
package ipnlocal package ipnlocal
import ( import (
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@ -18,7 +17,6 @@
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -2583,7 +2581,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() { if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server does not run on QNAP.") return errors.New("The Tailscale SSH server does not run on QNAP.")
} }
checkSELinux() b.updateSELinuxHealthWarning()
// otherwise okay // otherwise okay
case "darwin": case "darwin":
// okay only in tailscaled mode for now. // okay only in tailscaled mode for now.
@ -4705,12 +4703,8 @@ func (b *LocalBackend) sshServerOrInit() (_ SSHServer, err error) {
var warnSSHSELinux = health.NewWarnable() var warnSSHSELinux = health.NewWarnable()
func checkSELinux() { func (b *LocalBackend) updateSELinuxHealthWarning() {
if runtime.GOOS != "linux" { if hostinfo.IsSELinuxEnforcing() {
return
}
out, _ := exec.Command("getenforce").Output()
if string(bytes.TrimSpace(out)) == "Enforcing" {
warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux")) warnSSHSELinux.Set(errors.New("SELinux is enabled; Tailscale SSH may not work. See https://tailscale.com/s/ssh-selinux"))
} else { } else {
warnSSHSELinux.Set(nil) warnSSHSELinux.Set(nil)
@ -4722,7 +4716,7 @@ func (b *LocalBackend) handleSSHConn(c net.Conn) (err error) {
if err != nil { if err != nil {
return err return err
} }
checkSELinux() b.updateSELinuxHealthWarning()
return s.HandleSSHConn(c) return s.HandleSSHConn(c)
} }

View File

@ -34,6 +34,7 @@
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"tailscale.com/cmd/tailscaled/childproc" "tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/hostinfo"
"tailscale.com/tempfork/gliderlabs/ssh" "tailscale.com/tempfork/gliderlabs/ssh"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/version/distro" "tailscale.com/version/distro"
@ -120,10 +121,18 @@ func (ss *sshSession) newIncubatorCommand() (cmd *exec.Cmd) {
if isShell { if isShell {
incubatorArgs = append(incubatorArgs, "--shell") incubatorArgs = append(incubatorArgs, "--shell")
} }
if isShell || runtime.GOOS == "darwin" { // Only the macOS version of the login command supports executing a
// Only the macOS version of the login command supports executing a // command, all other versions only support launching a shell
// command, all other versions only support launching a shell // without taking any arguments.
// 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 { if lp, err := exec.LookPath("login"); err == nil {
incubatorArgs = append(incubatorArgs, "--login-cmd="+lp) incubatorArgs = append(incubatorArgs, "--login-cmd="+lp)
} }