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.
16 ContainerID int `json:"container_id"`
17 HostID int `json:"host_id"`
18 Size int `json:"size"`
21 type subIDRange struct {
26 type ranges []subIDRange
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 }
33 subuidFileName string = "/etc/subuid"
34 subgidFileName string = "/etc/subgid"
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)
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)
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)
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) {
63 xUID, err := ToHost(0, uidMap)
70 xGID, err := ToHost(0, gidMap)
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) {
86 for _, m := range idMap {
87 if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
88 contID := m.ContainerID + (hostID - m.HostID)
92 return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
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) {
102 for _, m := range idMap {
103 if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
104 hostID := m.HostID + (contID - m.ContainerID)
108 return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
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)
119 subgidRanges, err := parseSubgid(groupname)
123 if len(subuidRanges) == 0 {
124 return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
126 if len(subgidRanges) == 0 {
127 return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
130 return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
133 func createIDMap(subidRanges ranges) []IDMap {
136 // sort the ranges by lowest ID first
137 sort.Sort(subidRanges)
139 for _, idrange := range subidRanges {
140 idMap = append(idMap, IDMap{
141 ContainerID: containerID,
142 HostID: idrange.Start,
143 Size: idrange.Length,
145 containerID = containerID + idrange.Length
150 func parseSubuid(username string) (ranges, error) {
151 return parseSubidFile(subuidFileName, username)
154 func parseSubgid(username string) (ranges, error) {
155 return parseSubidFile(subgidFileName, username)
158 func parseSubidFile(path, username string) (ranges, error) {
161 subidFile, err := os.Open(path)
163 return rangeList, err
165 defer subidFile.Close()
167 s := bufio.NewScanner(subidFile)
169 if err := s.Err(); err != nil {
170 return rangeList, err
173 text := strings.TrimSpace(s.Text())
177 parts := strings.Split(text, ":")
179 return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
181 if parts[0] == username {
182 // return the first entry for a user; ignores potential for multiple ranges per user
183 startid, err := strconv.Atoi(parts[1])
185 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
187 length, err := strconv.Atoi(parts[2])
189 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
191 rangeList = append(rangeList, subIDRange{startid, length})
194 return rangeList, nil