mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #27599 from estesp/getent-path
Add support for looking up user/groups via `getent`
This commit is contained in:
commit
81683e898a
4 changed files with 160 additions and 31 deletions
|
@ -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/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
@ -913,7 +912,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)
|
||||||
}
|
}
|
||||||
|
@ -921,7 +920,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)
|
||||||
}
|
}
|
||||||
|
@ -934,7 +933,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)
|
||||||
|
@ -951,7 +950,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)
|
||||||
}
|
}
|
||||||
|
@ -966,14 +965,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]
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
pkg/idtools/utils_unix.go
Normal file
32
pkg/idtools/utils_unix.go
Normal file
|
@ -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()
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue