From b50b5ca3fb52160e64dc0e5f6fbda9e5955c6bc0 Mon Sep 17 00:00:00 2001 From: Lantao Liu Date: Fri, 30 Mar 2018 19:45:35 +0000 Subject: [PATCH] Add RunAsGroup support. Signed-off-by: Lantao Liu --- pkg/server/container_create.go | 39 +++++- pkg/server/sandbox_run.go | 12 +- .../containerd/oci/spec_opts_unix.go | 128 +++++++++++++----- 3 files changed, 141 insertions(+), 38 deletions(-) diff --git a/pkg/server/container_create.go b/pkg/server/container_create.go index be9a79890..c4f730d0d 100644 --- a/pkg/server/container_create.go +++ b/pkg/server/container_create.go @@ -19,6 +19,7 @@ package server import ( "os" "path/filepath" + "strconv" "strings" "time" @@ -219,11 +220,16 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta // Set container username. This could only be done by containerd, because it needs // access to the container rootfs. Pass user name to containerd, and let it overwrite // the spec for us. - if uid := securityContext.GetRunAsUser(); uid != nil { - specOpts = append(specOpts, oci.WithUserID(uint32(uid.GetValue()))) + userstr, err := generateUserString( + securityContext.GetRunAsUsername(), + securityContext.GetRunAsUser(), + securityContext.GetRunAsGroup(), + ) + if err != nil { + return nil, errors.Wrap(err, "failed to generate user string") } - if username := securityContext.GetRunAsUsername(); username != "" { - specOpts = append(specOpts, oci.WithUsername(username)) + if userstr != "" { + specOpts = append(specOpts, oci.WithUser(userstr)) } apparmorSpecOpts, err := generateApparmorSpecOpts( @@ -884,3 +890,28 @@ func ensureSharedOrSlave(path string, lookupMount func(string) (mount.Info, erro } return errors.Errorf("path %q is mounted on %q but it is not a shared or slave mount", path, mountInfo.Mountpoint) } + +// generateUserString generates valid user string based on OCI Image Spec v1.0.0. +// TODO(random-liu): Add group name support in CRI. +func generateUserString(username string, uid, gid *runtime.Int64Value) (string, error) { + var userstr, groupstr string + if uid != nil { + userstr = strconv.FormatInt(uid.GetValue(), 10) + } + if username != "" { + userstr = username + } + if gid != nil { + groupstr = strconv.FormatInt(gid.GetValue(), 10) + } + if userstr == "" { + if groupstr != "" { + return "", errors.Errorf("user group %q is specified without user", groupstr) + } + return "", nil + } + if groupstr != "" { + userstr = userstr + ":" + groupstr + } + return userstr, nil +} diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index e27ec8bc1..cfa085835 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -145,8 +145,16 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox logrus.Debugf("Sandbox container spec: %+v", spec) var specOpts []oci.SpecOpts - if uid := securityContext.GetRunAsUser(); uid != nil { - specOpts = append(specOpts, oci.WithUserID(uint32(uid.GetValue()))) + userstr, err := generateUserString( + "", + securityContext.GetRunAsUser(), + securityContext.GetRunAsGroup(), + ) + if err != nil { + return nil, errors.Wrap(err, "failed to generate user string") + } + if userstr != "" { + specOpts = append(specOpts, oci.WithUser(userstr)) } seccompSpecOpts, err := generateSeccompSpecOpts( diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go b/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go index 87d313add..e8ce21d52 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go @@ -118,32 +118,7 @@ func WithImageConfig(image Image) SpecOpts { } s.Process.Cwd = cwd if config.User != "" { - // According to OCI Image Spec v1.0.0, the following are valid for Linux: - // 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 WithUser(config.User)(ctx, client, c, s) } 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 func WithUIDGID(uid, gid uint32) SpecOpts { 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 { return 0, 0, err } - f, err := os.Open(ppath) - if err != nil { - return 0, 0, err - } - defer f.Close() - users, err := user.ParsePasswdFilter(f, filter) + users, err := user.ParsePasswdFileFilter(ppath, filter) if err != nil { 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 } +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 { return filepath.IsAbs(root) }