1// +build !windows
2
3package idtools // import "github.com/docker/docker/pkg/idtools"
4
5import (
6	"fmt"
7	"io/ioutil"
8	"os"
9	"os/user"
10	"path/filepath"
11	"testing"
12
13	"golang.org/x/sys/unix"
14	"gotest.tools/assert"
15	is "gotest.tools/assert/cmp"
16	"gotest.tools/skip"
17)
18
19const (
20	tempUser = "tempuser"
21)
22
23type node struct {
24	uid int
25	gid int
26}
27
28func TestMkdirAllAndChown(t *testing.T) {
29	RequiresRoot(t)
30	dirName, err := ioutil.TempDir("", "mkdirall")
31	if err != nil {
32		t.Fatalf("Couldn't create temp dir: %v", err)
33	}
34	defer os.RemoveAll(dirName)
35
36	testTree := map[string]node{
37		"usr":              {0, 0},
38		"usr/bin":          {0, 0},
39		"lib":              {33, 33},
40		"lib/x86_64":       {45, 45},
41		"lib/x86_64/share": {1, 1},
42	}
43
44	if err := buildTree(dirName, testTree); err != nil {
45		t.Fatal(err)
46	}
47
48	// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
49	if err := MkdirAllAndChown(filepath.Join(dirName, "usr", "share"), 0755, Identity{UID: 99, GID: 99}); err != nil {
50		t.Fatal(err)
51	}
52	testTree["usr/share"] = node{99, 99}
53	verifyTree, err := readTree(dirName, "")
54	if err != nil {
55		t.Fatal(err)
56	}
57	if err := compareTrees(testTree, verifyTree); err != nil {
58		t.Fatal(err)
59	}
60
61	// test 2-deep new directories--both should be owned by the uid/gid pair
62	if err := MkdirAllAndChown(filepath.Join(dirName, "lib", "some", "other"), 0755, Identity{UID: 101, GID: 101}); err != nil {
63		t.Fatal(err)
64	}
65	testTree["lib/some"] = node{101, 101}
66	testTree["lib/some/other"] = node{101, 101}
67	verifyTree, err = readTree(dirName, "")
68	if err != nil {
69		t.Fatal(err)
70	}
71	if err := compareTrees(testTree, verifyTree); err != nil {
72		t.Fatal(err)
73	}
74
75	// test a directory that already exists; should be chowned, but nothing else
76	if err := MkdirAllAndChown(filepath.Join(dirName, "usr"), 0755, Identity{UID: 102, GID: 102}); err != nil {
77		t.Fatal(err)
78	}
79	testTree["usr"] = node{102, 102}
80	verifyTree, err = readTree(dirName, "")
81	if err != nil {
82		t.Fatal(err)
83	}
84	if err := compareTrees(testTree, verifyTree); err != nil {
85		t.Fatal(err)
86	}
87}
88
89func TestMkdirAllAndChownNew(t *testing.T) {
90	RequiresRoot(t)
91	dirName, err := ioutil.TempDir("", "mkdirnew")
92	assert.NilError(t, err)
93	defer os.RemoveAll(dirName)
94
95	testTree := map[string]node{
96		"usr":              {0, 0},
97		"usr/bin":          {0, 0},
98		"lib":              {33, 33},
99		"lib/x86_64":       {45, 45},
100		"lib/x86_64/share": {1, 1},
101	}
102	assert.NilError(t, buildTree(dirName, testTree))
103
104	// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
105	err = MkdirAllAndChownNew(filepath.Join(dirName, "usr", "share"), 0755, Identity{UID: 99, GID: 99})
106	assert.NilError(t, err)
107
108	testTree["usr/share"] = node{99, 99}
109	verifyTree, err := readTree(dirName, "")
110	assert.NilError(t, err)
111	assert.NilError(t, compareTrees(testTree, verifyTree))
112
113	// test 2-deep new directories--both should be owned by the uid/gid pair
114	err = MkdirAllAndChownNew(filepath.Join(dirName, "lib", "some", "other"), 0755, Identity{UID: 101, GID: 101})
115	assert.NilError(t, err)
116	testTree["lib/some"] = node{101, 101}
117	testTree["lib/some/other"] = node{101, 101}
118	verifyTree, err = readTree(dirName, "")
119	assert.NilError(t, err)
120	assert.NilError(t, compareTrees(testTree, verifyTree))
121
122	// test a directory that already exists; should NOT be chowned
123	err = MkdirAllAndChownNew(filepath.Join(dirName, "usr"), 0755, Identity{UID: 102, GID: 102})
124	assert.NilError(t, err)
125	verifyTree, err = readTree(dirName, "")
126	assert.NilError(t, err)
127	assert.NilError(t, compareTrees(testTree, verifyTree))
128}
129
130func TestMkdirAndChown(t *testing.T) {
131	RequiresRoot(t)
132	dirName, err := ioutil.TempDir("", "mkdir")
133	if err != nil {
134		t.Fatalf("Couldn't create temp dir: %v", err)
135	}
136	defer os.RemoveAll(dirName)
137
138	testTree := map[string]node{
139		"usr": {0, 0},
140	}
141	if err := buildTree(dirName, testTree); err != nil {
142		t.Fatal(err)
143	}
144
145	// test a directory that already exists; should just chown to the requested uid/gid
146	if err := MkdirAndChown(filepath.Join(dirName, "usr"), 0755, Identity{UID: 99, GID: 99}); err != nil {
147		t.Fatal(err)
148	}
149	testTree["usr"] = node{99, 99}
150	verifyTree, err := readTree(dirName, "")
151	if err != nil {
152		t.Fatal(err)
153	}
154	if err := compareTrees(testTree, verifyTree); err != nil {
155		t.Fatal(err)
156	}
157
158	// create a subdir under a dir which doesn't exist--should fail
159	if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, Identity{UID: 102, GID: 102}); err == nil {
160		t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed")
161	}
162
163	// create a subdir under an existing dir; should only change the ownership of the new subdir
164	if err := MkdirAndChown(filepath.Join(dirName, "usr", "bin"), 0755, Identity{UID: 102, GID: 102}); err != nil {
165		t.Fatal(err)
166	}
167	testTree["usr/bin"] = node{102, 102}
168	verifyTree, err = readTree(dirName, "")
169	if err != nil {
170		t.Fatal(err)
171	}
172	if err := compareTrees(testTree, verifyTree); err != nil {
173		t.Fatal(err)
174	}
175}
176
177func buildTree(base string, tree map[string]node) error {
178	for path, node := range tree {
179		fullPath := filepath.Join(base, path)
180		if err := os.MkdirAll(fullPath, 0755); err != nil {
181			return fmt.Errorf("Couldn't create path: %s; error: %v", fullPath, err)
182		}
183		if err := os.Chown(fullPath, node.uid, node.gid); err != nil {
184			return fmt.Errorf("Couldn't chown path: %s; error: %v", fullPath, err)
185		}
186	}
187	return nil
188}
189
190func readTree(base, root string) (map[string]node, error) {
191	tree := make(map[string]node)
192
193	dirInfos, err := ioutil.ReadDir(base)
194	if err != nil {
195		return nil, fmt.Errorf("Couldn't read directory entries for %q: %v", base, err)
196	}
197
198	for _, info := range dirInfos {
199		s := &unix.Stat_t{}
200		if err := unix.Stat(filepath.Join(base, info.Name()), s); err != nil {
201			return nil, fmt.Errorf("Can't stat file %q: %v", filepath.Join(base, info.Name()), err)
202		}
203		tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)}
204		if info.IsDir() {
205			// read the subdirectory
206			subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name()))
207			if err != nil {
208				return nil, err
209			}
210			for path, nodeinfo := range subtree {
211				tree[path] = nodeinfo
212			}
213		}
214	}
215	return tree, nil
216}
217
218func compareTrees(left, right map[string]node) error {
219	if len(left) != len(right) {
220		return fmt.Errorf("Trees aren't the same size")
221	}
222	for path, nodeLeft := range left {
223		if nodeRight, ok := right[path]; ok {
224			if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid {
225				// mismatch
226				return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path,
227					nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid)
228			}
229			continue
230		}
231		return fmt.Errorf("right tree didn't contain path %q", path)
232	}
233	return nil
234}
235
236func delUser(t *testing.T, name string) {
237	_, err := execCmd("userdel", name)
238	assert.Check(t, err)
239}
240
241func TestParseSubidFileWithNewlinesAndComments(t *testing.T) {
242	tmpDir, err := ioutil.TempDir("", "parsesubid")
243	if err != nil {
244		t.Fatal(err)
245	}
246	fnamePath := filepath.Join(tmpDir, "testsubuid")
247	fcontent := `tss:100000:65536
248# empty default subuid/subgid file
249
250dockremap:231072:65536`
251	if err := ioutil.WriteFile(fnamePath, []byte(fcontent), 0644); err != nil {
252		t.Fatal(err)
253	}
254	ranges, err := parseSubidFile(fnamePath, "dockremap")
255	if err != nil {
256		t.Fatal(err)
257	}
258	if len(ranges) != 1 {
259		t.Fatalf("wanted 1 element in ranges, got %d instead", len(ranges))
260	}
261	if ranges[0].Start != 231072 {
262		t.Fatalf("wanted 231072, got %d instead", ranges[0].Start)
263	}
264	if ranges[0].Length != 65536 {
265		t.Fatalf("wanted 65536, got %d instead", ranges[0].Length)
266	}
267}
268
269func TestGetRootUIDGID(t *testing.T) {
270	uidMap := []IDMap{
271		{
272			ContainerID: 0,
273			HostID:      os.Getuid(),
274			Size:        1,
275		},
276	}
277	gidMap := []IDMap{
278		{
279			ContainerID: 0,
280			HostID:      os.Getgid(),
281			Size:        1,
282		},
283	}
284
285	uid, gid, err := GetRootUIDGID(uidMap, gidMap)
286	assert.Check(t, err)
287	assert.Check(t, is.Equal(os.Geteuid(), uid))
288	assert.Check(t, is.Equal(os.Getegid(), gid))
289
290	uidMapError := []IDMap{
291		{
292			ContainerID: 1,
293			HostID:      os.Getuid(),
294			Size:        1,
295		},
296	}
297	_, _, err = GetRootUIDGID(uidMapError, gidMap)
298	assert.Check(t, is.Error(err, "Container ID 0 cannot be mapped to a host ID"))
299}
300
301func TestToContainer(t *testing.T) {
302	uidMap := []IDMap{
303		{
304			ContainerID: 2,
305			HostID:      2,
306			Size:        1,
307		},
308	}
309
310	containerID, err := toContainer(2, uidMap)
311	assert.Check(t, err)
312	assert.Check(t, is.Equal(uidMap[0].ContainerID, containerID))
313}
314
315func TestNewIDMappings(t *testing.T) {
316	RequiresRoot(t)
317	_, _, err := AddNamespaceRangesUser(tempUser)
318	assert.Check(t, err)
319	defer delUser(t, tempUser)
320
321	tempUser, err := user.Lookup(tempUser)
322	assert.Check(t, err)
323
324	gids, err := tempUser.GroupIds()
325	assert.Check(t, err)
326	group, err := user.LookupGroupId(string(gids[0]))
327	assert.Check(t, err)
328
329	idMapping, err := NewIdentityMapping(tempUser.Username, group.Name)
330	assert.Check(t, err)
331
332	rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDs(), idMapping.GIDs())
333	assert.Check(t, err)
334
335	dirName, err := ioutil.TempDir("", "mkdirall")
336	assert.Check(t, err, "Couldn't create temp directory")
337	defer os.RemoveAll(dirName)
338
339	err = MkdirAllAndChown(dirName, 0700, Identity{UID: rootUID, GID: rootGID})
340	assert.Check(t, err, "Couldn't change ownership of file path. Got error")
341	assert.Check(t, CanAccess(dirName, idMapping.RootPair()), fmt.Sprintf("Unable to access %s directory with user UID:%d and GID:%d", dirName, rootUID, rootGID))
342}
343
344func TestLookupUserAndGroup(t *testing.T) {
345	RequiresRoot(t)
346	uid, gid, err := AddNamespaceRangesUser(tempUser)
347	assert.Check(t, err)
348	defer delUser(t, tempUser)
349
350	fetchedUser, err := LookupUser(tempUser)
351	assert.Check(t, err)
352
353	fetchedUserByID, err := LookupUID(uid)
354	assert.Check(t, err)
355	assert.Check(t, is.DeepEqual(fetchedUserByID, fetchedUser))
356
357	fetchedGroup, err := LookupGroup(tempUser)
358	assert.Check(t, err)
359
360	fetchedGroupByID, err := LookupGID(gid)
361	assert.Check(t, err)
362	assert.Check(t, is.DeepEqual(fetchedGroupByID, fetchedGroup))
363}
364
365func TestLookupUserAndGroupThatDoesNotExist(t *testing.T) {
366	fakeUser := "fakeuser"
367	_, err := LookupUser(fakeUser)
368	assert.Check(t, is.Error(err, "getent unable to find entry \""+fakeUser+"\" in passwd database"))
369
370	_, err = LookupUID(-1)
371	assert.Check(t, is.ErrorContains(err, ""))
372
373	fakeGroup := "fakegroup"
374	_, err = LookupGroup(fakeGroup)
375	assert.Check(t, is.Error(err, "getent unable to find entry \""+fakeGroup+"\" in group database"))
376
377	_, err = LookupGID(-1)
378	assert.Check(t, is.ErrorContains(err, ""))
379}
380
381// TestMkdirIsNotDir checks that mkdirAs() function (used by MkdirAll...)
382// returns a correct error in case a directory which it is about to create
383// already exists but is a file (rather than a directory).
384func TestMkdirIsNotDir(t *testing.T) {
385	file, err := ioutil.TempFile("", t.Name())
386	if err != nil {
387		t.Fatalf("Couldn't create temp dir: %v", err)
388	}
389	defer os.Remove(file.Name())
390
391	err = mkdirAs(file.Name(), 0755, Identity{UID: 0, GID: 0}, false, false)
392	assert.Check(t, is.Error(err, "mkdir "+file.Name()+": not a directory"))
393}
394
395func RequiresRoot(t *testing.T) {
396	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
397}
398