github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/idtools/usergroupadd_linux.go (about) 1 package idtools // import "github.com/demonoid81/moby/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 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 // AddNamespaceRangesUser takes a username and uses the standard system 35 // utility to create a system user/group pair used to hold the 36 // /etc/sub{uid,gid} ranges which will be used for user namespace 37 // mapping ranges in containers. 38 func AddNamespaceRangesUser(name string) (int, int, error) { 39 if err := addUser(name); err != nil { 40 return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err) 41 } 42 43 // Query the system for the created uid and gid pair 44 out, err := execCmd("id", name) 45 if err != nil { 46 return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err) 47 } 48 matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out))) 49 if len(matches) != 3 { 50 return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out)) 51 } 52 uid, err := strconv.Atoi(matches[1]) 53 if err != nil { 54 return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err) 55 } 56 gid, err := strconv.Atoi(matches[2]) 57 if err != nil { 58 return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err) 59 } 60 61 // Now we need to create the subuid/subgid ranges for our new user/group (system users 62 // do not get auto-created ranges in subuid/subgid) 63 64 if err := createSubordinateRanges(name); err != nil { 65 return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err) 66 } 67 return uid, gid, nil 68 } 69 70 func addUser(userName string) error { 71 once.Do(func() { 72 // set up which commands are used for adding users/groups dependent on distro 73 if _, err := resolveBinary("adduser"); err == nil { 74 userCommand = "adduser" 75 } else if _, err := resolveBinary("useradd"); err == nil { 76 userCommand = "useradd" 77 } 78 }) 79 if userCommand == "" { 80 return fmt.Errorf("Cannot add user; no useradd/adduser binary found") 81 } 82 args := fmt.Sprintf(cmdTemplates[userCommand], userName) 83 out, err := execCmd(userCommand, args) 84 if 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, fmt.Sprintf(cmdTemplates[userMod], "v", 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, fmt.Sprintf(cmdTemplates[userMod], "w", 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 }