diff --git a/pkg/idtools/idtools.go b/pkg/idtools/idtools.go index 82b325a2b7..0bd364605b 100644 --- a/pkg/idtools/idtools.go +++ b/pkg/idtools/idtools.go @@ -1,11 +1,10 @@ package idtools // import "github.com/docker/docker/pkg/idtools" import ( - "bufio" "fmt" "os" - "strconv" - "strings" + + "github.com/moby/sys/user" ) // IDMap contains a single entry for user namespace range remapping. An array @@ -17,17 +16,6 @@ type IDMap struct { Size int `json:"size"` } -type subIDRange struct { - Start int - Length int -} - -type subIDRanges []subIDRange - -func (e subIDRanges) Len() int { return len(e) } -func (e subIDRanges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } -func (e subIDRanges) Less(i, j int) bool { return e[i].Start < e[j].Start } - const ( subuidFileName = "/etc/subuid" subgidFileName = "/etc/subgid" @@ -162,65 +150,16 @@ func (i IdentityMapping) Empty() bool { return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0 } -func createIDMap(subidRanges subIDRanges) []IDMap { - idMap := []IDMap{} - - containerID := 0 - for _, idrange := range subidRanges { - idMap = append(idMap, IDMap{ - ContainerID: containerID, - HostID: idrange.Start, - Size: idrange.Length, - }) - containerID = containerID + idrange.Length - } - return idMap +func parseSubuid(username string) ([]user.SubID, error) { + return user.ParseSubIDFileFilter(subuidFileName, func(sid user.SubID) bool { + return sid.Name == username + }) } -func parseSubuid(username string) (subIDRanges, error) { - return parseSubidFile(subuidFileName, username) -} - -func parseSubgid(username string) (subIDRanges, error) { - return parseSubidFile(subgidFileName, username) -} - -// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid) -// and return all found subIDRanges for a specified username. If the special value -// "ALL" is supplied for username, then all subIDRanges in the file will be returned -func parseSubidFile(path, username string) (subIDRanges, error) { - var rangeList subIDRanges - - subidFile, err := os.Open(path) - if err != nil { - return rangeList, err - } - defer subidFile.Close() - - s := bufio.NewScanner(subidFile) - for s.Scan() { - text := strings.TrimSpace(s.Text()) - if text == "" || strings.HasPrefix(text, "#") { - continue - } - parts := strings.Split(text, ":") - if len(parts) != 3 { - return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path) - } - if parts[0] == username || username == "ALL" { - startid, err := strconv.Atoi(parts[1]) - if err != nil { - return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) - } - length, err := strconv.Atoi(parts[2]) - if err != nil { - return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) - } - rangeList = append(rangeList, subIDRange{startid, length}) - } - } - - return rangeList, s.Err() +func parseSubgid(username string) ([]user.SubID, error) { + return user.ParseSubIDFileFilter(subgidFileName, func(sid user.SubID) bool { + return sid.Name == username + }) } // CurrentIdentity returns the identity of the current process diff --git a/pkg/idtools/idtools_test.go b/pkg/idtools/idtools_test.go deleted file mode 100644 index 72ff779bc6..0000000000 --- a/pkg/idtools/idtools_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package idtools // import "github.com/docker/docker/pkg/idtools" - -import ( - "testing" - - "gotest.tools/v3/assert" -) - -func TestCreateIDMapOrder(t *testing.T) { - subidRanges := subIDRanges{ - {100000, 1000}, - {1000, 1}, - } - - idMap := createIDMap(subidRanges) - assert.DeepEqual(t, idMap, []IDMap{ - { - ContainerID: 0, - HostID: 100000, - Size: 1000, - }, - { - ContainerID: 1000, - HostID: 1000, - Size: 1, - }, - }) -} diff --git a/pkg/idtools/idtools_unix.go b/pkg/idtools/idtools_unix.go index cd621bdcc2..e045338387 100644 --- a/pkg/idtools/idtools_unix.go +++ b/pkg/idtools/idtools_unix.go @@ -228,11 +228,11 @@ func LoadIdentityMapping(name string) (IdentityMapping, error) { return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err) } - subuidRanges, err := lookupSubUIDRanges(usr) + subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr) if err != nil { return IdentityMapping{}, err } - subgidRanges, err := lookupSubGIDRanges(usr) + subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr) if err != nil { return IdentityMapping{}, err } @@ -243,36 +243,28 @@ func LoadIdentityMapping(name string) (IdentityMapping, error) { }, nil } -func lookupSubUIDRanges(usr user.User) ([]IDMap, error) { - rangeList, err := parseSubuid(strconv.Itoa(usr.Uid)) +func lookupSubRangesFile(path string, usr user.User) ([]IDMap, error) { + uidstr := strconv.Itoa(usr.Uid) + rangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool { + return sid.Name == usr.Name || sid.Name == uidstr + }) if err != nil { return nil, err } - if len(rangeList) == 0 { - rangeList, err = parseSubuid(usr.Name) - if err != nil { - return nil, err - } - } if len(rangeList) == 0 { return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name) } - return createIDMap(rangeList), nil -} -func lookupSubGIDRanges(usr user.User) ([]IDMap, error) { - rangeList, err := parseSubgid(strconv.Itoa(usr.Uid)) - if err != nil { - return nil, err + idMap := []IDMap{} + + containerID := 0 + for _, idrange := range rangeList { + idMap = append(idMap, IDMap{ + ContainerID: containerID, + HostID: int(idrange.SubID), + Size: int(idrange.Count), + }) + containerID = containerID + int(idrange.Count) } - if len(rangeList) == 0 { - rangeList, err = parseSubgid(usr.Name) - if err != nil { - return nil, err - } - } - if len(rangeList) == 0 { - return nil, fmt.Errorf("no subgid ranges found for user %q", usr.Name) - } - return createIDMap(rangeList), nil + return idMap, nil } diff --git a/pkg/idtools/idtools_unix_test.go b/pkg/idtools/idtools_unix_test.go index 689ef5251b..7ec219f67b 100644 --- a/pkg/idtools/idtools_unix_test.go +++ b/pkg/idtools/idtools_unix_test.go @@ -6,7 +6,7 @@ import ( "fmt" "os" "os/exec" - "os/user" + stduser "os/user" "path/filepath" "syscall" "testing" @@ -15,6 +15,8 @@ import ( "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" + + "github.com/moby/sys/user" ) const ( @@ -343,18 +345,20 @@ dockremap:231072:65536` if err := os.WriteFile(fnamePath, []byte(fcontent), 0o644); err != nil { t.Fatal(err) } - ranges, err := parseSubidFile(fnamePath, "dockremap") + ranges, err := user.ParseSubIDFileFilter(fnamePath, func(sid user.SubID) bool { + return sid.Name == "dockremap" + }) if err != nil { t.Fatal(err) } if len(ranges) != 1 { t.Fatalf("wanted 1 element in ranges, got %d instead", len(ranges)) } - if ranges[0].Start != 231072 { - t.Fatalf("wanted 231072, got %d instead", ranges[0].Start) + if ranges[0].SubID != 231072 { + t.Fatalf("wanted 231072, got %d instead", ranges[0].SubID) } - if ranges[0].Length != 65536 { - t.Fatalf("wanted 65536, got %d instead", ranges[0].Length) + if ranges[0].Count != 65536 { + t.Fatalf("wanted 65536, got %d instead", ranges[0].Count) } } @@ -410,7 +414,7 @@ func TestNewIDMappings(t *testing.T) { assert.Check(t, err) defer delUser(t, tempUser) - tempUser, err := user.Lookup(tempUser) + tempUser, err := stduser.Lookup(tempUser) assert.Check(t, err) idMapping, err := LoadIdentityMapping(tempUser.Username) diff --git a/pkg/idtools/usergroupadd_linux.go b/pkg/idtools/usergroupadd_linux.go index b1b90d4bcd..8a9cb65cf7 100644 --- a/pkg/idtools/usergroupadd_linux.go +++ b/pkg/idtools/usergroupadd_linux.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/docker/docker/internal/lazyregexp" + "github.com/moby/sys/user" ) // add a user and/or group to Linux /etc/passwd, /etc/group using standard @@ -129,38 +130,44 @@ func createSubordinateRanges(name string) error { } func findNextUIDRange() (int, error) { - ranges, err := parseSubuid("ALL") + ranges, err := user.CurrentUserSubUIDs() if err != nil { return -1, fmt.Errorf("couldn't parse all ranges in /etc/subuid file: %v", err) } - sort.Sort(ranges) + sortRanges(ranges) return findNextRangeStart(ranges) } func findNextGIDRange() (int, error) { - ranges, err := parseSubgid("ALL") + ranges, err := user.CurrentUserSubGIDs() if err != nil { return -1, fmt.Errorf("couldn't parse all ranges in /etc/subgid file: %v", err) } - sort.Sort(ranges) + sortRanges(ranges) return findNextRangeStart(ranges) } -func findNextRangeStart(rangeList subIDRanges) (int, error) { - startID := defaultRangeStart - for _, arange := range rangeList { - if wouldOverlap(arange, startID) { - startID = arange.Start + arange.Length - } - } - return startID, nil +func sortRanges(ranges []user.SubID) { + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].SubID < ranges[j].SubID + }) } -func wouldOverlap(arange subIDRange, ID int) bool { - low := ID - high := ID + defaultRangeLen - if (low >= arange.Start && low <= arange.Start+arange.Length) || - (high <= arange.Start+arange.Length && high >= arange.Start) { +func findNextRangeStart(rangeList []user.SubID) (int, error) { + var startID int64 = defaultRangeStart + for _, arange := range rangeList { + if wouldOverlap(arange, startID) { + startID = arange.SubID + arange.Count + } + } + return int(startID), nil +} + +func wouldOverlap(arange user.SubID, ID int64) bool { + var low int64 = ID + var high int64 = ID + defaultRangeLen + if (low >= arange.SubID && low <= arange.SubID+arange.Count) || + (high <= arange.SubID+arange.Count && high >= arange.SubID) { return true } return false