From 763d8392612942ff5c32a35f8bdafd7ae93d3321 Mon Sep 17 00:00:00 2001 From: Salahuddin Khan Date: Wed, 15 Nov 2017 22:20:33 -0800 Subject: [PATCH] Add ADD/COPY --chown flag support to Windows This implements chown support on Windows. Built-in accounts as well as accounts included in the SAM database of the container are supported. NOTE: IDPair is now named Identity and IDMappings is now named IdentityMapping. The following are valid examples: ADD --chown=Guest . COPY --chown=Administrator . COPY --chown=Guests . COPY --chown=ContainerUser . On Windows an owner is only granted the permission to read the security descriptor and read/write the discretionary access control list. This fix also grants read/write and execute permissions to the owner. Signed-off-by: Salahuddin Khan --- builder/dockerfile/builder.go | 30 +++--- builder/dockerfile/copy.go | 16 +-- builder/dockerfile/copy_unix.go | 4 +- builder/dockerfile/copy_windows.go | 74 ++++++++++++- builder/dockerfile/internals.go | 24 +++-- builder/dockerfile/internals_linux.go | 16 +-- builder/dockerfile/internals_linux_test.go | 51 ++++++--- builder/dockerfile/internals_windows.go | 119 ++++++++++++++++++++- cmd/dockerd/daemon.go | 2 +- container/container.go | 4 +- daemon/archive_tarcopyoptions.go | 4 +- daemon/archive_tarcopyoptions_unix.go | 4 +- daemon/container_operations_unix.go | 10 +- daemon/create.go | 5 +- daemon/create_unix.go | 2 +- daemon/daemon.go | 24 ++--- daemon/daemon_linux_test.go | 2 +- daemon/daemon_test.go | 2 +- daemon/daemon_unix.go | 20 ++-- daemon/daemon_windows.go | 8 +- daemon/export.go | 4 +- daemon/graphdriver/aufs/aufs.go | 6 +- daemon/graphdriver/btrfs/btrfs.go | 6 +- daemon/graphdriver/devmapper/deviceset.go | 4 +- daemon/graphdriver/devmapper/driver.go | 6 +- daemon/graphdriver/lcow/lcow.go | 6 +- daemon/graphdriver/overlay/overlay.go | 6 +- daemon/graphdriver/overlay2/overlay.go | 6 +- daemon/graphdriver/vfs/driver.go | 12 +-- daemon/graphdriver/windows/windows.go | 2 +- daemon/graphdriver/zfs/zfs.go | 4 +- daemon/info.go | 2 +- daemon/initlayer/setup_unix.go | 8 +- daemon/initlayer/setup_windows.go | 2 +- daemon/oci_linux.go | 8 +- daemon/oci_linux_test.go | 4 +- daemon/volumes_unix.go | 4 +- daemon/volumes_windows.go | 2 +- daemon/workdir.go | 2 +- hack/make/containerutility | 20 ++++ hack/make/cross | 2 + hack/make/cross-platform-dependent | 6 ++ layer/layer_store.go | 6 +- libcontainerd/client_daemon_linux.go | 4 +- pkg/archive/archive.go | 66 ++++++------ pkg/archive/archive_test.go | 10 +- pkg/archive/archive_unix.go | 6 +- pkg/archive/archive_windows.go | 4 +- pkg/archive/diff.go | 4 +- pkg/chrootarchive/archive.go | 14 +-- pkg/containerfs/archiver.go | 36 ++++--- pkg/idtools/idtools.go | 45 ++++---- pkg/idtools/idtools_unix.go | 9 +- pkg/idtools/idtools_unix_test.go | 28 ++--- pkg/idtools/idtools_windows.go | 10 +- pkg/system/syscall_windows.go | 72 ++++++++++++- plugin/manager_linux.go | 2 +- volume/local/local.go | 24 ++--- volume/local/local_test.go | 18 ++-- volume/mounts/mounts.go | 2 +- volume/service/default_driver.go | 2 +- volume/service/default_driver_stubs.go | 2 +- volume/service/service.go | 2 +- volume/service/service_linux_test.go | 2 +- 64 files changed, 610 insertions(+), 301 deletions(-) create mode 100644 hack/make/containerutility create mode 100644 hack/make/cross-platform-dependent diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index ee95f236892fe..a6094a8973f11 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -56,21 +56,21 @@ type SessionGetter interface { // BuildManager is shared across all Builder objects type BuildManager struct { - idMappings *idtools.IDMappings - backend builder.Backend - pathCache pathCache // TODO: make this persistent - sg SessionGetter - fsCache *fscache.FSCache + idMapping *idtools.IdentityMapping + backend builder.Backend + pathCache pathCache // TODO: make this persistent + sg SessionGetter + fsCache *fscache.FSCache } // NewBuildManager creates a BuildManager -func NewBuildManager(b builder.Backend, sg SessionGetter, fsCache *fscache.FSCache, idMappings *idtools.IDMappings) (*BuildManager, error) { +func NewBuildManager(b builder.Backend, sg SessionGetter, fsCache *fscache.FSCache, identityMapping *idtools.IdentityMapping) (*BuildManager, error) { bm := &BuildManager{ - backend: b, - pathCache: &syncmap.Map{}, - sg: sg, - idMappings: idMappings, - fsCache: fsCache, + backend: b, + pathCache: &syncmap.Map{}, + sg: sg, + idMapping: identityMapping, + fsCache: fsCache, } if err := fsCache.RegisterTransport(remotecontext.ClientSessionRemote, NewClientSessionTransport()); err != nil { return nil, err @@ -111,7 +111,7 @@ func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) ( ProgressWriter: config.ProgressWriter, Backend: bm.backend, PathCache: bm.pathCache, - IDMappings: bm.idMappings, + IDMapping: bm.idMapping, } b, err := newBuilder(ctx, builderOptions) if err != nil { @@ -159,7 +159,7 @@ type builderOptions struct { Backend builder.Backend ProgressWriter backend.ProgressWriter PathCache pathCache - IDMappings *idtools.IDMappings + IDMapping *idtools.IdentityMapping } // Builder is a Dockerfile builder @@ -175,7 +175,7 @@ type Builder struct { docker builder.Backend clientCtx context.Context - idMappings *idtools.IDMappings + idMapping *idtools.IdentityMapping disableCommit bool imageSources *imageSources pathCache pathCache @@ -199,7 +199,7 @@ func newBuilder(clientCtx context.Context, options builderOptions) (*Builder, er Aux: options.ProgressWriter.AuxFormatter, Output: options.ProgressWriter.Output, docker: options.Backend, - idMappings: options.IDMappings, + idMapping: options.IDMapping, imageSources: newImageSources(clientCtx, options), pathCache: options.PathCache, imageProber: newImageProber(options.Backend, config.CacheFrom, config.NoCache), diff --git a/builder/dockerfile/copy.go b/builder/dockerfile/copy.go index 74e245bdc40b9..c5e09619f966b 100644 --- a/builder/dockerfile/copy.go +++ b/builder/dockerfile/copy.go @@ -451,7 +451,7 @@ func downloadSource(output io.Writer, stdout io.Writer, srcURL string) (remote b type copyFileOptions struct { decompress bool - chownPair idtools.IDPair + identity idtools.Identity archiver Archiver } @@ -481,7 +481,7 @@ func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) return errors.Wrapf(err, "source path not found") } if src.IsDir() { - return copyDirectory(archiver, srcEndpoint, destEndpoint, options.chownPair) + return copyDirectory(archiver, srcEndpoint, destEndpoint, options.identity) } if options.decompress && isArchivePath(source.root, srcPath) && !source.noDecompress { return archiver.UntarPath(srcPath, destPath) @@ -499,7 +499,7 @@ func performCopyForInfo(dest copyInfo, source copyInfo, options copyFileOptions) destPath = dest.root.Join(destPath, source.root.Base(source.path)) destEndpoint = ©Endpoint{driver: dest.root, path: destPath} } - return copyFile(archiver, srcEndpoint, destEndpoint, options.chownPair) + return copyFile(archiver, srcEndpoint, destEndpoint, options.identity) } func isArchivePath(driver containerfs.ContainerFS, path string) bool { @@ -517,7 +517,7 @@ func isArchivePath(driver containerfs.ContainerFS, path string) bool { return err == nil } -func copyDirectory(archiver Archiver, source, dest *copyEndpoint, chownPair idtools.IDPair) error { +func copyDirectory(archiver Archiver, source, dest *copyEndpoint, identity idtools.Identity) error { destExists, err := isExistingDirectory(dest) if err != nil { return errors.Wrapf(err, "failed to query destination path") @@ -527,17 +527,17 @@ func copyDirectory(archiver Archiver, source, dest *copyEndpoint, chownPair idto return errors.Wrapf(err, "failed to copy directory") } // TODO: @gupta-ak. Investigate how LCOW permission mappings will work. - return fixPermissions(source.path, dest.path, chownPair, !destExists) + return fixPermissions(source.path, dest.path, identity, !destExists) } -func copyFile(archiver Archiver, source, dest *copyEndpoint, chownPair idtools.IDPair) error { +func copyFile(archiver Archiver, source, dest *copyEndpoint, identity idtools.Identity) error { if runtime.GOOS == "windows" && dest.driver.OS() == "linux" { // LCOW if err := dest.driver.MkdirAll(dest.driver.Dir(dest.path), 0755); err != nil { return errors.Wrapf(err, "failed to create new directory") } } else { - if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest.path), 0755, chownPair); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Dir(dest.path), 0755, identity); err != nil { // Normal containers return errors.Wrapf(err, "failed to create new directory") } @@ -547,7 +547,7 @@ func copyFile(archiver Archiver, source, dest *copyEndpoint, chownPair idtools.I return errors.Wrapf(err, "failed to copy file") } // TODO: @gupta-ak. Investigate how LCOW permission mappings will work. - return fixPermissions(source.path, dest.path, chownPair, false) + return fixPermissions(source.path, dest.path, identity, false) } func endsInSlash(driver containerfs.Driver, path string) bool { diff --git a/builder/dockerfile/copy_unix.go b/builder/dockerfile/copy_unix.go index 15453452e57f8..770226444545a 100644 --- a/builder/dockerfile/copy_unix.go +++ b/builder/dockerfile/copy_unix.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker/pkg/idtools" ) -func fixPermissions(source, destination string, rootIDs idtools.IDPair, overrideSkip bool) error { +func fixPermissions(source, destination string, identity idtools.Identity, overrideSkip bool) error { var ( skipChownRoot bool err error @@ -39,7 +39,7 @@ func fixPermissions(source, destination string, rootIDs idtools.IDPair, override } fullpath = filepath.Join(destination, cleaned) - return os.Lchown(fullpath, rootIDs.UID, rootIDs.GID) + return os.Lchown(fullpath, identity.UID, identity.GID) }) } diff --git a/builder/dockerfile/copy_windows.go b/builder/dockerfile/copy_windows.go index 907c34407cfd9..59c1a4b7c37bb 100644 --- a/builder/dockerfile/copy_windows.go +++ b/builder/dockerfile/copy_windows.go @@ -1,11 +1,17 @@ package dockerfile // import "github.com/docker/docker/builder/dockerfile" import ( - "errors" + "fmt" + "os" "path/filepath" "strings" + "github.com/Microsoft/go-winio" "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/reexec" + "github.com/docker/docker/pkg/system" + "github.com/pkg/errors" + "golang.org/x/sys/windows" ) var pathBlacklist = map[string]bool{ @@ -13,9 +19,69 @@ var pathBlacklist = map[string]bool{ "c:\\windows": true, } -func fixPermissions(source, destination string, rootIDs idtools.IDPair, overrideSkip bool) error { - // chown is not supported on Windows - return nil +func init() { + reexec.Register("windows-fix-permissions", fixPermissionsReexec) +} + +func fixPermissions(source, destination string, identity idtools.Identity, _ bool) error { + if identity.SID == "" { + return nil + } + + cmd := reexec.Command("windows-fix-permissions", source, destination, identity.SID) + output, err := cmd.CombinedOutput() + + return errors.Wrapf(err, "failed to exec windows-fix-permissions: %s", output) +} + +func fixPermissionsReexec() { + err := fixPermissionsWindows(os.Args[1], os.Args[2], os.Args[3]) + if err != nil { + fmt.Fprint(os.Stderr, err) + os.Exit(1) + } +} + +func fixPermissionsWindows(source, destination, SID string) error { + + privileges := []string{winio.SeRestorePrivilege, system.SeTakeOwnershipPrivilege} + + err := winio.EnableProcessPrivileges(privileges) + if err != nil { + return err + } + + defer winio.DisableProcessPrivileges(privileges) + + sid, err := windows.StringToSid(SID) + if err != nil { + return err + } + + // Owners on *nix have read/write/delete/read control and write DAC. + // Add an ACE that grants this to the user/group specified with the + // chown option. Currently Windows is not honoring the owner change, + // however, they are aware of this and it should be fixed at some + // point. + + sddlString := system.SddlAdministratorsLocalSystem + sddlString += "(A;OICI;GRGWGXRCWDSD;;;" + SID + ")" + + securityDescriptor, err := winio.SddlToSecurityDescriptor(sddlString) + if err != nil { + return err + } + + var daclPresent uint32 + var daclDefaulted uint32 + var dacl *byte + + err = system.GetSecurityDescriptorDacl(&securityDescriptor[0], &daclPresent, &dacl, &daclDefaulted) + if err != nil { + return err + } + + return system.SetNamedSecurityInfo(windows.StringToUTF16Ptr(destination), system.SE_FILE_OBJECT, system.OWNER_SECURITY_INFORMATION|system.DACL_SECURITY_INFORMATION, sid, nil, dacl, nil) } func validateCopySourcePath(imageSource *imageMount, origPath, platform string) error { diff --git a/builder/dockerfile/internals.go b/builder/dockerfile/internals.go index d892285a1c899..1635981f177a2 100644 --- a/builder/dockerfile/internals.go +++ b/builder/dockerfile/internals.go @@ -37,7 +37,7 @@ type Archiver interface { UntarPath(src, dst string) error CopyWithTar(src, dst string) error CopyFileWithTar(src, dst string) error - IDMappings() *idtools.IDMappings + IdentityMapping() *idtools.IdentityMapping } // The builder will use the following interfaces if the container fs implements @@ -68,11 +68,11 @@ func tarFunc(i interface{}) containerfs.TarFunc { func (b *Builder) getArchiver(src, dst containerfs.Driver) Archiver { t, u := tarFunc(src), untarFunc(dst) return &containerfs.Archiver{ - SrcDriver: src, - DstDriver: dst, - Tar: t, - Untar: u, - IDMappingsVar: b.idMappings, + SrcDriver: src, + DstDriver: dst, + Tar: t, + Untar: u, + IDMapping: b.idMapping, } } @@ -185,14 +185,18 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error { return err } - chownPair := b.idMappings.RootPair() + identity := b.idMapping.RootPair() // if a chown was requested, perform the steps to get the uid, gid // translated (if necessary because of user namespaces), and replace // the root pair with the chown pair for copy operations if inst.chownStr != "" { - chownPair, err = parseChownFlag(inst.chownStr, destInfo.root.Path(), b.idMappings) + identity, err = parseChownFlag(b, state, inst.chownStr, destInfo.root.Path(), b.idMapping) if err != nil { - return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping") + if b.options.Platform != "windows" { + return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping") + } + + return errors.Wrapf(err, "unable to map container user account name to SID") } } @@ -200,7 +204,7 @@ func (b *Builder) performCopy(req dispatchRequest, inst copyInstruction) error { opts := copyFileOptions{ decompress: inst.allowLocalDecompression, archiver: b.getArchiver(info.root, destInfo.root), - chownPair: chownPair, + identity: identity, } if err := performCopyForInfo(destInfo, info, opts); err != nil { return errors.Wrapf(err, "failed to copy files") diff --git a/builder/dockerfile/internals_linux.go b/builder/dockerfile/internals_linux.go index 1014b16a2102d..5dbd86f147009 100644 --- a/builder/dockerfile/internals_linux.go +++ b/builder/dockerfile/internals_linux.go @@ -11,11 +11,11 @@ import ( "github.com/pkg/errors" ) -func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) { +func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping *idtools.IdentityMapping) (idtools.Identity, error) { var userStr, grpStr string parts := strings.Split(chown, ":") if len(parts) > 2 { - return idtools.IDPair{}, errors.New("invalid chown string format: " + chown) + return idtools.Identity{}, errors.New("invalid chown string format: " + chown) } if len(parts) == 1 { // if no group specified, use the user spec as group as well @@ -26,25 +26,25 @@ func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) ( passwdPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "passwd"), ctrRootPath) if err != nil { - return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/passwd path in container rootfs") + return idtools.Identity{}, errors.Wrapf(err, "can't resolve /etc/passwd path in container rootfs") } groupPath, err := symlink.FollowSymlinkInScope(filepath.Join(ctrRootPath, "etc", "group"), ctrRootPath) if err != nil { - return idtools.IDPair{}, errors.Wrapf(err, "can't resolve /etc/group path in container rootfs") + return idtools.Identity{}, errors.Wrapf(err, "can't resolve /etc/group path in container rootfs") } uid, err := lookupUser(userStr, passwdPath) if err != nil { - return idtools.IDPair{}, errors.Wrapf(err, "can't find uid for user "+userStr) + return idtools.Identity{}, errors.Wrapf(err, "can't find uid for user "+userStr) } gid, err := lookupGroup(grpStr, groupPath) if err != nil { - return idtools.IDPair{}, errors.Wrapf(err, "can't find gid for group "+grpStr) + return idtools.Identity{}, errors.Wrapf(err, "can't find gid for group "+grpStr) } // convert as necessary because of user namespaces - chownPair, err := idMappings.ToHost(idtools.IDPair{UID: uid, GID: gid}) + chownPair, err := identityMapping.ToHost(idtools.Identity{UID: uid, GID: gid}) if err != nil { - return idtools.IDPair{}, errors.Wrapf(err, "unable to convert uid/gid to host mapping") + return idtools.Identity{}, errors.Wrapf(err, "unable to convert uid/gid to host mapping") } return chownPair, nil } diff --git a/builder/dockerfile/internals_linux_test.go b/builder/dockerfile/internals_linux_test.go index 1b3a99893a72b..28c9cc81a8c86 100644 --- a/builder/dockerfile/internals_linux_test.go +++ b/builder/dockerfile/internals_linux_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/idtools" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -34,7 +35,7 @@ othergrp:x:6666: }, } remapped := idtools.NewIDMappingsFromMaps(idMaps, idMaps) - unmapped := &idtools.IDMappings{} + unmapped := &idtools.IdentityMapping{} contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test") defer cleanup() @@ -49,56 +50,72 @@ othergrp:x:6666: // positive tests for _, testcase := range []struct { + builder *Builder name string chownStr string - idMapping *idtools.IDMappings - expected idtools.IDPair + idMapping *idtools.IdentityMapping + state *dispatchState + expected idtools.Identity }{ { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UIDNoMap", chownStr: "1", idMapping: unmapped, - expected: idtools.IDPair{UID: 1, GID: 1}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 1, GID: 1}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UIDGIDNoMap", chownStr: "0:1", idMapping: unmapped, - expected: idtools.IDPair{UID: 0, GID: 1}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 0, GID: 1}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UIDWithMap", chownStr: "0", idMapping: remapped, - expected: idtools.IDPair{UID: 100000, GID: 100000}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 100000, GID: 100000}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UIDGIDWithMap", chownStr: "1:33", idMapping: remapped, - expected: idtools.IDPair{UID: 100001, GID: 100033}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 100001, GID: 100033}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UserNoMap", chownStr: "bin:5555", idMapping: unmapped, - expected: idtools.IDPair{UID: 1, GID: 5555}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 1, GID: 5555}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "GroupWithMap", chownStr: "0:unicorn", idMapping: remapped, - expected: idtools.IDPair{UID: 100000, GID: 101002}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 100000, GID: 101002}, }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UserOnlyWithMap", chownStr: "unicorn", idMapping: remapped, - expected: idtools.IDPair{UID: 101001, GID: 101002}, + state: &dispatchState{}, + expected: idtools.Identity{UID: 101001, GID: 101002}, }, } { t.Run(testcase.name, func(t *testing.T) { - idPair, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping) + idPair, err := parseChownFlag(testcase.builder, testcase.state, testcase.chownStr, contextDir, testcase.idMapping) assert.NilError(t, err, "Failed to parse chown flag: %q", testcase.chownStr) assert.Check(t, is.DeepEqual(testcase.expected, idPair), "chown flag mapping failure") }) @@ -106,32 +123,40 @@ othergrp:x:6666: // error tests for _, testcase := range []struct { + builder *Builder name string chownStr string - idMapping *idtools.IDMappings + idMapping *idtools.IdentityMapping + state *dispatchState descr string }{ { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "BadChownFlagFormat", chownStr: "bob:1:555", idMapping: unmapped, + state: &dispatchState{}, descr: "invalid chown string format: bob:1:555", }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "UserNoExist", chownStr: "bob", idMapping: unmapped, + state: &dispatchState{}, descr: "can't find uid for user bob: no such user: bob", }, { + builder: &Builder{options: &types.ImageBuildOptions{Platform: "linux"}}, name: "GroupNoExist", chownStr: "root:bob", idMapping: unmapped, + state: &dispatchState{}, descr: "can't find gid for group bob: no such group: bob", }, } { t.Run(testcase.name, func(t *testing.T) { - _, err := parseChownFlag(testcase.chownStr, contextDir, testcase.idMapping) + _, err := parseChownFlag(testcase.builder, testcase.state, testcase.chownStr, contextDir, testcase.idMapping) assert.Check(t, is.Error(err, testcase.descr), "Expected error string doesn't match") }) } diff --git a/builder/dockerfile/internals_windows.go b/builder/dockerfile/internals_windows.go index 26978b48cfb0d..82135a12ae689 100644 --- a/builder/dockerfile/internals_windows.go +++ b/builder/dockerfile/internals_windows.go @@ -1,7 +1,120 @@ package dockerfile // import "github.com/docker/docker/builder/dockerfile" -import "github.com/docker/docker/pkg/idtools" +import ( + "bytes" + "os" + "path/filepath" + "strings" -func parseChownFlag(chown, ctrRootPath string, idMappings *idtools.IDMappings) (idtools.IDPair, error) { - return idMappings.RootPair(), nil + "github.com/containerd/containerd/platforms" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/system" + "github.com/pkg/errors" + "golang.org/x/sys/windows" +) + +func parseChownFlag(builder *Builder, state *dispatchState, chown, ctrRootPath string, identityMapping *idtools.IdentityMapping) (idtools.Identity, error) { + if builder.options.Platform == "windows" { + return getAccountIdentity(builder, chown, ctrRootPath, state) + } + + return identityMapping.RootPair(), nil +} + +func getAccountIdentity(builder *Builder, accountName string, ctrRootPath string, state *dispatchState) (idtools.Identity, error) { + // If this is potentially a string SID then attempt to convert it to verify + // this, otherwise continue looking for the account. + if strings.HasPrefix(accountName, "S-") || strings.HasPrefix(accountName, "s-") { + sid, err := windows.StringToSid(accountName) + + if err == nil { + accountSid, err := sid.String() + + if err != nil { + return idtools.Identity{SID: ""}, errors.Wrapf(err, "error converting SID to string") + } + + return idtools.Identity{SID: accountSid}, nil + } + } + + // Attempt to obtain the SID using the name. + sid, _, accType, err := windows.LookupSID("", accountName) + + // If this is a SID that is built-in and hence the same across all systems then use that. + if err == nil && (accType == windows.SidTypeAlias || accType == windows.SidTypeWellKnownGroup) { + accountSid, err := sid.String() + + if err != nil { + return idtools.Identity{SID: ""}, errors.Wrapf(err, "error converting SID to string") + } + + return idtools.Identity{SID: accountSid}, nil + } + + // Check if the account name is one unique to containers. + if strings.EqualFold(accountName, "ContainerAdministrator") { + return idtools.Identity{SID: system.ContainerAdministratorSidString}, nil + + } else if strings.EqualFold(accountName, "ContainerUser") { + return idtools.Identity{SID: system.ContainerUserSidString}, nil + } + + // All other lookups failed, so therefore determine if the account in + // question exists in the container and if so, obtain its SID. + return lookupNTAccount(builder, accountName, state) +} + +func lookupNTAccount(builder *Builder, accountName string, state *dispatchState) (idtools.Identity, error) { + + source, _ := filepath.Split(os.Args[0]) + + target := "C:\\Docker" + targetExecutable := target + "\\containerutility.exe" + + optionsPlatform, err := platforms.Parse(builder.options.Platform) + if err != nil { + return idtools.Identity{}, err + } + + runConfig := copyRunConfig(state.runConfig, + withCmdCommentString("internal run to obtain NT account information.", optionsPlatform.OS)) + + runConfig.ArgsEscaped = true + runConfig.Cmd = []string{targetExecutable, "getaccountsid", accountName} + + hostConfig := &container.HostConfig{Mounts: []mount.Mount{ + { + Type: mount.TypeBind, + Source: source, + Target: target, + ReadOnly: true, + }, + }, + } + + container, err := builder.containerManager.Create(runConfig, hostConfig) + if err != nil { + return idtools.Identity{}, err + } + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + + if err := builder.containerManager.Run(builder.clientCtx, container.ID, stdout, stderr); err != nil { + if err, ok := err.(*statusCodeError); ok { + return idtools.Identity{}, &jsonmessage.JSONError{ + Message: stderr.String(), + Code: err.StatusCode(), + } + } + return idtools.Identity{}, err + } + + accountSid := stdout.String() + + return idtools.Identity{SID: accountSid}, nil } diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index efefaa1ac38c7..783bd7c4e34b2 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -267,7 +267,7 @@ func newRouterOptions(config *config.Config, daemon *daemon.Daemon) (routerOptio return opts, errors.Wrap(err, "failed to create fscache") } - manager, err := dockerfile.NewBuildManager(daemon.BuilderBackend(), sm, buildCache, daemon.IDMappings()) + manager, err := dockerfile.NewBuildManager(daemon.BuilderBackend(), sm, buildCache, daemon.IdentityMapping()) if err != nil { return opts, err } diff --git a/container/container.go b/container/container.go index 5f31d8df12324..17ced148c33a6 100644 --- a/container/container.go +++ b/container/container.go @@ -254,7 +254,7 @@ func (container *Container) WriteHostConfig() (*containertypes.HostConfig, error } // SetupWorkingDirectory sets up the container's working directory as set in container.Config.WorkingDir -func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error { +func (container *Container) SetupWorkingDirectory(rootIdentity idtools.Identity) error { // TODO @jhowardmsft, @gupta-ak LCOW Support. This will need revisiting. // We will need to do remote filesystem operations here. if container.OS != runtime.GOOS { @@ -271,7 +271,7 @@ func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error return err } - if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIDs); err != nil { + if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIdentity); err != nil { pthInfo, err2 := os.Stat(pth) if err2 == nil && pthInfo != nil && !pthInfo.IsDir() { return errors.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) diff --git a/daemon/archive_tarcopyoptions.go b/daemon/archive_tarcopyoptions.go index 766ba9fdb1ca1..ab81dc4a102bd 100644 --- a/daemon/archive_tarcopyoptions.go +++ b/daemon/archive_tarcopyoptions.go @@ -9,7 +9,7 @@ import ( func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions { return &archive.TarOptions{ NoOverwriteDirNonDir: noOverwriteDirNonDir, - UIDMaps: daemon.idMappings.UIDs(), - GIDMaps: daemon.idMappings.GIDs(), + UIDMaps: daemon.idMapping.UIDs(), + GIDMaps: daemon.idMapping.GIDs(), } } diff --git a/daemon/archive_tarcopyoptions_unix.go b/daemon/archive_tarcopyoptions_unix.go index d70904564b6da..c456b410dd537 100644 --- a/daemon/archive_tarcopyoptions_unix.go +++ b/daemon/archive_tarcopyoptions_unix.go @@ -18,8 +18,10 @@ func (daemon *Daemon) tarCopyOptions(container *container.Container, noOverwrite return nil, err } + identity := idtools.Identity{UID: user.Uid, GID: user.Gid} + return &archive.TarOptions{ NoOverwriteDirNonDir: noOverwriteDirNonDir, - ChownOpts: &idtools.IDPair{UID: user.Uid, GID: user.Gid}, + ChownOpts: &identity, }, nil } diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index 97206a03811f5..9953c7f3fddc5 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -132,7 +132,7 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error { fallthrough case ipcMode.IsShareable(): - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() if !c.HasMountFor("/dev/shm") { shmPath, err := c.ShmResourcePath() if err != nil { @@ -179,7 +179,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { } // retrieve possible remapped range start for root UID, GID - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() for _, s := range c.SecretReferences { // TODO (ehazlett): use type switch when more are supported @@ -278,7 +278,7 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { // In practice this is using a tmpfs mount and is used for both "configs" and "secrets" func (daemon *Daemon) createSecretsDir(c *container.Container) error { // retrieve possible remapped range start for root UID, GID - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() dir, err := c.SecretMountPath() if err != nil { return errors.Wrap(err, "error getting container secrets dir") @@ -304,7 +304,7 @@ func (daemon *Daemon) remountSecretDir(c *container.Container) error { if err := label.Relabel(dir, c.MountLabel, false); err != nil { logrus.WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label") } - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID) // remount secrets ro @@ -407,5 +407,5 @@ func (daemon *Daemon) setupContainerMountsRoot(c *container.Container) error { if err != nil { return err } - return idtools.MkdirAllAndChown(p, 0700, daemon.idMappings.RootPair()) + return idtools.MkdirAllAndChown(p, 0700, daemon.idMapping.RootPair()) } diff --git a/daemon/create.go b/daemon/create.go index 6702243faf5df..1afb1bebea328 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -155,13 +155,14 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) ( } // Set RWLayer for container after mount labels have been set - rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMappings)) + rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMapping)) if err != nil { return nil, errdefs.System(err) } container.RWLayer = rwLayer - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() + if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil { return nil, err } diff --git a/daemon/create_unix.go b/daemon/create_unix.go index eb9b6537300b3..13857bab069a1 100644 --- a/daemon/create_unix.go +++ b/daemon/create_unix.go @@ -25,7 +25,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con } defer daemon.Unmount(container) - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() if err := container.SetupWorkingDirectory(rootIDs); err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index c579766e95e7b..c8a8251003265 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -87,7 +87,7 @@ type Daemon struct { seccompEnabled bool apparmorEnabled bool shutdown bool - idMappings *idtools.IDMappings + idMapping *idtools.IdentityMapping // TODO: move graphDrivers field to an InfoService graphDrivers map[string]string // By operating system @@ -594,11 +594,11 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe return nil, err } - idMappings, err := setupRemappedRoot(config) + idMapping, err := setupRemappedRoot(config) if err != nil { return nil, err } - rootIDs := idMappings.RootPair() + rootIDs := idMapping.RootPair() if err := setupDaemonProcess(config); err != nil { return nil, err } @@ -749,7 +749,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe MetadataStorePathTemplate: filepath.Join(config.Root, "image", "%s", "layerdb"), GraphDriver: gd, GraphDriverOptions: config.GraphOptions, - IDMappings: idMappings, + IDMapping: idMapping, PluginGetter: d.PluginStore, ExperimentalEnabled: config.Experimental, OS: operatingSystem, @@ -856,7 +856,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe d.EventsService = events.New() d.root = config.Root - d.idMappings = idMappings + d.idMapping = idMapping d.seccompEnabled = sysInfo.Seccomp d.apparmorEnabled = sysInfo.AppArmor @@ -1106,7 +1106,7 @@ func (daemon *Daemon) Subnets() ([]net.IPNet, []net.IPNet) { // 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, rootIDs idtools.IDPair) (string, error) { +func prepareTempDir(rootDir string, rootIdentity idtools.Identity) (string, error) { var tmpDir string if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { tmpDir = filepath.Join(rootDir, "tmp") @@ -1126,7 +1126,7 @@ func prepareTempDir(rootDir string, rootIDs idtools.IDPair) (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.MkdirAllAndChown(tmpDir, 0700, rootIDs) + return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0700, rootIdentity) } func (daemon *Daemon) setGenericResources(conf *config.Config) error { @@ -1274,11 +1274,11 @@ func CreateDaemonRoot(config *config.Config) error { } } - idMappings, err := setupRemappedRoot(config) + idMapping, err := setupRemappedRoot(config) if err != nil { return err } - return setupDaemonRoot(config, realRoot, idMappings.RootPair()) + return setupDaemonRoot(config, realRoot, idMapping.RootPair()) } // checkpointAndSave grabs a container lock to safely call container.CheckpointTo @@ -1304,9 +1304,9 @@ func (daemon *Daemon) GetAttachmentStore() *network.AttachmentStore { return &daemon.attachmentStore } -// IDMappings returns uid/gid mappings for the builder -func (daemon *Daemon) IDMappings() *idtools.IDMappings { - return daemon.idMappings +// 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 } // ImageService returns the Daemon's ImageService diff --git a/daemon/daemon_linux_test.go b/daemon/daemon_linux_test.go index 767925e2fb2b0..66d6aa20d8fb0 100644 --- a/daemon/daemon_linux_test.go +++ b/daemon/daemon_linux_test.go @@ -124,7 +124,7 @@ func TestTmpfsDevShmSizeOverride(t *testing.T) { mnt := "/dev/shm" d := Daemon{ - idMappings: &idtools.IDMappings{}, + idMapping: &idtools.IdentityMapping{}, } c := &container.Container{ HostConfig: &containertypes.HostConfig{ diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index 43f4f504b6f98..d89b0077fd6e2 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -118,7 +118,7 @@ func initDaemonWithVolumeStore(tmp string) (*Daemon, error) { repository: tmp, root: tmp, } - daemon.volumes, err = volumesservice.NewVolumeService(tmp, nil, idtools.IDPair{UID: 0, GID: 0}, daemon) + daemon.volumes, err = volumesservice.NewVolumeService(tmp, nil, idtools.Identity{UID: 0, GID: 0}, daemon) if err != nil { return nil, err } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 3c11f9d582637..c4fc72cedb2a0 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -1003,9 +1003,9 @@ func removeDefaultBridgeInterface() { } } -func setupInitLayer(idMappings *idtools.IDMappings) func(containerfs.ContainerFS) error { +func setupInitLayer(idMapping *idtools.IdentityMapping) func(containerfs.ContainerFS) error { return func(initPath containerfs.ContainerFS) error { - return initlayer.Setup(initPath, idMappings.RootPair()) + return initlayer.Setup(initPath, idMapping.RootPair()) } } @@ -1102,7 +1102,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) { return username, groupname, nil } -func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) { +func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error) { if runtime.GOOS != "linux" && config.RemappedRoot != "" { return nil, fmt.Errorf("User namespaces are only supported on Linux") } @@ -1118,22 +1118,22 @@ func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) { // 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 &idtools.IDMappings{}, nil + return &idtools.IdentityMapping{}, 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) - mappings, err := idtools.NewIDMappings(username, groupname) + mappings, err := idtools.NewIdentityMapping(username, groupname) if err != nil { return nil, errors.Wrap(err, "Can't create ID mappings") } return mappings, nil } - return &idtools.IDMappings{}, nil + return &idtools.IdentityMapping{}, nil } -func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error { +func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) 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 @@ -1158,10 +1158,10 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPa // 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", rootIDs.UID, rootIDs.GID)) + config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", rootIdentity.UID, rootIdentity.GID)) logrus.Debugf("Creating user namespaced daemon root: %s", config.Root) // Create the root directory if it doesn't exist - if err := idtools.MkdirAllAndChown(config.Root, 0700, rootIDs); err != nil { + if err := idtools.MkdirAllAndChown(config.Root, 0700, rootIdentity); 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 @@ -1174,7 +1174,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPa if dirPath == "/" { break } - if !idtools.CanAccess(dirPath, rootIDs) { + if !idtools.CanAccess(dirPath, rootIdentity) { 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_windows.go b/daemon/daemon_windows.go index 98f89f39d3639..4f4537eb6edf2 100644 --- a/daemon/daemon_windows.go +++ b/daemon/daemon_windows.go @@ -53,7 +53,7 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos return nil } -func setupInitLayer(idMappings *idtools.IDMappings) func(containerfs.ContainerFS) error { +func setupInitLayer(idMapping *idtools.IdentityMapping) func(containerfs.ContainerFS) error { return nil } @@ -465,11 +465,11 @@ func (daemon *Daemon) cleanupMounts() error { return nil } -func setupRemappedRoot(config *config.Config) (*idtools.IDMappings, error) { - return &idtools.IDMappings{}, nil +func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error) { + return &idtools.IdentityMapping{}, nil } -func setupDaemonRoot(config *config.Config, rootDir string, rootIDs idtools.IDPair) error { +func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error { config.Root = rootDir // Create the root directory if it doesn't exists if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil { diff --git a/daemon/export.go b/daemon/export.go index 737e161edcfec..27bc35967d220 100644 --- a/daemon/export.go +++ b/daemon/export.go @@ -68,8 +68,8 @@ func (daemon *Daemon) containerExport(container *container.Container) (arch io.R archive, err := archivePath(basefs, basefs.Path(), &archive.TarOptions{ Compression: archive.Uncompressed, - UIDMaps: daemon.idMappings.UIDs(), - GIDMaps: daemon.idMappings.GIDs(), + UIDMaps: daemon.idMapping.UIDs(), + GIDMaps: daemon.idMapping.GIDs(), }) if err != nil { rwlayer.Unmount() diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index ac2fe26ecd5c7..114aa9a615e09 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -135,13 +135,13 @@ func Init(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap return nil, err } // Create the root aufs driver dir - if err := idtools.MkdirAllAndChown(root, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(root, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } // Populate the dir structure for _, p := range paths { - if err := idtools.MkdirAllAndChown(path.Join(root, p), 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(path.Join(root, p), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } } @@ -289,7 +289,7 @@ func (a *Driver) createDirsFor(id string) error { // The path of directories are /mnt/ // and /diff/ for _, p := range paths { - if err := idtools.MkdirAllAndChown(path.Join(a.rootPath(), p, id), 0755, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(path.Join(a.rootPath(), p, id), 0755, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return err } } diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index cac62403039c6..4c14191d46774 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -72,7 +72,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap if err != nil { return nil, err } - if err := idtools.MkdirAllAndChown(home, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } @@ -502,7 +502,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { if err != nil { return err } - if err := idtools.MkdirAllAndChown(subvolumes, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(subvolumes, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return err } if parent == "" { @@ -537,7 +537,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil { return err } - if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return err } if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil { diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 2bfbf05a27c26..5dc01d71d9c52 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -268,7 +268,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { if err != nil { return "", err } - if err := idtools.MkdirAllAndChown(dirname, 0700, idtools.IDPair{UID: uid, GID: gid}); err != nil { + if err := idtools.MkdirAllAndChown(dirname, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil { return "", err } @@ -1691,7 +1691,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) { if err != nil { return err } - if err := idtools.MkdirAndChown(devices.root, 0700, idtools.IDPair{UID: uid, GID: gid}); err != nil { + if err := idtools.MkdirAndChown(devices.root, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil { return err } if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil { diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index df883de31d139..899b1f8670979 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -200,11 +200,11 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { } // Create the target directories if they don't exist - if err := idtools.MkdirAllAndChown(path.Join(d.home, "mnt"), 0755, idtools.IDPair{UID: uid, GID: gid}); err != nil { + if err := idtools.MkdirAllAndChown(path.Join(d.home, "mnt"), 0755, idtools.Identity{UID: uid, GID: gid}); err != nil { d.ctr.Decrement(mp) return nil, err } - if err := idtools.MkdirAndChown(mp, 0755, idtools.IDPair{UID: uid, GID: gid}); err != nil && !os.IsExist(err) { + if err := idtools.MkdirAndChown(mp, 0755, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) { d.ctr.Decrement(mp) return nil, err } @@ -215,7 +215,7 @@ func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { return nil, err } - if err := idtools.MkdirAllAndChown(rootFs, 0755, idtools.IDPair{UID: uid, GID: gid}); err != nil { + if err := idtools.MkdirAllAndChown(rootFs, 0755, idtools.Identity{UID: uid, GID: gid}); err != nil { d.ctr.Decrement(mp) d.DeviceSet.UnmountDevice(id, mp) return nil, err diff --git a/daemon/graphdriver/lcow/lcow.go b/daemon/graphdriver/lcow/lcow.go index 242aa8016e8c8..27a7e044a9647 100644 --- a/daemon/graphdriver/lcow/lcow.go +++ b/daemon/graphdriver/lcow/lcow.go @@ -183,17 +183,17 @@ func InitDriver(dataRoot string, options []string, _, _ []idtools.IDMap) (graphd } // Make sure the dataRoot directory is created - if err := idtools.MkdirAllAndChown(dataRoot, 0700, idtools.IDPair{UID: 0, GID: 0}); err != nil { + if err := idtools.MkdirAllAndChown(dataRoot, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { return nil, fmt.Errorf("%s failed to create '%s': %v", title, dataRoot, err) } // Make sure the cache directory is created under dataRoot - if err := idtools.MkdirAllAndChown(cd, 0700, idtools.IDPair{UID: 0, GID: 0}); err != nil { + if err := idtools.MkdirAllAndChown(cd, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { return nil, fmt.Errorf("%s failed to create '%s': %v", title, cd, err) } // Make sure the scratch directory is created under dataRoot - if err := idtools.MkdirAllAndChown(sd, 0700, idtools.IDPair{UID: 0, GID: 0}); err != nil { + if err := idtools.MkdirAllAndChown(sd, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { return nil, fmt.Errorf("%s failed to create '%s': %v", title, sd, err) } diff --git a/daemon/graphdriver/overlay/overlay.go b/daemon/graphdriver/overlay/overlay.go index 0c2167f083d4f..08c05e192f61c 100644 --- a/daemon/graphdriver/overlay/overlay.go +++ b/daemon/graphdriver/overlay/overlay.go @@ -168,7 +168,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap return nil, err } // Create the driver home dir - if err := idtools.MkdirAllAndChown(home, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } @@ -291,7 +291,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr if err != nil { return err } - root := idtools.IDPair{UID: rootUID, GID: rootGID} + root := idtools.Identity{UID: rootUID, GID: rootGID} if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil { return err @@ -413,7 +413,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, err erro if err != nil { return nil, err } - if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } var ( diff --git a/daemon/graphdriver/overlay2/overlay.go b/daemon/graphdriver/overlay2/overlay.go index 49b055cc03c18..6b3236f8f3cab 100644 --- a/daemon/graphdriver/overlay2/overlay.go +++ b/daemon/graphdriver/overlay2/overlay.go @@ -200,7 +200,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap return nil, err } // Create the driver home dir - if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(path.Join(home, linkDir), 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } @@ -378,7 +378,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr if err != nil { return err } - root := idtools.IDPair{UID: rootUID, GID: rootGID} + root := idtools.Identity{UID: rootUID, GID: rootGID} if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil { return err @@ -586,7 +586,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e if err != nil { return nil, err } - if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAndChown(mergedDir, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } diff --git a/daemon/graphdriver/vfs/driver.go b/daemon/graphdriver/vfs/driver.go index e51cb6c2509ae..33e6bf6cc9c2f 100644 --- a/daemon/graphdriver/vfs/driver.go +++ b/daemon/graphdriver/vfs/driver.go @@ -27,10 +27,10 @@ func init() { // This sets the home directory for the driver and returns NaiveDiffDriver. func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { d := &Driver{ - home: home, - idMappings: idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), + home: home, + idMapping: idtools.NewIDMappingsFromMaps(uidMaps, gidMaps), } - rootIDs := d.idMappings.RootPair() + rootIDs := d.idMapping.RootPair() if err := idtools.MkdirAllAndChown(home, 0700, rootIDs); err != nil { return nil, err } @@ -46,8 +46,8 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap // Driver must be wrapped in NaiveDiffDriver to be used as a graphdriver.Driver type Driver struct { driverQuota - home string - idMappings *idtools.IDMappings + home string + idMapping *idtools.IdentityMapping } func (d *Driver) String() string { @@ -105,7 +105,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { func (d *Driver) create(id, parent string, size uint64) error { dir := d.dir(id) - rootIDs := d.idMappings.RootPair() + rootIDs := d.idMapping.RootPair() if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0700, rootIDs); err != nil { return err } diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index 16a5229206c39..13bfac2d2ecbf 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/daemon/graphdriver/windows/windows.go @@ -95,7 +95,7 @@ func InitFilter(home string, options []string, uidMaps, gidMaps []idtools.IDMap) return nil, fmt.Errorf("%s is on an ReFS volume - ReFS volumes are not supported", home) } - if err := idtools.MkdirAllAndChown(home, 0700, idtools.IDPair{UID: 0, GID: 0}); err != nil { + if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: 0, GID: 0}); err != nil { return nil, fmt.Errorf("windowsfilter failed to create '%s': %v", home, err) } diff --git a/daemon/graphdriver/zfs/zfs.go b/daemon/graphdriver/zfs/zfs.go index 1d9153e1718a6..8a798778d2914 100644 --- a/daemon/graphdriver/zfs/zfs.go +++ b/daemon/graphdriver/zfs/zfs.go @@ -106,7 +106,7 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri if err != nil { return nil, fmt.Errorf("Failed to get root uid/guid: %v", err) } - if err := idtools.MkdirAllAndChown(base, 0700, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(base, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, fmt.Errorf("Failed to create '%s': %v", base, err) } @@ -385,7 +385,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e return nil, err } // Create the target directories if they don't exist - if err := idtools.MkdirAllAndChown(mountpoint, 0755, idtools.IDPair{UID: rootUID, GID: rootGID}); err != nil { + if err := idtools.MkdirAllAndChown(mountpoint, 0755, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { return nil, err } diff --git a/daemon/info.go b/daemon/info.go index 0ed81c3b5eaeb..423962857077e 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -162,7 +162,7 @@ func (daemon *Daemon) fillSecurityOptions(v *types.Info, sysInfo *sysinfo.SysInf if selinuxEnabled() { securityOptions = append(securityOptions, "name=selinux") } - if rootIDs := daemon.idMappings.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 { + if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 { securityOptions = append(securityOptions, "name=userns") } v.SecurityOptions = securityOptions diff --git a/daemon/initlayer/setup_unix.go b/daemon/initlayer/setup_unix.go index 035f62075fe52..4af7eafc679a8 100644 --- a/daemon/initlayer/setup_unix.go +++ b/daemon/initlayer/setup_unix.go @@ -17,7 +17,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(initLayerFs containerfs.ContainerFS, rootIDs idtools.IDPair) error { +func Setup(initLayerFs containerfs.ContainerFS, rootIdentity idtools.Identity) error { // Since all paths are local to the container, we can just extract initLayerFs.Path() initLayer := initLayerFs.Path() @@ -42,12 +42,12 @@ func Setup(initLayerFs containerfs.ContainerFS, rootIDs idtools.IDPair) error { if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil { if os.IsNotExist(err) { - if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootIDs); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, filepath.Dir(pth)), 0755, rootIdentity); err != nil { return err } switch typ { case "dir": - if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, pth), 0755, rootIDs); err != nil { + if err := idtools.MkdirAllAndChownNew(filepath.Join(initLayer, pth), 0755, rootIdentity); err != nil { return err } case "file": @@ -55,7 +55,7 @@ func Setup(initLayerFs containerfs.ContainerFS, rootIDs idtools.IDPair) error { if err != nil { return err } - f.Chown(rootIDs.UID, rootIDs.GID) + f.Chown(rootIdentity.UID, rootIdentity.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 1032092e6230e..f91fefd17624e 100644 --- a/daemon/initlayer/setup_windows.go +++ b/daemon/initlayer/setup_windows.go @@ -11,6 +11,6 @@ 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 containerfs.ContainerFS, rootIDs idtools.IDPair) error { +func Setup(initLayer containerfs.ContainerFS, rootIDs idtools.Identity) error { return nil } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index 824bb540782c7..7611fc054d13e 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -217,13 +217,13 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error userNS := false // user if c.HostConfig.UsernsMode.IsPrivate() { - uidMap := daemon.idMappings.UIDs() + uidMap := daemon.idMapping.UIDs() if uidMap != nil { userNS = true ns := specs.LinuxNamespace{Type: "user"} setNamespace(s, ns) s.Linux.UIDMappings = specMapping(uidMap) - s.Linux.GIDMappings = specMapping(daemon.idMappings.GIDs()) + s.Linux.GIDMappings = specMapping(daemon.idMapping.GIDs()) } } // network @@ -619,7 +619,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.idMappings.UIDs(); uidMap != nil || c.HostConfig.Privileged { + if uidMap := daemon.idMapping.UIDs(); uidMap != nil || c.HostConfig.Privileged { for i, m := range s.Mounts { if m.Type == "cgroup" { clearReadOnly(&s.Mounts[i]) @@ -642,7 +642,7 @@ func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) Path: c.BaseFS.Path(), Readonly: c.HostConfig.ReadonlyRootfs, } - if err := c.SetupWorkingDirectory(daemon.idMappings.RootPair()); err != nil { + if err := c.SetupWorkingDirectory(daemon.idMapping.RootPair()); err != nil { return err } cwd := c.Config.WorkingDir diff --git a/daemon/oci_linux_test.go b/daemon/oci_linux_test.go index e618951ef95ea..bb296a870327b 100644 --- a/daemon/oci_linux_test.go +++ b/daemon/oci_linux_test.go @@ -21,7 +21,7 @@ func TestTmpfsDevShmNoDupMount(t *testing.T) { d := Daemon{ // some empty structs to avoid getting a panic // caused by a null pointer dereference - idMappings: &idtools.IDMappings{}, + idMapping: &idtools.IdentityMapping{}, configStore: &config.Config{}, } c := &container.Container{ @@ -58,7 +58,7 @@ func TestIpcPrivateVsReadonly(t *testing.T) { d := Daemon{ // some empty structs to avoid getting a panic // caused by a null pointer dereference - idMappings: &idtools.IDMappings{}, + idMapping: &idtools.IdentityMapping{}, configStore: &config.Config{}, } c := &container.Container{ diff --git a/daemon/volumes_unix.go b/daemon/volumes_unix.go index efffefa76b54f..5ddb926de74a5 100644 --- a/daemon/volumes_unix.go +++ b/daemon/volumes_unix.go @@ -47,7 +47,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er return nil } - path, err := m.Setup(c.MountLabel, daemon.idMappings.RootPair(), checkfunc) + path, err := m.Setup(c.MountLabel, daemon.idMapping.RootPair(), checkfunc) if err != nil { return nil, err } @@ -77,7 +77,7 @@ 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) - rootIDs := daemon.idMappings.RootPair() + rootIDs := daemon.idMapping.RootPair() for _, mount := 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 diff --git a/daemon/volumes_windows.go b/daemon/volumes_windows.go index a2fb5152d1494..574cc48f1cf8b 100644 --- a/daemon/volumes_windows.go +++ b/daemon/volumes_windows.go @@ -24,7 +24,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, idtools.IDPair{UID: 0, GID: 0}, nil) + s, err := mount.Setup(c.MountLabel, idtools.Identity{}, nil) if err != nil { return nil, err } diff --git a/daemon/workdir.go b/daemon/workdir.go index 90bba79b571c1..4c0184998aef8 100644 --- a/daemon/workdir.go +++ b/daemon/workdir.go @@ -16,5 +16,5 @@ func (daemon *Daemon) ContainerCreateWorkdir(cID string) error { return err } defer daemon.Unmount(container) - return container.SetupWorkingDirectory(daemon.idMappings.RootPair()) + return container.SetupWorkingDirectory(daemon.idMapping.RootPair()) } diff --git a/hack/make/containerutility b/hack/make/containerutility new file mode 100644 index 0000000000000..8aefc00ff08d6 --- /dev/null +++ b/hack/make/containerutility @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -e + +CONTAINER_UTILITY_COMMIT=e004a1415a433447369e315b9d7df357102be0d2 # v0.9.0 + +( + git clone https://github.com/docker/windows-container-utility.git "$GOPATH/src/github.com/docker/windows-container-utility" + cd "$GOPATH/src/github.com/docker/windows-container-utility" + git checkout -q "$CONTAINER_UTILITY_COMMIT" + + echo Building: ${DEST}/containerutility.exe + + ( + make + ) + + mkdir -p ${ABS_DEST} + + cp containerutility.exe ${ABS_DEST}/containerutility.exe +) diff --git a/hack/make/cross b/hack/make/cross index 85dd3c637fb2c..497f02ae4ba3c 100644 --- a/hack/make/cross +++ b/hack/make/cross @@ -25,5 +25,7 @@ for platform in $DOCKER_CROSSPLATFORMS; do mkdir -p "$DEST" ABS_DEST="$(cd "$DEST" && pwd -P)" source "${MAKEDIR}/binary-daemon" + + source "${MAKEDIR}/cross-platform-dependent" ) done diff --git a/hack/make/cross-platform-dependent b/hack/make/cross-platform-dependent new file mode 100644 index 0000000000000..52632c30366bb --- /dev/null +++ b/hack/make/cross-platform-dependent @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -e + +if [ $platform == "windows/amd64" ]; then + source "${MAKEDIR}/containerutility" +fi diff --git a/layer/layer_store.go b/layer/layer_store.go index c1fbf85091730..bc3e8719fc5bc 100644 --- a/layer/layer_store.go +++ b/layer/layer_store.go @@ -45,7 +45,7 @@ type StoreOptions struct { MetadataStorePathTemplate string GraphDriver string GraphDriverOptions []string - IDMappings *idtools.IDMappings + IDMapping *idtools.IdentityMapping PluginGetter plugingetter.PluginGetter ExperimentalEnabled bool OS string @@ -56,8 +56,8 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) { driver, err := graphdriver.New(options.GraphDriver, options.PluginGetter, graphdriver.Options{ Root: options.Root, DriverOptions: options.GraphDriverOptions, - UIDMaps: options.IDMappings.UIDs(), - GIDMaps: options.IDMappings.GIDs(), + UIDMaps: options.IDMapping.UIDs(), + GIDMaps: options.IDMapping.GIDs(), ExperimentalEnabled: options.ExperimentalEnabled, }) if err != nil { diff --git a/libcontainerd/client_daemon_linux.go b/libcontainerd/client_daemon_linux.go index b57c4d3c50109..bebe5f7ae8812 100644 --- a/libcontainerd/client_daemon_linux.go +++ b/libcontainerd/client_daemon_linux.go @@ -58,7 +58,7 @@ func getSpecUser(ociSpec *specs.Spec) (int, int) { func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) { uid, gid := getSpecUser(ociSpec) if uid == 0 && gid == 0 { - return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.IDPair{UID: 0, GID: 0}) + return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.Identity{UID: 0, GID: 0}) } p := string(filepath.Separator) @@ -71,7 +71,7 @@ func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) { } if os.IsNotExist(err) || fi.Mode()&1 == 0 { p = fmt.Sprintf("%s.%d.%d", p, uid, gid) - if err := idtools.MkdirAndChown(p, 0700, idtools.IDPair{UID: uid, GID: gid}); err != nil && !os.IsExist(err) { + if err := idtools.MkdirAndChown(p, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) { return "", err } } diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 86922ddcac0d9..070dccb756907 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -52,7 +52,7 @@ type ( NoLchown bool UIDMaps []idtools.IDMap GIDMaps []idtools.IDMap - ChownOpts *idtools.IDPair + ChownOpts *idtools.Identity IncludeSourceDir bool // WhiteoutFormat is the expected on disk format for whiteout files. // This format will be converted to the standard format on pack @@ -72,13 +72,13 @@ type ( // this package with a pluggable Untar function. Also, to facilitate the passing of specific id // mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations. type Archiver struct { - Untar func(io.Reader, string, *TarOptions) error - IDMappingsVar *idtools.IDMappings + Untar func(io.Reader, string, *TarOptions) error + IDMapping *idtools.IdentityMapping } -// NewDefaultArchiver returns a new Archiver without any IDMappings +// NewDefaultArchiver returns a new Archiver without any IdentityMapping func NewDefaultArchiver() *Archiver { - return &Archiver{Untar: Untar, IDMappingsVar: &idtools.IDMappings{}} + return &Archiver{Untar: Untar, IDMapping: &idtools.IdentityMapping{}} } // breakoutError is used to differentiate errors related to breaking out @@ -420,9 +420,9 @@ type tarAppender struct { Buffer *bufio.Writer // for hardlink mapping - SeenFiles map[uint64]string - IDMappings *idtools.IDMappings - ChownOpts *idtools.IDPair + SeenFiles map[uint64]string + IdentityMapping *idtools.IdentityMapping + ChownOpts *idtools.Identity // For packing and unpacking whiteout files in the // non standard format. The whiteout files defined @@ -431,13 +431,13 @@ type tarAppender struct { WhiteoutConverter tarWhiteoutConverter } -func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer, chownOpts *idtools.IDPair) *tarAppender { +func newTarAppender(idMapping *idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender { return &tarAppender{ - SeenFiles: make(map[uint64]string), - TarWriter: tar.NewWriter(writer), - Buffer: pools.BufioWriter32KPool.Get(nil), - IDMappings: idMapping, - ChownOpts: chownOpts, + SeenFiles: make(map[uint64]string), + TarWriter: tar.NewWriter(writer), + Buffer: pools.BufioWriter32KPool.Get(nil), + IdentityMapping: idMapping, + ChownOpts: chownOpts, } } @@ -502,14 +502,12 @@ func (ta *tarAppender) addTarFile(path, name string) error { //handle re-mapping container ID mappings back to host ID mappings before //writing tar headers/files. We skip whiteout files because they were written //by the kernel and already have proper ownership relative to the host - if !isOverlayWhiteout && - !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && - !ta.IDMappings.Empty() { + if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && !ta.IdentityMapping.Empty() { fileIDPair, err := getFileUIDGID(fi.Sys()) if err != nil { return err } - hdr.Uid, hdr.Gid, err = ta.IDMappings.ToContainer(fileIDPair) + hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(fileIDPair) if err != nil { return err } @@ -572,7 +570,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { return nil } -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *idtools.IDPair, inUserns bool) error { +func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool, chownOpts *idtools.Identity, inUserns bool) error { // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) @@ -652,7 +650,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L // Lchown is not supported on Windows. if Lchown && runtime.GOOS != "windows" { if chownOpts == nil { - chownOpts = &idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid} + chownOpts = &idtools.Identity{UID: hdr.Uid, GID: hdr.Gid} } if err := os.Lchown(path, chownOpts.UID, chownOpts.GID); err != nil { return err @@ -901,8 +899,8 @@ func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) err defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header - idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) - rootIDs := idMappings.RootPair() + idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) + rootIDs := idMapping.RootPair() whiteoutConverter := getWhiteoutConverter(options.WhiteoutFormat) // Iterate through the files in the archive. @@ -981,7 +979,7 @@ loop: } trBuf.Reset(tr) - if err := remapIDs(idMappings, hdr); err != nil { + if err := remapIDs(idMapping, hdr); err != nil { return err } @@ -1068,8 +1066,8 @@ func (archiver *Archiver) TarUntar(src, dst string) error { } defer archive.Close() options := &TarOptions{ - UIDMaps: archiver.IDMappingsVar.UIDs(), - GIDMaps: archiver.IDMappingsVar.GIDs(), + UIDMaps: archiver.IDMapping.UIDs(), + GIDMaps: archiver.IDMapping.GIDs(), } return archiver.Untar(archive, dst, options) } @@ -1082,8 +1080,8 @@ func (archiver *Archiver) UntarPath(src, dst string) error { } defer archive.Close() options := &TarOptions{ - UIDMaps: archiver.IDMappingsVar.UIDs(), - GIDMaps: archiver.IDMappingsVar.GIDs(), + UIDMaps: archiver.IDMapping.UIDs(), + GIDMaps: archiver.IDMapping.GIDs(), } return archiver.Untar(archive, dst, options) } @@ -1104,7 +1102,7 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error { // if this Archiver is set up with ID mapping we need to create // the new destination directory with the remapped root UID/GID pair // as owner - rootIDs := archiver.IDMappingsVar.RootPair() + rootIDs := archiver.IDMapping.RootPair() // Create dst, copy src's content into it logrus.Debugf("Creating dest directory: %s", dst) if err := idtools.MkdirAllAndChownNew(dst, 0755, rootIDs); err != nil { @@ -1164,7 +1162,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { hdr.Name = filepath.Base(dst) hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode))) - if err := remapIDs(archiver.IDMappingsVar, hdr); err != nil { + if err := remapIDs(archiver.IDMapping, hdr); err != nil { return err } @@ -1192,13 +1190,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { return err } -// IDMappings returns the IDMappings of the archiver. -func (archiver *Archiver) IDMappings() *idtools.IDMappings { - return archiver.IDMappingsVar +// IdentityMapping returns the IdentityMapping of the archiver. +func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping { + return archiver.IDMapping } -func remapIDs(idMappings *idtools.IDMappings, hdr *tar.Header) error { - ids, err := idMappings.ToHost(idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}) +func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error { + ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid}) hdr.Uid, hdr.Gid = ids.UID, ids.GID return err } diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 9f06af9969c95..b448bac49a197 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -755,11 +755,11 @@ func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) { expectedUID int expectedGID int }{ - {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1337, GID: 42}}, 1337, 42}, - {&TarOptions{ChownOpts: &idtools.IDPair{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001}, - {&TarOptions{ChownOpts: &idtools.IDPair{UID: 0, GID: 0}, NoLchown: false}, 0, 0}, - {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1, GID: 1}, NoLchown: true}, 1, 1}, - {&TarOptions{ChownOpts: &idtools.IDPair{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000}, + {&TarOptions{ChownOpts: &idtools.Identity{UID: 1337, GID: 42}}, 1337, 42}, + {&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, UIDMaps: idMaps, GIDMaps: idMaps}, 100001, 100001}, + {&TarOptions{ChownOpts: &idtools.Identity{UID: 0, GID: 0}, NoLchown: false}, 0, 0}, + {&TarOptions{ChownOpts: &idtools.Identity{UID: 1, GID: 1}, NoLchown: true}, 1, 1}, + {&TarOptions{ChownOpts: &idtools.Identity{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000}, } for _, testCase := range cases { reader, err := TarWithOptions(filePath, testCase.opts) diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index fda3f50c6a786..1eec912b7b0b6 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -68,13 +68,13 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) { return } -func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { +func getFileUIDGID(stat interface{}) (idtools.Identity, error) { s, ok := stat.(*syscall.Stat_t) if !ok { - return idtools.IDPair{}, errors.New("cannot convert stat value to syscall.Stat_t") + return idtools.Identity{}, errors.New("cannot convert stat value to syscall.Stat_t") } - return idtools.IDPair{UID: int(s.Uid), GID: int(s.Gid)}, nil + return idtools.Identity{UID: int(s.Uid), GID: int(s.Gid)}, nil } // handleTarTypeBlockCharFifo is an OS-specific helper function used by diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index 3160c5a8fdb27..ae6b89fd719c4 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -61,7 +61,7 @@ func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { return nil } -func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { +func getFileUIDGID(stat interface{}) (idtools.Identity, error) { // no notion of file ownership mapping yet on Windows - return idtools.IDPair{UID: 0, GID: 0}, nil + return idtools.Identity{UID: 0, GID: 0}, nil } diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go index fae4b9de02c8b..6883ba13269cd 100644 --- a/pkg/archive/diff.go +++ b/pkg/archive/diff.go @@ -33,7 +33,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, if options.ExcludePatterns == nil { options.ExcludePatterns = []string{} } - idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) + idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) @@ -192,7 +192,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, srcData = tmpFile } - if err := remapIDs(idMappings, srcHdr); err != nil { + if err := remapIDs(idMapping, srcHdr); err != nil { return 0, err } diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index 47c9a2b94c5fc..2d9d662830b7b 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -12,13 +12,13 @@ import ( ) // NewArchiver returns a new Archiver which uses chrootarchive.Untar -func NewArchiver(idMappings *idtools.IDMappings) *archive.Archiver { - if idMappings == nil { - idMappings = &idtools.IDMappings{} +func NewArchiver(idMapping *idtools.IdentityMapping) *archive.Archiver { + if idMapping == nil { + idMapping = &idtools.IdentityMapping{} } return &archive.Archiver{ - Untar: Untar, - IDMappingsVar: idMappings, + Untar: Untar, + IDMapping: idMapping, } } @@ -49,8 +49,8 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions options.ExcludePatterns = []string{} } - idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) - rootIDs := idMappings.RootPair() + idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps) + rootIDs := idMapping.RootPair() dest = filepath.Clean(dest) if _, err := os.Stat(dest); os.IsNotExist(err) { diff --git a/pkg/containerfs/archiver.go b/pkg/containerfs/archiver.go index 1fb7ff7bdc8cf..fed0a07d7b9f8 100644 --- a/pkg/containerfs/archiver.go +++ b/pkg/containerfs/archiver.go @@ -22,11 +22,11 @@ type UntarFunc func(io.Reader, string, *archive.TarOptions) error // Archiver provides a similar implementation of the archive.Archiver package with the rootfs abstraction type Archiver struct { - SrcDriver Driver - DstDriver Driver - Tar TarFunc - Untar UntarFunc - IDMappingsVar *idtools.IDMappings + SrcDriver Driver + DstDriver Driver + Tar TarFunc + Untar UntarFunc + IDMapping *idtools.IdentityMapping } // TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other. @@ -39,8 +39,8 @@ func (archiver *Archiver) TarUntar(src, dst string) error { } defer tarArchive.Close() options := &archive.TarOptions{ - UIDMaps: archiver.IDMappingsVar.UIDs(), - GIDMaps: archiver.IDMappingsVar.GIDs(), + UIDMaps: archiver.IDMapping.UIDs(), + GIDMaps: archiver.IDMapping.GIDs(), } return archiver.Untar(tarArchive, dst, options) } @@ -53,8 +53,8 @@ func (archiver *Archiver) UntarPath(src, dst string) error { } defer tarArchive.Close() options := &archive.TarOptions{ - UIDMaps: archiver.IDMappingsVar.UIDs(), - GIDMaps: archiver.IDMappingsVar.GIDs(), + UIDMaps: archiver.IDMapping.UIDs(), + GIDMaps: archiver.IDMapping.GIDs(), } return archiver.Untar(tarArchive, dst, options) } @@ -75,9 +75,11 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error { // if this archiver is set up with ID mapping we need to create // the new destination directory with the remapped root UID/GID pair // as owner - rootIDs := archiver.IDMappingsVar.RootPair() + + identity := idtools.Identity{UID: archiver.IDMapping.RootPair().UID, GID: archiver.IDMapping.RootPair().GID} + // Create dst, copy src's content into it - if err := idtools.MkdirAllAndChownNew(dst, 0755, rootIDs); err != nil { + if err := idtools.MkdirAllAndChownNew(dst, 0755, identity); err != nil { return err } logrus.Debugf("Calling TarUntar(%s, %s)", src, dst) @@ -150,7 +152,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { hdr.Mode = int64(os.FileMode(hdr.Mode)) } - if err := remapIDs(archiver.IDMappingsVar, hdr); err != nil { + if err := remapIDs(archiver.IDMapping, hdr); err != nil { return err } @@ -178,13 +180,13 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { return err } -// IDMappings returns the IDMappings of the archiver. -func (archiver *Archiver) IDMappings() *idtools.IDMappings { - return archiver.IDMappingsVar +// IdentityMapping returns the IdentityMapping of the archiver. +func (archiver *Archiver) IdentityMapping() *idtools.IdentityMapping { + return archiver.IDMapping } -func remapIDs(idMappings *idtools.IDMappings, hdr *tar.Header) error { - ids, err := idMappings.ToHost(idtools.IDPair{UID: hdr.Uid, GID: hdr.Gid}) +func remapIDs(idMapping *idtools.IdentityMapping, hdr *tar.Header) error { + ids, err := idMapping.ToHost(idtools.Identity{UID: hdr.Uid, GID: hdr.Gid}) hdr.Uid, hdr.Gid = ids.UID, ids.GID return err } diff --git a/pkg/idtools/idtools.go b/pkg/idtools/idtools.go index d1f173a311ca5..230422eac827f 100644 --- a/pkg/idtools/idtools.go +++ b/pkg/idtools/idtools.go @@ -37,23 +37,23 @@ const ( // 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, owner IDPair) error { - return mkdirAs(path, mode, owner.UID, owner.GID, true, true) +func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, 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. // Note that unlike os.Mkdir(), this function does not return IsExist error // in case path already exists. -func MkdirAndChown(path string, mode os.FileMode, owner IDPair) error { - return mkdirAs(path, mode, owner.UID, owner.GID, false, true) +func MkdirAndChown(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, 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, owner IDPair) error { - return mkdirAs(path, mode, owner.UID, owner.GID, true, false) +func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, true, false) } // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. @@ -102,22 +102,23 @@ func toHost(contID int, idMap []IDMap) (int, error) { return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) } -// IDPair is a UID and GID pair -type IDPair struct { +// Identity is either a UID and GID pair or a SID (but not both) +type Identity struct { UID int GID int + SID string } -// IDMappings contains a mappings of UIDs and GIDs -type IDMappings struct { +// IdentityMapping contains a mappings of UIDs and GIDs +type IdentityMapping struct { uids []IDMap gids []IDMap } -// NewIDMappings takes a requested user and group name and +// NewIdentityMapping 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 NewIDMappings(username, groupname string) (*IDMappings, error) { +func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) { subuidRanges, err := parseSubuid(username) if err != nil { return nil, err @@ -133,7 +134,7 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) { return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) } - return &IDMappings{ + return &IdentityMapping{ uids: createIDMap(subuidRanges), gids: createIDMap(subgidRanges), }, nil @@ -141,21 +142,21 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) { // NewIDMappingsFromMaps creates a new mapping from two slices // Deprecated: this is a temporary shim while transitioning to IDMapping -func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings { - return &IDMappings{uids: uids, gids: gids} +func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping { + return &IdentityMapping{uids: uids, gids: gids} } // 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 *IDMappings) RootPair() IDPair { +func (i *IdentityMapping) RootPair() Identity { uid, gid, _ := GetRootUIDGID(i.uids, i.gids) - return IDPair{UID: uid, GID: gid} + return Identity{UID: uid, GID: 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 *IDMappings) ToHost(pair IDPair) (IDPair, error) { +func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) { var err error target := i.RootPair() @@ -173,7 +174,7 @@ func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { } // ToContainer returns the container UID and GID for the host uid and gid -func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { +func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) { uid, err := toContainer(pair.UID, i.uids) if err != nil { return -1, -1, err @@ -183,19 +184,19 @@ func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { } // Empty returns true if there are no id mappings -func (i *IDMappings) Empty() bool { +func (i *IdentityMapping) Empty() bool { return len(i.uids) == 0 && len(i.gids) == 0 } // UIDs return the UID mapping // TODO: remove this once everything has been refactored to use pairs -func (i *IDMappings) UIDs() []IDMap { +func (i *IdentityMapping) 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 { +func (i *IdentityMapping) GIDs() []IDMap { return i.gids } diff --git a/pkg/idtools/idtools_unix.go b/pkg/idtools/idtools_unix.go index 1d87ea3bcb407..fb239743a01a4 100644 --- a/pkg/idtools/idtools_unix.go +++ b/pkg/idtools/idtools_unix.go @@ -21,11 +21,12 @@ var ( getentCmd string ) -func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { +func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { // 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 // chown the full directory path if it exists + var paths []string stat, err := system.Stat(path) @@ -38,7 +39,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown } // short-circuit--we were called with an existing directory and chown was requested - return lazyChown(path, ownerUID, ownerGID, stat) + return lazyChown(path, owner.UID, owner.GID, stat) } if os.IsNotExist(err) { @@ -69,7 +70,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // 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 := lazyChown(pathComponent, ownerUID, ownerGID, nil); err != nil { + if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil { return err } } @@ -78,7 +79,7 @@ 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, pair IDPair) bool { +func CanAccess(path string, pair Identity) bool { statInfo, err := system.Stat(path) if err != nil { return false diff --git a/pkg/idtools/idtools_unix_test.go b/pkg/idtools/idtools_unix_test.go index 608000a66b207..be5d60262247f 100644 --- a/pkg/idtools/idtools_unix_test.go +++ b/pkg/idtools/idtools_unix_test.go @@ -46,7 +46,7 @@ func TestMkdirAllAndChown(t *testing.T) { } // 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"), 0755, IDPair{UID: 99, GID: 99}); err != nil { + if err := MkdirAllAndChown(filepath.Join(dirName, "usr", "share"), 0755, Identity{UID: 99, GID: 99}); err != nil { t.Fatal(err) } testTree["usr/share"] = node{99, 99} @@ -59,7 +59,7 @@ func TestMkdirAllAndChown(t *testing.T) { } // test 2-deep new directories--both should be owned by the uid/gid pair - if err := MkdirAllAndChown(filepath.Join(dirName, "lib", "some", "other"), 0755, IDPair{UID: 101, GID: 101}); err != nil { + if err := MkdirAllAndChown(filepath.Join(dirName, "lib", "some", "other"), 0755, Identity{UID: 101, GID: 101}); err != nil { t.Fatal(err) } testTree["lib/some"] = node{101, 101} @@ -73,7 +73,7 @@ func TestMkdirAllAndChown(t *testing.T) { } // test a directory that already exists; should be chowned, but nothing else - if err := MkdirAllAndChown(filepath.Join(dirName, "usr"), 0755, IDPair{UID: 102, GID: 102}); err != nil { + if err := MkdirAllAndChown(filepath.Join(dirName, "usr"), 0755, Identity{UID: 102, GID: 102}); err != nil { t.Fatal(err) } testTree["usr"] = node{102, 102} @@ -102,7 +102,7 @@ func TestMkdirAllAndChownNew(t *testing.T) { 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"), 0755, IDPair{UID: 99, GID: 99}) + err = MkdirAllAndChownNew(filepath.Join(dirName, "usr", "share"), 0755, Identity{UID: 99, GID: 99}) assert.NilError(t, err) testTree["usr/share"] = node{99, 99} @@ -111,7 +111,7 @@ func TestMkdirAllAndChownNew(t *testing.T) { 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"), 0755, IDPair{UID: 101, GID: 101}) + err = MkdirAllAndChownNew(filepath.Join(dirName, "lib", "some", "other"), 0755, Identity{UID: 101, GID: 101}) assert.NilError(t, err) testTree["lib/some"] = node{101, 101} testTree["lib/some/other"] = node{101, 101} @@ -120,7 +120,7 @@ func TestMkdirAllAndChownNew(t *testing.T) { assert.NilError(t, compareTrees(testTree, verifyTree)) // test a directory that already exists; should NOT be chowned - err = MkdirAllAndChownNew(filepath.Join(dirName, "usr"), 0755, IDPair{UID: 102, GID: 102}) + err = MkdirAllAndChownNew(filepath.Join(dirName, "usr"), 0755, Identity{UID: 102, GID: 102}) assert.NilError(t, err) verifyTree, err = readTree(dirName, "") assert.NilError(t, err) @@ -143,7 +143,7 @@ func TestMkdirAndChown(t *testing.T) { } // test a directory that already exists; should just chown to the requested uid/gid - if err := MkdirAndChown(filepath.Join(dirName, "usr"), 0755, IDPair{UID: 99, GID: 99}); err != nil { + if err := MkdirAndChown(filepath.Join(dirName, "usr"), 0755, Identity{UID: 99, GID: 99}); err != nil { t.Fatal(err) } testTree["usr"] = node{99, 99} @@ -156,12 +156,12 @@ func TestMkdirAndChown(t *testing.T) { } // create a subdir under a dir which doesn't exist--should fail - if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, IDPair{UID: 102, GID: 102}); err == nil { + if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, 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"), 0755, IDPair{UID: 102, GID: 102}); err != nil { + if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin"), 0755, Identity{UID: 102, GID: 102}); err != nil { t.Fatal(err) } testTree["usr/bin"] = node{102, 102} @@ -326,19 +326,19 @@ func TestNewIDMappings(t *testing.T) { group, err := user.LookupGroupId(string(gids[0])) assert.Check(t, err) - idMappings, err := NewIDMappings(tempUser.Username, group.Name) + idMapping, err := NewIdentityMapping(tempUser.Username, group.Name) assert.Check(t, err) - rootUID, rootGID, err := GetRootUIDGID(idMappings.UIDs(), idMappings.GIDs()) + rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDs(), idMapping.GIDs()) assert.Check(t, err) dirName, err := ioutil.TempDir("", "mkdirall") assert.Check(t, err, "Couldn't create temp directory") defer os.RemoveAll(dirName) - err = MkdirAllAndChown(dirName, 0700, IDPair{UID: rootUID, GID: rootGID}) + err = MkdirAllAndChown(dirName, 0700, Identity{UID: rootUID, GID: rootGID}) assert.Check(t, err, "Couldn't change ownership of file path. Got error") - assert.Check(t, CanAccess(dirName, idMappings.RootPair()), fmt.Sprintf("Unable to access %s directory with user UID:%d and GID:%d", dirName, rootUID, rootGID)) + assert.Check(t, CanAccess(dirName, idMapping.RootPair()), fmt.Sprintf("Unable to access %s directory with user UID:%d and GID:%d", dirName, rootUID, rootGID)) } func TestLookupUserAndGroup(t *testing.T) { @@ -388,7 +388,7 @@ func TestMkdirIsNotDir(t *testing.T) { } defer os.Remove(file.Name()) - err = mkdirAs(file.Name(), 0755, 0, 0, false, false) + err = mkdirAs(file.Name(), 0755, Identity{UID: 0, GID: 0}, false, false) assert.Check(t, is.Error(err, "mkdir "+file.Name()+": not a directory")) } diff --git a/pkg/idtools/idtools_windows.go b/pkg/idtools/idtools_windows.go index d72cc28929c5b..4ae38a1b17bb0 100644 --- a/pkg/idtools/idtools_windows.go +++ b/pkg/idtools/idtools_windows.go @@ -6,9 +6,11 @@ import ( "github.com/docker/docker/pkg/system" ) -// Platforms such as Windows do not support the UID/GID concept. So make this -// just a wrapper around system.MkdirAll. -func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { +// This is currently a wrapper around MkdirAll, however, 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, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { if err := system.MkdirAll(path, mode, ""); err != nil { return err } @@ -18,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, pair IDPair) bool { +func CanAccess(path string, identity Identity) bool { return true } diff --git a/pkg/system/syscall_windows.go b/pkg/system/syscall_windows.go index ee7e0256f333b..4ae92fa6c7d7b 100644 --- a/pkg/system/syscall_windows.go +++ b/pkg/system/syscall_windows.go @@ -2,16 +2,62 @@ package system // import "github.com/docker/docker/pkg/system" import ( "fmt" + "syscall" "unsafe" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" ) +const ( + OWNER_SECURITY_INFORMATION = 0x00000001 + GROUP_SECURITY_INFORMATION = 0x00000002 + DACL_SECURITY_INFORMATION = 0x00000004 + SACL_SECURITY_INFORMATION = 0x00000008 + LABEL_SECURITY_INFORMATION = 0x00000010 + ATTRIBUTE_SECURITY_INFORMATION = 0x00000020 + SCOPE_SECURITY_INFORMATION = 0x00000040 + PROCESS_TRUST_LABEL_SECURITY_INFORMATION = 0x00000080 + ACCESS_FILTER_SECURITY_INFORMATION = 0x00000100 + BACKUP_SECURITY_INFORMATION = 0x00010000 + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000 + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 +) + +const ( + SE_UNKNOWN_OBJECT_TYPE = iota + SE_FILE_OBJECT + SE_SERVICE + SE_PRINTER + SE_REGISTRY_KEY + SE_LMSHARE + SE_KERNEL_OBJECT + SE_WINDOW_OBJECT + SE_DS_OBJECT + SE_DS_OBJECT_ALL + SE_PROVIDER_DEFINED_OBJECT + SE_WMIGUID_OBJECT + SE_REGISTRY_WOW64_32KEY +) + +const ( + SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" +) + +const ( + ContainerAdministratorSidString = "S-1-5-93-2-1" + ContainerUserSidString = "S-1-5-93-2-2" +) + var ( - ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") - procGetVersionExW = modkernel32.NewProc("GetVersionExW") - procGetProductInfo = modkernel32.NewProc("GetProductInfo") + ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + procGetVersionExW = modkernel32.NewProc("GetVersionExW") + procGetProductInfo = modkernel32.NewProc("GetProductInfo") + procSetNamedSecurityInfo = modadvapi32.NewProc("SetNamedSecurityInfoW") + procGetSecurityDescriptorDacl = modadvapi32.NewProc("GetSecurityDescriptorDacl") ) // OSVersion is a wrapper for Windows version information @@ -125,3 +171,23 @@ func HasWin32KSupport() bool { // APIs. return ntuserApiset.Load() == nil } + +func SetNamedSecurityInfo(objectName *uint16, objectType uint32, securityInformation uint32, sidOwner *windows.SID, sidGroup *windows.SID, dacl *byte, sacl *byte) (result error) { + r0, _, _ := syscall.Syscall9(procSetNamedSecurityInfo.Addr(), 7, uintptr(unsafe.Pointer(objectName)), uintptr(objectType), uintptr(securityInformation), uintptr(unsafe.Pointer(sidOwner)), uintptr(unsafe.Pointer(sidGroup)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(sacl)), 0, 0) + if r0 != 0 { + result = syscall.Errno(r0) + } + return +} + +func GetSecurityDescriptorDacl(securityDescriptor *byte, daclPresent *uint32, dacl **byte, daclDefaulted *uint32) (result error) { + r1, _, e1 := syscall.Syscall6(procGetSecurityDescriptorDacl.Addr(), 4, uintptr(unsafe.Pointer(securityDescriptor)), uintptr(unsafe.Pointer(daclPresent)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(daclDefaulted)), 0, 0) + if r1 == 0 { + if e1 != 0 { + result = syscall.Errno(e1) + } else { + result = syscall.EINVAL + } + } + return +} diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 3c6f9c553a965..df1fe5b73dfac 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -53,7 +53,7 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error { } rootFS := containerfs.NewLocalContainerFS(filepath.Join(pm.config.Root, p.PluginObj.ID, rootFSFileName)) - if err := initlayer.Setup(rootFS, idtools.IDPair{UID: 0, GID: 0}); err != nil { + if err := initlayer.Setup(rootFS, idtools.Identity{UID: 0, GID: 0}); err != nil { return errors.WithStack(err) } diff --git a/volume/local/local.go b/volume/local/local.go index d97347423a621..7190de9ed60e0 100644 --- a/volume/local/local.go +++ b/volume/local/local.go @@ -46,18 +46,18 @@ 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, rootIDs idtools.IDPair) (*Root, error) { +func New(scope string, rootIdentity idtools.Identity) (*Root, error) { rootDirectory := filepath.Join(scope, volumesPathName) - if err := idtools.MkdirAllAndChown(rootDirectory, 0700, rootIDs); err != nil { + if err := idtools.MkdirAllAndChown(rootDirectory, 0700, rootIdentity); err != nil { return nil, err } r := &Root{ - scope: scope, - path: rootDirectory, - volumes: make(map[string]*localVolume), - rootIDs: rootIDs, + scope: scope, + path: rootDirectory, + volumes: make(map[string]*localVolume), + rootIdentity: rootIdentity, } dirs, err := ioutil.ReadDir(rootDirectory) @@ -101,11 +101,11 @@ func New(scope string, rootIDs idtools.IDPair) (*Root, error) { // manages the creation/removal of volumes. It uses only standard vfs // commands to create/remove dirs within its provided scope. type Root struct { - m sync.Mutex - scope string - path string - volumes map[string]*localVolume - rootIDs idtools.IDPair + m sync.Mutex + scope string + path string + volumes map[string]*localVolume + rootIdentity idtools.Identity } // List lists all the volumes @@ -146,7 +146,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error } path := r.DataPath(name) - if err := idtools.MkdirAllAndChown(path, 0755, r.rootIDs); err != nil { + if err := idtools.MkdirAllAndChown(path, 0755, r.rootIdentity); err != nil { return nil, errors.Wrapf(errdefs.System(err), "error while creating volume path '%s'", path) } diff --git a/volume/local/local_test.go b/volume/local/local_test.go index 4cb47ba045229..51df1a7edfcec 100644 --- a/volume/local/local_test.go +++ b/volume/local/local_test.go @@ -38,7 +38,7 @@ func TestRemove(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, idtools.IDPair{UID: os.Geteuid(), GID: os.Getegid()}) + r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -80,7 +80,7 @@ func TestInitializeWithVolumes(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, idtools.IDPair{UID: os.Geteuid(), GID: os.Getegid()}) + r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -90,7 +90,7 @@ func TestInitializeWithVolumes(t *testing.T) { t.Fatal(err) } - r, err = New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -112,7 +112,7 @@ func TestCreate(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -149,7 +149,7 @@ func TestCreate(t *testing.T) { } } - r, err = New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -186,7 +186,7 @@ func TestCreateWithOpts(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -261,7 +261,7 @@ func TestCreateWithOpts(t *testing.T) { t.Fatal("expected mount to still be active") } - r, err = New(rootDir, idtools.IDPair{UID: 0, GID: 0}) + r, err = New(rootDir, idtools.Identity{UID: 0, GID: 0}) if err != nil { t.Fatal(err) } @@ -283,7 +283,7 @@ func TestRelaodNoOpts(t *testing.T) { } defer os.RemoveAll(rootDir) - r, err := New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err := New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } @@ -311,7 +311,7 @@ func TestRelaodNoOpts(t *testing.T) { t.Fatal(err) } - r, err = New(rootDir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + r, err = New(rootDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) if err != nil { t.Fatal(err) } diff --git a/volume/mounts/mounts.go b/volume/mounts/mounts.go index a1c5dce1c864e..63a1406814698 100644 --- a/volume/mounts/mounts.go +++ b/volume/mounts/mounts.go @@ -95,7 +95,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, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) { +func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.Identity, checkFun func(m *MountPoint) error) (path string, err error) { if m.SkipMountpointCreation { return m.Source, nil } diff --git a/volume/service/default_driver.go b/volume/service/default_driver.go index 1c1d5c54bc6d6..b5986ab7176c1 100644 --- a/volume/service/default_driver.go +++ b/volume/service/default_driver.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" ) -func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.IDPair) error { +func setupDefaultDriver(store *drivers.Store, root string, rootIDs idtools.Identity) error { d, err := local.New(root, rootIDs) if err != nil { return errors.Wrap(err, "error setting up default driver") diff --git a/volume/service/default_driver_stubs.go b/volume/service/default_driver_stubs.go index fdb275eb9d39d..0539b1369b692 100644 --- a/volume/service/default_driver_stubs.go +++ b/volume/service/default_driver_stubs.go @@ -7,4 +7,4 @@ import ( "github.com/docker/docker/volume/drivers" ) -func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.IDPair) error { return nil } +func setupDefaultDriver(_ *drivers.Store, _ string, _ idtools.Identity) error { return nil } diff --git a/volume/service/service.go b/volume/service/service.go index a62a32de5094f..ebb5e205e90ca 100644 --- a/volume/service/service.go +++ b/volume/service/service.go @@ -35,7 +35,7 @@ type VolumesService struct { } // NewVolumeService creates a new volume service -func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.IDPair, logger volumeEventLogger) (*VolumesService, error) { +func NewVolumeService(root string, pg plugingetter.PluginGetter, rootIDs idtools.Identity, logger volumeEventLogger) (*VolumesService, error) { ds := drivers.NewStore(pg) if err := setupDefaultDriver(ds, root, rootIDs); err != nil { return nil, err diff --git a/volume/service/service_linux_test.go b/volume/service/service_linux_test.go index ae70d7e2c5f99..e009cd1325cba 100644 --- a/volume/service/service_linux_test.go +++ b/volume/service/service_linux_test.go @@ -25,7 +25,7 @@ func TestLocalVolumeSize(t *testing.T) { assert.Assert(t, err) defer os.RemoveAll(dir) - l, err := local.New(dir, idtools.IDPair{UID: os.Getuid(), GID: os.Getegid()}) + l, err := local.New(dir, idtools.Identity{UID: os.Getuid(), GID: os.Getegid()}) assert.Assert(t, err) assert.Assert(t, ds.Register(l, volume.DefaultDriverName)) assert.Assert(t, ds.Register(testutils.NewFakeDriver("fake"), "fake"))