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 <bradfitz@tailscale.com>pull/6789/head
parent
fc0fe99edf
commit
651e0d8aad
|
@ -35,6 +35,7 @@ import (
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"tailscale.com/cmd/tailscaled/childproc"
|
"tailscale.com/cmd/tailscaled/childproc"
|
||||||
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||||
"tailscale.com/types/logger"
|
"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 {
|
func defaultPathForUser(u *user.User) string {
|
||||||
|
if s := defaultPathTmpl(); s != "" {
|
||||||
|
return expandDefaultPathTmpl(s, u)
|
||||||
|
}
|
||||||
isRoot := u.Uid == "0"
|
isRoot := u.Uid == "0"
|
||||||
switch distro.Get() {
|
switch distro.Get() {
|
||||||
case distro.Debian:
|
case distro.Debian:
|
||||||
|
@ -626,19 +641,24 @@ func pathFromPAMEnvLine(line []byte, u *user.User) (path string) {
|
||||||
rest := strings.TrimSpace(strings.TrimPrefix(string(line), "PATH"))
|
rest := strings.TrimSpace(strings.TrimPrefix(string(line), "PATH"))
|
||||||
if quoted, ok := strs.CutPrefix(rest, "DEFAULT="); ok {
|
if quoted, ok := strs.CutPrefix(rest, "DEFAULT="); ok {
|
||||||
if path, err := strconv.Unquote(quoted); err == nil {
|
if path, err := strconv.Unquote(quoted); err == nil {
|
||||||
path = strings.NewReplacer(
|
return expandDefaultPathTmpl(path, u)
|
||||||
"@{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 ""
|
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
|
// updateStringInSlice mutates ss to change the first occurrence of a
|
||||||
// to b.
|
// to b.
|
||||||
func updateStringInSlice(ss []string, a, b string) {
|
func updateStringInSlice(ss []string, a, b string) {
|
||||||
|
|
|
@ -764,7 +764,7 @@ func TestPathFromPAMEnvLine(t *testing.T) {
|
||||||
u *user.User
|
u *user.User
|
||||||
want string
|
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"`,
|
{`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"},
|
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}"`,
|
{`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) {
|
func TestPathFromPAMEnvLineOnNixOS(t *testing.T) {
|
||||||
if runtime.GOOS != "linux" {
|
if runtime.GOOS != "linux" {
|
||||||
t.Skip("skipping on non-linux")
|
t.Skip("skipping on non-linux")
|
||||||
|
|
Loading…
Reference in New Issue