gopkg.in/docker/docker.v23@v23.0.11/pkg/idtools/idtools.go (about)

     1  package idtools // import "github.com/docker/docker/pkg/idtools"
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  // IDMap contains a single entry for user namespace range remapping. An array
    12  // of IDMap entries represents the structure that will be provided to the Linux
    13  // kernel for creating a user namespace.
    14  type IDMap struct {
    15  	ContainerID int `json:"container_id"`
    16  	HostID      int `json:"host_id"`
    17  	Size        int `json:"size"`
    18  }
    19  
    20  type subIDRange struct {
    21  	Start  int
    22  	Length int
    23  }
    24  
    25  type ranges []subIDRange
    26  
    27  func (e ranges) Len() int           { return len(e) }
    28  func (e ranges) Swap(i, j int)      { e[i], e[j] = e[j], e[i] }
    29  func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
    30  
    31  const (
    32  	subuidFileName = "/etc/subuid"
    33  	subgidFileName = "/etc/subgid"
    34  )
    35  
    36  // MkdirAllAndChown creates a directory (include any along the path) and then modifies
    37  // ownership to the requested uid/gid.  If the directory already exists, this
    38  // function will still change ownership and permissions.
    39  func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
    40  	return mkdirAs(path, mode, owner, true, true)
    41  }
    42  
    43  // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
    44  // If the directory already exists, this function still changes ownership and permissions.
    45  // Note that unlike os.Mkdir(), this function does not return IsExist error
    46  // in case path already exists.
    47  func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
    48  	return mkdirAs(path, mode, owner, false, true)
    49  }
    50  
    51  // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
    52  // ownership ONLY of newly created directories to the requested uid/gid. If the
    53  // directories along the path exist, no change of ownership or permissions will be performed
    54  func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
    55  	return mkdirAs(path, mode, owner, true, false)
    56  }
    57  
    58  // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
    59  // If the maps are empty, then the root uid/gid will default to "real" 0/0
    60  func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
    61  	uid, err := toHost(0, uidMap)
    62  	if err != nil {
    63  		return -1, -1, err
    64  	}
    65  	gid, err := toHost(0, gidMap)
    66  	if err != nil {
    67  		return -1, -1, err
    68  	}
    69  	return uid, gid, nil
    70  }
    71  
    72  // toContainer takes an id mapping, and uses it to translate a
    73  // host ID to the remapped ID. If no map is provided, then the translation
    74  // assumes a 1-to-1 mapping and returns the passed in id
    75  func toContainer(hostID int, idMap []IDMap) (int, error) {
    76  	if idMap == nil {
    77  		return hostID, nil
    78  	}
    79  	for _, m := range idMap {
    80  		if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
    81  			contID := m.ContainerID + (hostID - m.HostID)
    82  			return contID, nil
    83  		}
    84  	}
    85  	return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
    86  }
    87  
    88  // toHost takes an id mapping and a remapped ID, and translates the
    89  // ID to the mapped host ID. If no map is provided, then the translation
    90  // assumes a 1-to-1 mapping and returns the passed in id #
    91  func toHost(contID int, idMap []IDMap) (int, error) {
    92  	if idMap == nil {
    93  		return contID, nil
    94  	}
    95  	for _, m := range idMap {
    96  		if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
    97  			hostID := m.HostID + (contID - m.ContainerID)
    98  			return hostID, nil
    99  		}
   100  	}
   101  	return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
   102  }
   103  
   104  // Identity is either a UID and GID pair or a SID (but not both)
   105  type Identity struct {
   106  	UID int
   107  	GID int
   108  	SID string
   109  }
   110  
   111  // Chown changes the numeric uid and gid of the named file to id.UID and id.GID.
   112  func (id Identity) Chown(name string) error {
   113  	return os.Chown(name, id.UID, id.GID)
   114  }
   115  
   116  // IdentityMapping contains a mappings of UIDs and GIDs.
   117  // The zero value represents an empty mapping.
   118  type IdentityMapping struct {
   119  	UIDMaps []IDMap `json:"UIDMaps"`
   120  	GIDMaps []IDMap `json:"GIDMaps"`
   121  }
   122  
   123  // RootPair returns a uid and gid pair for the root user. The error is ignored
   124  // because a root user always exists, and the defaults are correct when the uid
   125  // and gid maps are empty.
   126  func (i IdentityMapping) RootPair() Identity {
   127  	uid, gid, _ := GetRootUIDGID(i.UIDMaps, i.GIDMaps)
   128  	return Identity{UID: uid, GID: gid}
   129  }
   130  
   131  // ToHost returns the host UID and GID for the container uid, gid.
   132  // Remapping is only performed if the ids aren't already the remapped root ids
   133  func (i IdentityMapping) ToHost(pair Identity) (Identity, error) {
   134  	var err error
   135  	target := i.RootPair()
   136  
   137  	if pair.UID != target.UID {
   138  		target.UID, err = toHost(pair.UID, i.UIDMaps)
   139  		if err != nil {
   140  			return target, err
   141  		}
   142  	}
   143  
   144  	if pair.GID != target.GID {
   145  		target.GID, err = toHost(pair.GID, i.GIDMaps)
   146  	}
   147  	return target, err
   148  }
   149  
   150  // ToContainer returns the container UID and GID for the host uid and gid
   151  func (i IdentityMapping) ToContainer(pair Identity) (int, int, error) {
   152  	uid, err := toContainer(pair.UID, i.UIDMaps)
   153  	if err != nil {
   154  		return -1, -1, err
   155  	}
   156  	gid, err := toContainer(pair.GID, i.GIDMaps)
   157  	return uid, gid, err
   158  }
   159  
   160  // Empty returns true if there are no id mappings
   161  func (i IdentityMapping) Empty() bool {
   162  	return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
   163  }
   164  
   165  // UIDs returns the mapping for UID.
   166  //
   167  // Deprecated: reference the UIDMaps field directly.
   168  func (i IdentityMapping) UIDs() []IDMap {
   169  	return i.UIDMaps
   170  }
   171  
   172  // GIDs returns the mapping for GID.
   173  //
   174  // Deprecated: reference the GIDMaps field directly.
   175  func (i IdentityMapping) GIDs() []IDMap {
   176  	return i.GIDMaps
   177  }
   178  
   179  func createIDMap(subidRanges ranges) []IDMap {
   180  	idMap := []IDMap{}
   181  
   182  	containerID := 0
   183  	for _, idrange := range subidRanges {
   184  		idMap = append(idMap, IDMap{
   185  			ContainerID: containerID,
   186  			HostID:      idrange.Start,
   187  			Size:        idrange.Length,
   188  		})
   189  		containerID = containerID + idrange.Length
   190  	}
   191  	return idMap
   192  }
   193  
   194  func parseSubuid(username string) (ranges, error) {
   195  	return parseSubidFile(subuidFileName, username)
   196  }
   197  
   198  func parseSubgid(username string) (ranges, error) {
   199  	return parseSubidFile(subgidFileName, username)
   200  }
   201  
   202  // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
   203  // and return all found ranges for a specified username. If the special value
   204  // "ALL" is supplied for username, then all ranges in the file will be returned
   205  func parseSubidFile(path, username string) (ranges, error) {
   206  	var rangeList ranges
   207  
   208  	subidFile, err := os.Open(path)
   209  	if err != nil {
   210  		return rangeList, err
   211  	}
   212  	defer subidFile.Close()
   213  
   214  	s := bufio.NewScanner(subidFile)
   215  	for s.Scan() {
   216  		text := strings.TrimSpace(s.Text())
   217  		if text == "" || strings.HasPrefix(text, "#") {
   218  			continue
   219  		}
   220  		parts := strings.Split(text, ":")
   221  		if len(parts) != 3 {
   222  			return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
   223  		}
   224  		if parts[0] == username || username == "ALL" {
   225  			startid, err := strconv.Atoi(parts[1])
   226  			if err != nil {
   227  				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
   228  			}
   229  			length, err := strconv.Atoi(parts[2])
   230  			if err != nil {
   231  				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
   232  			}
   233  			rangeList = append(rangeList, subIDRange{startid, length})
   234  		}
   235  	}
   236  
   237  	return rangeList, s.Err()
   238  }
   239  
   240  // CurrentIdentity returns the identity of the current process
   241  func CurrentIdentity() Identity {
   242  	return Identity{UID: os.Getuid(), GID: os.Getegid()}
   243  }