github.com/kunnos/engine@v1.13.1/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  func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
    41  	return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
    42  }
    43  
    44  // MkdirAllNewAs creates a directory (include any along the path) and then modifies
    45  // ownership ONLY of newly created directories to the requested uid/gid. If the
    46  // directories along the path exist, no change of ownership will be performed
    47  func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
    48  	return mkdirAs(path, mode, ownerUID, ownerGID, true, false)
    49  }
    50  
    51  // MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
    52  // If the directory already exists, this function still changes ownership
    53  func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
    54  	return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
    55  }
    56  
    57  // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
    58  // If the maps are empty, then the root uid/gid will default to "real" 0/0
    59  func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
    60  	var uid, gid int
    61  
    62  	if uidMap != nil {
    63  		xUID, err := ToHost(0, uidMap)
    64  		if err != nil {
    65  			return -1, -1, err
    66  		}
    67  		uid = xUID
    68  	}
    69  	if gidMap != nil {
    70  		xGID, err := ToHost(0, gidMap)
    71  		if err != nil {
    72  			return -1, -1, err
    73  		}
    74  		gid = xGID
    75  	}
    76  	return uid, gid, nil
    77  }
    78  
    79  // ToContainer takes an id mapping, and uses it to translate a
    80  // host ID to the remapped ID. If no map is provided, then the translation
    81  // assumes a 1-to-1 mapping and returns the passed in id
    82  func ToContainer(hostID int, idMap []IDMap) (int, error) {
    83  	if idMap == nil {
    84  		return hostID, nil
    85  	}
    86  	for _, m := range idMap {
    87  		if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
    88  			contID := m.ContainerID + (hostID - m.HostID)
    89  			return contID, nil
    90  		}
    91  	}
    92  	return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
    93  }
    94  
    95  // ToHost takes an id mapping and a remapped ID, and translates the
    96  // ID to the mapped host ID. If no map is provided, then the translation
    97  // assumes a 1-to-1 mapping and returns the passed in id #
    98  func ToHost(contID int, idMap []IDMap) (int, error) {
    99  	if idMap == nil {
   100  		return contID, nil
   101  	}
   102  	for _, m := range idMap {
   103  		if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
   104  			hostID := m.HostID + (contID - m.ContainerID)
   105  			return hostID, nil
   106  		}
   107  	}
   108  	return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
   109  }
   110  
   111  // CreateIDMappings takes a requested user and group name and
   112  // using the data from /etc/sub{uid,gid} ranges, creates the
   113  // proper uid and gid remapping ranges for that user/group pair
   114  func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) {
   115  	subuidRanges, err := parseSubuid(username)
   116  	if err != nil {
   117  		return nil, nil, err
   118  	}
   119  	subgidRanges, err := parseSubgid(groupname)
   120  	if err != nil {
   121  		return nil, nil, err
   122  	}
   123  	if len(subuidRanges) == 0 {
   124  		return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
   125  	}
   126  	if len(subgidRanges) == 0 {
   127  		return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
   128  	}
   129  
   130  	return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
   131  }
   132  
   133  func createIDMap(subidRanges ranges) []IDMap {
   134  	idMap := []IDMap{}
   135  
   136  	// sort the ranges by lowest ID first
   137  	sort.Sort(subidRanges)
   138  	containerID := 0
   139  	for _, idrange := range subidRanges {
   140  		idMap = append(idMap, IDMap{
   141  			ContainerID: containerID,
   142  			HostID:      idrange.Start,
   143  			Size:        idrange.Length,
   144  		})
   145  		containerID = containerID + idrange.Length
   146  	}
   147  	return idMap
   148  }
   149  
   150  func parseSubuid(username string) (ranges, error) {
   151  	return parseSubidFile(subuidFileName, username)
   152  }
   153  
   154  func parseSubgid(username string) (ranges, error) {
   155  	return parseSubidFile(subgidFileName, username)
   156  }
   157  
   158  // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
   159  // and return all found ranges for a specified username. If the special value
   160  // "ALL" is supplied for username, then all ranges in the file will be returned
   161  func parseSubidFile(path, username string) (ranges, error) {
   162  	var rangeList ranges
   163  
   164  	subidFile, err := os.Open(path)
   165  	if err != nil {
   166  		return rangeList, err
   167  	}
   168  	defer subidFile.Close()
   169  
   170  	s := bufio.NewScanner(subidFile)
   171  	for s.Scan() {
   172  		if err := s.Err(); err != nil {
   173  			return rangeList, err
   174  		}
   175  
   176  		text := strings.TrimSpace(s.Text())
   177  		if text == "" || strings.HasPrefix(text, "#") {
   178  			continue
   179  		}
   180  		parts := strings.Split(text, ":")
   181  		if len(parts) != 3 {
   182  			return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
   183  		}
   184  		if parts[0] == username || username == "ALL" {
   185  			startid, err := strconv.Atoi(parts[1])
   186  			if err != nil {
   187  				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
   188  			}
   189  			length, err := strconv.Atoi(parts[2])
   190  			if err != nil {
   191  				return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
   192  			}
   193  			rangeList = append(rangeList, subIDRange{startid, length})
   194  		}
   195  	}
   196  	return rangeList, nil
   197  }