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:
parent
c601606f84
commit
f436f4c828
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user