github.com/lacework-dev/go-moby@v20.10.12+incompatible/pkg/idtools/idtools_unix.go (about)

     1  // +build !windows
     2  
     3  package idtools // import "github.com/docker/docker/pkg/idtools"
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"sync"
    13  	"syscall"
    14  
    15  	"github.com/docker/docker/pkg/system"
    16  	"github.com/opencontainers/runc/libcontainer/user"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  var (
    21  	entOnce   sync.Once
    22  	getentCmd string
    23  )
    24  
    25  func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
    26  	// make an array containing the original path asked for, plus (for mkAll == true)
    27  	// all path components leading up to the complete path that don't exist before we MkdirAll
    28  	// so that we can chown all of them properly at the end.  If chownExisting is false, we won't
    29  	// chown the full directory path if it exists
    30  
    31  	var paths []string
    32  
    33  	stat, err := system.Stat(path)
    34  	if err == nil {
    35  		if !stat.IsDir() {
    36  			return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
    37  		}
    38  		if !chownExisting {
    39  			return nil
    40  		}
    41  
    42  		// short-circuit--we were called with an existing directory and chown was requested
    43  		return setPermissions(path, mode, owner.UID, owner.GID, stat)
    44  	}
    45  
    46  	if os.IsNotExist(err) {
    47  		paths = []string{path}
    48  	}
    49  
    50  	if mkAll {
    51  		// walk back to "/" looking for directories which do not exist
    52  		// and add them to the paths array for chown after creation
    53  		dirPath := path
    54  		for {
    55  			dirPath = filepath.Dir(dirPath)
    56  			if dirPath == "/" {
    57  				break
    58  			}
    59  			if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
    60  				paths = append(paths, dirPath)
    61  			}
    62  		}
    63  		if err := system.MkdirAll(path, mode); err != nil {
    64  			return err
    65  		}
    66  	} else {
    67  		if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
    68  			return err
    69  		}
    70  	}
    71  	// even if it existed, we will chown the requested path + any subpaths that
    72  	// didn't exist when we called MkdirAll
    73  	for _, pathComponent := range paths {
    74  		if err := setPermissions(pathComponent, mode, owner.UID, owner.GID, nil); err != nil {
    75  			return err
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  // CanAccess takes a valid (existing) directory and a uid, gid pair and determines
    82  // if that uid, gid pair has access (execute bit) to the directory
    83  func CanAccess(path string, pair Identity) bool {
    84  	statInfo, err := system.Stat(path)
    85  	if err != nil {
    86  		return false
    87  	}
    88  	fileMode := os.FileMode(statInfo.Mode())
    89  	permBits := fileMode.Perm()
    90  	return accessible(statInfo.UID() == uint32(pair.UID),
    91  		statInfo.GID() == uint32(pair.GID), permBits)
    92  }
    93  
    94  func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
    95  	if isOwner && (perms&0100 == 0100) {
    96  		return true
    97  	}
    98  	if isGroup && (perms&0010 == 0010) {
    99  		return true
   100  	}
   101  	if perms&0001 == 0001 {
   102  		return true
   103  	}
   104  	return false
   105  }
   106  
   107  // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
   108  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   109  func LookupUser(name string) (user.User, error) {
   110  	// first try a local system files lookup using existing capabilities
   111  	usr, err := user.LookupUser(name)
   112  	if err == nil {
   113  		return usr, nil
   114  	}
   115  	// local files lookup failed; attempt to call `getent` to query configured passwd dbs
   116  	usr, err = getentUser(name)
   117  	if err != nil {
   118  		return user.User{}, err
   119  	}
   120  	return usr, nil
   121  }
   122  
   123  // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
   124  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   125  func LookupUID(uid int) (user.User, error) {
   126  	// first try a local system files lookup using existing capabilities
   127  	usr, err := user.LookupUid(uid)
   128  	if err == nil {
   129  		return usr, nil
   130  	}
   131  	// local files lookup failed; attempt to call `getent` to query configured passwd dbs
   132  	return getentUser(strconv.Itoa(uid))
   133  }
   134  
   135  func getentUser(name string) (user.User, error) {
   136  	reader, err := callGetent("passwd", name)
   137  	if err != nil {
   138  		return user.User{}, err
   139  	}
   140  	users, err := user.ParsePasswd(reader)
   141  	if err != nil {
   142  		return user.User{}, err
   143  	}
   144  	if len(users) == 0 {
   145  		return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", name)
   146  	}
   147  	return users[0], nil
   148  }
   149  
   150  // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
   151  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   152  func LookupGroup(name string) (user.Group, error) {
   153  	// first try a local system files lookup using existing capabilities
   154  	group, err := user.LookupGroup(name)
   155  	if err == nil {
   156  		return group, nil
   157  	}
   158  	// local files lookup failed; attempt to call `getent` to query configured group dbs
   159  	return getentGroup(name)
   160  }
   161  
   162  // LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
   163  // followed by a call to `getent` for supporting host configured non-files passwd and group dbs
   164  func LookupGID(gid int) (user.Group, error) {
   165  	// first try a local system files lookup using existing capabilities
   166  	group, err := user.LookupGid(gid)
   167  	if err == nil {
   168  		return group, nil
   169  	}
   170  	// local files lookup failed; attempt to call `getent` to query configured group dbs
   171  	return getentGroup(strconv.Itoa(gid))
   172  }
   173  
   174  func getentGroup(name string) (user.Group, error) {
   175  	reader, err := callGetent("group", name)
   176  	if err != nil {
   177  		return user.Group{}, err
   178  	}
   179  	groups, err := user.ParseGroup(reader)
   180  	if err != nil {
   181  		return user.Group{}, err
   182  	}
   183  	if len(groups) == 0 {
   184  		return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", name)
   185  	}
   186  	return groups[0], nil
   187  }
   188  
   189  func callGetent(database, key string) (io.Reader, error) {
   190  	entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
   191  	// if no `getent` command on host, can't do anything else
   192  	if getentCmd == "" {
   193  		return nil, fmt.Errorf("unable to find getent command")
   194  	}
   195  	out, err := execCmd(getentCmd, database, key)
   196  	if err != nil {
   197  		exitCode, errC := system.GetExitCode(err)
   198  		if errC != nil {
   199  			return nil, err
   200  		}
   201  		switch exitCode {
   202  		case 1:
   203  			return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
   204  		case 2:
   205  			return nil, fmt.Errorf("getent unable to find entry %q in %s database", key, database)
   206  		case 3:
   207  			return nil, fmt.Errorf("getent database doesn't support enumeration")
   208  		default:
   209  			return nil, err
   210  		}
   211  
   212  	}
   213  	return bytes.NewReader(out), nil
   214  }
   215  
   216  // setPermissions performs a chown/chmod only if the uid/gid don't match what's requested
   217  // Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
   218  // dir is on an NFS share, so don't call chown unless we absolutely must.
   219  // Likewise for setting permissions.
   220  func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT) error {
   221  	if stat == nil {
   222  		var err error
   223  		stat, err = system.Stat(p)
   224  		if err != nil {
   225  			return err
   226  		}
   227  	}
   228  	if os.FileMode(stat.Mode()).Perm() != mode.Perm() {
   229  		if err := os.Chmod(p, mode.Perm()); err != nil {
   230  			return err
   231  		}
   232  	}
   233  	if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
   234  		return nil
   235  	}
   236  	return os.Chown(p, uid, gid)
   237  }
   238  
   239  // NewIdentityMapping takes a requested username and
   240  // using the data from /etc/sub{uid,gid} ranges, creates the
   241  // proper uid and gid remapping ranges for that user/group pair
   242  func NewIdentityMapping(name string) (*IdentityMapping, error) {
   243  	usr, err := LookupUser(name)
   244  	if err != nil {
   245  		return nil, fmt.Errorf("Could not get user for username %s: %v", name, err)
   246  	}
   247  
   248  	subuidRanges, err := lookupSubUIDRanges(usr)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	subgidRanges, err := lookupSubGIDRanges(usr)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	return &IdentityMapping{
   258  		uids: subuidRanges,
   259  		gids: subgidRanges,
   260  	}, nil
   261  }
   262  
   263  func lookupSubUIDRanges(usr user.User) ([]IDMap, error) {
   264  	rangeList, err := parseSubuid(strconv.Itoa(usr.Uid))
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  	if len(rangeList) == 0 {
   269  		rangeList, err = parseSubuid(usr.Name)
   270  		if err != nil {
   271  			return nil, err
   272  		}
   273  	}
   274  	if len(rangeList) == 0 {
   275  		return nil, errors.Errorf("no subuid ranges found for user %q", usr.Name)
   276  	}
   277  	return createIDMap(rangeList), nil
   278  }
   279  
   280  func lookupSubGIDRanges(usr user.User) ([]IDMap, error) {
   281  	rangeList, err := parseSubgid(strconv.Itoa(usr.Uid))
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	if len(rangeList) == 0 {
   286  		rangeList, err = parseSubgid(usr.Name)
   287  		if err != nil {
   288  			return nil, err
   289  		}
   290  	}
   291  	if len(rangeList) == 0 {
   292  		return nil, errors.Errorf("no subgid ranges found for user %q", usr.Name)
   293  	}
   294  	return createIDMap(rangeList), nil
   295  }