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