github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/pkg/idtools/idtools.go (about)

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