From 09cd96c5ad2de369912cdf708c3c50f41e4586ac Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 19 May 2017 18:06:46 -0400 Subject: [PATCH] Partial refactor of UID/GID usage to use a unified struct. Signed-off-by: Daniel Nephin --- container/container.go | 4 +- daemon/archive.go | 13 +++--- daemon/archive_tarcopyoptions.go | 5 +-- daemon/container_operations_unix.go | 24 +++++----- daemon/create.go | 6 +-- daemon/create_unix.go | 4 +- daemon/daemon.go | 52 +++++++-------------- daemon/daemon_test.go | 3 +- daemon/daemon_unix.go | 26 +++++------ daemon/daemon_unix_test.go | 3 +- daemon/daemon_windows.go | 6 +-- daemon/export.go | 5 +-- daemon/info.go | 4 +- daemon/initlayer/setup_unix.go | 8 ++-- daemon/initlayer/setup_windows.go | 6 ++- daemon/oci_linux.go | 10 ++--- daemon/oci_solaris.go | 4 +- daemon/volumes_unix.go | 8 ++-- daemon/volumes_windows.go | 3 +- daemon/workdir.go | 4 +- layer/layer_store.go | 7 ++- pkg/idtools/idtools.go | 70 ++++++++++++++++++++++++++--- pkg/idtools/idtools_unix.go | 6 +-- pkg/idtools/idtools_windows.go | 2 +- plugin/manager_linux.go | 3 +- volume/local/local.go | 12 +++-- volume/local/local_test.go | 19 ++++---- volume/volume.go | 4 +- 28 files changed, 180 insertions(+), 141 deletions(-) diff --git a/container/container.go b/container/container.go index e89340610c..f22b4aa574 100644 --- a/container/container.go +++ b/container/container.go @@ -216,7 +216,7 @@ func (container *Container) WriteHostConfig() error { } // SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir -func (container *Container) SetupWorkingDirectory(rootUID, rootGID int) error { +func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error { if container.Config.WorkingDir == "" { return nil } @@ -228,7 +228,7 @@ func (container *Container) SetupWorkingDirectory(rootUID, rootGID int) error { return err } - if err := idtools.MkdirAllNewAs(pth, 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIDs); err != nil { pthInfo, err2 := os.Stat(pth) if err2 == nil && pthInfo != nil && !pthInfo.IsDir() { return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) diff --git a/daemon/archive.go b/daemon/archive.go index dbef0b4d48..c41298df0e 100644 --- a/daemon/archive.go +++ b/daemon/archive.go @@ -375,7 +375,7 @@ func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decomp destExists := true destDir := false - rootUID, rootGID := daemon.GetRemappedUIDGID() + rootIDs, _ := daemon.idMappings.RootPair() // Work in daemon-local OS specific file paths destPath = filepath.FromSlash(destPath) @@ -413,11 +413,10 @@ func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decomp destExists = false } - uidMaps, gidMaps := daemon.GetUIDGIDMaps() archiver := &archive.Archiver{ Untar: chrootarchive.Untar, - UIDMaps: uidMaps, - GIDMaps: gidMaps, + UIDMaps: daemon.idMappings.UIDs(), + GIDMaps: daemon.idMappings.GIDs(), } src, err := os.Stat(fullSrcPath) @@ -430,7 +429,7 @@ func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decomp if err := archiver.CopyWithTar(fullSrcPath, destPath); err != nil { return err } - return fixPermissions(fullSrcPath, destPath, rootUID, rootGID, destExists) + return fixPermissions(fullSrcPath, destPath, rootIDs.UID, rootIDs.GID, destExists) } if decompress && archive.IsArchivePath(fullSrcPath) { // Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file) @@ -459,12 +458,12 @@ func (daemon *Daemon) CopyOnBuild(cID, destPath, srcRoot, srcPath string, decomp destPath = filepath.Join(destPath, filepath.Base(srcPath)) } - if err := idtools.MkdirAllNewAs(filepath.Dir(destPath), 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Dir(destPath), 0755, rootIDs); err != nil { return err } if err := archiver.CopyFileWithTar(fullSrcPath, destPath); err != nil { return err } - return fixPermissions(fullSrcPath, destPath, rootUID, rootGID, destExists) + return fixPermissions(fullSrcPath, destPath, rootIDs.UID, rootIDs.GID, destExists) } diff --git a/daemon/archive_tarcopyoptions.go b/daemon/archive_tarcopyoptions.go index cbd1fc2cee..fe7722fdb4 100644 --- a/daemon/archive_tarcopyoptions.go +++ b/daemon/archive_tarcopyoptions.go @@ -7,10 +7,9 @@ import ( // defaultTarCopyOptions is the setting that is used when unpacking an archive // for a copy API event. func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions { - uidMaps, gidMaps := daemon.GetUIDGIDMaps() return &archive.TarOptions{ NoOverwriteDirNonDir: noOverwriteDirNonDir, - UIDMaps: uidMaps, - GIDMaps: gidMaps, + UIDMaps: daemon.idMappings.UIDs(), + GIDMaps: daemon.idMappings.GIDs(), } } diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 6889c10956..5ae7a68f0b 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -109,14 +109,14 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error { } c.ShmPath = "/dev/shm" } else { - rootUID, rootGID := daemon.GetRemappedUIDGID() + rootIDs, _ := daemon.idMappings.RootPair() if !c.HasMountFor("/dev/shm") { shmPath, err := c.ShmResourcePath() if err != nil { return err } - if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(shmPath, 0700, rootIDs); err != nil { return err } @@ -128,7 +128,7 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error { if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil { return fmt.Errorf("mounting shm tmpfs: %s", err) } - if err := os.Chown(shmPath, rootUID, rootGID); err != nil { + if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil { return err } } @@ -147,9 +147,9 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { logrus.Debugf("secrets: setting up secret dir: %s", localMountPath) // retrieve possible remapped range start for root UID, GID - rootUID, rootGID := daemon.GetRemappedUIDGID() + rootIDs, _ := daemon.idMappings.RootPair() // create tmpfs - if err := idtools.MkdirAllAs(localMountPath, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(localMountPath, 0700, rootIDs); err != nil { return errors.Wrap(err, "error creating secret local mount path") } @@ -164,7 +164,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { } }() - tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootUID, rootGID) + tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID) if err := mount.Mount("tmpfs", localMountPath, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil { return errors.Wrap(err, "unable to setup secret mount") } @@ -183,7 +183,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { // secrets are created in the SecretMountPath on the host, at a // single level fPath := c.SecretFilePath(*s) - if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0700, rootIDs); err != nil { return errors.Wrap(err, "error creating secret mount path") } @@ -208,7 +208,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { return err } - if err := os.Chown(fPath, rootUID+uid, rootGID+gid); err != nil { + if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil { return errors.Wrap(err, "error setting ownership for secret") } } @@ -232,9 +232,9 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) { logrus.Debugf("configs: setting up config dir: %s", localPath) // retrieve possible remapped range start for root UID, GID - rootUID, rootGID := daemon.GetRemappedUIDGID() + rootIDs, _ := daemon.idMappings.RootPair() // create tmpfs - if err := idtools.MkdirAllAs(localPath, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(localPath, 0700, rootIDs); err != nil { return errors.Wrap(err, "error creating config dir") } @@ -261,7 +261,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) { log := logrus.WithFields(logrus.Fields{"name": configRef.File.Name, "path": fPath}) - if err := idtools.MkdirAllAs(filepath.Dir(fPath), 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0700, rootIDs); err != nil { return errors.Wrap(err, "error creating config path") } @@ -283,7 +283,7 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) { return err } - if err := os.Chown(fPath, rootUID+uid, rootGID+gid); err != nil { + if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil { return errors.Wrap(err, "error setting ownership for config") } } diff --git a/daemon/create.go b/daemon/create.go index de129ca60d..52b4b0e6fa 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -117,14 +117,14 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) ( return nil, err } - rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) + rootIDs, err := daemon.idMappings.RootPair() if err != nil { return nil, err } - if err := idtools.MkdirAs(container.Root, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil { return nil, err } - if err := idtools.MkdirAs(container.CheckpointDir(), 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil { return nil, err } diff --git a/daemon/create_unix.go b/daemon/create_unix.go index 7b067bdc22..27471e7556 100644 --- a/daemon/create_unix.go +++ b/daemon/create_unix.go @@ -22,8 +22,8 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain } defer daemon.Unmount(container) - rootUID, rootGID := daemon.GetRemappedUIDGID() - if err := container.SetupWorkingDirectory(rootUID, rootGID); err != nil { + rootIDs, _ := daemon.idMappings.RootPair() + if err := container.SetupWorkingDirectory(rootIDs); err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 05017775d4..3e8b3106a2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -94,8 +94,7 @@ type Daemon struct { seccompEnabled bool apparmorEnabled bool shutdown bool - uidMaps []idtools.IDMap - gidMaps []idtools.IDMap + idMappings *idtools.IDMappings layerStore layer.Store imageStore image.Store PluginStore *plugin.Store // todo: remove @@ -524,11 +523,11 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe return nil, err } - uidMaps, gidMaps, err := setupRemappedRoot(config) + idMappings, err := setupRemappedRoot(config) if err != nil { return nil, err } - rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) + rootIDs, err := idMappings.RootPair() if err != nil { return nil, err } @@ -538,7 +537,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe } // set up the tmpDir to use a canonical path - tmp, err := prepareTempDir(config.Root, rootUID, rootGID) + tmp, err := prepareTempDir(config.Root, rootIDs) if err != nil { return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err) } @@ -587,7 +586,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe } daemonRepo := filepath.Join(config.Root, "containers") - if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { + if err := idtools.MkdirAllAndChown(daemonRepo, 0700, rootIDs); err != nil && !os.IsExist(err) { return nil, err } @@ -632,8 +631,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), GraphDriver: driverName, GraphDriverOptions: config.GraphOptions, - UIDMaps: uidMaps, - GIDMaps: gidMaps, + IDMappings: idMappings, PluginGetter: d.PluginStore, ExperimentalEnabled: config.Experimental, }) @@ -665,7 +663,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe } // Configure the volumes driver - volStore, err := d.configureVolumes(rootUID, rootGID) + volStore, err := d.configureVolumes(rootIDs) if err != nil { return nil, err } @@ -728,8 +726,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe d.EventsService = eventsService d.volumes = volStore d.root = config.Root - d.uidMaps = uidMaps - d.gidMaps = gidMaps + d.idMappings = idMappings d.seccompEnabled = sysInfo.Seccomp d.apparmorEnabled = sysInfo.AppArmor @@ -970,25 +967,10 @@ func (daemon *Daemon) GraphDriverName() string { return daemon.layerStore.DriverName() } -// GetUIDGIDMaps returns the current daemon's user namespace settings -// for the full uid and gid maps which will be applied to containers -// started in this instance. -func (daemon *Daemon) GetUIDGIDMaps() ([]idtools.IDMap, []idtools.IDMap) { - return daemon.uidMaps, daemon.gidMaps -} - -// GetRemappedUIDGID returns the current daemon's uid and gid values -// if user namespaces are in use for this daemon instance. If not -// this function will return "real" root values of 0, 0. -func (daemon *Daemon) GetRemappedUIDGID() (int, int) { - uid, gid, _ := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) - return uid, gid -} - // prepareTempDir prepares and returns the default directory to use // for temporary files. // If it doesn't exist, it is created. If it exists, its content is removed. -func prepareTempDir(rootDir string, rootUID, rootGID int) (string, error) { +func prepareTempDir(rootDir string, rootIDs idtools.IDPair) (string, error) { var tmpDir string if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { tmpDir = filepath.Join(rootDir, "tmp") @@ -1008,12 +990,12 @@ func prepareTempDir(rootDir string, rootUID, rootGID int) (string, error) { } // We don't remove the content of tmpdir if it's not the default, // it may hold things that do not belong to us. - return tmpDir, idtools.MkdirAllAs(tmpDir, 0700, rootUID, rootGID) + return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0700, rootIDs) } func (daemon *Daemon) setupInitLayer(initPath string) error { - rootUID, rootGID := daemon.GetRemappedUIDGID() - return initlayer.Setup(initPath, rootUID, rootGID) + rootIDs, _ := daemon.idMappings.RootPair() + return initlayer.Setup(initPath, rootIDs) } func setDefaultMtu(conf *config.Config) { @@ -1024,8 +1006,8 @@ func setDefaultMtu(conf *config.Config) { conf.Mtu = config.DefaultNetworkMtu } -func (daemon *Daemon) configureVolumes(rootUID, rootGID int) (*store.VolumeStore, error) { - volumesDriver, err := local.New(daemon.configStore.Root, rootUID, rootGID) +func (daemon *Daemon) configureVolumes(rootIDs idtools.IDPair) (*store.VolumeStore, error) { + volumesDriver, err := local.New(daemon.configStore.Root, rootIDs) if err != nil { return nil, err } @@ -1171,16 +1153,16 @@ func CreateDaemonRoot(config *config.Config) error { } } - uidMaps, gidMaps, err := setupRemappedRoot(config) + idMappings, err := setupRemappedRoot(config) if err != nil { return err } - rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) + rootIDs, err := idMappings.RootPair() if err != nil { return err } - if err := setupDaemonRoot(config, realRoot, rootUID, rootGID); err != nil { + if err := setupDaemonRoot(config, realRoot, rootIDs); err != nil { return err } diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index eaa3be44d8..e728abddf8 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -11,6 +11,7 @@ import ( containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" _ "github.com/docker/docker/pkg/discovery/memory" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/registrar" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/volume" @@ -127,7 +128,7 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) { return nil, err } - volumesDriver, err := local.New(tmp, 0, 0) + volumesDriver, err := local.New(tmp, idtools.IDPair{UID: 0, GID: 0}) if err != nil { return nil, err } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 031cdc4a8b..07c323d63f 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -1026,40 +1026,38 @@ func parseRemappedRoot(usergrp string) (string, string, error) { return username, groupname, nil } -func setupRemappedRoot(config *config.Config) ([]idtools.IDMap, []idtools.IDMap, error) { +func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) { if runtime.GOOS != "linux" && config.RemappedRoot != "" { - return nil, nil, fmt.Errorf("User namespaces are only supported on Linux") + return nil, fmt.Errorf("User namespaces are only supported on Linux") } // if the daemon was started with remapped root option, parse // the config option to the int uid,gid values - var ( - uidMaps, gidMaps []idtools.IDMap - ) if config.RemappedRoot != "" { username, groupname, err := parseRemappedRoot(config.RemappedRoot) if err != nil { - return nil, nil, err + return nil, err } if username == "root" { // Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op // effectively logrus.Warn("User namespaces: root cannot be remapped with itself; user namespaces are OFF") - return uidMaps, gidMaps, nil + return &idtools.IDMappings{}, nil } logrus.Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s", username, groupname) // update remapped root setting now that we have resolved them to actual names config.RemappedRoot = fmt.Sprintf("%s:%s", username, groupname) - uidMaps, gidMaps, err = idtools.CreateIDMappings(username, groupname) + mappings, err := idtools.NewIDMappings(username, groupname) if err != nil { - return nil, nil, fmt.Errorf("Can't create ID mappings: %v", err) + return nil, errors.Wrapf(err, "Can't create ID mappings: %v") } + return mappings, nil } - return uidMaps, gidMaps, nil + return &idtools.IDMappings{}, nil } -func setupDaemonRoot(config *config.Config, rootDir string, rootUID, rootGID int) error { +func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) 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 @@ -1084,10 +1082,10 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootUID, rootGID int // 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", rootUID, rootGID)) + config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootIDs.UID, rootIDs.GID)) logrus.Debugf("Creating user namespaced daemon root: %s", config.Root) // Create the root directory if it doesn't exist - if err := idtools.MkdirAllAs(config.Root, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(config.Root, 0700, rootIDs); 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 @@ -1100,7 +1098,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootUID, rootGID int if dirPath == "/" { break } - if !idtools.CanAccess(dirPath, rootUID, rootGID) { + if !idtools.CanAccess(dirPath, rootIDs) { 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) } } diff --git a/daemon/daemon_unix_test.go b/daemon/daemon_unix_test.go index e8afe629e0..a2847bfbee 100644 --- a/daemon/daemon_unix_test.go +++ b/daemon/daemon_unix_test.go @@ -11,6 +11,7 @@ import ( containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/daemon/config" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/volume" "github.com/docker/docker/volume/drivers" "github.com/docker/docker/volume/local" @@ -277,7 +278,7 @@ func TestMigratePre17Volumes(t *testing.T) { if err != nil { t.Fatal(err) } - drv, err := local.New(volumeRoot, 0, 0) + drv, err := local.New(volumeRoot, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } diff --git a/daemon/daemon_windows.go b/daemon/daemon_windows.go index efe1b1f6a0..aced740df3 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -454,11 +454,11 @@ func (daemon *Daemon) cleanupMounts() error { return nil } -func setupRemappedRoot(config *config.Config) ([]idtools.IDMap, []idtools.IDMap, error) { - return nil, nil, nil +func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) { + return &idtools.IDMappings{}, nil } -func setupDaemonRoot(config *config.Config, rootDir string, rootUID, rootGID int) error { +func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error { config.Root = rootDir // Create the root directory if it doesn't exists if err := system.MkdirAllWithACL(config.Root, 0); err != nil && !os.IsExist(err) { diff --git a/daemon/export.go b/daemon/export.go index 5ef6dbb0e5..402e67583d 100644 --- a/daemon/export.go +++ b/daemon/export.go @@ -40,11 +40,10 @@ func (daemon *Daemon) containerExport(container *container.Container) (io.ReadCl return nil, err } - uidMaps, gidMaps := daemon.GetUIDGIDMaps() archive, err := archive.TarWithOptions(container.BaseFS, &archive.TarOptions{ Compression: archive.Uncompressed, - UIDMaps: uidMaps, - GIDMaps: gidMaps, + UIDMaps: daemon.idMappings.UIDs(), + GIDMaps: daemon.idMappings.GIDs(), }) if err != nil { daemon.Unmount(container) diff --git a/daemon/info.go b/daemon/info.go index 2be4b395fa..3cadea791e 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -72,8 +72,8 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { if selinuxEnabled() { securityOptions = append(securityOptions, "name=selinux") } - uid, gid := daemon.GetRemappedUIDGID() - if uid != 0 || gid != 0 { + rootIDs, _ := daemon.idMappings.RootPair() + if rootIDs.UID != 0 || rootIDs.GID != 0 { securityOptions = append(securityOptions, "name=userns") } diff --git a/daemon/initlayer/setup_unix.go b/daemon/initlayer/setup_unix.go index e83c2751ed..cdd8973481 100644 --- a/daemon/initlayer/setup_unix.go +++ b/daemon/initlayer/setup_unix.go @@ -16,7 +16,7 @@ import ( // // This extra layer is used by all containers as the top-most ro layer. It protects // the container from unwanted side-effects on the rw layer. -func Setup(initLayer string, rootUID, rootGID int) error { +func Setup(initLayer string, rootIDs idtools.IDPair) error { for pth, typ := range map[string]string{ "/dev/pts": "dir", "/dev/shm": "dir", @@ -38,12 +38,12 @@ func Setup(initLayer string, rootUID, rootGID int) error { if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil { if os.IsNotExist(err) { - if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootIDs); err != nil { return err } switch typ { case "dir": - if err := idtools.MkdirAllNewAs(filepath.Join(initLayer, pth), 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, pth), 0755, rootIDs); err != nil { return err } case "file": @@ -51,7 +51,7 @@ func Setup(initLayer string, rootUID, rootGID int) error { if err != nil { return err } - f.Chown(rootUID, rootGID) + f.Chown(rootIDs.UID, rootIDs.GID) f.Close() default: if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil { diff --git a/daemon/initlayer/setup_windows.go b/daemon/initlayer/setup_windows.go index 48a9d71aa5..2b22f58b5e 100644 --- a/daemon/initlayer/setup_windows.go +++ b/daemon/initlayer/setup_windows.go @@ -2,12 +2,16 @@ package initlayer +import ( + "github.com/docker/docker/pkg/idtools" +) + // Setup populates a directory with mountpoints suitable // for bind-mounting dockerinit into the container. The mountpoint is simply an // empty file at /.dockerinit // // This extra layer is used by all containers as the top-most ro layer. It protects // the container from unwanted side-effects on the rw layer. -func Setup(initLayer string, rootUID, rootGID int) error { +func Setup(initLayer string, rootIDs idtools.IDPair) error { return nil } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index 55a6cd8ae0..850fb76ecf 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -271,13 +271,13 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error userNS := false // user if c.HostConfig.UsernsMode.IsPrivate() { - uidMap, gidMap := daemon.GetUIDGIDMaps() + uidMap := daemon.idMappings.UIDs() if uidMap != nil { userNS = true ns := specs.LinuxNamespace{Type: "user"} setNamespace(s, ns) s.Linux.UIDMappings = specMapping(uidMap) - s.Linux.GIDMappings = specMapping(gidMap) + s.Linux.GIDMappings = specMapping(daemon.idMappings.GIDs()) } } // network @@ -591,7 +591,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c // TODO: until a kernel/mount solution exists for handling remount in a user namespace, // we must clear the readonly flag for the cgroups mount (@mrunalp concurs) - if uidMap, _ := daemon.GetUIDGIDMaps(); uidMap != nil || c.HostConfig.Privileged { + if uidMap := daemon.idMappings.UIDs(); uidMap != nil || c.HostConfig.Privileged { for i, m := range s.Mounts { if m.Type == "cgroup" { clearReadOnly(&s.Mounts[i]) @@ -611,8 +611,8 @@ func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) Path: c.BaseFS, Readonly: c.HostConfig.ReadonlyRootfs, } - rootUID, rootGID := daemon.GetRemappedUIDGID() - if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil { + rootIDs, _ := daemon.idMappings.RootPair() + if err := c.SetupWorkingDirectory(rootIDs); err != nil { return err } cwd := c.Config.WorkingDir diff --git a/daemon/oci_solaris.go b/daemon/oci_solaris.go index 0c757f9196..b4d39e476d 100644 --- a/daemon/oci_solaris.go +++ b/daemon/oci_solaris.go @@ -130,8 +130,8 @@ func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) Path: filepath.Dir(c.BaseFS), Readonly: c.HostConfig.ReadonlyRootfs, } - rootUID, rootGID := daemon.GetRemappedUIDGID() - if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil { + rootIDs, _ := daemon.idMappings.RootPair() + if err := c.SetupWorkingDirectory(rootIDs); err != nil { return err } cwd := c.Config.WorkingDir diff --git a/daemon/volumes_unix.go b/daemon/volumes_unix.go index 370eb74218..8736b7dffc 100644 --- a/daemon/volumes_unix.go +++ b/daemon/volumes_unix.go @@ -54,8 +54,8 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er return nil } - rootUID, rootGID := daemon.GetRemappedUIDGID() - path, err := m.Setup(c.MountLabel, rootUID, rootGID, checkfunc) + rootIDs, _ := daemon.idMappings.RootPair() + path, err := m.Setup(c.MountLabel, rootIDs, checkfunc) if err != nil { return nil, err } @@ -85,9 +85,9 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er // 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) - rootUID, rootGID := daemon.GetRemappedUIDGID() + rootIDs, _ := daemon.idMappings.RootPair() for _, mount := range netMounts { - if err := os.Chown(mount.Source, rootUID, rootGID); err != nil { + if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil { return nil, err } } diff --git a/daemon/volumes_windows.go b/daemon/volumes_windows.go index 76940c67df..62c9e23ac0 100644 --- a/daemon/volumes_windows.go +++ b/daemon/volumes_windows.go @@ -6,6 +6,7 @@ import ( "sort" "github.com/docker/docker/container" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/volume" ) @@ -24,7 +25,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil { return nil, err } - s, err := mount.Setup(c.MountLabel, 0, 0, nil) + s, err := mount.Setup(c.MountLabel, idtools.IDPair{0, 0}, nil) if err != nil { return nil, err } diff --git a/daemon/workdir.go b/daemon/workdir.go index 99a2a8ea57..90e4d709a2 100644 --- a/daemon/workdir.go +++ b/daemon/workdir.go @@ -16,6 +16,6 @@ func (daemon *Daemon) ContainerCreateWorkdir(cID string) error { return err } defer daemon.Unmount(container) - rootUID, rootGID := daemon.GetRemappedUIDGID() - return container.SetupWorkingDirectory(rootUID, rootGID) + rootIDs, _ := daemon.idMappings.RootPair() + return container.SetupWorkingDirectory(rootIDs) } diff --git a/layer/layer_store.go b/layer/layer_store.go index 5caa5d41f2..25861c6669 100644 --- a/layer/layer_store.go +++ b/layer/layer_store.go @@ -44,8 +44,7 @@ type StoreOptions struct { MetadataStorePathTemplate string GraphDriver string GraphDriverOptions []string - UIDMaps []idtools.IDMap - GIDMaps []idtools.IDMap + IDMappings *idtools.IDMappings PluginGetter plugingetter.PluginGetter ExperimentalEnabled bool } @@ -55,8 +54,8 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) { driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{ Root: options.StorePath, DriverOptions: options.GraphDriverOptions, - UIDMaps: options.UIDMaps, - GIDMaps: options.GIDMaps, + UIDMaps: options.IDMappings.UIDs(), + GIDMaps: options.IDMappings.GIDs(), ExperimentalEnabled: options.ExperimentalEnabled, }) if err != nil { diff --git a/pkg/idtools/idtools.go b/pkg/idtools/idtools.go index 6bca466286..d7a3a45387 100644 --- a/pkg/idtools/idtools.go +++ b/pkg/idtools/idtools.go @@ -37,6 +37,7 @@ const ( // MkdirAllAs 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 to the requested uid/gid pair. +// Deprecated: Use MkdirAllAndChown func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { return mkdirAs(path, mode, ownerUID, ownerGID, true, true) } @@ -44,16 +45,38 @@ func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { // MkdirAllNewAs 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 will be performed +// Deprecated: Use MkdirAllAndChownNew func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { return mkdirAs(path, mode, ownerUID, ownerGID, true, false) } // MkdirAs creates a directory and then modifies ownership to the requested uid/gid. // If the directory already exists, this function still changes ownership +// Deprecated: Use MkdirAndChown with a IDPair func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { return mkdirAs(path, mode, ownerUID, ownerGID, false, true) } +// 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 to the requested uid/gid pair. +func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, true, true) +} + +// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. +// If the directory already exists, this function still changes ownership +func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, false, true) +} + +// 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 will be performed +func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error { + return mkdirAs(path, mode, ids.UID, ids.GID, true, false) +} + // 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) { @@ -108,26 +131,59 @@ func ToHost(contID int, idMap []IDMap) (int, error) { return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) } -// CreateIDMappings takes a requested user and group name and +// IDPair is a UID and GID pair +type IDPair struct { + UID int + GID int +} + +// IDMappings contains a mappings of UIDs and GIDs +type IDMappings struct { + uids []IDMap + gids []IDMap +} + +// NewIDMappings takes a requested user and group name and // using the data from /etc/sub{uid,gid} ranges, creates the // proper uid and gid remapping ranges for that user/group pair -func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) { +func NewIDMappings(username, groupname string) (*IDMappings, error) { subuidRanges, err := parseSubuid(username) if err != nil { - return nil, nil, err + return nil, err } subgidRanges, err := parseSubgid(groupname) if err != nil { - return nil, nil, err + return nil, err } if len(subuidRanges) == 0 { - return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username) + return nil, fmt.Errorf("No subuid ranges found for user %q", username) } if len(subgidRanges) == 0 { - return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname) + return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) } - return createIDMap(subuidRanges), createIDMap(subgidRanges), nil + return &IDMappings{ + uids: createIDMap(subuidRanges), + gids: createIDMap(subgidRanges), + }, nil +} + +// RootPair returns a uid and gid pair for the root user +func (i *IDMappings) RootPair() (IDPair, error) { + uid, gid, err := GetRootUIDGID(i.uids, i.gids) + return IDPair{UID: uid, GID: gid}, err +} + +// UIDs return the UID mapping +// TODO: remove this once everything has been refactored to use pairs +func (i *IDMappings) UIDs() []IDMap { + return i.uids +} + +// GIDs return the UID mapping +// TODO: remove this once everything has been refactored to use pairs +func (i *IDMappings) GIDs() []IDMap { + return i.gids } func createIDMap(subidRanges ranges) []IDMap { diff --git a/pkg/idtools/idtools_unix.go b/pkg/idtools/idtools_unix.go index 7c7e82aee2..0b28249fa8 100644 --- a/pkg/idtools/idtools_unix.go +++ b/pkg/idtools/idtools_unix.go @@ -69,15 +69,15 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory -func CanAccess(path string, uid, gid int) bool { +func CanAccess(path string, pair IDPair) bool { statInfo, err := system.Stat(path) if err != nil { return false } fileMode := os.FileMode(statInfo.Mode()) permBits := fileMode.Perm() - return accessible(statInfo.UID() == uint32(uid), - statInfo.GID() == uint32(gid), permBits) + return accessible(statInfo.UID() == uint32(pair.UID), + statInfo.GID() == uint32(pair.GID), permBits) } func accessible(isOwner, isGroup bool, perms os.FileMode) bool { diff --git a/pkg/idtools/idtools_windows.go b/pkg/idtools/idtools_windows.go index 49f67e78c1..8ed8353060 100644 --- a/pkg/idtools/idtools_windows.go +++ b/pkg/idtools/idtools_windows.go @@ -20,6 +20,6 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory // Windows does not require/support this function, so always return true -func CanAccess(path string, uid, gid int) bool { +func CanAccess(path string, pair IDPair) bool { return true } diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 80fc041623..5396b15bec 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -15,6 +15,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/initlayer" "github.com/docker/docker/libcontainerd" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/stringid" @@ -58,7 +59,7 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error { } } - if err := initlayer.Setup(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName), 0, 0); err != nil { + if err := initlayer.Setup(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName), idtools.IDPair{0, 0}); err != nil { return errors.WithStack(err) } diff --git a/volume/local/local.go b/volume/local/local.go index 6631423bbc..43ba1e1db7 100644 --- a/volume/local/local.go +++ b/volume/local/local.go @@ -55,10 +55,10 @@ type activeMount struct { // New instantiates a new Root instance with the provided scope. Scope // is the base path that the Root instance uses to store its // volumes. The base path is created here if it does not exist. -func New(scope string, rootUID, rootGID int) (*Root, error) { +func New(scope string, rootIDs idtools.IDPair) (*Root, error) { rootDirectory := filepath.Join(scope, volumesPathName) - if err := idtools.MkdirAllAs(rootDirectory, 0700, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChown(rootDirectory, 0700, rootIDs); err != nil { return nil, err } @@ -66,8 +66,7 @@ func New(scope string, rootUID, rootGID int) (*Root, error) { scope: scope, path: rootDirectory, volumes: make(map[string]*localVolume), - rootUID: rootUID, - rootGID: rootGID, + rootIDs: rootIDs, } dirs, err := ioutil.ReadDir(rootDirectory) @@ -125,8 +124,7 @@ type Root struct { scope string path string volumes map[string]*localVolume - rootUID int - rootGID int + rootIDs idtools.IDPair } // List lists all the volumes @@ -167,7 +165,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error } path := r.DataPath(name) - if err := idtools.MkdirAllAs(path, 0755, r.rootUID, r.rootGID); err != nil { + if err := idtools.MkdirAllAndChown(path, 0755, r.rootIDs); err != nil { if os.IsExist(err) { return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path)) } diff --git a/volume/local/local_test.go b/volume/local/local_test.go index f5a519b883..2353391aa5 100644 --- a/volume/local/local_test.go +++ b/volume/local/local_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/mount" ) @@ -40,7 +41,7 @@ func TestRemove(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, 0, 0) + r, err := New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -82,7 +83,7 @@ func TestInitializeWithVolumes(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, 0, 0) + r, err := New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -92,7 +93,7 @@ func TestInitializeWithVolumes(t *testing.T) { t.Fatal(err) } - r, err = New(rootDir, 0, 0) + r, err = New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -114,7 +115,7 @@ func TestCreate(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, 0, 0) + r, err := New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -151,7 +152,7 @@ func TestCreate(t *testing.T) { } } - r, err = New(rootDir, 0, 0) + r, err = New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -189,7 +190,7 @@ func TestCreateWithOpts(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, 0, 0) + r, err := New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -270,7 +271,7 @@ func TestCreateWithOpts(t *testing.T) { t.Fatal("expected mount to still be active") } - r, err = New(rootDir, 0, 0) + r, err = New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -292,7 +293,7 @@ func TestRealodNoOpts(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, 0, 0) + r, err := New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -320,7 +321,7 @@ func TestRealodNoOpts(t *testing.T) { t.Fatal(err) } - r, err = New(rootDir, 0, 0) + r, err = New(rootDir, idtools.IDPair{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } diff --git a/volume/volume.go b/volume/volume.go index 4d761fa10c..2abfcc7b58 100644 --- a/volume/volume.go +++ b/volume/volume.go @@ -151,7 +151,7 @@ func (m *MountPoint) Cleanup() error { // configured, or creating the source directory if supplied. // The, optional, checkFun parameter allows doing additional checking // before creating the source directory on the host. -func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int, checkFun func(m *MountPoint) error) (path string, err error) { +func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) { defer func() { if err == nil { if label.RelabelNeeded(m.Mode) { @@ -196,7 +196,7 @@ func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int, checkFun fun } // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it - if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil { + if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil { if perr, ok := err.(*os.PathError); ok { if perr.Err != syscall.ENOTDIR { return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)