diff --git a/integration/image/commit_test.go b/integration/image/commit_test.go index 1852a72c30..2efa1a25b3 100644 --- a/integration/image/commit_test.go +++ b/integration/image/commit_test.go @@ -1,12 +1,14 @@ package image // import "github.com/docker/docker/integration/image" import ( + "context" "strings" "testing" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/testutil/daemon" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/skip" @@ -47,3 +49,26 @@ func TestCommitInheritsEnv(t *testing.T) { expectedEnv2 := []string{"PATH=/usr/bin:/bin"} assert.Check(t, is.DeepEqual(expectedEnv2, image2.Config.Env)) } + +// Verify that files created are owned by the remapped user even after a commit +func TestUsernsCommit(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + skip.If(t, testEnv.IsRemoteDaemon()) + skip.If(t, !testEnv.IsUserNamespaceInKernel()) + skip.If(t, testEnv.IsRootless()) + + ctx := context.Background() + dUserRemap := daemon.New(t, daemon.WithUserNsRemap("default")) + dUserRemap.StartWithBusybox(ctx, t) + clientUserRemap := dUserRemap.NewClientT(t) + defer clientUserRemap.Close() + + container.Run(ctx, t, clientUserRemap, container.WithName(t.Name()), container.WithImage("busybox"), container.WithCmd("sh", "-c", "echo hello world > /hello.txt && chown 1000:1000 /hello.txt")) + img, err := clientUserRemap.ContainerCommit(ctx, t.Name(), containertypes.CommitOptions{}) + assert.NilError(t, err) + + res := container.RunAttach(ctx, t, clientUserRemap, container.WithImage(img.ID), container.WithCmd("sh", "-c", "stat -c %u:%g /hello.txt")) + assert.Check(t, is.Equal(res.ExitCode, 0)) + assert.Check(t, is.Equal(res.Stderr.String(), "")) + assert.Assert(t, is.Equal(strings.TrimSpace(res.Stdout.String()), "1000:1000")) +} diff --git a/testutil/daemon/daemon.go b/testutil/daemon/daemon.go index 65b9cba62e..280f0dc7b2 100644 --- a/testutil/daemon/daemon.go +++ b/testutil/daemon/daemon.go @@ -83,6 +83,7 @@ type Daemon struct { args []string extraEnv []string containerdSocket string + usernsRemap string rootlessUser *user.User rootlessXDGRuntimeDir string @@ -457,6 +458,10 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { d.args = append(d.args, "--containerd", d.containerdSocket) } + if d.usernsRemap != "" { + d.args = append(d.args, "--userns-remap", d.usernsRemap) + } + if d.defaultCgroupNamespaceMode != "" { d.args = append(d.args, "--default-cgroupns-mode", d.defaultCgroupNamespaceMode) } diff --git a/testutil/daemon/ops.go b/testutil/daemon/ops.go index 61676f78e0..d1c78886d8 100644 --- a/testutil/daemon/ops.go +++ b/testutil/daemon/ops.go @@ -19,6 +19,12 @@ func WithContainerdSocket(socket string) Option { } } +func WithUserNsRemap(remap string) Option { + return func(d *Daemon) { + d.usernsRemap = remap + } +} + // WithDefaultCgroupNamespaceMode sets the default cgroup namespace mode for the daemon func WithDefaultCgroupNamespaceMode(mode string) Option { return func(d *Daemon) {