Add WithUsername spec opt
This option will mount and inspect the /etc/passwd file of an image to get the uid/gid of a user. Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
		| @@ -8,6 +8,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| 	"testing" | ||||
| @@ -364,3 +365,80 @@ func TestContainerAttach(t *testing.T) { | ||||
| 		t.Errorf("expected output %q but received %q", expected, output) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestContainerUsername(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() | ||||
|  | ||||
| 	if runtime.GOOS != "windows" { | ||||
| 		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) | ||||
| 	}() | ||||
|  | ||||
| 	// squid user in the alpine image has a uid of 31 | ||||
| 	container, err := client.NewContainer(ctx, id, | ||||
| 		withNewSnapshot(id, image), | ||||
| 		WithNewSpec(withImageConfig(image), WithUsername("squid"), WithProcessArgs("id", "-u")), | ||||
| 	) | ||||
| 	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 != "31" { | ||||
| 		t.Errorf("expected squid uid to be 31 but received %q", output) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -6,10 +6,14 @@ import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
|  | ||||
| 	"github.com/containerd/containerd/containers" | ||||
| 	"github.com/containerd/containerd/content" | ||||
| 	"github.com/containerd/containerd/images" | ||||
| @@ -17,6 +21,7 @@ import ( | ||||
| 	"github.com/containerd/containerd/typeurl" | ||||
| 	"github.com/opencontainers/image-spec/identity" | ||||
| 	"github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/opencontainers/runc/libcontainer/user" | ||||
| 	specs "github.com/opencontainers/runtime-spec/specs-go" | ||||
| ) | ||||
|  | ||||
| @@ -100,6 +105,10 @@ func WithImageConfig(i Image) SpecOpts { | ||||
| 			case 1: | ||||
| 				v, err := strconv.ParseUint(parts[0], 0, 10) | ||||
| 				if err != nil { | ||||
| 					// if we cannot parse as a uint they try to see if it is a username | ||||
| 					if err := WithUsername(config.User)(ctx, client, c, s); err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 					return err | ||||
| 				} | ||||
| 				uid, gid = uint32(v), uint32(v) | ||||
| @@ -319,3 +328,50 @@ func WithUserIDs(uid, gid uint32) SpecOpts { | ||||
| 		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 | ||||
| func WithUsername(username string) SpecOpts { | ||||
| 	return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { | ||||
| 		if c.Snapshotter == "" { | ||||
| 			return fmt.Errorf("no snapshotter set for container") | ||||
| 		} | ||||
| 		if c.RootFS == "" { | ||||
| 			return fmt.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.Name == username | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if len(users) == 0 { | ||||
| 			return fmt.Errorf("no users found for %s", username) | ||||
| 		} | ||||
| 		u := users[0] | ||||
| 		s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Crosby
					Michael Crosby