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