github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/idtools/idtools_unix_test.go (about) 1 // +build !windows 2 3 package idtools // import "github.com/demonoid81/moby/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 gids, err := tempUser.GroupIds() 325 assert.Check(t, err) 326 group, err := user.LookupGroupId(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 344 func 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 365 func 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). 384 func 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 395 func RequiresRoot(t *testing.T) { 396 skip.If(t, os.Getuid() != 0, "skipping test that requires root") 397 }