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