mirror of
https://github.com/containers/podman
synced 2024-10-21 09:45:32 +00:00
Merge pull request #1715 from baude/getusergroup
get user and group information using securejoin and runc's user library
This commit is contained in:
commit
319a7a7043
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -14,9 +15,9 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/containers/buildah/imagebuildah"
|
||||
"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"
|
||||
|
@ -1029,7 +1030,8 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
|
|||
func (c *Container) generatePasswd() (string, error) {
|
||||
var (
|
||||
groupspec string
|
||||
gid uint32
|
||||
group *user.Group
|
||||
gid int
|
||||
)
|
||||
if c.config.User == "" {
|
||||
return "", nil
|
||||
|
@ -1044,21 +1046,27 @@ func (c *Container) generatePasswd() (string, error) {
|
|||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
// if UID exists inside of container rootfs /etc/passwd then
|
||||
// don't generate passwd
|
||||
if _, _, err := chrootuser.LookupUIDInContainer(c.state.Mountpoint, uid); err == nil {
|
||||
// Lookup the user to see if it exists in the container image
|
||||
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
|
||||
if err != nil && err != user.ErrNoPasswdEntries {
|
||||
return "", err
|
||||
}
|
||||
if err == nil {
|
||||
return "", nil
|
||||
}
|
||||
if err == nil && groupspec != "" {
|
||||
if groupspec != "" {
|
||||
if !c.state.Mounted {
|
||||
return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID())
|
||||
}
|
||||
gid, err = chrootuser.GetGroup(c.state.Mountpoint, groupspec)
|
||||
group, err = lookup.GetGroup(c.state.Mountpoint, groupspec)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "unable to get gid from %s formporary passwd file")
|
||||
if err == user.ErrNoGroupEntries {
|
||||
return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
gid = group.Gid
|
||||
}
|
||||
|
||||
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
|
||||
orig, err := ioutil.ReadFile(originPasswdFile)
|
||||
if err != nil {
|
||||
|
@ -1153,6 +1161,7 @@ func (c *Container) generateHosts() (string, error) {
|
|||
}
|
||||
|
||||
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")
|
||||
|
@ -1176,6 +1185,18 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator)
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -1186,19 +1207,6 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator)
|
|||
continue
|
||||
}
|
||||
volumePath := filepath.Join(c.config.StaticDir, "volumes", 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
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the symlinks are resolved
|
||||
resolvedSymlink, err := imagebuildah.ResolveSymLink(mountPoint, k)
|
||||
|
@ -1218,7 +1226,7 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator)
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
@ -1228,7 +1236,7 @@ func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator)
|
|||
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())
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,10 @@ import (
|
|||
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
crioAnnotations "github.com/containers/libpod/pkg/annotations"
|
||||
"github.com/containers/libpod/pkg/chrootuser"
|
||||
"github.com/containers/libpod/pkg/criu"
|
||||
"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))
|
||||
}
|
||||
|
|
156
pkg/lookup/lookup.go
Normal file
156
pkg/lookup/lookup.go
Normal file
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue