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>
| ... | ... |
@@ -39,7 +39,6 @@ import ( |
| 39 | 39 |
"github.com/opencontainers/runc/libcontainer/cgroups" |
| 40 | 40 |
"github.com/opencontainers/runc/libcontainer/label" |
| 41 | 41 |
rsystem "github.com/opencontainers/runc/libcontainer/system" |
| 42 |
- "github.com/opencontainers/runc/libcontainer/user" |
|
| 43 | 42 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 44 | 43 |
"github.com/vishvananda/netlink" |
| 45 | 44 |
) |
| ... | ... |
@@ -894,7 +893,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
|
| 894 | 894 |
if uid, err := strconv.ParseInt(idparts[0], 10, 32); err == nil {
|
| 895 | 895 |
// must be a uid; take it as valid |
| 896 | 896 |
userID = int(uid) |
| 897 |
- luser, err := user.LookupUid(userID) |
|
| 897 |
+ luser, err := idtools.LookupUID(userID) |
|
| 898 | 898 |
if err != nil {
|
| 899 | 899 |
return "", "", fmt.Errorf("Uid %d has no entry in /etc/passwd: %v", userID, err)
|
| 900 | 900 |
} |
| ... | ... |
@@ -902,7 +901,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
|
| 902 | 902 |
if len(idparts) == 1 {
|
| 903 | 903 |
// if the uid was numeric and no gid was specified, take the uid as the gid |
| 904 | 904 |
groupID = userID |
| 905 |
- lgrp, err := user.LookupGid(groupID) |
|
| 905 |
+ lgrp, err := idtools.LookupGID(groupID) |
|
| 906 | 906 |
if err != nil {
|
| 907 | 907 |
return "", "", fmt.Errorf("Gid %d has no entry in /etc/group: %v", groupID, err)
|
| 908 | 908 |
} |
| ... | ... |
@@ -915,7 +914,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
|
| 915 | 915 |
if lookupName == defaultIDSpecifier {
|
| 916 | 916 |
lookupName = defaultRemappedID |
| 917 | 917 |
} |
| 918 |
- luser, err := user.LookupUser(lookupName) |
|
| 918 |
+ luser, err := idtools.LookupUser(lookupName) |
|
| 919 | 919 |
if err != nil && idparts[0] != defaultIDSpecifier {
|
| 920 | 920 |
// error if the name requested isn't the special "dockremap" ID |
| 921 | 921 |
return "", "", fmt.Errorf("Error during uid lookup for %q: %v", lookupName, err)
|
| ... | ... |
@@ -932,7 +931,7 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
|
| 932 | 932 |
username = luser.Name |
| 933 | 933 |
if len(idparts) == 1 {
|
| 934 | 934 |
// we only have a string username, and no group specified; look up gid from username as group |
| 935 |
- group, err := user.LookupGroup(lookupName) |
|
| 935 |
+ group, err := idtools.LookupGroup(lookupName) |
|
| 936 | 936 |
if err != nil {
|
| 937 | 937 |
return "", "", fmt.Errorf("Error during gid lookup for %q: %v", lookupName, err)
|
| 938 | 938 |
} |
| ... | ... |
@@ -947,14 +946,14 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
|
| 947 | 947 |
if gid, err := strconv.ParseInt(idparts[1], 10, 32); err == nil {
|
| 948 | 948 |
// must be a gid, take it as valid |
| 949 | 949 |
groupID = int(gid) |
| 950 |
- lgrp, err := user.LookupGid(groupID) |
|
| 950 |
+ lgrp, err := idtools.LookupGID(groupID) |
|
| 951 | 951 |
if err != nil {
|
| 952 | 952 |
return "", "", fmt.Errorf("Gid %d has no entry in /etc/passwd: %v", groupID, err)
|
| 953 | 953 |
} |
| 954 | 954 |
groupname = lgrp.Name |
| 955 | 955 |
} else {
|
| 956 | 956 |
// not a number; attempt a lookup |
| 957 |
- if _, err := user.LookupGroup(idparts[1]); err != nil {
|
|
| 957 |
+ if _, err := idtools.LookupGroup(idparts[1]); err != nil {
|
|
| 958 | 958 |
return "", "", fmt.Errorf("Error during groupname lookup for %q: %v", idparts[1], err)
|
| 959 | 959 |
} |
| 960 | 960 |
groupname = idparts[1] |
| ... | ... |
@@ -3,10 +3,22 @@ |
| 3 | 3 |
package idtools |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "bytes" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 6 | 9 |
"os" |
| 7 | 10 |
"path/filepath" |
| 11 |
+ "strings" |
|
| 12 |
+ "sync" |
|
| 8 | 13 |
|
| 14 |
+ "github.com/docker/docker/pkg/integration/cmd" |
|
| 9 | 15 |
"github.com/docker/docker/pkg/system" |
| 16 |
+ "github.com/opencontainers/runc/libcontainer/user" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+var ( |
|
| 20 |
+ entOnce sync.Once |
|
| 21 |
+ getentCmd string |
|
| 10 | 22 |
) |
| 11 | 23 |
|
| 12 | 24 |
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 {
|
| 84 | 84 |
} |
| 85 | 85 |
return false |
| 86 | 86 |
} |
| 87 |
+ |
|
| 88 |
+// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username, |
|
| 89 |
+// followed by a call to `getent` for supporting host configured non-files passwd and group dbs |
|
| 90 |
+func LookupUser(username string) (user.User, error) {
|
|
| 91 |
+ // first try a local system files lookup using existing capabilities |
|
| 92 |
+ usr, err := user.LookupUser(username) |
|
| 93 |
+ if err == nil {
|
|
| 94 |
+ return usr, nil |
|
| 95 |
+ } |
|
| 96 |
+ // local files lookup failed; attempt to call `getent` to query configured passwd dbs |
|
| 97 |
+ usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ return user.User{}, err
|
|
| 100 |
+ } |
|
| 101 |
+ return usr, nil |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid, |
|
| 105 |
+// followed by a call to `getent` for supporting host configured non-files passwd and group dbs |
|
| 106 |
+func LookupUID(uid int) (user.User, error) {
|
|
| 107 |
+ // first try a local system files lookup using existing capabilities |
|
| 108 |
+ usr, err := user.LookupUid(uid) |
|
| 109 |
+ if err == nil {
|
|
| 110 |
+ return usr, nil |
|
| 111 |
+ } |
|
| 112 |
+ // local files lookup failed; attempt to call `getent` to query configured passwd dbs |
|
| 113 |
+ return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
|
| 114 |
+} |
|
| 115 |
+ |
|
| 116 |
+func getentUser(args string) (user.User, error) {
|
|
| 117 |
+ reader, err := callGetent(args) |
|
| 118 |
+ if err != nil {
|
|
| 119 |
+ return user.User{}, err
|
|
| 120 |
+ } |
|
| 121 |
+ users, err := user.ParsePasswd(reader) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ return user.User{}, err
|
|
| 124 |
+ } |
|
| 125 |
+ if len(users) == 0 {
|
|
| 126 |
+ return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
|
| 127 |
+ } |
|
| 128 |
+ return users[0], nil |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, |
|
| 132 |
+// followed by a call to `getent` for supporting host configured non-files passwd and group dbs |
|
| 133 |
+func LookupGroup(groupname string) (user.Group, error) {
|
|
| 134 |
+ // first try a local system files lookup using existing capabilities |
|
| 135 |
+ group, err := user.LookupGroup(groupname) |
|
| 136 |
+ if err == nil {
|
|
| 137 |
+ return group, nil |
|
| 138 |
+ } |
|
| 139 |
+ // local files lookup failed; attempt to call `getent` to query configured group dbs |
|
| 140 |
+ return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
|
| 141 |
+} |
|
| 142 |
+ |
|
| 143 |
+// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID, |
|
| 144 |
+// followed by a call to `getent` for supporting host configured non-files passwd and group dbs |
|
| 145 |
+func LookupGID(gid int) (user.Group, error) {
|
|
| 146 |
+ // first try a local system files lookup using existing capabilities |
|
| 147 |
+ group, err := user.LookupGid(gid) |
|
| 148 |
+ if err == nil {
|
|
| 149 |
+ return group, nil |
|
| 150 |
+ } |
|
| 151 |
+ // local files lookup failed; attempt to call `getent` to query configured group dbs |
|
| 152 |
+ return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+func getentGroup(args string) (user.Group, error) {
|
|
| 156 |
+ reader, err := callGetent(args) |
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ return user.Group{}, err
|
|
| 159 |
+ } |
|
| 160 |
+ groups, err := user.ParseGroup(reader) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return user.Group{}, err
|
|
| 163 |
+ } |
|
| 164 |
+ if len(groups) == 0 {
|
|
| 165 |
+ return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
|
| 166 |
+ } |
|
| 167 |
+ return groups[0], nil |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+func callGetent(args string) (io.Reader, error) {
|
|
| 171 |
+ entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
|
| 172 |
+ // if no `getent` command on host, can't do anything else |
|
| 173 |
+ if getentCmd == "" {
|
|
| 174 |
+ return nil, fmt.Errorf("")
|
|
| 175 |
+ } |
|
| 176 |
+ out, err := execCmd(getentCmd, args) |
|
| 177 |
+ if err != nil {
|
|
| 178 |
+ exitCode, errC := cmd.GetExitCode(err) |
|
| 179 |
+ if errC != nil {
|
|
| 180 |
+ return nil, err |
|
| 181 |
+ } |
|
| 182 |
+ switch exitCode {
|
|
| 183 |
+ case 1: |
|
| 184 |
+ return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
|
| 185 |
+ case 2: |
|
| 186 |
+ terms := strings.Split(args, " ") |
|
| 187 |
+ return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
|
| 188 |
+ case 3: |
|
| 189 |
+ return nil, fmt.Errorf("getent database doesn't support enumeration")
|
|
| 190 |
+ default: |
|
| 191 |
+ return nil, err |
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ } |
|
| 195 |
+ return bytes.NewReader(out), nil |
|
| 196 |
+} |
| ... | ... |
@@ -2,8 +2,6 @@ package idtools |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- "os/exec" |
|
| 6 |
- "path/filepath" |
|
| 7 | 5 |
"regexp" |
| 8 | 6 |
"sort" |
| 9 | 7 |
"strconv" |
| ... | ... |
@@ -33,23 +31,6 @@ var ( |
| 33 | 33 |
userMod = "usermod" |
| 34 | 34 |
) |
| 35 | 35 |
|
| 36 |
-func resolveBinary(binname string) (string, error) {
|
|
| 37 |
- binaryPath, err := exec.LookPath(binname) |
|
| 38 |
- if err != nil {
|
|
| 39 |
- return "", err |
|
| 40 |
- } |
|
| 41 |
- resolvedPath, err := filepath.EvalSymlinks(binaryPath) |
|
| 42 |
- if err != nil {
|
|
| 43 |
- return "", err |
|
| 44 |
- } |
|
| 45 |
- //only return no error if the final resolved binary basename |
|
| 46 |
- //matches what was searched for |
|
| 47 |
- if filepath.Base(resolvedPath) == binname {
|
|
| 48 |
- return resolvedPath, nil |
|
| 49 |
- } |
|
| 50 |
- return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 | 36 |
// AddNamespaceRangesUser takes a username and uses the standard system |
| 54 | 37 |
// utility to create a system user/group pair used to hold the |
| 55 | 38 |
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
| ... | ... |
@@ -181,8 +162,3 @@ func wouldOverlap(arange subIDRange, ID int) bool {
|
| 181 | 181 |
} |
| 182 | 182 |
return false |
| 183 | 183 |
} |
| 184 |
- |
|
| 185 |
-func execCmd(cmd, args string) ([]byte, error) {
|
|
| 186 |
- execCmd := exec.Command(cmd, strings.Split(args, " ")...) |
|
| 187 |
- return execCmd.CombinedOutput() |
|
| 188 |
-} |
| 189 | 184 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package idtools |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "strings" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func resolveBinary(binname string) (string, error) {
|
|
| 12 |
+ binaryPath, err := exec.LookPath(binname) |
|
| 13 |
+ if err != nil {
|
|
| 14 |
+ return "", err |
|
| 15 |
+ } |
|
| 16 |
+ resolvedPath, err := filepath.EvalSymlinks(binaryPath) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ return "", err |
|
| 19 |
+ } |
|
| 20 |
+ //only return no error if the final resolved binary basename |
|
| 21 |
+ //matches what was searched for |
|
| 22 |
+ if filepath.Base(resolvedPath) == binname {
|
|
| 23 |
+ return resolvedPath, nil |
|
| 24 |
+ } |
|
| 25 |
+ return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+func execCmd(cmd, args string) ([]byte, error) {
|
|
| 29 |
+ execCmd := exec.Command(cmd, strings.Split(args, " ")...) |
|
| 30 |
+ return execCmd.CombinedOutput() |
|
| 31 |
+} |