389 lines
13 KiB
Diff
389 lines
13 KiB
Diff
|
|
From 1dd7f13dfbc1dd377eabace0239b1c05cd60b144 Mon Sep 17 00:00:00 2001
|
||
|
|
From: baude <bbaude@redhat.com>
|
||
|
|
Date: Thu, 25 Oct 2018 13:39:25 -0500
|
||
|
|
Subject: [PATCH] get user and group information using securejoin and runc's
|
||
|
|
user library
|
||
|
|
|
||
|
|
for the purposes of performance and security, we use securejoin to contstruct
|
||
|
|
the root fs's path so that symlinks are what they appear to be and no pointing
|
||
|
|
to something naughty.
|
||
|
|
|
||
|
|
then instead of chrooting to parse /etc/passwd|/etc/group, we now use the runc user/group
|
||
|
|
methods which saves us quite a bit of performance.
|
||
|
|
|
||
|
|
Signed-off-by: baude <bbaude@redhat.com>
|
||
|
|
---
|
||
|
|
libpod/container_api.go | 8 +-
|
||
|
|
libpod/container_internal.go | 56 ++++++-----
|
||
|
|
libpod/container_internal_linux.go | 44 ++------
|
||
|
|
pkg/lookup/lookup.go | 156 +++++++++++++++++++++++++++++
|
||
|
|
4 files changed, 201 insertions(+), 63 deletions(-)
|
||
|
|
create mode 100644 pkg/lookup/lookup.go
|
||
|
|
|
||
|
|
diff --git a/libpod/container_api.go b/libpod/container_api.go
|
||
|
|
index 41a131ea212..83f93cf9eb6 100644
|
||
|
|
--- a/libpod/container_api.go
|
||
|
|
+++ b/libpod/container_api.go
|
||
|
|
@@ -10,8 +10,8 @@ import (
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/containers/libpod/libpod/driver"
|
||
|
|
- "github.com/containers/libpod/pkg/chrootuser"
|
||
|
|
"github.com/containers/libpod/pkg/inspect"
|
||
|
|
+ "github.com/containers/libpod/pkg/lookup"
|
||
|
|
"github.com/containers/storage/pkg/stringid"
|
||
|
|
"github.com/docker/docker/daemon/caps"
|
||
|
|
"github.com/pkg/errors"
|
||
|
|
@@ -292,13 +292,13 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
|
||
|
|
// the host
|
||
|
|
hostUser := ""
|
||
|
|
if user != "" {
|
||
|
|
- uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, user)
|
||
|
|
+ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil)
|
||
|
|
if err != nil {
|
||
|
|
- return errors.Wrapf(err, "error getting user to launch exec session as")
|
||
|
|
+ return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// runc expects user formatted as uid:gid
|
||
|
|
- hostUser = fmt.Sprintf("%d:%d", uid, gid)
|
||
|
|
+ hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.Gid)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate exec session ID
|
||
|
|
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
|
||
|
|
index 2af216358b4..d928c4aedd5 100644
|
||
|
|
--- a/libpod/container_internal.go
|
||
|
|
+++ b/libpod/container_internal.go
|
||
|
|
@@ -12,9 +13,9 @@ import (
|
||
|
|
"strings"
|
||
|
|
"syscall"
|
||
|
|
|
||
|
|
- "github.com/containers/libpod/pkg/chrootuser"
|
||
|
|
"github.com/containers/libpod/pkg/hooks"
|
||
|
|
"github.com/containers/libpod/pkg/hooks/exec"
|
||
|
|
+ "github.com/containers/libpod/pkg/lookup"
|
||
|
|
"github.com/containers/libpod/pkg/resolvconf"
|
||
|
|
"github.com/containers/libpod/pkg/rootless"
|
||
|
|
"github.com/containers/libpod/pkg/secrets"
|
||
|
|
@@ -1094,6 +1095,7 @@ func (c *Container) generateHosts() (str
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error {
|
||
|
|
+ var uid, gid int
|
||
|
|
mountPoint := c.state.Mountpoint
|
||
|
|
if !c.state.Mounted {
|
||
|
|
return errors.Wrapf(ErrInternal, "container is not mounted")
|
||
|
|
@@ -1117,6 +1119,18 @@ func (c *Container) addLocalVolumes(ctx
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
+ if c.config.User != "" {
|
||
|
|
+ if !c.state.Mounted {
|
||
|
|
+ return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
||
|
|
+ }
|
||
|
|
+ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return err
|
||
|
|
+ }
|
||
|
|
+ uid = execUser.Uid
|
||
|
|
+ gid = execUser.Gid
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
for k := range imageData.ContainerConfig.Volumes {
|
||
|
|
mount := spec.Mount{
|
||
|
|
Destination: k,
|
||
|
|
@@ -1129,27 +1143,13 @@ func (c *Container) addLocalVolumes(ctx
|
||
|
|
volumePath := filepath.Join(c.config.StaticDir, "volumes", k)
|
||
|
|
srcPath := filepath.Join(mountPoint, k)
|
||
|
|
|
||
|
|
- var (
|
||
|
|
- uid uint32
|
||
|
|
- gid uint32
|
||
|
|
- )
|
||
|
|
- if c.config.User != "" {
|
||
|
|
- if !c.state.Mounted {
|
||
|
|
- return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
||
|
|
- }
|
||
|
|
- uid, gid, err = chrootuser.GetUser(c.state.Mountpoint, c.config.User)
|
||
|
|
- if err != nil {
|
||
|
|
- return err
|
||
|
|
- }
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
if _, err := os.Stat(srcPath); os.IsNotExist(err) {
|
||
|
|
logrus.Infof("Volume image mount point %s does not exist in root FS, need to create it", k)
|
||
|
|
if err = os.MkdirAll(srcPath, 0755); err != nil {
|
||
|
|
return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
- if err = os.Chown(srcPath, int(uid), int(gid)); err != nil {
|
||
|
|
+ if err = os.Chown(srcPath, uid, gid); err != nil {
|
||
|
|
return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@@ -1159,7 +1159,7 @@ func (c *Container) addLocalVolumes(ctx
|
||
|
|
return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
- if err = os.Chown(volumePath, int(uid), int(gid)); err != nil {
|
||
|
|
+ if err = os.Chown(volumePath, uid, gid); err != nil {
|
||
|
|
return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID)
|
||
|
|
}
|
||
|
|
|
||
|
|
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
|
||
|
|
index 0a1784ba740..7bf2c71cac6 100644
|
||
|
|
--- a/libpod/container_internal_linux.go
|
||
|
|
+++ b/libpod/container_internal_linux.go
|
||
|
|
@@ -17,11 +17,9 @@ import (
|
||
|
|
|
||
|
|
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
||
|
|
crioAnnotations "github.com/containers/libpod/pkg/annotations"
|
||
|
|
- "github.com/containers/libpod/pkg/chrootuser"
|
||
|
|
+ "github.com/containers/libpod/pkg/lookup"
|
||
|
|
"github.com/containers/libpod/pkg/rootless"
|
||
|
|
"github.com/containers/storage/pkg/idtools"
|
||
|
|
- "github.com/cyphar/filepath-securejoin"
|
||
|
|
- "github.com/opencontainers/runc/libcontainer/user"
|
||
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||
|
|
"github.com/opencontainers/runtime-tools/generate"
|
||
|
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||
|
|
@@ -135,6 +133,10 @@ func (c *Container) cleanupNetwork() error {
|
||
|
|
// Generate spec for a container
|
||
|
|
// Accepts a map of the container's dependencies
|
||
|
|
func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||
|
|
+ execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
g := generate.NewFromSpec(c.config.Spec)
|
||
|
|
|
||
|
|
// If network namespace was requested, add it now
|
||
|
|
@@ -188,7 +190,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- var err error
|
||
|
|
if !rootless.IsRootless() {
|
||
|
|
if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil {
|
||
|
|
return nil, errors.Wrapf(err, "error setting up OCI Hooks")
|
||
|
|
@@ -206,13 +207,9 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||
|
|
if !c.state.Mounted {
|
||
|
|
return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
||
|
|
}
|
||
|
|
- uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
// User and Group must go together
|
||
|
|
- g.SetProcessUID(uid)
|
||
|
|
- g.SetProcessGID(gid)
|
||
|
|
+ g.SetProcessUID(uint32(execUser.Uid))
|
||
|
|
+ g.SetProcessGID(uint32(execUser.Gid))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add addition groups if c.config.GroupAdd is not empty
|
||
|
|
@@ -220,11 +217,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||
|
|
if !c.state.Mounted {
|
||
|
|
return nil, errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to add additional groups", c.ID())
|
||
|
|
}
|
||
|
|
- for _, group := range c.config.Groups {
|
||
|
|
- gid, err := chrootuser.GetGroup(c.state.Mountpoint, group)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
+ gids, _ := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, nil)
|
||
|
|
+ for _, gid := range gids {
|
||
|
|
g.AddProcessAdditionalGid(gid)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@@ -237,26 +231,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||
|
|
|
||
|
|
// Look up and add groups the user belongs to, if a group wasn't directly specified
|
||
|
|
if !rootless.IsRootless() && !strings.Contains(c.config.User, ":") {
|
||
|
|
- var groupDest, passwdDest string
|
||
|
|
- defaultExecUser := user.ExecUser{
|
||
|
|
- Uid: 0,
|
||
|
|
- Gid: 0,
|
||
|
|
- Home: "/",
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty
|
||
|
|
- if groupDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/group"); err != nil {
|
||
|
|
- logrus.Debug(err)
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
- if passwdDest, err = securejoin.SecureJoin(c.state.Mountpoint, "/etc/passwd"); err != nil {
|
||
|
|
- logrus.Debug(err)
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
- execUser, err := user.GetExecUserPath(c.config.User, &defaultExecUser, passwdDest, groupDest)
|
||
|
|
- if err != nil {
|
||
|
|
- return nil, err
|
||
|
|
- }
|
||
|
|
for _, gid := range execUser.Sgids {
|
||
|
|
g.AddProcessAdditionalGid(uint32(gid))
|
||
|
|
}
|
||
|
|
diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go
|
||
|
|
new file mode 100644
|
||
|
|
index 00000000000..b27e2a724bc
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/pkg/lookup/lookup.go
|
||
|
|
@@ -0,0 +1,156 @@
|
||
|
|
+package lookup
|
||
|
|
+
|
||
|
|
+import (
|
||
|
|
+ "github.com/cyphar/filepath-securejoin"
|
||
|
|
+ "github.com/opencontainers/runc/libcontainer/user"
|
||
|
|
+ "github.com/sirupsen/logrus"
|
||
|
|
+ "strconv"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+const (
|
||
|
|
+ etcpasswd = "/etc/passwd"
|
||
|
|
+ etcgroup = "/etc/group"
|
||
|
|
+)
|
||
|
|
+
|
||
|
|
+// Overrides allows you to override defaults in GetUserGroupInfo
|
||
|
|
+type Overrides struct {
|
||
|
|
+ DefaultUser *user.ExecUser
|
||
|
|
+ ContainerEtcPasswdPath string
|
||
|
|
+ ContainerEtcGroupPath string
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// GetUserGroupInfo takes string forms of the the container's mount path and the container user and
|
||
|
|
+// returns a ExecUser with uid, gid, sgids, and home. And override can be provided for defaults.
|
||
|
|
+func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) {
|
||
|
|
+ var (
|
||
|
|
+ passwdDest, groupDest string
|
||
|
|
+ defaultExecUser *user.ExecUser
|
||
|
|
+ err error
|
||
|
|
+ )
|
||
|
|
+ passwdPath := etcpasswd
|
||
|
|
+ groupPath := etcgroup
|
||
|
|
+
|
||
|
|
+ if override != nil {
|
||
|
|
+ // Check for an override /etc/passwd path
|
||
|
|
+ if override.ContainerEtcPasswdPath != "" {
|
||
|
|
+ passwdPath = override.ContainerEtcPasswdPath
|
||
|
|
+ }
|
||
|
|
+ // Check for an override for /etc/group path
|
||
|
|
+ if override.ContainerEtcGroupPath != "" {
|
||
|
|
+ groupPath = override.ContainerEtcGroupPath
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // Check for an override default user
|
||
|
|
+ if override != nil && override.DefaultUser != nil {
|
||
|
|
+ defaultExecUser = override.DefaultUser
|
||
|
|
+ } else {
|
||
|
|
+ // Define a default container user
|
||
|
|
+ //defaultExecUser = &user.ExecUser{
|
||
|
|
+ // Uid: 0,
|
||
|
|
+ // Gid: 0,
|
||
|
|
+ // Home: "/",
|
||
|
|
+ defaultExecUser = nil
|
||
|
|
+
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty
|
||
|
|
+ if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil {
|
||
|
|
+ logrus.Debug(err)
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil {
|
||
|
|
+ logrus.Debug(err)
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest)
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc
|
||
|
|
+// function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found,
|
||
|
|
+// or the given group data is nil, the id will be returned as-is provided it is in the legal range.
|
||
|
|
+func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) {
|
||
|
|
+ var (
|
||
|
|
+ groupDest string
|
||
|
|
+ err error
|
||
|
|
+ uintgids []uint32
|
||
|
|
+ )
|
||
|
|
+
|
||
|
|
+ groupPath := etcgroup
|
||
|
|
+ if override != nil && override.ContainerEtcGroupPath != "" {
|
||
|
|
+ groupPath = override.ContainerEtcGroupPath
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil {
|
||
|
|
+ logrus.Debug(err)
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ gids, err := user.GetAdditionalGroupsPath(groups, groupDest)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ // For libpod, we want []uint32s
|
||
|
|
+ for _, gid := range gids {
|
||
|
|
+ uintgids = append(uintgids, uint32(gid))
|
||
|
|
+ }
|
||
|
|
+ return uintgids, nil
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// GetUser takes a containermount path and user name or id and returns
|
||
|
|
+// a matching User structure from /etc/passwd. If it cannot locate a user
|
||
|
|
+// with the provided information, an ErrNoPasswdEntries is returned.
|
||
|
|
+func GetUser(containerMount, userIDorName string) (*user.User, error) {
|
||
|
|
+ var inputIsName bool
|
||
|
|
+ uid, err := strconv.Atoi(userIDorName)
|
||
|
|
+ if err != nil {
|
||
|
|
+ inputIsName = true
|
||
|
|
+ }
|
||
|
|
+ passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool {
|
||
|
|
+ if inputIsName {
|
||
|
|
+ return u.Name == userIDorName
|
||
|
|
+ }
|
||
|
|
+ return u.Uid == uid
|
||
|
|
+ })
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ if len(users) > 0 {
|
||
|
|
+ return &users[0], nil
|
||
|
|
+ }
|
||
|
|
+ return nil, user.ErrNoPasswdEntries
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+// GetGroup takes ac ontainermount path and a group name or id and returns
|
||
|
|
+// a match Group struct from /etc/group. if it cannot locate a group,
|
||
|
|
+// an ErrNoGroupEntries error is returned.
|
||
|
|
+func GetGroup(containerMount, groupIDorName string) (*user.Group, error) {
|
||
|
|
+ var inputIsName bool
|
||
|
|
+ gid, err := strconv.Atoi(groupIDorName)
|
||
|
|
+ if err != nil {
|
||
|
|
+ inputIsName = true
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ groupDest, err := securejoin.SecureJoin(containerMount, etcgroup)
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool {
|
||
|
|
+ if inputIsName {
|
||
|
|
+ return g.Name == groupIDorName
|
||
|
|
+ }
|
||
|
|
+ return g.Gid == gid
|
||
|
|
+ })
|
||
|
|
+ if err != nil {
|
||
|
|
+ return nil, err
|
||
|
|
+ }
|
||
|
|
+ if len(groups) > 0 {
|
||
|
|
+ return &groups[0], nil
|
||
|
|
+ }
|
||
|
|
+ return nil, user.ErrNoGroupEntries
|
||
|
|
+}
|