mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Split internal idtools functionality
Separare idtools functionality that is used internally from the functionlality used by importers. The `pkg/idtools` package is now much smaller and more generic. Signed-off-by: Derek McGowan <derek@mcg.dev> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
73
internal/usergroup/add_linux_test.go
Normal file
73
internal/usergroup/add_linux_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
)
|
||||
|
||||
const (
|
||||
tempUser = "tempuser"
|
||||
)
|
||||
|
||||
func TestNewIDMappings(t *testing.T) {
|
||||
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
|
||||
_, _, err := AddNamespaceRangesUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
defer delUser(t, tempUser)
|
||||
|
||||
tempUser, err := user.Lookup(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
idMapping, err := LoadIdentityMapping(tempUser.Username)
|
||||
assert.Check(t, err)
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps)
|
||||
assert.Check(t, err)
|
||||
|
||||
dirName, err := os.MkdirTemp("", "mkdirall")
|
||||
assert.Check(t, err, "Couldn't create temp directory")
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
err = idtools.MkdirAllAndChown(dirName, 0o700, idtools.Identity{UID: rootUID, GID: rootGID})
|
||||
assert.Check(t, err, "Couldn't change ownership of file path. Got error")
|
||||
cmd := exec.Command("ls", "-la", dirName)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Credential: &syscall.Credential{Uid: uint32(rootUID), Gid: uint32(rootGID)},
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.Check(t, err, "Unable to access %s directory with user UID:%d and GID:%d:\n%s", dirName, rootUID, rootGID, string(out))
|
||||
}
|
||||
|
||||
func TestLookupUserAndGroup(t *testing.T) {
|
||||
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
|
||||
uid, gid, err := AddNamespaceRangesUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
defer delUser(t, tempUser)
|
||||
|
||||
fetchedUser, err := LookupUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
fetchedUserByID, err := LookupUID(uid)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.DeepEqual(fetchedUserByID, fetchedUser))
|
||||
|
||||
fetchedGroup, err := LookupGroup(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
fetchedGroupByID, err := LookupGID(gid)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.DeepEqual(fetchedGroupByID, fetchedGroup))
|
||||
}
|
||||
|
||||
func delUser(t *testing.T, name string) {
|
||||
out, err := exec.Command("userdel", name).CombinedOutput()
|
||||
assert.Check(t, err, out)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !linux
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package usergroup
|
||||
|
||||
import "fmt"
|
||||
|
||||
10
internal/usergroup/const_windows.go
Normal file
10
internal/usergroup/const_windows.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package usergroup
|
||||
|
||||
const (
|
||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
ContainerAdministratorSidString = "S-1-5-93-2-1"
|
||||
ContainerUserSidString = "S-1-5-93-2-2"
|
||||
)
|
||||
188
internal/usergroup/lookup_unix.go
Normal file
188
internal/usergroup/lookup_unix.go
Normal file
@@ -0,0 +1,188 @@
|
||||
//go:build !windows
|
||||
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUser(name string) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUser(name)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
usr, err = getentUser(name)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUID(uid int) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUid(uid)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
return getentUser(strconv.Itoa(uid))
|
||||
}
|
||||
|
||||
func getentUser(name string) (user.User, error) {
|
||||
reader, err := callGetent("passwd", name)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
users, err := user.ParsePasswd(reader)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", name)
|
||||
}
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGroup(name string) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGroup(name)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(name)
|
||||
}
|
||||
|
||||
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGID(gid int) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGid(gid)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(strconv.Itoa(gid))
|
||||
}
|
||||
|
||||
func getentGroup(name string) (user.Group, error) {
|
||||
reader, err := callGetent("group", name)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
groups, err := user.ParseGroup(reader)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", name)
|
||||
}
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
func callGetent(database, key string) (io.Reader, error) {
|
||||
getentCmd, err := resolveBinary("getent")
|
||||
// if no `getent` command within the execution environment, can't do anything else
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find getent command: %w", err)
|
||||
}
|
||||
command := exec.Command(getentCmd, database, key)
|
||||
// we run getent within container filesystem, but without /dev so /dev/null is not available for exec to mock stdin
|
||||
command.Stdin = io.NopCloser(bytes.NewReader(nil))
|
||||
out, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
exitCode, errC := getExitCode(err)
|
||||
if errC != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch exitCode {
|
||||
case 1:
|
||||
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||
case 2:
|
||||
return nil, fmt.Errorf("getent unable to find entry %q in %s database", key, database)
|
||||
case 3:
|
||||
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bytes.NewReader(out), nil
|
||||
}
|
||||
|
||||
// getExitCode returns the ExitStatus of the specified error if its type is
|
||||
// exec.ExitError, returns 0 and an error otherwise.
|
||||
func getExitCode(err error) (int, error) {
|
||||
exitCode := 0
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
}
|
||||
}
|
||||
return exitCode, fmt.Errorf("failed to get exit code")
|
||||
}
|
||||
|
||||
// LoadIdentityMapping takes a requested username and
|
||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||
// proper uid and gid remapping ranges for that user/group pair
|
||||
func LoadIdentityMapping(name string) (idtools.IdentityMapping, error) {
|
||||
usr, err := LookupUser(name)
|
||||
if err != nil {
|
||||
return idtools.IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
|
||||
}
|
||||
|
||||
subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr)
|
||||
if err != nil {
|
||||
return idtools.IdentityMapping{}, err
|
||||
}
|
||||
subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr)
|
||||
if err != nil {
|
||||
return idtools.IdentityMapping{}, err
|
||||
}
|
||||
|
||||
return idtools.IdentityMapping{
|
||||
UIDMaps: subuidRanges,
|
||||
GIDMaps: subgidRanges,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func lookupSubRangesFile(path string, usr user.User) ([]idtools.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 {
|
||||
return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name)
|
||||
}
|
||||
|
||||
idMap := []idtools.IDMap{}
|
||||
|
||||
containerID := 0
|
||||
for _, idrange := range rangeList {
|
||||
idMap = append(idMap, idtools.IDMap{
|
||||
ContainerID: containerID,
|
||||
HostID: int(idrange.SubID),
|
||||
Size: int(idrange.Count),
|
||||
})
|
||||
containerID = containerID + int(idrange.Count)
|
||||
}
|
||||
return idMap, nil
|
||||
}
|
||||
26
internal/usergroup/lookup_unix_test.go
Normal file
26
internal/usergroup/lookup_unix_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build !windows
|
||||
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestLookupUserAndGroupThatDoesNotExist(t *testing.T) {
|
||||
fakeUser := "fakeuser"
|
||||
_, err := LookupUser(fakeUser)
|
||||
assert.Check(t, is.Error(err, `getent unable to find entry "fakeuser" in passwd database`))
|
||||
|
||||
_, err = LookupUID(-1)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
fakeGroup := "fakegroup"
|
||||
_, err = LookupGroup(fakeGroup)
|
||||
assert.Check(t, is.Error(err, `getent unable to find entry "fakegroup" in group database`))
|
||||
|
||||
_, err = LookupGID(-1)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
}
|
||||
22
internal/usergroup/parser.go
Normal file
22
internal/usergroup/parser.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
const (
|
||||
subuidFileName = "/etc/subuid"
|
||||
subgidFileName = "/etc/subgid"
|
||||
)
|
||||
|
||||
func parseSubuid(username string) ([]user.SubID, error) {
|
||||
return user.ParseSubIDFileFilter(subuidFileName, func(sid user.SubID) bool {
|
||||
return sid.Name == username
|
||||
})
|
||||
}
|
||||
|
||||
func parseSubgid(username string) ([]user.SubID, error) {
|
||||
return user.ParseSubIDFileFilter(subgidFileName, func(sid user.SubID) bool {
|
||||
return sid.Name == username
|
||||
})
|
||||
}
|
||||
39
internal/usergroup/parser_test.go
Normal file
39
internal/usergroup/parser_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
func TestParseSubidFileWithNewlinesAndComments(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "parsesubid")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fnamePath := filepath.Join(tmpDir, "testsubuid")
|
||||
fcontent := `tss:100000:65536
|
||||
# empty default subuid/subgid file
|
||||
|
||||
dockremap:231072:65536`
|
||||
if err := os.WriteFile(fnamePath, []byte(fcontent), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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].SubID != 231072 {
|
||||
t.Fatalf("wanted 231072, got %d instead", ranges[0].SubID)
|
||||
}
|
||||
if ranges[0].Count != 65536 {
|
||||
t.Fatalf("wanted 65536, got %d instead", ranges[0].Count)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package usergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,10 +1,8 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
// IDMap contains a single entry for user namespace range remapping. An array
|
||||
@@ -16,11 +14,6 @@ type IDMap struct {
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
const (
|
||||
subuidFileName = "/etc/subuid"
|
||||
subgidFileName = "/etc/subgid"
|
||||
)
|
||||
|
||||
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
|
||||
// ownership to the requested uid/gid. If the directory already exists, this
|
||||
// function will still change ownership and permissions.
|
||||
@@ -150,18 +143,6 @@ func (i IdentityMapping) Empty() bool {
|
||||
return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
|
||||
}
|
||||
|
||||
func parseSubuid(username string) ([]user.SubID, error) {
|
||||
return user.ParseSubIDFileFilter(subuidFileName, func(sid user.SubID) bool {
|
||||
return sid.Name == username
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
func CurrentIdentity() Identity {
|
||||
return Identity{UID: os.Getuid(), GID: os.Getegid()}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
//go:build !windows
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
@@ -72,127 +69,25 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username
|
||||
//
|
||||
// Deprecated: use [user.LookupUser] instead
|
||||
func LookupUser(name string) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUser(name)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
usr, err = getentUser(name)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
return usr, nil
|
||||
return user.LookupUser(name)
|
||||
}
|
||||
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid
|
||||
//
|
||||
// Deprecated: use [user.LookupUid] instead
|
||||
func LookupUID(uid int) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUid(uid)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
return getentUser(strconv.Itoa(uid))
|
||||
}
|
||||
|
||||
func getentUser(name string) (user.User, error) {
|
||||
reader, err := callGetent("passwd", name)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
users, err := user.ParsePasswd(reader)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", name)
|
||||
}
|
||||
return users[0], nil
|
||||
return user.LookupUid(uid)
|
||||
}
|
||||
|
||||
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
//
|
||||
// Deprecated: use [user.LookupGroup] instead
|
||||
func LookupGroup(name string) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGroup(name)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(name)
|
||||
}
|
||||
|
||||
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGID(gid int) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGid(gid)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(strconv.Itoa(gid))
|
||||
}
|
||||
|
||||
func getentGroup(name string) (user.Group, error) {
|
||||
reader, err := callGetent("group", name)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
groups, err := user.ParseGroup(reader)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", name)
|
||||
}
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
func callGetent(database, key string) (io.Reader, error) {
|
||||
getentCmd, err := resolveBinary("getent")
|
||||
// if no `getent` command within the execution environment, can't do anything else
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find getent command: %w", err)
|
||||
}
|
||||
command := exec.Command(getentCmd, database, key)
|
||||
// we run getent within container filesystem, but without /dev so /dev/null is not available for exec to mock stdin
|
||||
command.Stdin = io.NopCloser(bytes.NewReader(nil))
|
||||
out, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
exitCode, errC := getExitCode(err)
|
||||
if errC != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch exitCode {
|
||||
case 1:
|
||||
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||
case 2:
|
||||
return nil, fmt.Errorf("getent unable to find entry %q in %s database", key, database)
|
||||
case 3:
|
||||
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bytes.NewReader(out), nil
|
||||
}
|
||||
|
||||
// getExitCode returns the ExitStatus of the specified error if its type is
|
||||
// exec.ExitError, returns 0 and an error otherwise.
|
||||
func getExitCode(err error) (int, error) {
|
||||
exitCode := 0
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
}
|
||||
}
|
||||
return exitCode, fmt.Errorf("failed to get exit code")
|
||||
return user.LookupGroup(name)
|
||||
}
|
||||
|
||||
// setPermissions performs a chown/chmod only if the uid/gid don't match what's requested
|
||||
@@ -223,7 +118,8 @@ func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo
|
||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||
// proper uid and gid remapping ranges for that user/group pair
|
||||
func LoadIdentityMapping(name string) (IdentityMapping, error) {
|
||||
usr, err := LookupUser(name)
|
||||
// TODO: Consider adding support for calling out to "getent"
|
||||
usr, err := user.LookupUser(name)
|
||||
if err != nil {
|
||||
return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
//go:build !windows
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
stduser "os/user"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/skip"
|
||||
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
const (
|
||||
tempUser = "tempuser"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
@@ -327,41 +318,6 @@ func compareTrees(left, right map[string]node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func delUser(t *testing.T, name string) {
|
||||
out, err := exec.Command("userdel", name).CombinedOutput()
|
||||
assert.Check(t, err, out)
|
||||
}
|
||||
|
||||
func TestParseSubidFileWithNewlinesAndComments(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "parsesubid")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fnamePath := filepath.Join(tmpDir, "testsubuid")
|
||||
fcontent := `tss:100000:65536
|
||||
# empty default subuid/subgid file
|
||||
|
||||
dockremap:231072:65536`
|
||||
if err := os.WriteFile(fnamePath, []byte(fcontent), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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].SubID != 231072 {
|
||||
t.Fatalf("wanted 231072, got %d instead", ranges[0].SubID)
|
||||
}
|
||||
if ranges[0].Count != 65536 {
|
||||
t.Fatalf("wanted 65536, got %d instead", ranges[0].Count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRootUIDGID(t *testing.T) {
|
||||
uidMap := []IDMap{
|
||||
{
|
||||
@@ -408,72 +364,6 @@ func TestToContainer(t *testing.T) {
|
||||
assert.Check(t, is.Equal(uidMap[0].ContainerID, containerID))
|
||||
}
|
||||
|
||||
func TestNewIDMappings(t *testing.T) {
|
||||
RequiresRoot(t)
|
||||
_, _, err := AddNamespaceRangesUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
defer delUser(t, tempUser)
|
||||
|
||||
tempUser, err := stduser.Lookup(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
idMapping, err := LoadIdentityMapping(tempUser.Username)
|
||||
assert.Check(t, err)
|
||||
|
||||
rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps)
|
||||
assert.Check(t, err)
|
||||
|
||||
dirName, err := os.MkdirTemp("", "mkdirall")
|
||||
assert.Check(t, err, "Couldn't create temp directory")
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
err = MkdirAllAndChown(dirName, 0o700, Identity{UID: rootUID, GID: rootGID})
|
||||
assert.Check(t, err, "Couldn't change ownership of file path. Got error")
|
||||
cmd := exec.Command("ls", "-la", dirName)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Credential: &syscall.Credential{Uid: uint32(rootUID), Gid: uint32(rootGID)},
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
assert.Check(t, err, "Unable to access %s directory with user UID:%d and GID:%d:\n%s", dirName, rootUID, rootGID, string(out))
|
||||
}
|
||||
|
||||
func TestLookupUserAndGroup(t *testing.T) {
|
||||
RequiresRoot(t)
|
||||
uid, gid, err := AddNamespaceRangesUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
defer delUser(t, tempUser)
|
||||
|
||||
fetchedUser, err := LookupUser(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
fetchedUserByID, err := LookupUID(uid)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.DeepEqual(fetchedUserByID, fetchedUser))
|
||||
|
||||
fetchedGroup, err := LookupGroup(tempUser)
|
||||
assert.Check(t, err)
|
||||
|
||||
fetchedGroupByID, err := LookupGID(gid)
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.DeepEqual(fetchedGroupByID, fetchedGroup))
|
||||
}
|
||||
|
||||
func TestLookupUserAndGroupThatDoesNotExist(t *testing.T) {
|
||||
fakeUser := "fakeuser"
|
||||
_, err := LookupUser(fakeUser)
|
||||
assert.Check(t, is.Error(err, `getent unable to find entry "fakeuser" in passwd database`))
|
||||
|
||||
_, err = LookupUID(-1)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
|
||||
fakeGroup := "fakegroup"
|
||||
_, err = LookupGroup(fakeGroup)
|
||||
assert.Check(t, is.Error(err, `getent unable to find entry "fakegroup" in group database`))
|
||||
|
||||
_, err = LookupGID(-1)
|
||||
assert.Check(t, is.ErrorContains(err, ""))
|
||||
}
|
||||
|
||||
// TestMkdirIsNotDir checks that mkdirAs() function (used by MkdirAll...)
|
||||
// returns a correct error in case a directory which it is about to create
|
||||
// already exists but is a file (rather than a directory).
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// Deprecated: copy value locally
|
||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
// Deprecated: copy value locally
|
||||
ContainerAdministratorSidString = "S-1-5-93-2-1"
|
||||
ContainerUserSidString = "S-1-5-93-2-2"
|
||||
|
||||
// Deprecated: copy value locally
|
||||
ContainerUserSidString = "S-1-5-93-2-2"
|
||||
)
|
||||
|
||||
// This is currently a wrapper around [os.MkdirAll] since currently
|
||||
|
||||
Reference in New Issue
Block a user