github.com/damirazo/docker@v1.9.0/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) 42 } 43 44 // MkdirAs creates a directory and then modifies ownership to the requested uid/gid. 45 // If the directory already exists, this function still changes ownership 46 func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 47 return mkdirAs(path, mode, ownerUID, ownerGID, false) 48 } 49 50 // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. 51 // If the maps are empty, then the root uid/gid will default to "real" 0/0 52 func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { 53 var uid, gid int 54 55 if uidMap != nil { 56 xUID, err := ToHost(0, uidMap) 57 if err != nil { 58 return -1, -1, err 59 } 60 uid = xUID 61 } 62 if gidMap != nil { 63 xGID, err := ToHost(0, gidMap) 64 if err != nil { 65 return -1, -1, err 66 } 67 gid = xGID 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 // CreateIDMappings takes a requested user and group name and 105 // using the data from /etc/sub{uid,gid} ranges, creates the 106 // proper uid and gid remapping ranges for that user/group pair 107 func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) { 108 subuidRanges, err := parseSubuid(username) 109 if err != nil { 110 return nil, nil, err 111 } 112 subgidRanges, err := parseSubgid(groupname) 113 if err != nil { 114 return nil, nil, err 115 } 116 if len(subuidRanges) == 0 { 117 return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username) 118 } 119 if len(subgidRanges) == 0 { 120 return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname) 121 } 122 123 return createIDMap(subuidRanges), createIDMap(subgidRanges), nil 124 } 125 126 func createIDMap(subidRanges ranges) []IDMap { 127 idMap := []IDMap{} 128 129 // sort the ranges by lowest ID first 130 sort.Sort(subidRanges) 131 containerID := 0 132 for _, idrange := range subidRanges { 133 idMap = append(idMap, IDMap{ 134 ContainerID: containerID, 135 HostID: idrange.Start, 136 Size: idrange.Length, 137 }) 138 containerID = containerID + idrange.Length 139 } 140 return idMap 141 } 142 143 func parseSubuid(username string) (ranges, error) { 144 return parseSubidFile(subuidFileName, username) 145 } 146 147 func parseSubgid(username string) (ranges, error) { 148 return parseSubidFile(subgidFileName, username) 149 } 150 151 func parseSubidFile(path, username string) (ranges, error) { 152 var rangeList ranges 153 154 subidFile, err := os.Open(path) 155 if err != nil { 156 return rangeList, err 157 } 158 defer subidFile.Close() 159 160 s := bufio.NewScanner(subidFile) 161 for s.Scan() { 162 if err := s.Err(); err != nil { 163 return rangeList, err 164 } 165 166 text := strings.TrimSpace(s.Text()) 167 if text == "" { 168 continue 169 } 170 parts := strings.Split(text, ":") 171 if len(parts) != 3 { 172 return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path) 173 } 174 if parts[0] == username { 175 // return the first entry for a user; ignores potential for multiple ranges per user 176 startid, err := strconv.Atoi(parts[1]) 177 if err != nil { 178 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 179 } 180 length, err := strconv.Atoi(parts[2]) 181 if err != nil { 182 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 183 } 184 rangeList = append(rangeList, subIDRange{startid, length}) 185 } 186 } 187 return rangeList, nil 188 }