usergroupadd_linux.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. package idtools // import "github.com/docker/docker/pkg/idtools"
  2. import (
  3. "fmt"
  4. "os/exec"
  5. "regexp"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. )
  11. // add a user and/or group to Linux /etc/passwd, /etc/group using standard
  12. // Linux distribution commands:
  13. // adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
  14. // useradd -r -s /bin/false <username>
  15. var (
  16. once sync.Once
  17. userCommand string
  18. idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
  19. )
  20. const (
  21. // default length for a UID/GID subordinate range
  22. defaultRangeLen = 65536
  23. defaultRangeStart = 100000
  24. )
  25. // AddNamespaceRangesUser takes a username and uses the standard system
  26. // utility to create a system user/group pair used to hold the
  27. // /etc/sub{uid,gid} ranges which will be used for user namespace
  28. // mapping ranges in containers.
  29. func AddNamespaceRangesUser(name string) (int, int, error) {
  30. if err := addUser(name); err != nil {
  31. return -1, -1, fmt.Errorf("error adding user %q: %v", name, err)
  32. }
  33. // Query the system for the created uid and gid pair
  34. out, err := exec.Command("id", name).CombinedOutput()
  35. if err != nil {
  36. return -1, -1, fmt.Errorf("error trying to find uid/gid for new user %q: %v", name, err)
  37. }
  38. matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
  39. if len(matches) != 3 {
  40. return -1, -1, fmt.Errorf("can't find uid, gid from `id` output: %q", string(out))
  41. }
  42. uid, err := strconv.Atoi(matches[1])
  43. if err != nil {
  44. return -1, -1, fmt.Errorf("can't convert found uid (%s) to int: %v", matches[1], err)
  45. }
  46. gid, err := strconv.Atoi(matches[2])
  47. if err != nil {
  48. return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err)
  49. }
  50. // Now we need to create the subuid/subgid ranges for our new user/group (system users
  51. // do not get auto-created ranges in subuid/subgid)
  52. if err := createSubordinateRanges(name); err != nil {
  53. return -1, -1, fmt.Errorf("couldn't create subordinate ID ranges: %v", err)
  54. }
  55. return uid, gid, nil
  56. }
  57. func addUser(name string) error {
  58. once.Do(func() {
  59. // set up which commands are used for adding users/groups dependent on distro
  60. if _, err := resolveBinary("adduser"); err == nil {
  61. userCommand = "adduser"
  62. } else if _, err := resolveBinary("useradd"); err == nil {
  63. userCommand = "useradd"
  64. }
  65. })
  66. var args []string
  67. switch userCommand {
  68. case "adduser":
  69. args = []string{"--system", "--shell", "/bin/false", "--no-create-home", "--disabled-login", "--disabled-password", "--group", name}
  70. case "useradd":
  71. args = []string{"-r", "-s", "/bin/false", name}
  72. default:
  73. return fmt.Errorf("cannot add user; no useradd/adduser binary found")
  74. }
  75. if out, err := exec.Command(userCommand, args...).CombinedOutput(); err != nil {
  76. return fmt.Errorf("failed to add user with error: %v; output: %q", err, string(out))
  77. }
  78. return nil
  79. }
  80. func createSubordinateRanges(name string) error {
  81. // first, we should verify that ranges weren't automatically created
  82. // by the distro tooling
  83. ranges, err := parseSubuid(name)
  84. if err != nil {
  85. return fmt.Errorf("error while looking for subuid ranges for user %q: %v", name, err)
  86. }
  87. if len(ranges) == 0 {
  88. // no UID ranges; let's create one
  89. startID, err := findNextUIDRange()
  90. if err != nil {
  91. return fmt.Errorf("can't find available subuid range: %v", err)
  92. }
  93. idRange := fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1)
  94. out, err := exec.Command("usermod", "-v", idRange, name).CombinedOutput()
  95. if err != nil {
  96. return fmt.Errorf("unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
  97. }
  98. }
  99. ranges, err = parseSubgid(name)
  100. if err != nil {
  101. return fmt.Errorf("error while looking for subgid ranges for user %q: %v", name, err)
  102. }
  103. if len(ranges) == 0 {
  104. // no GID ranges; let's create one
  105. startID, err := findNextGIDRange()
  106. if err != nil {
  107. return fmt.Errorf("can't find available subgid range: %v", err)
  108. }
  109. idRange := fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1)
  110. out, err := exec.Command("usermod", "-w", idRange, name).CombinedOutput()
  111. if err != nil {
  112. return fmt.Errorf("unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
  113. }
  114. }
  115. return nil
  116. }
  117. func findNextUIDRange() (int, error) {
  118. ranges, err := parseSubuid("ALL")
  119. if err != nil {
  120. return -1, fmt.Errorf("couldn't parse all ranges in /etc/subuid file: %v", err)
  121. }
  122. sort.Sort(ranges)
  123. return findNextRangeStart(ranges)
  124. }
  125. func findNextGIDRange() (int, error) {
  126. ranges, err := parseSubgid("ALL")
  127. if err != nil {
  128. return -1, fmt.Errorf("couldn't parse all ranges in /etc/subgid file: %v", err)
  129. }
  130. sort.Sort(ranges)
  131. return findNextRangeStart(ranges)
  132. }
  133. func findNextRangeStart(rangeList ranges) (int, error) {
  134. startID := defaultRangeStart
  135. for _, arange := range rangeList {
  136. if wouldOverlap(arange, startID) {
  137. startID = arange.Start + arange.Length
  138. }
  139. }
  140. return startID, nil
  141. }
  142. func wouldOverlap(arange subIDRange, ID int) bool {
  143. low := ID
  144. high := ID + defaultRangeLen
  145. if (low >= arange.Start && low <= arange.Start+arange.Length) ||
  146. (high <= arange.Start+arange.Length && high >= arange.Start) {
  147. return true
  148. }
  149. return false
  150. }