github.com/rumpl/bof@v23.0.0-rc.2+incompatible/pkg/idtools/idtools_unix.go (about)

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