diff --git a/container_linux_test.go b/container_linux_test.go index 9612410c4..c77063256 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -275,12 +275,10 @@ func TestContainerAttach(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Error(err) - return - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return } container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image)) @@ -382,12 +380,10 @@ func TestContainerUsername(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Error(err) - return - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return } direct, err := NewDirectIO(ctx, false) if err != nil { @@ -466,12 +462,10 @@ func TestContainerAttachProcess(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Error(err) - return - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return } container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) @@ -578,3 +572,78 @@ func TestContainerAttachProcess(t *testing.T) { } <-status } + +func TestContainerUserID(t *testing.T) { + t.Parallel() + + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext() + id = t.Name() + ) + defer cancel() + + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + direct, err := NewDirectIO(ctx, false) + if err != nil { + t.Error(err) + return + } + defer direct.Delete() + var ( + wg sync.WaitGroup + buf = bytes.NewBuffer(nil) + ) + wg.Add(1) + go func() { + defer wg.Done() + io.Copy(buf, direct.Stdout) + }() + + // adm user in the alpine image has a uid of 3 and gid of 4. + container, err := client.NewContainer(ctx, id, + withNewSnapshot(id, image), + WithNewSpec(withImageConfig(image), WithUserID(3), WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")), + ) + if err != nil { + t.Error(err) + return + } + defer container.Delete(ctx, WithSnapshotCleanup) + + task, err := container.NewTask(ctx, direct.IOCreate) + if err != nil { + t.Error(err) + return + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Error(err) + return + } + + if err := task.Start(ctx); err != nil { + t.Error(err) + return + } + <-statusC + + wg.Wait() + + output := strings.TrimSuffix(buf.String(), "\n") + if output != "3:4" { + t.Errorf("expected uid:gid to be 3:4, but received %q", output) + } +} diff --git a/spec_opts_unix.go b/spec_opts_unix.go index 8aec1f22b..7c9a575a3 100644 --- a/spec_opts_unix.go +++ b/spec_opts_unix.go @@ -94,9 +94,6 @@ func WithImageConfig(i Image) SpecOpts { return fmt.Errorf("unknown image config media type %s", ic.MediaType) } s.Process.Env = append(s.Process.Env, config.Env...) - var ( - uid, gid uint32 - ) cmd := config.Cmd s.Process.Args = append(config.Entrypoint, cmd...) if config.User != "" { @@ -111,22 +108,24 @@ func WithImageConfig(i Image) SpecOpts { } return err } - uid, gid = uint32(v), uint32(v) + if err := WithUserID(uint32(v))(ctx, client, c, s); err != nil { + return err + } case 2: v, err := strconv.ParseUint(parts[0], 0, 10) if err != nil { return err } - uid = uint32(v) + uid := uint32(v) if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { return err } - gid = uint32(v) + gid := uint32(v) + s.Process.User.UID, s.Process.User.GID = uid, gid default: return fmt.Errorf("invalid USER value %s", config.User) } } - s.Process.User.UID, s.Process.User.GID = uid, gid cwd := config.WorkingDir if cwd == "" { cwd = "/" @@ -287,8 +286,8 @@ func WithNamespacedCgroup() SpecOpts { } } -// WithUserIDs allows the UID and GID for the Process to be set -func WithUserIDs(uid, gid uint32) SpecOpts { +// WithUidGid allows the UID and GID for the Process to be set +func WithUidGid(uid, gid uint32) SpecOpts { return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { s.Process.User.UID = uid s.Process.User.GID = gid @@ -296,9 +295,57 @@ func WithUserIDs(uid, gid uint32) SpecOpts { } } +// WithUserID sets the correct UID and GID for the container based +// on the image's /etc/passwd contents. If uid is not found in +// /etc/passwd, it sets uid but leaves gid 0, and not returns error. +func WithUserID(uid uint32) SpecOpts { + return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { + if c.Snapshotter == "" { + return errors.Errorf("no snapshotter set for container") + } + if c.RootFS == "" { + return errors.Errorf("rootfs not created for container") + } + snapshotter := client.SnapshotService(c.Snapshotter) + mounts, err := snapshotter.Mounts(ctx, c.RootFS) + if err != nil { + return err + } + root, err := ioutil.TempDir("", "ctd-username") + if err != nil { + return err + } + defer os.RemoveAll(root) + for _, m := range mounts { + if err := m.Mount(root); err != nil { + return err + } + } + defer unix.Unmount(root, 0) + f, err := os.Open(filepath.Join(root, "/etc/passwd")) + if err != nil { + return err + } + defer f.Close() + users, err := user.ParsePasswdFilter(f, func(u user.User) bool { + return u.Uid == int(uid) + }) + if err != nil { + return err + } + if len(users) == 0 { + s.Process.User.UID = uid + return nil + } + u := users[0] + s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid) + return nil + } +} + // WithUsername sets the correct UID and GID for the container -// based on the the image's /etc/passwd contents. -// id is the snapshot id that is used +// based on the the image's /etc/passwd contents. If the username +// is not found in /etc/passwd, it returns error. func WithUsername(username string) SpecOpts { return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { if c.Snapshotter == "" {