ssh/tailssh: calculate passthrough environment at latest possible stage

This allows passing through any environment variables that we set ourselves, for example DBUS_SESSION_BUS_ADDRESS.

Updates #11175

Co-authored-by: Mario Minardi <mario@tailscale.com>
Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann 2024-10-11 14:59:47 -05:00 committed by Percy Wegmann
parent ecc8035f73
commit 12e6094d9c

View File

@ -210,8 +210,6 @@ type incubatorArgs struct {
debugTest bool debugTest bool
isSELinuxEnforcing bool isSELinuxEnforcing bool
encodedEnv string encodedEnv string
allowListEnvKeys string
forwardedEnviron []string
} }
func parseIncubatorArgs(args []string) (incubatorArgs, error) { func parseIncubatorArgs(args []string) (incubatorArgs, error) {
@ -246,31 +244,35 @@ func parseIncubatorArgs(args []string) (incubatorArgs, error) {
ia.gids = append(ia.gids, gid) ia.gids = append(ia.gids, gid)
} }
ia.forwardedEnviron = os.Environ() return ia, nil
}
func (ia incubatorArgs) forwadedEnviron() ([]string, string, error) {
environ := os.Environ()
// pass through SSH_AUTH_SOCK environment variable to support ssh agent forwarding // pass through SSH_AUTH_SOCK environment variable to support ssh agent forwarding
ia.allowListEnvKeys = "SSH_AUTH_SOCK" allowListKeys := "SSH_AUTH_SOCK"
if ia.encodedEnv != "" { if ia.encodedEnv != "" {
unquoted, err := strconv.Unquote(ia.encodedEnv) unquoted, err := strconv.Unquote(ia.encodedEnv)
if err != nil { if err != nil {
return ia, fmt.Errorf("unable to parse encodedEnv %q: %w", ia.encodedEnv, err) return nil, "", fmt.Errorf("unable to parse encodedEnv %q: %w", ia.encodedEnv, err)
} }
var extraEnviron []string var extraEnviron []string
err = json.Unmarshal([]byte(unquoted), &extraEnviron) err = json.Unmarshal([]byte(unquoted), &extraEnviron)
if err != nil { if err != nil {
return ia, fmt.Errorf("unable to parse encodedEnv %q: %w", ia.encodedEnv, err) return nil, "", fmt.Errorf("unable to parse encodedEnv %q: %w", ia.encodedEnv, err)
} }
ia.forwardedEnviron = append(ia.forwardedEnviron, extraEnviron...) environ = append(environ, extraEnviron...)
for _, v := range extraEnviron { for _, v := range extraEnviron {
ia.allowListEnvKeys = fmt.Sprintf("%s,%s", ia.allowListEnvKeys, strings.Split(v, "=")[0]) allowListKeys = fmt.Sprintf("%s,%s", allowListKeys, strings.Split(v, "=")[0])
} }
} }
return ia, nil return environ, allowListKeys, nil
} }
// beIncubator is the entrypoint to the `tailscaled be-child ssh` subcommand. // beIncubator is the entrypoint to the `tailscaled be-child ssh` subcommand.
@ -450,8 +452,13 @@ func tryExecLogin(dlogf logger.Logf, ia incubatorArgs) error {
loginArgs := ia.loginArgs(loginCmdPath) loginArgs := ia.loginArgs(loginCmdPath)
dlogf("logging in with %+v", loginArgs) dlogf("logging in with %+v", loginArgs)
environ, _, err := ia.forwadedEnviron()
if err != nil {
return err
}
// If Exec works, the Go code will not proceed past this: // If Exec works, the Go code will not proceed past this:
err = unix.Exec(loginCmdPath, loginArgs, ia.forwardedEnviron) err = unix.Exec(loginCmdPath, loginArgs, environ)
// If we made it here, Exec failed. // If we made it here, Exec failed.
return err return err
@ -484,9 +491,14 @@ func trySU(dlogf logger.Logf, ia incubatorArgs) (handled bool, err error) {
defer sessionCloser() defer sessionCloser()
} }
environ, allowListEnvKeys, err := ia.forwadedEnviron()
if err != nil {
return false, err
}
loginArgs := []string{ loginArgs := []string{
su, su,
"-w", ia.allowListEnvKeys, "-w", allowListEnvKeys,
"-l", "-l",
ia.localUser, ia.localUser,
} }
@ -498,7 +510,7 @@ func trySU(dlogf logger.Logf, ia incubatorArgs) (handled bool, err error) {
dlogf("logging in with %+v", loginArgs) dlogf("logging in with %+v", loginArgs)
// If Exec works, the Go code will not proceed past this: // If Exec works, the Go code will not proceed past this:
err = unix.Exec(su, loginArgs, ia.forwardedEnviron) err = unix.Exec(su, loginArgs, environ)
// If we made it here, Exec failed. // If we made it here, Exec failed.
return true, err return true, err
@ -527,11 +539,16 @@ func findSU(dlogf logger.Logf, ia incubatorArgs) string {
return "" return ""
} }
_, allowListEnvKeys, err := ia.forwadedEnviron()
if err != nil {
return ""
}
// First try to execute su -w <allow listed env> -l <user> -c true // First try to execute su -w <allow listed env> -l <user> -c true
// to make sure su supports the necessary arguments. // to make sure su supports the necessary arguments.
err = exec.Command( err = exec.Command(
su, su,
"-w", ia.allowListEnvKeys, "-w", allowListEnvKeys,
"-l", "-l",
ia.localUser, ia.localUser,
"-c", "true", "-c", "true",
@ -558,10 +575,15 @@ func handleSSHInProcess(dlogf logger.Logf, ia incubatorArgs) error {
return err return err
} }
environ, _, err := ia.forwadedEnviron()
if err != nil {
return err
}
args := shellArgs(ia.isShell, ia.cmd) args := shellArgs(ia.isShell, ia.cmd)
dlogf("running %s %q", ia.loginShell, args) dlogf("running %s %q", ia.loginShell, args)
cmd := newCommand(ia.hasTTY, ia.loginShell, ia.forwardedEnviron, args) cmd := newCommand(ia.hasTTY, ia.loginShell, environ, args)
err := cmd.Run() err = cmd.Run()
if ee, ok := err.(*exec.ExitError); ok { if ee, ok := err.(*exec.ExitError); ok {
ps := ee.ProcessState ps := ee.ProcessState
code := ps.ExitCode() code := ps.ExitCode()