Explorar o código

ssh/tailssh: add envknob for default PATH

As backup plan, just in case the earlier fix's logic wasn't correct
and we want to experiment in the field or have users have a quicker
fix.

Updates #5285

Change-Id: I7447466374d11f8f609de6dfbc4d9a944770826d
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick %!s(int64=3) %!d(string=hai) anos
pai
achega
651e0d8aad
Modificáronse 2 ficheiros con 49 adicións e 9 borrados
  1. 28 8
      ssh/tailssh/incubator.go
  2. 21 1
      ssh/tailssh/tailssh_test.go

+ 28 - 8
ssh/tailssh/incubator.go

@@ -35,6 +35,7 @@ import (
 	gossh "golang.org/x/crypto/ssh"
 	"golang.org/x/sys/unix"
 	"tailscale.com/cmd/tailscaled/childproc"
+	"tailscale.com/envknob"
 	"tailscale.com/hostinfo"
 	"tailscale.com/tempfork/gliderlabs/ssh"
 	"tailscale.com/types/logger"
@@ -583,7 +584,21 @@ func envForUser(u *user.User) []string {
 	}
 }
 
+// defaultPathTmpl specifies the default PATH template to use for new sessions.
+//
+// If empty, a default value is used based on the OS & distro to match OpenSSH's
+// usually-hardcoded behavior. (see
+// https://github.com/tailscale/tailscale/issues/5285 for background).
+//
+// The template may contain @{HOME} or @{PAM_USER} which expand to the user's
+// home directory and username, respectively. (PAM is not used, despite the
+// name)
+var defaultPathTmpl = envknob.RegisterString("TAILSCALE_SSH_DEFAULT_PATH")
+
 func defaultPathForUser(u *user.User) string {
+	if s := defaultPathTmpl(); s != "" {
+		return expandDefaultPathTmpl(s, u)
+	}
 	isRoot := u.Uid == "0"
 	switch distro.Get() {
 	case distro.Debian:
@@ -626,19 +641,24 @@ func pathFromPAMEnvLine(line []byte, u *user.User) (path string) {
 	rest := strings.TrimSpace(strings.TrimPrefix(string(line), "PATH"))
 	if quoted, ok := strs.CutPrefix(rest, "DEFAULT="); ok {
 		if path, err := strconv.Unquote(quoted); err == nil {
-			path = strings.NewReplacer(
-				"@{HOME}", u.HomeDir,
-				"@{PAM_USER}", u.Username,
-			).Replace(path)
-			if !strings.Contains(path, "@{") {
-				// If no more expansions, use it. Otherwise we fail closed.
-				return path
-			}
+			return expandDefaultPathTmpl(path, u)
 		}
 	}
 	return ""
 }
 
+func expandDefaultPathTmpl(t string, u *user.User) string {
+	p := strings.NewReplacer(
+		"@{HOME}", u.HomeDir,
+		"@{PAM_USER}", u.Username,
+	).Replace(t)
+	if strings.Contains(p, "@{") {
+		// If there are unknown expansions, conservatively fail closed.
+		return ""
+	}
+	return p
+}
+
 // updateStringInSlice mutates ss to change the first occurrence of a
 // to b.
 func updateStringInSlice(ss []string, a, b string) {

+ 21 - 1
ssh/tailssh/tailssh_test.go

@@ -764,7 +764,7 @@ func TestPathFromPAMEnvLine(t *testing.T) {
 		u    *user.User
 		want string
 	}{
-		{"", &user.User{}, ""},
+		{"", u, ""},
 		{`PATH   DEFAULT="/run/wrappers/bin:@{HOME}/.nix-profile/bin:/etc/profiles/per-user/@{PAM_USER}/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"`,
 			u, "/run/wrappers/bin:/Homes/Foo/.nix-profile/bin:/etc/profiles/per-user/foo/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"},
 		{`PATH   DEFAULT="@{SOMETHING_ELSE}:nope:@{HOME}"`,
@@ -778,6 +778,26 @@ func TestPathFromPAMEnvLine(t *testing.T) {
 	}
 }
 
+func TestExpandDefaultPathTmpl(t *testing.T) {
+	u := &user.User{Username: "foo", HomeDir: "/Homes/Foo"}
+	tests := []struct {
+		t    string
+		u    *user.User
+		want string
+	}{
+		{"", u, ""},
+		{`/run/wrappers/bin:@{HOME}/.nix-profile/bin:/etc/profiles/per-user/@{PAM_USER}/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin`,
+			u, "/run/wrappers/bin:/Homes/Foo/.nix-profile/bin:/etc/profiles/per-user/foo/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"},
+		{`@{SOMETHING_ELSE}:nope:@{HOME}`, u, ""},
+	}
+	for i, tt := range tests {
+		got := expandDefaultPathTmpl(tt.t, tt.u)
+		if got != tt.want {
+			t.Errorf("%d. got %q; want %q", i, got, tt.want)
+		}
+	}
+}
+
 func TestPathFromPAMEnvLineOnNixOS(t *testing.T) {
 	if runtime.GOOS != "linux" {
 		t.Skip("skipping on non-linux")