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