github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/pkg/idtools/idtools_unix.go (about)

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