Merge pull request #49566 from dmcgowan/update-moby-user-idtools

Update `github.com/moby/sys/user` and use of idtools
This commit is contained in:
Sebastiaan van Stijn
2025-04-04 18:31:19 +02:00
committed by GitHub
23 changed files with 327 additions and 476 deletions

View File

@@ -212,6 +212,10 @@ issues:
linters:
- staticcheck
- text: "SA1019: idtools\\.(CurrentIdentity|FromUserIdentityMapping|IDMap|MkdirAndChown|MkdirAllAndChown|MkdirAllAndChownNew) is deprecated"
linters:
- staticcheck
# Ignore "nested context in function literal (fatcontext)" as we intentionally set up tracing on a base-context for tests.
# FIXME(thaJeztah): see if there's a more iodiomatic way to do this.
- text: 'nested context in function literal'

View File

@@ -2,6 +2,7 @@ package daemon // import "github.com/docker/docker/daemon"
import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
)
// defaultTarCopyOptions is the setting that is used when unpacking an archive
@@ -9,6 +10,6 @@ import (
func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions {
return &archive.TarOptions{
NoOverwriteDirNonDir: noOverwriteDirNonDir,
IDMap: daemon.idMapping,
IDMap: idtools.FromUserIdentityMapping(daemon.idMapping),
}
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/docker/docker/pkg/process"
"github.com/docker/docker/pkg/stringid"
"github.com/moby/sys/mount"
"github.com/moby/sys/user"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
@@ -251,14 +252,14 @@ func (daemon *Daemon) setupIPCDirs(ctr *container.Container) error {
fallthrough
case ipcMode.IsShareable():
rootIDs := daemon.idMapping.RootPair()
uid, gid := daemon.idMapping.RootPair()
if !ctr.HasMountFor("/dev/shm") {
shmPath, err := ctr.ShmResourcePath()
if err != nil {
return err
}
if err := idtools.MkdirAllAndChown(shmPath, 0o700, rootIDs); err != nil {
if err := user.MkdirAllAndChown(shmPath, 0o700, uid, gid); err != nil {
return err
}
@@ -266,7 +267,7 @@ func (daemon *Daemon) setupIPCDirs(ctr *container.Container) error {
if err := unix.Mount("shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), label.FormatMountLabel(shmproperty, ctr.GetMountLabel())); err != nil {
return fmt.Errorf("mounting shm tmpfs: %s", err)
}
if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil {
if err := os.Chown(shmPath, uid, gid); err != nil {
return err
}
ctr.ShmPath = shmPath
@@ -298,7 +299,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
}
// retrieve possible remapped range start for root UID, GID
rootIDs := daemon.idMapping.RootPair()
ruid, rgid := daemon.idMapping.RootPair()
for _, s := range ctr.SecretReferences {
// TODO (ehazlett): use type switch when more are supported
@@ -313,7 +314,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
if err != nil {
return errors.Wrap(err, "error getting secret file path")
}
if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil {
if err := user.MkdirAllAndChown(filepath.Dir(fPath), 0o700, ruid, rgid); err != nil {
return errors.Wrap(err, "error creating secret mount path")
}
@@ -338,7 +339,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
return err
}
if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
if err := os.Chown(fPath, ruid+uid, rgid+gid); err != nil {
return errors.Wrap(err, "error setting ownership for secret")
}
if err := os.Chmod(fPath, s.File.Mode); err != nil {
@@ -364,7 +365,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
if err != nil {
return errors.Wrap(err, "error getting config file path for container")
}
if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil {
if err := user.MkdirAllAndChown(filepath.Dir(fPath), 0o700, ruid, rgid); err != nil {
return errors.Wrap(err, "error creating config mount path")
}
@@ -389,7 +390,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
return err
}
if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
if err := os.Chown(fPath, ruid+uid, rgid+gid); err != nil {
return errors.Wrap(err, "error setting ownership for config")
}
if err := os.Chmod(fPath, configRef.File.Mode); err != nil {
@@ -404,18 +405,18 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
// In practice this is using a tmpfs mount and is used for both "configs" and "secrets"
func (daemon *Daemon) createSecretsDir(ctr *container.Container) error {
// retrieve possible remapped range start for root UID, GID
rootIDs := daemon.idMapping.RootPair()
uid, gid := daemon.idMapping.RootPair()
dir, err := ctr.SecretMountPath()
if err != nil {
return errors.Wrap(err, "error getting container secrets dir")
}
// create tmpfs
if err := idtools.MkdirAllAndChown(dir, 0o700, rootIDs); err != nil {
if err := user.MkdirAllAndChown(dir, 0o700, uid, gid); err != nil {
return errors.Wrap(err, "error creating secret local mount path")
}
tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", uid, gid)
if err := mount.Mount("tmpfs", dir, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil {
return errors.Wrap(err, "unable to setup secret mount")
}
@@ -430,8 +431,8 @@ func (daemon *Daemon) remountSecretDir(ctr *container.Container) error {
if err := label.Relabel(dir, ctr.MountLabel, false); err != nil {
log.G(context.TODO()).WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label")
}
rootIDs := daemon.idMapping.RootPair()
tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
uid, gid := daemon.idMapping.RootPair()
tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", uid, gid)
// remount secrets ro
if err := mount.Mount("tmpfs", dir, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil {

View File

@@ -14,6 +14,7 @@ import (
"github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/idtools"
volumemounts "github.com/docker/docker/volume/mounts"
volumeopts "github.com/docker/docker/volume/service/opts"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -27,8 +28,8 @@ func (daemon *Daemon) createContainerOSSpecificSettings(ctx context.Context, con
}
defer daemon.Unmount(container)
rootIDs := daemon.idMapping.RootPair()
if err := container.SetupWorkingDirectory(rootIDs); err != nil {
uid, gid := daemon.idMapping.RootPair()
if err := container.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid}); err != nil {
return err
}
@@ -107,8 +108,8 @@ func (daemon *Daemon) populateVolume(ctx context.Context, c *container.Container
}
return err
}
volumePath, cleanup, err := mnt.Setup(ctx, c.MountLabel, daemon.idMapping.RootPair(), nil)
uid, gid := daemon.idMapping.RootPair()
volumePath, cleanup, err := mnt.Setup(ctx, c.MountLabel, idtools.Identity{UID: uid, GID: gid}, nil)
if err != nil {
if errdefs.IsNotFound(err) {
return nil

View File

@@ -75,6 +75,7 @@ import (
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/tracing"
"github.com/moby/locker"
"github.com/moby/sys/user"
"github.com/moby/sys/userns"
"github.com/pkg/errors"
"go.etcd.io/bbolt"
@@ -113,7 +114,7 @@ type Daemon struct {
sysInfoOnce sync.Once
sysInfo *sysinfo.SysInfo
shutdown bool
idMapping idtools.IdentityMapping
idMapping user.IdentityMapping
PluginStore *plugin.Store // TODO: remove
pluginManager *plugin.Manager
linkIndex *linkIndex
@@ -791,7 +792,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
if err != nil {
return nil, err
}
rootIDs := idMapping.RootPair()
uid, gid := idMapping.RootPair()
// set up the tmpDir to use a canonical path
tmp, err := prepareTempDir(config.Root)
@@ -878,10 +879,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
}
daemonRepo := filepath.Join(cfgStore.Root, "containers")
if err := idtools.MkdirAllAndChown(daemonRepo, 0o710, idtools.Identity{
UID: idtools.CurrentIdentity().UID,
GID: rootIDs.GID,
}); err != nil {
if err := user.MkdirAllAndChown(daemonRepo, 0o710, os.Getuid(), gid); err != nil {
return nil, err
}
@@ -999,7 +997,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
}
log.G(ctx).Debugf("Using default logging driver %s", d.defaultLogConfig.Type)
d.volumes, err = volumesservice.NewVolumeService(cfgStore.Root, d.PluginStore, rootIDs, d)
d.volumes, err = volumesservice.NewVolumeService(cfgStore.Root, d.PluginStore, idtools.Identity{UID: uid, GID: gid}, d)
if err != nil {
return nil, err
}
@@ -1074,15 +1072,15 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
RegistryHosts: d.RegistryHosts,
Registry: d.registryService,
EventsService: d.EventsService,
IDMapping: idMapping,
RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idMapping),
IDMapping: idtools.FromUserIdentityMapping(idMapping),
RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idtools.FromUserIdentityMapping(idMapping)),
})
} else {
layerStore, err := layer.NewStoreFromOptions(layer.StoreOptions{
Root: cfgStore.Root,
GraphDriver: driverName,
GraphDriverOptions: cfgStore.GraphOptions,
IDMapping: idMapping,
IDMapping: idtools.FromUserIdentityMapping(idMapping),
})
if err != nil {
return nil, err
@@ -1423,7 +1421,7 @@ func prepareTempDir(rootDir string) (string, error) {
}
}
}
return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0o700, idtools.CurrentIdentity())
return tmpDir, user.MkdirAllAndChown(tmpDir, 0o700, os.Getuid(), os.Getegid())
}
func (daemon *Daemon) setGenericResources(conf *config.Config) error {
@@ -1545,7 +1543,8 @@ func CreateDaemonRoot(config *config.Config) error {
if err != nil {
return err
}
return setupDaemonRoot(config, realRoot, idMapping.RootPair())
uid, gid := idMapping.RootPair()
return setupDaemonRoot(config, realRoot, uid, gid)
}
// RemapContainerdNamespaces returns the right containerd namespaces to use:
@@ -1561,16 +1560,16 @@ func RemapContainerdNamespaces(config *config.Config) (ns string, pluginNs strin
if idMapping.Empty() {
return config.ContainerdNamespace, config.ContainerdPluginNamespace, nil
}
root := idMapping.RootPair()
uid, gid := idMapping.RootPair()
ns = config.ContainerdNamespace
if _, ok := config.ValuesSet["containerd-namespace"]; !ok {
ns = fmt.Sprintf("%s-%d.%d", config.ContainerdNamespace, root.UID, root.GID)
ns = fmt.Sprintf("%s-%d.%d", config.ContainerdNamespace, uid, gid)
}
pluginNs = config.ContainerdPluginNamespace
if _, ok := config.ValuesSet["containerd-plugin-namespace"]; !ok {
pluginNs = fmt.Sprintf("%s-%d.%d", config.ContainerdPluginNamespace, root.UID, root.GID)
pluginNs = fmt.Sprintf("%s-%d.%d", config.ContainerdPluginNamespace, uid, gid)
}
return ns, pluginNs, nil
@@ -1601,7 +1600,7 @@ func (daemon *Daemon) GetAttachmentStore() *network.AttachmentStore {
// IdentityMapping returns uid/gid mapping or a SID (in the case of Windows) for the builder
func (daemon *Daemon) IdentityMapping() idtools.IdentityMapping {
return daemon.idMapping
return idtools.FromUserIdentityMapping(daemon.idMapping)
}
// ImageService returns the Daemon's ImageService

View File

@@ -45,6 +45,7 @@ import (
"github.com/docker/docker/runconfig"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/moby/sys/mount"
"github.com/moby/sys/user"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -1250,9 +1251,10 @@ func removeDefaultBridgeInterface() {
}
}
func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
func setupInitLayer(idMapping user.IdentityMapping) func(string) error {
return func(initPath string) error {
return initlayer.Setup(initPath, idMapping.RootPair())
uid, gid := idMapping.RootPair()
return initlayer.Setup(initPath, idtools.Identity{UID: uid, GID: gid})
}
}
@@ -1349,9 +1351,9 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
return username, groupname, nil
}
func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
func setupRemappedRoot(config *config.Config) (user.IdentityMapping, error) {
if runtime.GOOS != "linux" && config.RemappedRoot != "" {
return idtools.IdentityMapping{}, fmt.Errorf("User namespaces are only supported on Linux")
return user.IdentityMapping{}, fmt.Errorf("User namespaces are only supported on Linux")
}
// if the daemon was started with remapped root option, parse
@@ -1359,13 +1361,13 @@ func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
if config.RemappedRoot != "" {
username, groupname, err := parseRemappedRoot(config.RemappedRoot)
if err != nil {
return idtools.IdentityMapping{}, err
return user.IdentityMapping{}, err
}
if username == "root" {
// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
// effectively
log.G(context.TODO()).Warn("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
return idtools.IdentityMapping{}, nil
return user.IdentityMapping{}, nil
}
log.G(context.TODO()).Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s", username)
// update remapped root setting now that we have resolved them to actual names
@@ -1373,14 +1375,14 @@ func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
mappings, err := usergroup.LoadIdentityMapping(username)
if err != nil {
return idtools.IdentityMapping{}, errors.Wrap(err, "Can't create ID mappings")
return user.IdentityMapping{}, errors.Wrap(err, "Can't create ID mappings")
}
return mappings, nil
}
return idtools.IdentityMapping{}, nil
return user.IdentityMapping{}, nil
}
func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools.Identity) error {
func setupDaemonRoot(config *config.Config, rootDir string, uid, gid int) error {
config.Root = rootDir
// the docker root metadata directory needs to have execute permissions for all users (g+x,o+x)
// so that syscalls executing as non-root, operating on subdirectories of the graph root
@@ -1400,9 +1402,9 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
}
}
id := idtools.Identity{UID: idtools.CurrentIdentity().UID, GID: remappedRoot.GID}
curuid := os.Getuid()
// First make sure the current root dir has the correct perms.
if err := idtools.MkdirAllAndChown(config.Root, 0o710, id); err != nil {
if err := user.MkdirAllAndChown(config.Root, 0o710, curuid, gid); err != nil {
return errors.Wrapf(err, "could not create or set daemon root permissions: %s", config.Root)
}
@@ -1411,10 +1413,10 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
// `chdir()` to work for containers namespaced to that uid/gid)
if config.RemappedRoot != "" {
config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", remappedRoot.UID, remappedRoot.GID))
config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", uid, gid))
log.G(context.TODO()).Debugf("Creating user namespaced daemon root: %s", config.Root)
// Create the root directory if it doesn't exist
if err := idtools.MkdirAllAndChown(config.Root, 0o710, id); err != nil {
if err := user.MkdirAllAndChown(config.Root, 0o710, curuid, gid); err != nil {
return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
}
// we also need to verify that any pre-existing directories in the path to
@@ -1427,7 +1429,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
if dirPath == "/" {
break
}
if !canAccess(dirPath, remappedRoot) {
if !canAccess(dirPath, uid, gid) {
return fmt.Errorf("a subdirectory in your graphroot path (%s) restricts access to the remapped root uid/gid; please fix by allowing 'o+x' permissions on existing directories", config.Root)
}
}
@@ -1445,7 +1447,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
// Note: this is a very rudimentary check, and may not produce accurate results,
// so should not be used for anything other than the current use, see:
// https://github.com/moby/moby/issues/43724
func canAccess(path string, pair idtools.Identity) bool {
func canAccess(path string, uid, gid int) bool {
statInfo, err := os.Stat(path)
if err != nil {
return false
@@ -1456,11 +1458,11 @@ func canAccess(path string, pair idtools.Identity) bool {
return true
}
ssi := statInfo.Sys().(*syscall.Stat_t)
if ssi.Uid == uint32(pair.UID) && (perms&0o100 == 0o100) {
if ssi.Uid == uint32(uid) && (perms&0o100 == 0o100) {
// owner access.
return true
}
if ssi.Gid == uint32(pair.GID) && (perms&0o010 == 0o010) {
if ssi.Gid == uint32(gid) && (perms&0o010 == 0o010) {
// group access.
return true
}

View File

@@ -24,10 +24,10 @@ import (
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
"github.com/docker/docker/libnetwork/scope"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/parsers/operatingsystem"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/moby/sys/user"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc/mgr"
@@ -56,7 +56,7 @@ func (daemon *Daemon) parseSecurityOpt(daemonCfg *config.Config, securityOptions
return nil
}
func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
func setupInitLayer(idMapping user.IdentityMapping) func(string) error {
return nil
}
@@ -462,11 +462,11 @@ func recursiveUnmount(_ string) error {
return nil
}
func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
return idtools.IdentityMapping{}, nil
func setupRemappedRoot(config *config.Config) (user.IdentityMapping, error) {
return user.IdentityMapping{}, nil
}
func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
func setupDaemonRoot(config *config.Config, rootDir string, uid, gid int) error {
config.Root = rootDir
// Create the root directory if it doesn't exists
if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil {

View File

@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/idtools"
)
// ContainerExport writes the contents of the container to the given
@@ -65,7 +66,7 @@ func (daemon *Daemon) containerExport(ctx context.Context, ctr *container.Contai
archv, err := chrootarchive.Tar(basefs, &archive.TarOptions{
Compression: archive.Uncompressed,
IDMap: daemon.idMapping,
IDMap: idtools.FromUserIdentityMapping(daemon.idMapping),
}, basefs)
if err != nil {
return err

View File

@@ -191,7 +191,7 @@ func (daemon *Daemon) fillSecurityOptions(v *system.Info, sysInfo *sysinfo.SysIn
if selinux.GetEnabled() {
securityOptions = append(securityOptions, "name=selinux")
}
if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
if uid, gid := daemon.idMapping.RootPair(); uid != 0 || gid != 0 {
securityOptions = append(securityOptions, "name=userns")
}
if Rootless(cfg) {

View File

@@ -360,13 +360,13 @@ func WithNamespaces(daemon *Daemon, c *container.Container) coci.SpecOpts {
}
}
func specMapping(s []idtools.IDMap) []specs.LinuxIDMapping {
func specMapping(s []user.IDMap) []specs.LinuxIDMapping {
var ids []specs.LinuxIDMapping
for _, item := range s {
ids = append(ids, specs.LinuxIDMapping{
HostID: uint32(item.HostID),
ContainerID: uint32(item.ContainerID),
Size: uint32(item.Size),
HostID: uint32(item.ParentID),
ContainerID: uint32(item.ID),
Size: uint32(item.Count),
})
}
return ids
@@ -712,7 +712,8 @@ func withCommonOptions(daemon *Daemon, daemonCfg *dconfig.Config, c *container.C
Path: c.BaseFS,
Readonly: c.HostConfig.ReadonlyRootfs,
}
if err := c.SetupWorkingDirectory(daemon.idMapping.RootPair()); err != nil {
uid, gid := daemon.idMapping.RootPair()
if err := c.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid}); err != nil {
return err
}
cwd := c.Config.WorkingDir

View File

@@ -14,6 +14,7 @@ import (
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/container"
"github.com/docker/docker/internal/cleanups"
"github.com/docker/docker/pkg/idtools"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/pkg/errors"
)
@@ -61,7 +62,8 @@ func (daemon *Daemon) setupMounts(ctx context.Context, c *container.Container) (
return nil
}
path, clean, err := m.Setup(ctx, c.MountLabel, daemon.idMapping.RootPair(), checkfunc)
uid, gid := daemon.idMapping.RootPair()
path, clean, err := m.Setup(ctx, c.MountLabel, idtools.Identity{UID: uid, GID: gid}, checkfunc)
if err != nil {
return nil, nil, err
}
@@ -106,13 +108,13 @@ func (daemon *Daemon) setupMounts(ctx context.Context, c *container.Container) (
// if we are going to mount any of the network files from container
// metadata, the ownership must be set properly for potential container
// remapped root (user namespaces)
rootIDs := daemon.idMapping.RootPair()
uid, gid := daemon.idMapping.RootPair()
for _, mnt := range netMounts {
// we should only modify ownership of network files within our own container
// metadata repository. If the user specifies a mount path external, it is
// up to the user to make sure the file has proper ownership for userns
if strings.Index(mnt.Source, daemon.repository) == 0 {
if err := os.Chown(mnt.Source, rootIDs.UID, rootIDs.GID); err != nil {
if err := os.Chown(mnt.Source, uid, gid); err != nil {
return nil, nil, err
}
}

View File

@@ -1,5 +1,7 @@
package daemon // import "github.com/docker/docker/daemon"
import "github.com/docker/docker/pkg/idtools"
// ContainerCreateWorkdir creates the working directory. This solves the
// issue arising from https://github.com/docker/docker/issues/27545,
// which was initially fixed by https://github.com/docker/docker/pull/27884. But that fix
@@ -16,5 +18,6 @@ func (daemon *Daemon) ContainerCreateWorkdir(cID string) error {
return err
}
defer daemon.Unmount(container)
return container.SetupWorkingDirectory(daemon.idMapping.RootPair())
uid, gid := daemon.idMapping.RootPair()
return container.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid})
}

View File

@@ -7,7 +7,7 @@ import (
"syscall"
"testing"
"github.com/docker/docker/pkg/idtools"
mobyuser "github.com/moby/sys/user"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
@@ -29,14 +29,13 @@ func TestNewIDMappings(t *testing.T) {
idMapping, err := LoadIdentityMapping(tempUser.Username)
assert.Check(t, err)
rootUID, rootGID, err := idtools.GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps)
assert.Check(t, err)
rootUID, rootGID := idMapping.RootPair()
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})
err = mobyuser.MkdirAllAndChown(dirName, 0o700, rootUID, rootGID)
assert.Check(t, err, "Couldn't change ownership of file path. Got error")
cmd := exec.Command("ls", "-la", dirName)
cmd.SysProcAttr = &syscall.SysProcAttr{

View File

@@ -10,7 +10,6 @@ import (
"strconv"
"syscall"
"github.com/docker/docker/pkg/idtools"
"github.com/moby/sys/user"
)
@@ -140,28 +139,28 @@ func getExitCode(err error) (int, error) {
// 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) {
func LoadIdentityMapping(name string) (user.IdentityMapping, error) {
usr, err := LookupUser(name)
if err != nil {
return idtools.IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
return user.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
return user.IdentityMapping{}, err
}
subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr)
if err != nil {
return idtools.IdentityMapping{}, err
return user.IdentityMapping{}, err
}
return idtools.IdentityMapping{
return user.IdentityMapping{
UIDMaps: subuidRanges,
GIDMaps: subgidRanges,
}, nil
}
func lookupSubRangesFile(path string, usr user.User) ([]idtools.IDMap, error) {
func lookupSubRangesFile(path string, usr user.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
@@ -173,16 +172,16 @@ func lookupSubRangesFile(path string, usr user.User) ([]idtools.IDMap, error) {
return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name)
}
idMap := []idtools.IDMap{}
idMap := []user.IDMap{}
containerID := 0
containerID := int64(0)
for _, idrange := range rangeList {
idMap = append(idMap, idtools.IDMap{
ContainerID: containerID,
HostID: int(idrange.SubID),
Size: int(idrange.Count),
idMap = append(idMap, user.IDMap{
ID: containerID,
ParentID: idrange.SubID,
Count: idrange.Count,
})
containerID = containerID + int(idrange.Count)
containerID = containerID + idrange.Count
}
return idMap, nil
}

View File

@@ -3,11 +3,15 @@ package idtools
import (
"fmt"
"os"
"github.com/moby/sys/user"
)
// IDMap contains a single entry for user namespace range remapping. An array
// of IDMap entries represents the structure that will be provided to the Linux
// kernel for creating a user namespace.
//
// Deprecated: use [user.IDMap] instead.
type IDMap struct {
ContainerID int `json:"container_id"`
HostID int `json:"host_id"`
@@ -17,28 +21,42 @@ type IDMap struct {
// 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.
//
// Deprecated: use [user.MkdirAllAndChown] instead.
func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, true, true)
return user.MkdirAllAndChown(path, mode, owner.UID, owner.GID)
}
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership and permissions.
// Note that unlike os.Mkdir(), this function does not return IsExist error
// in case path already exists.
//
// Deprecated: use [user.MkdirAndChown] instead.
func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, false, true)
return user.MkdirAndChown(path, mode, owner.UID, owner.GID)
}
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership or permissions will be performed
//
// Deprecated: use [user.MkdirAllAndChown] instead.
func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
return mkdirAs(path, mode, owner, true, false)
return user.MkdirAllAndChown(path, mode, owner.UID, owner.GID, user.WithOnlyNew)
}
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
//
// Deprecated: use [(user.IdentityMapping).RootPair] instead.
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
return getRootUIDGID(uidMap, gidMap)
}
// getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func getRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
uid, err := toHost(0, uidMap)
if err != nil {
return -1, -1, err
@@ -101,11 +119,36 @@ type IdentityMapping struct {
GIDMaps []IDMap `json:"GIDMaps"`
}
// FromUserIdentityMapping converts a [user.IdentityMapping] to an [idtools.IdentityMapping].
//
// Deprecated: use [user.IdentityMapping] directly, this is transitioning to user package.
func FromUserIdentityMapping(u user.IdentityMapping) IdentityMapping {
return IdentityMapping{
UIDMaps: fromUserIDMap(u.UIDMaps),
GIDMaps: fromUserIDMap(u.GIDMaps),
}
}
func fromUserIDMap(u []user.IDMap) []IDMap {
if u == nil {
return nil
}
m := make([]IDMap, len(u))
for i := range u {
m[i] = IDMap{
ContainerID: int(u[i].ID),
HostID: int(u[i].ParentID),
Size: int(u[i].Count),
}
}
return m
}
// RootPair returns a uid and gid pair for the root user. The error is ignored
// because a root user always exists, and the defaults are correct when the uid
// and gid maps are empty.
func (i IdentityMapping) RootPair() Identity {
uid, gid, _ := GetRootUIDGID(i.UIDMaps, i.GIDMaps)
uid, gid, _ := getRootUIDGID(i.UIDMaps, i.GIDMaps)
return Identity{UID: uid, GID: gid}
}
@@ -144,6 +187,8 @@ func (i IdentityMapping) Empty() bool {
}
// CurrentIdentity returns the identity of the current process
//
// Deprecated: use [os.Getuid] and [os.Getegid] instead.
func CurrentIdentity() Identity {
return Identity{UID: os.Getuid(), GID: os.Getegid()}
}

View File

@@ -3,321 +3,13 @@
package idtools
import (
"fmt"
"os"
"path/filepath"
"testing"
"golang.org/x/sys/unix"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
type node struct {
uid int
gid int
}
func TestMkdirAllAndChown(t *testing.T) {
RequiresRoot(t)
dirName, err := os.MkdirTemp("", "mkdirall")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
"usr/bin": {0, 0},
"lib": {33, 33},
"lib/x86_64": {45, 45},
"lib/x86_64/share": {1, 1},
}
if err := buildTree(dirName, testTree); err != nil {
t.Fatal(err)
}
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
if err := MkdirAllAndChown(filepath.Join(dirName, "usr", "share"), 0o755, Identity{UID: 99, GID: 99}); err != nil {
t.Fatal(err)
}
testTree["usr/share"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test 2-deep new directories--both should be owned by the uid/gid pair
if err := MkdirAllAndChown(filepath.Join(dirName, "lib", "some", "other"), 0o755, Identity{UID: 101, GID: 101}); err != nil {
t.Fatal(err)
}
testTree["lib/some"] = node{101, 101}
testTree["lib/some/other"] = node{101, 101}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// test a directory that already exists; should be chowned, but nothing else
if err := MkdirAllAndChown(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 102, GID: 102}); err != nil {
t.Fatal(err)
}
testTree["usr"] = node{102, 102}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
}
func TestMkdirAllAndChownNew(t *testing.T) {
RequiresRoot(t)
dirName, err := os.MkdirTemp("", "mkdirnew")
assert.NilError(t, err)
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
"usr/bin": {0, 0},
"lib": {33, 33},
"lib/x86_64": {45, 45},
"lib/x86_64/share": {1, 1},
}
assert.NilError(t, buildTree(dirName, testTree))
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
err = MkdirAllAndChownNew(filepath.Join(dirName, "usr", "share"), 0o755, Identity{UID: 99, GID: 99})
assert.NilError(t, err)
testTree["usr/share"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
assert.NilError(t, err)
assert.NilError(t, compareTrees(testTree, verifyTree))
// test 2-deep new directories--both should be owned by the uid/gid pair
err = MkdirAllAndChownNew(filepath.Join(dirName, "lib", "some", "other"), 0o755, Identity{UID: 101, GID: 101})
assert.NilError(t, err)
testTree["lib/some"] = node{101, 101}
testTree["lib/some/other"] = node{101, 101}
verifyTree, err = readTree(dirName, "")
assert.NilError(t, err)
assert.NilError(t, compareTrees(testTree, verifyTree))
// test a directory that already exists; should NOT be chowned
err = MkdirAllAndChownNew(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 102, GID: 102})
assert.NilError(t, err)
verifyTree, err = readTree(dirName, "")
assert.NilError(t, err)
assert.NilError(t, compareTrees(testTree, verifyTree))
}
func TestMkdirAllAndChownNewRelative(t *testing.T) {
RequiresRoot(t)
tests := []struct {
in string
out []string
}{
{
in: "dir1",
out: []string{"dir1"},
},
{
in: "dir2/subdir2",
out: []string{"dir2", "dir2/subdir2"},
},
{
in: "dir3/subdir3/",
out: []string{"dir3", "dir3/subdir3"},
},
{
in: "dir4/subdir4/.",
out: []string{"dir4", "dir4/subdir4"},
},
{
in: "dir5/././subdir5/",
out: []string{"dir5", "dir5/subdir5"},
},
{
in: "./dir6",
out: []string{"dir6"},
},
{
in: "./dir7/subdir7",
out: []string{"dir7", "dir7/subdir7"},
},
{
in: "./dir8/subdir8/",
out: []string{"dir8", "dir8/subdir8"},
},
{
in: "./dir9/subdir9/.",
out: []string{"dir9", "dir9/subdir9"},
},
{
in: "./dir10/././subdir10/",
out: []string{"dir10", "dir10/subdir10"},
},
}
// Set the current working directory to the temp-dir, as we're
// testing relative paths.
tmpDir := t.TempDir()
setWorkingDirectory(t, tmpDir)
const expectedUIDGID = 101
for _, tc := range tests {
t.Run(tc.in, func(t *testing.T) {
for _, p := range tc.out {
_, err := os.Stat(p)
assert.ErrorIs(t, err, os.ErrNotExist)
}
err := MkdirAllAndChownNew(tc.in, 0o755, Identity{UID: expectedUIDGID, GID: expectedUIDGID})
assert.Check(t, err)
for _, p := range tc.out {
s := &unix.Stat_t{}
err = unix.Stat(p, s)
if assert.Check(t, err) {
assert.Check(t, is.Equal(uint64(s.Uid), uint64(expectedUIDGID)))
assert.Check(t, is.Equal(uint64(s.Gid), uint64(expectedUIDGID)))
}
}
})
}
}
// Change the current working directory for the duration of the test. This may
// break if tests are run in parallel.
func setWorkingDirectory(t *testing.T, dir string) {
t.Helper()
cwd, err := os.Getwd()
assert.NilError(t, err)
t.Cleanup(func() {
assert.NilError(t, os.Chdir(cwd))
})
err = os.Chdir(dir)
assert.NilError(t, err)
}
func TestMkdirAndChown(t *testing.T) {
RequiresRoot(t)
dirName, err := os.MkdirTemp("", "mkdir")
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
testTree := map[string]node{
"usr": {0, 0},
}
if err := buildTree(dirName, testTree); err != nil {
t.Fatal(err)
}
// test a directory that already exists; should just chown to the requested uid/gid
if err := MkdirAndChown(filepath.Join(dirName, "usr"), 0o755, Identity{UID: 99, GID: 99}); err != nil {
t.Fatal(err)
}
testTree["usr"] = node{99, 99}
verifyTree, err := readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
// create a subdir under a dir which doesn't exist--should fail
if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin", "subdir"), 0o755, Identity{UID: 102, GID: 102}); err == nil {
t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed")
}
// create a subdir under an existing dir; should only change the ownership of the new subdir
if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin"), 0o755, Identity{UID: 102, GID: 102}); err != nil {
t.Fatal(err)
}
testTree["usr/bin"] = node{102, 102}
verifyTree, err = readTree(dirName, "")
if err != nil {
t.Fatal(err)
}
if err := compareTrees(testTree, verifyTree); err != nil {
t.Fatal(err)
}
}
func buildTree(base string, tree map[string]node) error {
for path, node := range tree {
fullPath := filepath.Join(base, path)
if err := os.MkdirAll(fullPath, 0o755); err != nil {
return fmt.Errorf("couldn't create path: %s; error: %v", fullPath, err)
}
if err := os.Chown(fullPath, node.uid, node.gid); err != nil {
return fmt.Errorf("couldn't chown path: %s; error: %v", fullPath, err)
}
}
return nil
}
func readTree(base, root string) (map[string]node, error) {
tree := make(map[string]node)
dirInfos, err := os.ReadDir(base)
if err != nil {
return nil, fmt.Errorf("couldn't read directory entries for %q: %v", base, err)
}
for _, info := range dirInfos {
s := &unix.Stat_t{}
if err := unix.Stat(filepath.Join(base, info.Name()), s); err != nil {
return nil, fmt.Errorf("can't stat file %q: %v", filepath.Join(base, info.Name()), err)
}
tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)}
if info.IsDir() {
// read the subdirectory
subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name()))
if err != nil {
return nil, err
}
for path, nodeinfo := range subtree {
tree[path] = nodeinfo
}
}
}
return tree, nil
}
func compareTrees(left, right map[string]node) error {
if len(left) != len(right) {
return fmt.Errorf("trees aren't the same size")
}
for path, nodeLeft := range left {
if nodeRight, ok := right[path]; ok {
if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid {
// mismatch
return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path,
nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid)
}
continue
}
return fmt.Errorf("right tree didn't contain path %q", path)
}
return nil
}
func TestGetRootUIDGID(t *testing.T) {
uidMap := []IDMap{
{
@@ -334,7 +26,7 @@ func TestGetRootUIDGID(t *testing.T) {
},
}
uid, gid, err := GetRootUIDGID(uidMap, gidMap)
uid, gid, err := getRootUIDGID(uidMap, gidMap)
assert.Check(t, err)
assert.Check(t, is.Equal(os.Geteuid(), uid))
assert.Check(t, is.Equal(os.Getegid(), gid))
@@ -346,7 +38,7 @@ func TestGetRootUIDGID(t *testing.T) {
Size: 1,
},
}
_, _, err = GetRootUIDGID(uidMapError, gidMap)
_, _, err = getRootUIDGID(uidMapError, gidMap)
assert.Check(t, is.Error(err, "Container ID 0 cannot be mapped to a host ID"))
}
@@ -363,21 +55,3 @@ func TestToContainer(t *testing.T) {
assert.Check(t, err)
assert.Check(t, is.Equal(uidMap[0].ContainerID, containerID))
}
// 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).
func TestMkdirIsNotDir(t *testing.T) {
file, err := os.CreateTemp("", t.Name())
if err != nil {
t.Fatalf("Couldn't create temp dir: %v", err)
}
defer os.Remove(file.Name())
err = mkdirAs(file.Name(), 0o755, Identity{UID: 0, GID: 0}, false, false)
assert.Check(t, is.Error(err, "mkdir "+file.Name()+": not a directory"))
}
func RequiresRoot(t *testing.T) {
skip.If(t, os.Getuid() != 0, "skipping test that requires root")
}

View File

@@ -1,9 +1,5 @@
package idtools
import (
"os"
)
const (
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
)
@@ -14,11 +10,3 @@ const (
ContainerUserSidString = "S-1-5-93-2-2"
)
// This is currently a wrapper around [os.MkdirAll] since currently
// permissions aren't set through this path, the identity isn't utilized.
// Ownership is handled elsewhere, but in the future could be support here
// too.
func mkdirAs(path string, _ os.FileMode, _ Identity, _, _ bool) error {
return os.MkdirAll(path, 0)
}

View File

@@ -76,7 +76,7 @@ require (
github.com/moby/sys/sequential v0.6.0
github.com/moby/sys/signal v0.7.1
github.com/moby/sys/symlink v0.3.0
github.com/moby/sys/user v0.3.0
github.com/moby/sys/user v0.4.0
github.com/moby/sys/userns v0.1.0
github.com/moby/term v0.5.2
github.com/morikuni/aec v1.0.0

View File

@@ -409,8 +409,8 @@ github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=

141
vendor/github.com/moby/sys/user/idtools.go generated vendored Normal file
View File

@@ -0,0 +1,141 @@
package user
import (
"fmt"
"os"
)
// MkdirOpt is a type for options to pass to Mkdir calls
type MkdirOpt func(*mkdirOptions)
type mkdirOptions struct {
onlyNew bool
}
// WithOnlyNew is an option for MkdirAllAndChown that will only change ownership and permissions
// on newly created directories. If the directory already exists, it will not be modified
func WithOnlyNew(o *mkdirOptions) {
o.onlyNew = true
}
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid. By default, if the directory already exists, this
// function will still change ownership and permissions. If WithOnlyNew is passed as an
// option, then only the newly created directories will have ownership and permissions changed.
func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error {
var options mkdirOptions
for _, opt := range opts {
opt(&options)
}
return mkdirAs(path, mode, uid, gid, true, options.onlyNew)
}
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// By default, if the directory already exists, this function still changes ownership and permissions.
// If WithOnlyNew is passed as an option, then only the newly created directory will have ownership
// and permissions changed.
// Note that unlike os.Mkdir(), this function does not return IsExist error
// in case path already exists.
func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error {
var options mkdirOptions
for _, opt := range opts {
opt(&options)
}
return mkdirAs(path, mode, uid, gid, false, options.onlyNew)
}
// getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func getRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
uid, err := toHost(0, uidMap)
if err != nil {
return -1, -1, err
}
gid, err := toHost(0, gidMap)
if err != nil {
return -1, -1, err
}
return uid, gid, nil
}
// toContainer takes an id mapping, and uses it to translate a
// host ID to the remapped ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id
func toContainer(hostID int, idMap []IDMap) (int, error) {
if idMap == nil {
return hostID, nil
}
for _, m := range idMap {
if (int64(hostID) >= m.ParentID) && (int64(hostID) <= (m.ParentID + m.Count - 1)) {
contID := int(m.ID + (int64(hostID) - m.ParentID))
return contID, nil
}
}
return -1, fmt.Errorf("host ID %d cannot be mapped to a container ID", hostID)
}
// toHost takes an id mapping and a remapped ID, and translates the
// ID to the mapped host ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id #
func toHost(contID int, idMap []IDMap) (int, error) {
if idMap == nil {
return contID, nil
}
for _, m := range idMap {
if (int64(contID) >= m.ID) && (int64(contID) <= (m.ID + m.Count - 1)) {
hostID := int(m.ParentID + (int64(contID) - m.ID))
return hostID, nil
}
}
return -1, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID)
}
// IdentityMapping contains a mappings of UIDs and GIDs.
// The zero value represents an empty mapping.
type IdentityMapping struct {
UIDMaps []IDMap `json:"UIDMaps"`
GIDMaps []IDMap `json:"GIDMaps"`
}
// RootPair returns a uid and gid pair for the root user. The error is ignored
// because a root user always exists, and the defaults are correct when the uid
// and gid maps are empty.
func (i IdentityMapping) RootPair() (int, int) {
uid, gid, _ := getRootUIDGID(i.UIDMaps, i.GIDMaps)
return uid, gid
}
// ToHost returns the host UID and GID for the container uid, gid.
// Remapping is only performed if the ids aren't already the remapped root ids
func (i IdentityMapping) ToHost(uid, gid int) (int, int, error) {
var err error
ruid, rgid := i.RootPair()
if uid != ruid {
ruid, err = toHost(uid, i.UIDMaps)
if err != nil {
return ruid, rgid, err
}
}
if gid != rgid {
rgid, err = toHost(gid, i.GIDMaps)
}
return ruid, rgid, err
}
// ToContainer returns the container UID and GID for the host uid and gid
func (i IdentityMapping) ToContainer(uid, gid int) (int, int, error) {
ruid, err := toContainer(uid, i.UIDMaps)
if err != nil {
return -1, -1, err
}
rgid, err := toContainer(gid, i.GIDMaps)
return ruid, rgid, err
}
// Empty returns true if there are no id mappings
func (i IdentityMapping) Empty() bool {
return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
}

View File

@@ -1,6 +1,6 @@
//go:build !windows
package idtools
package user
import (
"fmt"
@@ -8,11 +8,9 @@ import (
"path/filepath"
"strconv"
"syscall"
"github.com/moby/sys/user"
)
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) error {
path, err := filepath.Abs(path)
if err != nil {
return err
@@ -23,21 +21,21 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
if !stat.IsDir() {
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
if !chownExisting {
if onlyNew {
return nil
}
// short-circuit -- we were called with an existing directory and chown was requested
return setPermissions(path, mode, owner, stat)
return setPermissions(path, mode, uid, gid, stat)
}
// make an array containing the original path asked for, plus (for mkAll == true)
// all path components leading up to the complete path that don't exist before we MkdirAll
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
// so that we can chown all of them properly at the end. If onlyNew is true, we won't
// chown the full directory path if it exists
var paths []string
if os.IsNotExist(err) {
paths = []string{path}
paths = append(paths, path)
}
if mkAll {
@@ -49,7 +47,7 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
if dirPath == "/" {
break
}
if _, err = os.Stat(dirPath); err != nil && os.IsNotExist(err) {
if _, err = os.Stat(dirPath); os.IsNotExist(err) {
paths = append(paths, dirPath)
}
}
@@ -62,39 +60,18 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
// even if it existed, we will chown the requested path + any subpaths that
// didn't exist when we called MkdirAll
for _, pathComponent := range paths {
if err = setPermissions(pathComponent, mode, owner, nil); err != nil {
if err = setPermissions(pathComponent, mode, uid, gid, nil); err != nil {
return err
}
}
return nil
}
// 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) {
return user.LookupUser(name)
}
// 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) {
return user.LookupUid(uid)
}
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
//
// Deprecated: use [user.LookupGroup] instead
func LookupGroup(name string) (user.Group, error) {
return user.LookupGroup(name)
}
// setPermissions performs a chown/chmod only if the uid/gid don't match what's requested
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
// dir is on an NFS share, so don't call chown unless we absolutely must.
// Likewise for setting permissions.
func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo) error {
func setPermissions(p string, mode os.FileMode, uid, gid int, stat os.FileInfo) error {
if stat == nil {
var err error
stat, err = os.Stat(p)
@@ -108,10 +85,10 @@ func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo
}
}
ssi := stat.Sys().(*syscall.Stat_t)
if ssi.Uid == uint32(owner.UID) && ssi.Gid == uint32(owner.GID) {
if ssi.Uid == uint32(uid) && ssi.Gid == uint32(gid) {
return nil
}
return os.Chown(p, owner.UID, owner.GID)
return os.Chown(p, uid, gid)
}
// LoadIdentityMapping takes a requested username and
@@ -119,9 +96,9 @@ func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo
// proper uid and gid remapping ranges for that user/group pair
func LoadIdentityMapping(name string) (IdentityMapping, error) {
// TODO: Consider adding support for calling out to "getent"
usr, err := user.LookupUser(name)
usr, err := LookupUser(name)
if err != nil {
return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %w", name, err)
}
subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr)
@@ -139,9 +116,9 @@ func LoadIdentityMapping(name string) (IdentityMapping, error) {
}, nil
}
func lookupSubRangesFile(path string, usr user.User) ([]IDMap, error) {
func lookupSubRangesFile(path string, usr User) ([]IDMap, error) {
uidstr := strconv.Itoa(usr.Uid)
rangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool {
rangeList, err := ParseSubIDFileFilter(path, func(sid SubID) bool {
return sid.Name == usr.Name || sid.Name == uidstr
})
if err != nil {
@@ -153,14 +130,14 @@ func lookupSubRangesFile(path string, usr user.User) ([]IDMap, error) {
idMap := []IDMap{}
containerID := 0
var containerID int64
for _, idrange := range rangeList {
idMap = append(idMap, IDMap{
ContainerID: containerID,
HostID: int(idrange.SubID),
Size: int(idrange.Count),
ID: containerID,
ParentID: idrange.SubID,
Count: idrange.Count,
})
containerID = containerID + int(idrange.Count)
containerID = containerID + idrange.Count
}
return idMap, nil
}

13
vendor/github.com/moby/sys/user/idtools_windows.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package user
import (
"os"
)
// This is currently a wrapper around [os.MkdirAll] since currently
// permissions aren't set through this path, the identity isn't utilized.
// Ownership is handled elsewhere, but in the future could be support here
// too.
func mkdirAs(path string, _ os.FileMode, _, _ int, _, _ bool) error {
return os.MkdirAll(path, 0)
}

2
vendor/modules.txt vendored
View File

@@ -1025,7 +1025,7 @@ github.com/moby/sys/signal
# github.com/moby/sys/symlink v0.3.0
## explicit; go 1.17
github.com/moby/sys/symlink
# github.com/moby/sys/user v0.3.0
# github.com/moby/sys/user v0.4.0
## explicit; go 1.17
github.com/moby/sys/user
# github.com/moby/sys/userns v0.1.0