From 6cb8392be9cdc5bf44436a092dd88b39968ffc7d Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Thu, 20 Oct 2016 14:43:42 -0500 Subject: [PATCH] Add support for looking up user/groups via `getent` When processing the --userns-remap flag, add the capability to call out to `getent` if the user and group information is not found via local file parsing code already in libcontainer/user. Signed-off-by: Phil Estes --- daemon/daemon_unix.go | 13 ++-- pkg/idtools/idtools_unix.go | 122 ++++++++++++++++++++++++++++++ pkg/idtools/usergroupadd_linux.go | 24 ------ pkg/idtools/utils_unix.go | 32 ++++++++ 4 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 pkg/idtools/utils_unix.go diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 2ea6be25be..534a32b66c 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -39,7 +39,6 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/label" rsystem "github.com/opencontainers/runc/libcontainer/system" - "github.com/opencontainers/runc/libcontainer/user" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/vishvananda/netlink" ) @@ -894,7 +893,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) { if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil { // must be a uid; take it as valid userID = int(uid) - luser, err := user.LookupUid(userID) + luser, err := idtools.LookupUID(userID) if err != nil { return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err) } @@ -902,7 +901,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) { if len(idparts) == 1 { // if the uid was numeric and no gid was specified, take the uid as the gid groupID = userID - lgrp, err := user.LookupGid(groupID) + lgrp, err := idtools.LookupGID(groupID) if err != nil { return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err) } @@ -915,7 +914,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) { if lookupName == defaultIDSpecifier { lookupName = defaultRemappedID } - luser, err := user.LookupUser(lookupName) + luser, err := idtools.LookupUser(lookupName) if err != nil && idparts[0] != defaultIDSpecifier { // error if the name requested isn't the special "dockremap" ID return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err) @@ -932,7 +931,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) { username = luser.Name if len(idparts) == 1 { // we only have a string username, and no group specified; look up gid from username as group - group, err := user.LookupGroup(lookupName) + group, err := idtools.LookupGroup(lookupName) if err != nil { return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err) } @@ -947,14 +946,14 @@ func parseRemappedRoot(usergrp string) (string, string, error) { if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil { // must be a gid, take it as valid groupID = int(gid) - lgrp, err := user.LookupGid(groupID) + lgrp, err := idtools.LookupGID(groupID) if err != nil { return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err) } groupname = lgrp.Name } else { // not a number; attempt a lookup - if _, err := user.LookupGroup(idparts[1]); err != nil { + if _, err := idtools.LookupGroup(idparts[1]); err != nil { return "", "", fmt.Errorf("Error during groupname lookup for %q: %v", idparts[1], err) } groupname = idparts[1] diff --git a/pkg/idtools/idtools_unix.go b/pkg/idtools/idtools_unix.go index dcbb305f69..a911163b39 100644 --- a/pkg/idtools/idtools_unix.go +++ b/pkg/idtools/idtools_unix.go @@ -3,10 +3,22 @@ package idtools import ( + "bytes" + "fmt" + "io" "os" "path/filepath" + "strings" + "sync" + "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/system" + "github.com/opencontainers/runc/libcontainer/user" +) + +var ( + entOnce sync.Once + getentCmd string ) func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { @@ -84,3 +96,113 @@ func accessible(isOwner, isGroup bool, perms os.FileMode) bool { } return false } + +// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username, +// followed by a call to `getent` for supporting host configured non-files passwd and group dbs +func LookupUser(username string) (user.User, error) { + // first try a local system files lookup using existing capabilities + usr, err := user.LookupUser(username) + if err == nil { + return usr, nil + } + // local files lookup failed; attempt to call `getent` to query configured passwd dbs + usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username)) + if err != nil { + return user.User{}, err + } + return usr, nil +} + +// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid, +// followed by a call to `getent` for supporting host configured non-files passwd and group dbs +func LookupUID(uid int) (user.User, error) { + // first try a local system files lookup using existing capabilities + usr, err := user.LookupUid(uid) + if err == nil { + return usr, nil + } + // local files lookup failed; attempt to call `getent` to query configured passwd dbs + return getentUser(fmt.Sprintf("%s %d", "passwd", uid)) +} + +func getentUser(args string) (user.User, error) { + reader, err := callGetent(args) + if err != nil { + return user.User{}, err + } + users, err := user.ParsePasswd(reader) + if err != nil { + return user.User{}, err + } + if len(users) == 0 { + return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1]) + } + return users[0], nil +} + +// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, +// followed by a call to `getent` for supporting host configured non-files passwd and group dbs +func LookupGroup(groupname string) (user.Group, error) { + // first try a local system files lookup using existing capabilities + group, err := user.LookupGroup(groupname) + if err == nil { + return group, nil + } + // local files lookup failed; attempt to call `getent` to query configured group dbs + return getentGroup(fmt.Sprintf("%s %s", "group", groupname)) +} + +// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID, +// followed by a call to `getent` for supporting host configured non-files passwd and group dbs +func LookupGID(gid int) (user.Group, error) { + // first try a local system files lookup using existing capabilities + group, err := user.LookupGid(gid) + if err == nil { + return group, nil + } + // local files lookup failed; attempt to call `getent` to query configured group dbs + return getentGroup(fmt.Sprintf("%s %d", "group", gid)) +} + +func getentGroup(args string) (user.Group, error) { + reader, err := callGetent(args) + if err != nil { + return user.Group{}, err + } + groups, err := user.ParseGroup(reader) + if err != nil { + return user.Group{}, err + } + if len(groups) == 0 { + return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1]) + } + return groups[0], nil +} + +func callGetent(args string) (io.Reader, error) { + entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") }) + // if no `getent` command on host, can't do anything else + if getentCmd == "" { + return nil, fmt.Errorf("") + } + out, err := execCmd(getentCmd, args) + if err != nil { + exitCode, errC := cmd.GetExitCode(err) + if errC != nil { + return nil, err + } + switch exitCode { + case 1: + return nil, fmt.Errorf("getent reported invalid parameters/database unknown") + case 2: + terms := strings.Split(args, " ") + return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0]) + case 3: + return nil, fmt.Errorf("getent database doesn't support enumeration") + default: + return nil, err + } + + } + return bytes.NewReader(out), nil +} diff --git a/pkg/idtools/usergroupadd_linux.go b/pkg/idtools/usergroupadd_linux.go index 4a4aaed04d..9da7975e2c 100644 --- a/pkg/idtools/usergroupadd_linux.go +++ b/pkg/idtools/usergroupadd_linux.go @@ -2,8 +2,6 @@ package idtools import ( "fmt" - "os/exec" - "path/filepath" "regexp" "sort" "strconv" @@ -33,23 +31,6 @@ var ( userMod = "usermod" ) -func resolveBinary(binname string) (string, error) { - binaryPath, err := exec.LookPath(binname) - if err != nil { - return "", err - } - resolvedPath, err := filepath.EvalSymlinks(binaryPath) - if err != nil { - return "", err - } - //only return no error if the final resolved binary basename - //matches what was searched for - if filepath.Base(resolvedPath) == binname { - return resolvedPath, nil - } - return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath) -} - // AddNamespaceRangesUser takes a username and uses the standard system // utility to create a system user/group pair used to hold the // /etc/sub{uid,gid} ranges which will be used for user namespace @@ -181,8 +162,3 @@ func wouldOverlap(arange subIDRange, ID int) bool { } return false } - -func execCmd(cmd, args string) ([]byte, error) { - execCmd := exec.Command(cmd, strings.Split(args, " ")...) - return execCmd.CombinedOutput() -} diff --git a/pkg/idtools/utils_unix.go b/pkg/idtools/utils_unix.go new file mode 100644 index 0000000000..9703ecbd9d --- /dev/null +++ b/pkg/idtools/utils_unix.go @@ -0,0 +1,32 @@ +// +build !windows + +package idtools + +import ( + "fmt" + "os/exec" + "path/filepath" + "strings" +) + +func resolveBinary(binname string) (string, error) { + binaryPath, err := exec.LookPath(binname) + if err != nil { + return "", err + } + resolvedPath, err := filepath.EvalSymlinks(binaryPath) + if err != nil { + return "", err + } + //only return no error if the final resolved binary basename + //matches what was searched for + if filepath.Base(resolvedPath) == binname { + return resolvedPath, nil + } + return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath) +} + +func execCmd(cmd, args string) ([]byte, error) { + execCmd := exec.Command(cmd, strings.Split(args, " ")...) + return execCmd.CombinedOutput() +}