Files
tailscale/util/osuser/group_ids.go
Gesa Stupperich ac74dfa5cd util/osuser: extend id command fallback for group IDs to freebsd
Users on FreeBSD run into a similar problem as has been reported for
Linux #11682 and fixed in #11682: because the tailscaled binaries
that we distribute are static and don't link cgo tailscaled fails to
fetch group IDs that are returned via NSS when spawning an ssh child
process.

This change extends the fallback on the 'id' command that was put in
place as part of #11682 to FreeBSD. More precisely, we try to fetch
the group IDs with the 'id' command first, and only if that fails do
we fall back on the logic in the os/user package.

Updates #14025

Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
2026-03-09 08:39:07 +00:00

70 lines
1.7 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// 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 == "plan9" {
return nil, nil
}
if runtime.GOOS != "linux" && runtime.GOOS != "freebsd" {
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)
if runtime.GOOS == "freebsd" {
cmd = exec.CommandContext(ctx, "id", "-G", usernameOrUID)
}
out, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("running 'id' command: %w", err)
}
return parseGroupIds(out), nil
}
func parseGroupIds(cmdOutput []byte) []string {
s := strings.TrimSpace(string(cmdOutput))
// Parse NUL-delimited output.
if strings.ContainsRune(s, '\x00') {
return strings.Split(strings.Trim(s, "\x00"), "\x00")
}
// Parse whitespace-delimited output.
return strings.Fields(s)
}