Skip to content

Commit bb9e2bf

Browse files
committed
Add WithUserID which gets uid and gid from image's /etc/passwd.
Signed-off-by: Lantao Liu <lantaol@google.com>
1 parent ab1968d commit bb9e2bf

File tree

2 files changed

+145
-29
lines changed

2 files changed

+145
-29
lines changed

container_linux_test.go

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,10 @@ func TestContainerAttach(t *testing.T) {
275275
)
276276
defer cancel()
277277

278-
if runtime.GOOS != "windows" {
279-
image, err = client.GetImage(ctx, testImage)
280-
if err != nil {
281-
t.Error(err)
282-
return
283-
}
278+
image, err = client.GetImage(ctx, testImage)
279+
if err != nil {
280+
t.Error(err)
281+
return
284282
}
285283

286284
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image))
@@ -382,12 +380,10 @@ func TestContainerUsername(t *testing.T) {
382380
)
383381
defer cancel()
384382

385-
if runtime.GOOS != "windows" {
386-
image, err = client.GetImage(ctx, testImage)
387-
if err != nil {
388-
t.Error(err)
389-
return
390-
}
383+
image, err = client.GetImage(ctx, testImage)
384+
if err != nil {
385+
t.Error(err)
386+
return
391387
}
392388
direct, err := NewDirectIO(ctx, false)
393389
if err != nil {
@@ -466,12 +462,10 @@ func TestContainerAttachProcess(t *testing.T) {
466462
)
467463
defer cancel()
468464

469-
if runtime.GOOS != "windows" {
470-
image, err = client.GetImage(ctx, testImage)
471-
if err != nil {
472-
t.Error(err)
473-
return
474-
}
465+
image, err = client.GetImage(ctx, testImage)
466+
if err != nil {
467+
t.Error(err)
468+
return
475469
}
476470

477471
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
@@ -578,3 +572,78 @@ func TestContainerAttachProcess(t *testing.T) {
578572
}
579573
<-status
580574
}
575+
576+
func TestContainerUserID(t *testing.T) {
577+
t.Parallel()
578+
579+
client, err := newClient(t, address)
580+
if err != nil {
581+
t.Fatal(err)
582+
}
583+
defer client.Close()
584+
585+
var (
586+
image Image
587+
ctx, cancel = testContext()
588+
id = t.Name()
589+
)
590+
defer cancel()
591+
592+
image, err = client.GetImage(ctx, testImage)
593+
if err != nil {
594+
t.Error(err)
595+
return
596+
}
597+
direct, err := NewDirectIO(ctx, false)
598+
if err != nil {
599+
t.Error(err)
600+
return
601+
}
602+
defer direct.Delete()
603+
var (
604+
wg sync.WaitGroup
605+
buf = bytes.NewBuffer(nil)
606+
)
607+
wg.Add(1)
608+
go func() {
609+
defer wg.Done()
610+
io.Copy(buf, direct.Stdout)
611+
}()
612+
613+
// adm user in the alpine image has a uid of 3 and gid of 4.
614+
container, err := client.NewContainer(ctx, id,
615+
withNewSnapshot(id, image),
616+
WithNewSpec(withImageConfig(image), WithUserID(3), WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")),
617+
)
618+
if err != nil {
619+
t.Error(err)
620+
return
621+
}
622+
defer container.Delete(ctx, WithSnapshotCleanup)
623+
624+
task, err := container.NewTask(ctx, direct.IOCreate)
625+
if err != nil {
626+
t.Error(err)
627+
return
628+
}
629+
defer task.Delete(ctx)
630+
631+
statusC, err := task.Wait(ctx)
632+
if err != nil {
633+
t.Error(err)
634+
return
635+
}
636+
637+
if err := task.Start(ctx); err != nil {
638+
t.Error(err)
639+
return
640+
}
641+
<-statusC
642+
643+
wg.Wait()
644+
645+
output := strings.TrimSuffix(buf.String(), "\n")
646+
if output != "3:4" {
647+
t.Errorf("expected uid:gid to be 3:4, but received %q", output)
648+
}
649+
}

spec_opts_unix.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,6 @@ func WithImageConfig(i Image) SpecOpts {
9494
return fmt.Errorf("unknown image config media type %s", ic.MediaType)
9595
}
9696
s.Process.Env = append(s.Process.Env, config.Env...)
97-
var (
98-
uid, gid uint32
99-
)
10097
cmd := config.Cmd
10198
s.Process.Args = append(config.Entrypoint, cmd...)
10299
if config.User != "" {
@@ -111,22 +108,24 @@ func WithImageConfig(i Image) SpecOpts {
111108
}
112109
return err
113110
}
114-
uid, gid = uint32(v), uint32(v)
111+
if err := WithUserID(uint32(v))(ctx, client, c, s); err != nil {
112+
return err
113+
}
115114
case 2:
116115
v, err := strconv.ParseUint(parts[0], 0, 10)
117116
if err != nil {
118117
return err
119118
}
120-
uid = uint32(v)
119+
uid := uint32(v)
121120
if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil {
122121
return err
123122
}
124-
gid = uint32(v)
123+
gid := uint32(v)
124+
s.Process.User.UID, s.Process.User.GID = uid, gid
125125
default:
126126
return fmt.Errorf("invalid USER value %s", config.User)
127127
}
128128
}
129-
s.Process.User.UID, s.Process.User.GID = uid, gid
130129
cwd := config.WorkingDir
131130
if cwd == "" {
132131
cwd = "/"
@@ -287,18 +286,66 @@ func WithNamespacedCgroup() SpecOpts {
287286
}
288287
}
289288

290-
// WithUserIDs allows the UID and GID for the Process to be set
291-
func WithUserIDs(uid, gid uint32) SpecOpts {
289+
// WithUidGid allows the UID and GID for the Process to be set
290+
func WithUidGid(uid, gid uint32) SpecOpts {
292291
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
293292
s.Process.User.UID = uid
294293
s.Process.User.GID = gid
295294
return nil
296295
}
297296
}
298297

298+
// WithUserID sets the correct UID and GID for the container based
299+
// on the image's /etc/passwd contents. If uid is not found in
300+
// /etc/passwd, it sets uid but leaves gid 0, and not returns error.
301+
func WithUserID(uid uint32) SpecOpts {
302+
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
303+
if c.Snapshotter == "" {
304+
return errors.Errorf("no snapshotter set for container")
305+
}
306+
if c.RootFS == "" {
307+
return errors.Errorf("rootfs not created for container")
308+
}
309+
snapshotter := client.SnapshotService(c.Snapshotter)
310+
mounts, err := snapshotter.Mounts(ctx, c.RootFS)
311+
if err != nil {
312+
return err
313+
}
314+
root, err := ioutil.TempDir("", "ctd-username")
315+
if err != nil {
316+
return err
317+
}
318+
defer os.RemoveAll(root)
319+
for _, m := range mounts {
320+
if err := m.Mount(root); err != nil {
321+
return err
322+
}
323+
}
324+
defer unix.Unmount(root, 0)
325+
f, err := os.Open(filepath.Join(root, "/etc/passwd"))
326+
if err != nil {
327+
return err
328+
}
329+
defer f.Close()
330+
users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
331+
return u.Uid == int(uid)
332+
})
333+
if err != nil {
334+
return err
335+
}
336+
if len(users) == 0 {
337+
s.Process.User.UID = uid
338+
return nil
339+
}
340+
u := users[0]
341+
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
342+
return nil
343+
}
344+
}
345+
299346
// WithUsername sets the correct UID and GID for the container
300-
// based on the the image's /etc/passwd contents.
301-
// id is the snapshot id that is used
347+
// based on the the image's /etc/passwd contents. If the username
348+
// is not found in /etc/passwd, it returns error.
302349
func WithUsername(username string) SpecOpts {
303350
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
304351
if c.Snapshotter == "" {

0 commit comments

Comments
 (0)