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