Teach "Run" to dig user IDs out of containers

When we have cgo, use fgetpwent() to try to look up user IDs and primary
GIDs in containers.  If that fails for any reason (or if we don't have
cgo), fall back to doing what we were doing before (i.e., trying to look
up the information on the host).

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #63
Approved by: rhatdan
This commit is contained in:
Nalin Dahyabhai 2017-04-04 17:31:02 -04:00 committed by Atomic Bot
parent 933a4a1107
commit b1bb73e01c
5 changed files with 286 additions and 27 deletions

20
run.go
View File

@ -82,14 +82,6 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if err != nil {
return err
}
if options.User != "" {
user, err = getUser(options.User)
} else {
user, err = getUser(image.Config.User)
}
if err != nil {
return err
}
g := generate.New()
if image.OS != "" {
@ -98,8 +90,6 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if image.Architecture != "" {
g.SetPlatformArch(image.Architecture)
}
g.SetProcessUID(user.UID)
g.SetProcessGID(user.GID)
for _, envSpec := range append(image.Config.Env, options.Env...) {
env := strings.SplitN(envSpec, "=", 2)
if len(env) > 1 {
@ -149,6 +139,16 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if !options.NetworkDisabled {
g.RemoveLinuxNamespace("network")
}
if options.User != "" {
user, err = getUser(mountPoint, options.User)
} else {
user, err = getUser(mountPoint, image.Config.User)
}
if err != nil {
return err
}
g.SetProcessUID(user.UID)
g.SetProcessGID(user.GID)
spec := g.Spec()
if spec.Process.Cwd == "" {
spec.Process.Cwd = DefaultWorkingDir

View File

@ -22,3 +22,76 @@ load helpers
buildah unmount $cid
buildah rm $cid
}
@test "run-user" {
if ! which runc ; then
skip
fi
eval $(go env)
echo CGO_ENABLED=${CGO_ENABLED}
if test "$CGO_ENABLED" -ne 1; then
skip
fi
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
root=$(buildah mount $cid)
testuser=jimbo
testgroup=jimbogroup
testuid=$RANDOM
testgid=$RANDOM
testgroupid=$RANDOM
echo "$testuser:x:$testuid:$testgid:Jimbo Jenkins:/home/$testuser:/bin/sh" >> $root/etc/passwd
echo "$testgroup:x:$testgroupid:" >> $root/etc/group
buildah config $cid -u ""
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = 0 ]
run buildah --debug=false run -- $cid id -g
[ "$output" = 0 ]
buildah config $cid -u ${testuser}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgid ]
buildah config $cid -u ${testuid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgid ]
buildah config $cid -u ${testuser}:${testgroup}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgroupid ]
buildah config $cid -u ${testuid}:${testgroup}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgroupid ]
buildah config $cid -u ${testuser}:${testgroupid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgroupid ]
buildah config $cid -u ${testuid}:${testgroupid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$output" = $testgroupid ]
buildah unmount $cid
buildah rm $cid
}

79
user.go
View File

