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 |
+} |