Selaa lähdekoodia

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 <estesp@linux.vnet.ibm.com>
Phil Estes 8 vuotta sitten
vanhempi
commit
6cb8392be9

+ 6 - 7
daemon/daemon_unix.go

@@ -39,7 +39,6 @@ import (
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/label"
 	"github.com/opencontainers/runc/libcontainer/label"
 	rsystem "github.com/opencontainers/runc/libcontainer/system"
 	rsystem "github.com/opencontainers/runc/libcontainer/system"
-	"github.com/opencontainers/runc/libcontainer/user"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/vishvananda/netlink"
 	"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 {
 	if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
 		// must be a uid; take it as valid
 		// must be a uid; take it as valid
 		userID = int(uid)
 		userID = int(uid)
-		luser, err := user.LookupUid(userID)
+		luser, err := idtools.LookupUID(userID)
 		if err != nil {
 		if err != nil {
 			return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
 			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 len(idparts) == 1 {
 			// if the uid was numeric and no gid was specified, take the uid as the gid
 			// if the uid was numeric and no gid was specified, take the uid as the gid
 			groupID = userID
 			groupID = userID
-			lgrp, err := user.LookupGid(groupID)
+			lgrp, err := idtools.LookupGID(groupID)
 			if err != nil {
 			if err != nil {
 				return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
 				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 {
 		if lookupName == defaultIDSpecifier {
 			lookupName = defaultRemappedID
 			lookupName = defaultRemappedID
 		}
 		}
-		luser, err := user.LookupUser(lookupName)
+		luser, err := idtools.LookupUser(lookupName)
 		if err != nil && idparts[0] != defaultIDSpecifier {
 		if err != nil && idparts[0] != defaultIDSpecifier {
 			// error if the name requested isn't the special "dockremap" ID
 			// error if the name requested isn't the special "dockremap" ID
 			return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
 			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
 		username = luser.Name
 		if len(idparts) == 1 {
 		if len(idparts) == 1 {
 			// we only have a string username, and no group specified; look up gid from username as group
 			// 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 {
 			if err != nil {
 				return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
 				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 {
 		if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
 			// must be a gid, take it as valid
 			// must be a gid, take it as valid
 			groupID = int(gid)
 			groupID = int(gid)
-			lgrp, err := user.LookupGid(groupID)
+			lgrp, err := idtools.LookupGID(groupID)
 			if err != nil {
 			if err != nil {
 				return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
 				return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
 			}
 			}
 			groupname = lgrp.Name
 			groupname = lgrp.Name
 		} else {
 		} else {
 			// not a number; attempt a lookup
 			// 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)
 				return "", "", fmt.Errorf("Error during groupname lookup for %q: %v", idparts[1], err)
 			}
 			}
 			groupname = idparts[1]
 			groupname = idparts[1]

+ 122 - 0
pkg/idtools/idtools_unix.go

@@ -3,10 +3,22 @@
 package idtools
 package idtools
 
 
 import (
 import (
+	"bytes"
+	"fmt"
+	"io"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
+	"strings"
+	"sync"
 
 
+	"github.com/docker/docker/pkg/integration/cmd"
 	"github.com/docker/docker/pkg/system"
 	"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 {
 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
 	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
+}

+ 0 - 24
pkg/idtools/usergroupadd_linux.go

@@ -2,8 +2,6 @@ package idtools
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"os/exec"
-	"path/filepath"
 	"regexp"
 	"regexp"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
@@ -33,23 +31,6 @@ var (
 	userMod           = "usermod"
 	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
 // AddNamespaceRangesUser takes a username and uses the standard system
 // utility to create a system user/group pair used to hold the
 // utility to create a system user/group pair used to hold the
 // /etc/sub{uid,gid} ranges which will be used for user namespace
 // /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
 	return false
 }
 }
-
-func execCmd(cmd, args string) ([]byte, error) {
-	execCmd := exec.Command(cmd, strings.Split(args, " ")...)
-	return execCmd.CombinedOutput()
-}

+ 32 - 0
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()
+}