@ -1,33 +1,78 @@
package buildah
import (
"fmt"
"os/user"
"strconv"
"strings"
"github.com/opencontainers/runtime-spec/specs-go"
)
// TODO: we should doing these lookups using data that's actually in the container.
func getUser(username string) (specs.User, error) {
if username == "" {
func getUser(rootdir, userspec string) (specs.User, error) {
var gid64 uint64
var gerr error = user.UnknownGroupError("error looking up group")
spec := strings.SplitN(userspec, ":", 2)
userspec = spec[0]
groupspec := ""
if userspec == "" {
return specs.User{}, nil
}
runuser, err := user.Lookup(username)
if err != nil {
return specs.User{}, err
if len(spec) > 1 {
groupspec = spec[1]
}
uid, err := strconv.ParseUint(runuser.Uid, 10, 32)
if err != nil {
return specs.User{}, nil
uid64, uerr := strconv.ParseUint(userspec, 10, 32)
if uerr == nil && groupspec == "" {
// We parsed the user name as a number, and there's no group
// component, so we need to look up the user's primary GID.
name := ""
name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
if gerr == nil {
userspec = name
} else {
if userrec, err := user.LookupId(userspec); err == nil {
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
userspec = userrec.Name
}
}
}
gid, err := strconv.ParseUint(runuser.Gid, 10, 32)
if err != nil {
return specs.User{}, nil
if uerr != nil {
uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
gerr = uerr
}
u := specs.User{
UID: uint32(uid),
GID: uint32(gid),
Username: username,
if uerr != nil {
if userrec, err := user.Lookup(userspec); err == nil {
uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
}
}
return u, nil
if groupspec != "" {
gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
if gerr != nil {
gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
}
if gerr != nil {
if group, err := user.LookupGroup(groupspec); err == nil {
gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
}
}
}
if uerr == nil && gerr == nil {
u := specs.User{
UID: uint32(uid64),
GID: uint32(gid64),
Username: userspec,
}
return u, nil
}
err := fmt.Errorf("%v determining run uid", uerr)
if uerr == nil {
err = fmt.Errorf("%v determining run gid", gerr)
}
return specs.User{}, err
}

19
user_basic.go Normal file
View File

@ -0,0 +1,19 @@
// +build !cgo !linux
package buildah
import (
"fmt"
)
func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
return 0, 0, fmt.Errorf("user lookup not supported")
}
func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
return 0, fmt.Errorf("group lookup not supported")
}
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
return "", 0, fmt.Errorf("primary group lookup by uid not supported")
}

122
user_unix_cgo.go Normal file
View File

@ -0,0 +1,122 @@
// +build cgo
// +build linux
package buildah
// #include <sys/types.h>
// #include <grp.h>
// #include <pwd.h>
// #include <stdlib.h>
// #include <stdio.h>
// #include <string.h>
// typedef FILE * pFILE;
import "C"
import (
"fmt"
"os/user"
"path/filepath"
"sync"
"syscall"
"unsafe"
)
func fopenContainerFile(rootdir, filename string) (C.pFILE, error) {
var st, lst syscall.Stat_t
ctrfile := filepath.Join(rootdir, filename)
cctrfile := C.CString(ctrfile)
defer C.free(unsafe.Pointer(cctrfile))
mode := C.CString("r")
defer C.free(unsafe.Pointer(mode))
f, err := C.fopen(cctrfile, mode)
if f == nil || err != nil {
return nil, fmt.Errorf("error opening %q: %v", ctrfile, err)
}
if err = syscall.Fstat(int(C.fileno(f)), &st); err != nil {
return nil, fmt.Errorf("fstat(%q): %v", ctrfile, err)
}
if err = syscall.Lstat(ctrfile, &lst); err != nil {
return nil, fmt.Errorf("lstat(%q): %v", ctrfile, err)
}
if st.Dev != lst.Dev || st.Ino != lst.Ino {
return nil, fmt.Errorf("%q is not a regular file")
}
return f, nil
}
var (
lookupUser, lookupGroup sync.Mutex
)
func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
name := C.CString(username)
defer C.free(unsafe.Pointer(name))
f, err := fopenContainerFile(rootdir, "/etc/passwd")
if err != nil {
return 0, 0, err
}
defer C.fclose(f)
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := C.fgetpwent(f)
for pwd != nil {
if C.strcmp(pwd.pw_name, name) != 0 {
pwd = C.fgetpwent(f)
continue
}
return uint64(pwd.pw_uid), uint64(pwd.pw_gid), nil
}
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
}
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
f, err := fopenContainerFile(rootdir, "/etc/passwd")
if err != nil {
return "", 0, err
}
defer C.fclose(f)
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := C.fgetpwent(f)
for pwd != nil {
if uint64(pwd.pw_uid) != userid {
pwd = C.fgetpwent(f)
continue
}
return C.GoString(pwd.pw_name), uint64(pwd.pw_gid), nil
}
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
}
func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
name := C.CString(groupname)
defer C.free(unsafe.Pointer(name))
f, err := fopenContainerFile(rootdir, "/etc/group")
if err != nil {
return 0, err
}
defer C.fclose(f)
lookupGroup.Lock()
defer lookupGroup.Unlock()
grp := C.fgetgrent(f)
for grp != nil {
if C.strcmp(grp.gr_name, name) != 0 {
grp = C.fgetgrent(f)
continue
}
return uint64(grp.gr_gid), nil
}
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
}