2015-10-14 14:35:48 -04:00
// +build !windows
2018-02-05 16:05:59 -05:00
package idtools // import "github.com/docker/docker/pkg/idtools"
2015-10-14 14:35:48 -04:00
import (
"fmt"
"io/ioutil"
"os"
2017-09-26 03:01:08 -04:00
"os/user"
2015-10-14 14:35:48 -04:00
"path/filepath"
"testing"
2017-05-31 17:36:48 -04:00
2017-09-26 03:01:08 -04:00
"github.com/gotestyourself/gotestyourself/skip"
"github.com/stretchr/testify/assert"
2017-05-31 17:36:48 -04:00
"github.com/stretchr/testify/require"
2017-07-27 03:51:23 -04:00
"golang.org/x/sys/unix"
2015-10-14 14:35:48 -04:00
)
2017-09-26 03:01:08 -04:00
const (
tempUser = "tempuser"
)
2015-10-14 14:35:48 -04:00
type node struct {
uid int
gid int
}
2017-11-18 18:41:09 -05:00
func TestMkdirAllAndChown ( t * testing . T ) {
2017-09-26 03:01:08 -04:00
RequiresRoot ( t )
2015-10-14 14:35:48 -04:00
dirName , err := ioutil . TempDir ( "" , "mkdirall" )
if err != nil {
t . Fatalf ( "Couldn't create temp dir: %v" , err )
}
defer os . RemoveAll ( dirName )
testTree := map [ string ] node {
"usr" : { 0 , 0 } ,
"usr/bin" : { 0 , 0 } ,
"lib" : { 33 , 33 } ,
"lib/x86_64" : { 45 , 45 } ,
"lib/x86_64/share" : { 1 , 1 } ,
}
if err := buildTree ( dirName , testTree ) ; err != nil {
t . Fatal ( err )
}
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
2017-11-18 18:41:09 -05:00
if err := MkdirAllAndChown ( filepath . Join ( dirName , "usr" , "share" ) , 0755 , IDPair { UID : 99 , GID : 99 } ) ; err != nil {
2015-10-14 14:35:48 -04:00
t . Fatal ( err )
}
testTree [ "usr/share" ] = node { 99 , 99 }
verifyTree , err := readTree ( dirName , "" )
if err != nil {
t . Fatal ( err )
}
if err := compareTrees ( testTree , verifyTree ) ; err != nil {
t . Fatal ( err )
}
// test 2-deep new directories--both should be owned by the uid/gid pair
2017-11-18 18:41:09 -05:00
if err := MkdirAllAndChown ( filepath . Join ( dirName , "lib" , "some" , "other" ) , 0755 , IDPair { UID : 101 , GID : 101 } ) ; err != nil {
2015-10-14 14:35:48 -04:00
t . Fatal ( err )
}
testTree [ "lib/some" ] = node { 101 , 101 }
testTree [ "lib/some/other" ] = node { 101 , 101 }
verifyTree , err = readTree ( dirName , "" )
if err != nil {
t . Fatal ( err )
}
if err := compareTrees ( testTree , verifyTree ) ; err != nil {
t . Fatal ( err )
}
// test a directory that already exists; should be chowned, but nothing else
2017-11-18 18:41:09 -05:00
if err := MkdirAllAndChown ( filepath . Join ( dirName , "usr" ) , 0755 , IDPair { UID : 102 , GID : 102 } ) ; err != nil {
2015-10-14 14:35:48 -04:00
t . Fatal ( err )
}
testTree [ "usr" ] = node { 102 , 102 }
verifyTree , err = readTree ( dirName , "" )
if err != nil {
t . Fatal ( err )
}
if err := compareTrees ( testTree , verifyTree ) ; err != nil {
t . Fatal ( err )
}
}
2017-05-31 17:36:48 -04:00
func TestMkdirAllAndChownNew ( t * testing . T ) {
2017-09-26 03:01:08 -04:00
RequiresRoot ( t )
2015-10-14 14:35:48 -04:00
dirName , err := ioutil . TempDir ( "" , "mkdirnew" )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
2015-10-14 14:35:48 -04:00
defer os . RemoveAll ( dirName )
testTree := map [ string ] node {
"usr" : { 0 , 0 } ,
"usr/bin" : { 0 , 0 } ,
"lib" : { 33 , 33 } ,
"lib/x86_64" : { 45 , 45 } ,
"lib/x86_64/share" : { 1 , 1 } ,
}
2017-05-31 17:36:48 -04:00
require . NoError ( t , buildTree ( dirName , testTree ) )
2015-10-14 14:35:48 -04:00
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
2017-11-18 18:41:09 -05:00
err = MkdirAllAndChownNew ( filepath . Join ( dirName , "usr" , "share" ) , 0755 , IDPair { UID : 99 , GID : 99 } )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
2015-10-14 14:35:48 -04:00
testTree [ "usr/share" ] = node { 99 , 99 }
verifyTree , err := readTree ( dirName , "" )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
require . NoError ( t , compareTrees ( testTree , verifyTree ) )
2015-10-14 14:35:48 -04:00
// test 2-deep new directories--both should be owned by the uid/gid pair
2017-11-18 18:41:09 -05:00
err = MkdirAllAndChownNew ( filepath . Join ( dirName , "lib" , "some" , "other" ) , 0755 , IDPair { UID : 101 , GID : 101 } )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
2015-10-14 14:35:48 -04:00
testTree [ "lib/some" ] = node { 101 , 101 }
testTree [ "lib/some/other" ] = node { 101 , 101 }
verifyTree , err = readTree ( dirName , "" )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
require . NoError ( t , compareTrees ( testTree , verifyTree ) )
2015-10-14 14:35:48 -04:00
// test a directory that already exists; should NOT be chowned
2017-11-18 18:41:09 -05:00
err = MkdirAllAndChownNew ( filepath . Join ( dirName , "usr" ) , 0755 , IDPair { UID : 102 , GID : 102 } )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
2015-10-14 14:35:48 -04:00
verifyTree , err = readTree ( dirName , "" )
2017-05-31 17:36:48 -04:00
require . NoError ( t , err )
require . NoError ( t , compareTrees ( testTree , verifyTree ) )
2015-10-14 14:35:48 -04:00
}
2017-11-18 18:41:09 -05:00
func TestMkdirAndChown ( t * testing . T ) {
2017-09-26 03:01:08 -04:00
RequiresRoot ( t )
2015-10-14 14:35:48 -04:00
dirName , err := ioutil . TempDir ( "" , "mkdir" )
if err != nil {
t . Fatalf ( "Couldn't create temp dir: %v" , err )
}
defer os . RemoveAll ( dirName )
testTree := map [ string ] node {
"usr" : { 0 , 0 } ,
}
if err := buildTree ( dirName , testTree ) ; err != nil {
t . Fatal ( err )
}
// test a directory that already exists; should just chown to the requested uid/gid
2017-11-18 18:41:09 -05:00
if err := MkdirAndChown ( filepath . Join ( dirName , "usr" ) , 0755 , IDPair { UID : 99 , GID : 99 } ) ; err != nil {
2015-10-14 14:35:48 -04:00
t . Fatal ( err )
}
testTree [ "usr" ] = node { 99 , 99 }
verifyTree , err := readTree ( dirName , "" )
if err != nil {
t . Fatal ( err )
}
if err := compareTrees ( testTree , verifyTree ) ; err != nil {
t . Fatal ( err )
}
// create a subdir under a dir which doesn't exist--should fail
2017-11-18 18:41:09 -05:00
if err := MkdirAndChown ( filepath . Join ( dirName , "usr" , "bin" , "subdir" ) , 0755 , IDPair { UID : 102 , GID : 102 } ) ; err == nil {
2015-10-14 14:35:48 -04:00
t . Fatalf ( "Trying to create a directory with Mkdir where the parent doesn't exist should have failed" )
}
// create a subdir under an existing dir; should only change the ownership of the new subdir
2017-11-18 18:41:09 -05:00
if err := MkdirAndChown ( filepath . Join ( dirName , "usr" , "bin" ) , 0755 , IDPair { UID : 102 , GID : 102 } ) ; err != nil {
2015-10-14 14:35:48 -04:00
t . Fatal ( err )
}
testTree [ "usr/bin" ] = node { 102 , 102 }
verifyTree , err = readTree ( dirName , "" )
if err != nil {
t . Fatal ( err )
}
if err := compareTrees ( testTree , verifyTree ) ; err != nil {
t . Fatal ( err )
}
}
func buildTree ( base string , tree map [ string ] node ) error {
for path , node := range tree {
fullPath := filepath . Join ( base , path )
if err := os . MkdirAll ( fullPath , 0755 ) ; err != nil {
return fmt . Errorf ( "Couldn't create path: %s; error: %v" , fullPath , err )
}
if err := os . Chown ( fullPath , node . uid , node . gid ) ; err != nil {
return fmt . Errorf ( "Couldn't chown path: %s; error: %v" , fullPath , err )
}
}
return nil
}
func readTree ( base , root string ) ( map [ string ] node , error ) {
tree := make ( map [ string ] node )
dirInfos , err := ioutil . ReadDir ( base )
if err != nil {
return nil , fmt . Errorf ( "Couldn't read directory entries for %q: %v" , base , err )
}
for _ , info := range dirInfos {
2017-07-27 03:51:23 -04:00
s := & unix . Stat_t { }
if err := unix . Stat ( filepath . Join ( base , info . Name ( ) ) , s ) ; err != nil {
2015-10-14 14:35:48 -04:00
return nil , fmt . Errorf ( "Can't stat file %q: %v" , filepath . Join ( base , info . Name ( ) ) , err )
}
tree [ filepath . Join ( root , info . Name ( ) ) ] = node { int ( s . Uid ) , int ( s . Gid ) }
if info . IsDir ( ) {
// read the subdirectory
subtree , err := readTree ( filepath . Join ( base , info . Name ( ) ) , filepath . Join ( root , info . Name ( ) ) )
if err != nil {
return nil , err
}
for path , nodeinfo := range subtree {
tree [ path ] = nodeinfo
}
}
}
return tree , nil
}
func compareTrees ( left , right map [ string ] node ) error {
if len ( left ) != len ( right ) {
return fmt . Errorf ( "Trees aren't the same size" )
}
for path , nodeLeft := range left {
if nodeRight , ok := right [ path ] ; ok {
if nodeRight . uid != nodeLeft . uid || nodeRight . gid != nodeLeft . gid {
// mismatch
return fmt . Errorf ( "mismatched ownership for %q: expected: %d:%d, got: %d:%d" , path ,
nodeLeft . uid , nodeLeft . gid , nodeRight . uid , nodeRight . gid )
}
continue
}
return fmt . Errorf ( "right tree didn't contain path %q" , path )
}
return nil
}
2016-02-26 08:49:43 -05:00
2017-09-26 03:01:08 -04:00
func delUser ( t * testing . T , name string ) {
_ , err := execCmd ( "userdel" , name )
assert . NoError ( t , err )
}
2016-02-26 08:49:43 -05:00
func TestParseSubidFileWithNewlinesAndComments ( t * testing . T ) {
tmpDir , err := ioutil . TempDir ( "" , "parsesubid" )
if err != nil {
t . Fatal ( err )
}
fnamePath := filepath . Join ( tmpDir , "testsubuid" )
fcontent := ` tss : 100000 : 65536
# empty default subuid / subgid file
dockremap : 231072 : 65536 `
if err := ioutil . WriteFile ( fnamePath , [ ] byte ( fcontent ) , 0644 ) ; err != nil {
t . Fatal ( err )
}
ranges , err := parseSubidFile ( fnamePath , "dockremap" )
if err != nil {
t . Fatal ( err )
}
if len ( ranges ) != 1 {
t . Fatalf ( "wanted 1 element in ranges, got %d instead" , len ( ranges ) )
}
if ranges [ 0 ] . Start != 231072 {
t . Fatalf ( "wanted 231072, got %d instead" , ranges [ 0 ] . Start )
}
if ranges [ 0 ] . Length != 65536 {
t . Fatalf ( "wanted 65536, got %d instead" , ranges [ 0 ] . Length )
}
}
2017-09-26 03:01:08 -04:00
func TestGetRootUIDGID ( t * testing . T ) {
uidMap := [ ] IDMap {
{
ContainerID : 0 ,
HostID : os . Getuid ( ) ,
Size : 1 ,
} ,
}
gidMap := [ ] IDMap {
{
ContainerID : 0 ,
HostID : os . Getgid ( ) ,
Size : 1 ,
} ,
}
uid , gid , err := GetRootUIDGID ( uidMap , gidMap )
assert . NoError ( t , err )
assert . Equal ( t , os . Getegid ( ) , uid )
assert . Equal ( t , os . Getegid ( ) , gid )
uidMapError := [ ] IDMap {
{
ContainerID : 1 ,
HostID : os . Getuid ( ) ,
Size : 1 ,
} ,
}
_ , _ , err = GetRootUIDGID ( uidMapError , gidMap )
assert . EqualError ( t , err , "Container ID 0 cannot be mapped to a host ID" )
}
func TestToContainer ( t * testing . T ) {
uidMap := [ ] IDMap {
{
ContainerID : 2 ,
HostID : 2 ,
Size : 1 ,
} ,
}
containerID , err := toContainer ( 2 , uidMap )
assert . NoError ( t , err )
assert . Equal ( t , uidMap [ 0 ] . ContainerID , containerID )
}
func TestNewIDMappings ( t * testing . T ) {
RequiresRoot ( t )
_ , _ , err := AddNamespaceRangesUser ( tempUser )
assert . NoError ( t , err )
defer delUser ( t , tempUser )
tempUser , err := user . Lookup ( tempUser )
assert . NoError ( t , err )
gids , err := tempUser . GroupIds ( )
assert . NoError ( t , err )
group , err := user . LookupGroupId ( string ( gids [ 0 ] ) )
assert . NoError ( t , err )
idMappings , err := NewIDMappings ( tempUser . Username , group . Name )
assert . NoError ( t , err )
rootUID , rootGID , err := GetRootUIDGID ( idMappings . UIDs ( ) , idMappings . GIDs ( ) )
assert . NoError ( t , err )
dirName , err := ioutil . TempDir ( "" , "mkdirall" )
assert . NoError ( t , err , "Couldn't create temp directory" )
defer os . RemoveAll ( dirName )
2017-11-18 18:41:09 -05:00
err = MkdirAllAndChown ( dirName , 0700 , IDPair { UID : rootUID , GID : rootGID } )
2017-09-26 03:01:08 -04:00
assert . NoError ( t , err , "Couldn't change ownership of file path. Got error" )
assert . True ( t , CanAccess ( dirName , idMappings . RootPair ( ) ) , fmt . Sprintf ( "Unable to access %s directory with user UID:%d and GID:%d" , dirName , rootUID , rootGID ) )
}
func TestLookupUserAndGroup ( t * testing . T ) {
RequiresRoot ( t )
uid , gid , err := AddNamespaceRangesUser ( tempUser )
assert . NoError ( t , err )
defer delUser ( t , tempUser )
fetchedUser , err := LookupUser ( tempUser )
assert . NoError ( t , err )
fetchedUserByID , err := LookupUID ( uid )
assert . NoError ( t , err )
assert . Equal ( t , fetchedUserByID , fetchedUser )
fetchedGroup , err := LookupGroup ( tempUser )
assert . NoError ( t , err )
fetchedGroupByID , err := LookupGID ( gid )
assert . NoError ( t , err )
assert . Equal ( t , fetchedGroupByID , fetchedGroup )
}
func TestLookupUserAndGroupThatDoesNotExist ( t * testing . T ) {
fakeUser := "fakeuser"
_ , err := LookupUser ( fakeUser )
assert . EqualError ( t , err , "getent unable to find entry \"" + fakeUser + "\" in passwd database" )
_ , err = LookupUID ( - 1 )
assert . Error ( t , err )
fakeGroup := "fakegroup"
_ , err = LookupGroup ( fakeGroup )
assert . EqualError ( t , err , "getent unable to find entry \"" + fakeGroup + "\" in group database" )
_ , err = LookupGID ( - 1 )
assert . Error ( t , err )
}
2017-11-27 16:11:11 -05:00
// TestMkdirIsNotDir checks that mkdirAs() function (used by MkdirAll...)
// returns a correct error in case a directory which it is about to create
// already exists but is a file (rather than a directory).
func TestMkdirIsNotDir ( t * testing . T ) {
file , err := ioutil . TempFile ( "" , t . Name ( ) )
if err != nil {
t . Fatalf ( "Couldn't create temp dir: %v" , err )
}
defer os . Remove ( file . Name ( ) )
err = mkdirAs ( file . Name ( ) , 0755 , 0 , 0 , false , false )
assert . EqualError ( t , err , "mkdir " + file . Name ( ) + ": not a directory" )
}
2017-09-26 03:01:08 -04:00
func RequiresRoot ( t * testing . T ) {
skip . IfCondition ( t , os . Getuid ( ) != 0 , "skipping test that requires root" )
}