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:
parent
933a4a1107
commit
b1bb73e01c
20
run.go
20
run.go
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
79
user.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
Loading…
Reference in New Issue