pkg/idtools: rewrite to use moby/sys/user

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-01-07 12:27:49 +01:00
parent 3c628f7b45
commit 24ad9bef9d
5 changed files with 63 additions and 149 deletions

View File

@@ -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

View File

@@ -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,
},
})
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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