Add oci.WithUser helper function.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
84a7b9c115
commit
45b0045593
@ -475,6 +475,80 @@ func TestContainerUsername(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("UserNameAndGroupName", func(t *testing.T) { testContainerUser(t, "squid:squid", "31:31") })
|
||||||
|
t.Run("UserIDAndGroupName", func(t *testing.T) { testContainerUser(t, "1001:squid", "1001:31") })
|
||||||
|
t.Run("UserNameAndGroupID", func(t *testing.T) { testContainerUser(t, "squid:1002", "31:1002") })
|
||||||
|
t.Run("UserIDAndGroupID", func(t *testing.T) { testContainerUser(t, "1001:1002", "1001:1002") })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testContainerUser(t *testing.T, userstr, expectedOutput string) {
|
||||||
|
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.Fatal(err)
|
||||||
|
}
|
||||||
|
direct, err := newDirectIO(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer direct.Delete()
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
buf = bytes.NewBuffer(nil)
|
||||||
|
)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
io.Copy(buf, direct.Stdout)
|
||||||
|
}()
|
||||||
|
|
||||||
|
container, err := client.NewContainer(ctx, id,
|
||||||
|
WithNewSnapshot(id, image),
|
||||||
|
WithNewSpec(oci.WithImageConfig(image), oci.WithUser(userstr), oci.WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer container.Delete(ctx, WithSnapshotCleanup)
|
||||||
|
|
||||||
|
task, err := container.NewTask(ctx, direct.IOCreate)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
|
statusC, err := task.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := task.Start(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
<-statusC
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
output := strings.TrimSuffix(buf.String(), "\n")
|
||||||
|
if output != expectedOutput {
|
||||||
|
t.Errorf("expected uid:gid to be %q, but received %q", expectedOutput, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContainerAttachProcess(t *testing.T) {
|
func TestContainerAttachProcess(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -118,32 +118,7 @@ func WithImageConfig(image Image) SpecOpts {
|
|||||||
}
|
}
|
||||||
s.Process.Cwd = cwd
|
s.Process.Cwd = cwd
|
||||||
if config.User != "" {
|
if config.User != "" {
|
||||||
// According to OCI Image Spec v1.0.0, the following are valid for Linux:
|
return WithUser(config.User)(ctx, client, c, s)
|
||||||
// user, uid, user:group, uid:gid, uid:group, user:gid
|
|
||||||
parts := strings.Split(config.User, ":")
|
|
||||||
switch len(parts) {
|
|
||||||
case 1:
|
|
||||||
v, err := strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
// if we cannot parse as a uint they try to see if it is a username
|
|
||||||
return WithUsername(config.User)(ctx, client, c, s)
|
|
||||||
}
|
|
||||||
return WithUserID(uint32(v))(ctx, client, c, s)
|
|
||||||
case 2:
|
|
||||||
// TODO: support username and groupname
|
|
||||||
v, err := strconv.Atoi(parts[0])
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "parse uid %s", parts[0])
|
|
||||||
}
|
|
||||||
uid := uint32(v)
|
|
||||||
if v, err = strconv.Atoi(parts[1]); err != nil {
|
|
||||||
return errors.Wrapf(err, "parse gid %s", parts[1])
|
|
||||||
}
|
|
||||||
gid := uint32(v)
|
|
||||||
s.Process.User.UID, s.Process.User.GID = uid, gid
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid USER value %s", config.User)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -259,6 +234,82 @@ func WithNamespacedCgroup() SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithUser accepts a valid user string in OCI Image Spec v1.0.0:
|
||||||
|
// user, uid, user:group, uid:gid, uid:group, user:gid
|
||||||
|
// and set the correct UID and GID for container.
|
||||||
|
func WithUser(userstr string) SpecOpts {
|
||||||
|
return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error {
|
||||||
|
parts := strings.Split(userstr, ":")
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
v, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
// if we cannot parse as a uint they try to see if it is a username
|
||||||
|
return WithUsername(userstr)(ctx, client, c, s)
|
||||||
|
}
|
||||||
|
return WithUserID(uint32(v))(ctx, client, c, s)
|
||||||
|
case 2:
|
||||||
|
var username, groupname string
|
||||||
|
var uid, gid uint32
|
||||||
|
v, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
username = parts[0]
|
||||||
|
} else {
|
||||||
|
uid = uint32(v)
|
||||||
|
}
|
||||||
|
if v, err = strconv.Atoi(parts[1]); err != nil {
|
||||||
|
groupname = parts[1]
|
||||||
|
} else {
|
||||||
|
gid = uint32(v)
|
||||||
|
}
|
||||||
|
if username == "" && groupname == "" {
|
||||||
|
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f := func(root string) error {
|
||||||
|
if username != "" {
|
||||||
|
uid, _, err = getUIDGIDFromPath(root, func(u user.User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if groupname != "" {
|
||||||
|
gid, err = getGIDFromPath(root, func(g user.Group) bool {
|
||||||
|
return g.Name == groupname
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Process.User.UID, s.Process.User.GID = uid, gid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Snapshotter == "" && c.SnapshotKey == "" {
|
||||||
|
if !isRootfsAbs(s.Root.Path) {
|
||||||
|
return errors.New("rootfs absolute path is required")
|
||||||
|
}
|
||||||
|
return f(s.Root.Path)
|
||||||
|
}
|
||||||
|
if c.Snapshotter == "" {
|
||||||
|
return errors.New("no snapshotter set for container")
|
||||||
|
}
|
||||||
|
if c.SnapshotKey == "" {
|
||||||
|
return errors.New("rootfs snapshot not created for container")
|
||||||
|
}
|
||||||
|
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||||
|
mounts, err := snapshotter.Mounts(ctx, c.SnapshotKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mount.WithTempMount(ctx, mounts, f)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid USER value %s", userstr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithUIDGID allows the UID and GID for the Process to be set
|
// WithUIDGID allows the UID and GID for the Process to be set
|
||||||
func WithUIDGID(uid, gid uint32) SpecOpts {
|
func WithUIDGID(uid, gid uint32) SpecOpts {
|
||||||
return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
|
return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
@ -398,12 +449,7 @@ func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint3
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
f, err := os.Open(ppath)
|
users, err := user.ParsePasswdFileFilter(ppath, filter)
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
users, err := user.ParsePasswdFilter(f, filter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@ -414,6 +460,24 @@ func getUIDGIDFromPath(root string, filter func(user.User) bool) (uid, gid uint3
|
|||||||
return uint32(u.Uid), uint32(u.Gid), nil
|
return uint32(u.Uid), uint32(u.Gid), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errNoGroupsFound = errors.New("no groups found")
|
||||||
|
|
||||||
|
func getGIDFromPath(root string, filter func(user.Group) bool) (gid uint32, err error) {
|
||||||
|
gpath, err := fs.RootPath(root, "/etc/group")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
groups, err := user.ParseGroupFileFilter(gpath, filter)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return 0, errNoGroupsFound
|
||||||
|
}
|
||||||
|
g := groups[0]
|
||||||
|
return uint32(g.Gid), nil
|
||||||
|
}
|
||||||
|
|
||||||
func isRootfsAbs(root string) bool {
|
func isRootfsAbs(root string) bool {
|
||||||
return filepath.IsAbs(root)
|
return filepath.IsAbs(root)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user