github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/pkg/idtools/idtools_unix_test.go (about)

     1  // +build !windows
     2  
     3  package idtools // import "github.com/docker/docker/pkg/idtools"
     4  
     5  import (
     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/v3/assert"
    15  	is "gotest.tools/v3/assert/cmp"
    16  	"gotest.tools/v3/skip"
    17  )
    18  
    19  const (
    20  	tempUser = "tempuser"
    21  )
    22  
    23  type node struct {
    24  	uid int
    25  	gid int
    26  }
    27  
    28  func 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  
    89  func 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  
   130  func 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  
   177  func 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  
   190  func 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  
   218  func 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  
   236  func delUser(t *testing.T, name string) {
   237  	_, err := execCmd("userdel", name)
   238  	assert.Check(t, err)
   239  }
   240  
   241  func 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  
   250  dockremap: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  
   269  func 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  
   301  func 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  
   315  func 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  	idMapping, err := NewIdentityMapping(tempUser.Username)
   325  	assert.Check(t, err)
   326  
   327  	rootUID, rootGID, err := GetRootUIDGID(idMapping.UIDs(), idMapping.GIDs())
   328  	assert.Check(t, err)
   329  
   330  	dirName, err := ioutil.TempDir("", "mkdirall")
   331  	assert.Check(t, err, "Couldn't create temp directory")
   332  	defer os.RemoveAll(dirName)
   333  
   334  	err = MkdirAllAndChown(dirName, 0700, Identity{UID: rootUID, GID: rootGID})
   335  	assert.Check(t, err, "Couldn't change ownership of file path. Got error")
   336  	assert.Check(t, CanAccess(dirName, idMapping.RootPair()), fmt.Sprintf("Unable to access %s directory with user UID:%d and GID:%d", dirName, rootUID, rootGID))
   337  }
   338  
   339  func TestLookupUserAndGroup(t *testing.T) {
   340  	RequiresRoot(t)
   341  	uid, gid, err := AddNamespaceRangesUser(tempUser)
   342  	assert.Check(t, err)
   343  	defer delUser(t, tempUser)
   344  
   345  	fetchedUser, err := LookupUser(tempUser)
   346  	assert.Check(t, err)
   347  
   348  	fetchedUserByID, err := LookupUID(uid)
   349  	assert.Check(t, err)
   350  	assert.Check(t, is.DeepEqual(fetchedUserByID, fetchedUser))
   351  
   352  	fetchedGroup, err := LookupGroup(tempUser)
   353  	assert.Check(t, err)
   354  
   355  	fetchedGroupByID, err := LookupGID(gid)
   356  	assert.Check(t, err)
   357  	assert.Check(t, is.DeepEqual(fetchedGroupByID, fetchedGroup))
   358  }
   359  
   360  func TestLookupUserAndGroupThatDoesNotExist(t *testing.T) {
   361  	fakeUser := "fakeuser"
   362  	_, err := LookupUser(fakeUser)
   363  	assert.Check(t, is.Error(err, "getent unable to find entry \""+fakeUser+"\" in passwd database"))
   364  
   365  	_, err = LookupUID(-1)
   366  	assert.Check(t, is.ErrorContains(err, ""))
   367  
   368  	fakeGroup := "fakegroup"
   369  	_, err = LookupGroup(fakeGroup)
   370  	assert.Check(t, is.Error(err, "getent unable to find entry \""+fakeGroup+"\" in group database"))
   371  
   372  	_, err = LookupGID(-1)
   373  	assert.Check(t, is.ErrorContains(err, ""))
   374  }
   375  
   376  // TestMkdirIsNotDir checks that mkdirAs() function (used by MkdirAll...)
   377  // returns a correct error in case a directory which it is about to create
   378  // already exists but is a file (rather than a directory).
   379  func TestMkdirIsNotDir(t *testing.T) {
   380  	file, err := ioutil.TempFile("", t.Name())
   381  	if err != nil {
   382  		t.Fatalf("Couldn't create temp dir: %v", err)
   383  	}
   384  	defer os.Remove(file.Name())
   385  
   386  	err = mkdirAs(file.Name(), 0755, Identity{UID: 0, GID: 0}, false, false)
   387  	assert.Check(t, is.Error(err, "mkdir "+file.Name()+": not a directory"))
   388  }
   389  
   390  func RequiresRoot(t *testing.T) {
   391  	skip.If(t, os.Getuid() != 0, "skipping test that requires root")
   392  }