ssh/tailssh: try fetching group IDs for user with the 'id' command

Since the tailscaled binaries that we distribute are static and don't
link cgo, we previously wouldn't fetch group IDs that are returned via
NSS. Try shelling out to the 'id' command, similar to how we call
'getent', to detect such cases.

Updates #11682

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I9bdc938bd76c71bc130d44a97cc2233064d64799
This commit is contained in:
Andrew Dunham 2024-04-21 22:00:28 -04:00 committed by Percy Wegmann
parent 9779eb6dba
commit e985c6e58f
2 changed files with 52 additions and 8 deletions

View File

@ -34,14 +34,7 @@ type userMeta struct {
// GroupIds returns the list of group IDs that the user is a member of.
func (u *userMeta) GroupIds() ([]string, error) {
if runtime.GOOS == "linux" && distro.Get() == distro.Gokrazy {
// Gokrazy is a single-user appliance with ~no userspace.
// There aren't users to look up (no /etc/passwd, etc)
// so rather than fail below, just hardcode root.
// TODO(bradfitz): fix os/user upstream instead?
return []string{"0"}, nil
}
return u.User.GroupIds()
return osuser.GetGroupIds(&u.User)
}
// userLookup is like os/user.Lookup but it returns a *userMeta wrapper
@ -51,6 +44,7 @@ func userLookup(username string) (*userMeta, error) {
if err != nil {
return nil, err
}
return &userMeta{User: *u, loginShellCached: s}, nil
}

50
util/osuser/group_ids.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package osuser
import (
"context"
"fmt"
"os/exec"
"os/user"
"runtime"
"strings"
"time"
"tailscale.com/version/distro"
)
// GetGroupIds returns the list of group IDs that the user is a member of, or
// an error. It will first try to use the 'id' command to get the group IDs,
// and if that fails, it will fall back to the user.GroupIds method.
func GetGroupIds(user *user.User) ([]string, error) {
if runtime.GOOS != "linux" {
return user.GroupIds()
}
if distro.Get() == distro.Gokrazy {
// Gokrazy is a single-user appliance with ~no userspace.
// There aren't users to look up (no /etc/passwd, etc)
// so rather than fail below, just hardcode root.
// TODO(bradfitz): fix os/user upstream instead?
return []string{"0"}, nil
}
if ids, err := getGroupIdsWithId(user.Username); err == nil {
return ids, nil
}
return user.GroupIds()
}
func getGroupIdsWithId(usernameOrUID string) ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "id", "-Gz", usernameOrUID)
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("running 'id' command: %w", err)
}
return strings.Split(string(out), "\x00"), nil
}