group_ids.go 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package osuser
  4. import (
  5. "context"
  6. "fmt"
  7. "os/exec"
  8. "os/user"
  9. "runtime"
  10. "strings"
  11. "time"
  12. "tailscale.com/version/distro"
  13. )
  14. // GetGroupIds returns the list of group IDs that the user is a member of, or
  15. // an error. It will first try to use the 'id' command to get the group IDs,
  16. // and if that fails, it will fall back to the user.GroupIds method.
  17. func GetGroupIds(user *user.User) ([]string, error) {
  18. if runtime.GOOS == "plan9" {
  19. return nil, nil
  20. }
  21. if runtime.GOOS != "linux" {
  22. return user.GroupIds()
  23. }
  24. if distro.Get() == distro.Gokrazy {
  25. // Gokrazy is a single-user appliance with ~no userspace.
  26. // There aren't users to look up (no /etc/passwd, etc)
  27. // so rather than fail below, just hardcode root.
  28. // TODO(bradfitz): fix os/user upstream instead?
  29. return []string{"0"}, nil
  30. }
  31. if ids, err := getGroupIdsWithId(user.Username); err == nil {
  32. return ids, nil
  33. }
  34. return user.GroupIds()
  35. }
  36. func getGroupIdsWithId(usernameOrUID string) ([]string, error) {
  37. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  38. defer cancel()
  39. cmd := exec.CommandContext(ctx, "id", "-Gz", usernameOrUID)
  40. out, err := cmd.Output()
  41. if err != nil {
  42. return nil, fmt.Errorf("running 'id' command: %w", err)
  43. }
  44. return parseGroupIds(out), nil
  45. }
  46. func parseGroupIds(cmdOutput []byte) []string {
  47. return strings.Split(strings.Trim(string(cmdOutput), "\n\x00"), "\x00")
  48. }