github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/pkg/idtools/idtools_unix.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package idtools // import "github.com/docker/docker/pkg/idtools" 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "strconv" 13 "sync" 14 "syscall" 15 16 "github.com/docker/docker/pkg/system" 17 "github.com/opencontainers/runc/libcontainer/user" 18 "github.com/pkg/errors" 19 ) 20 21 var ( 22 entOnce sync.Once 23 getentCmd string 24 ) 25 26 func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { 27 // make an array containing the original path asked for, plus (for mkAll == true) 28 // all path components leading up to the complete path that don't exist before we MkdirAll 29 // so that we can chown all of them properly at the end. If chownExisting is false, we won't 30 // chown the full directory path if it exists 31 32 var paths []string 33 34 stat, err := system.Stat(path) 35 if err == nil { 36 if !stat.IsDir() { 37 return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} 38 } 39 if !chownExisting { 40 return nil 41 } 42 43 // short-circuit--we were called with an existing directory and chown was requested 44 return setPermissions(path, mode, owner.UID, owner.GID, stat) 45 } 46 47 if os.IsNotExist(err) { 48 paths = []string{path} 49 } 50 51 if mkAll { 52 // walk back to "/" looking for directories which do not exist 53 // and add them to the paths array for chown after creation 54 dirPath := path 55 for { 56 dirPath = filepath.Dir(dirPath) 57 if dirPath == "/" { 58 break 59 } 60 if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) { 61 paths = append(paths, dirPath) 62 } 63 } 64 if err := system.MkdirAll(path, mode); err != nil { 65 return err 66 } 67 } else { 68 if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) { 69 return err 70 } 71 } 72 // even if it existed, we will chown the requested path + any subpaths that 73 // didn't exist when we called MkdirAll 74 for _, pathComponent := range paths { 75 if err := setPermissions(pathComponent, mode, owner.UID, owner.GID, nil); err != nil { 76 return err 77 } 78 } 79 return nil 80 } 81 82 // CanAccess takes a valid (existing) directory and a uid, gid pair and determines 83 // if that uid, gid pair has access (execute bit) to the directory 84 func CanAccess(path string, pair Identity) bool { 85 statInfo, err := system.Stat(path) 86 if err != nil { 87 return false 88 } 89 fileMode := os.FileMode(statInfo.Mode()) 90 permBits := fileMode.Perm() 91 return accessible(statInfo.UID() == uint32(pair.UID), 92 statInfo.GID() == uint32(pair.GID), permBits) 93 } 94 95 func accessible(isOwner, isGroup bool, perms os.FileMode) bool { 96 if isOwner && (perms&0100 == 0100) { 97 return true 98 } 99 if isGroup && (perms&0010 == 0010) { 100 return true 101 } 102 if perms&0001 == 0001 { 103 return true 104 } 105 return false 106 } 107 108 // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username, 109 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 110 func LookupUser(name string) (user.User, error) { 111 // first try a local system files lookup using existing capabilities 112 usr, err := user.LookupUser(name) 113 if err == nil { 114 return usr, nil 115 } 116 // local files lookup failed; attempt to call `getent` to query configured passwd dbs 117 usr, err = getentUser(name) 118 if err != nil { 119 return user.User{}, err 120 } 121 return usr, nil 122 } 123 124 // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid, 125 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 126 func LookupUID(uid int) (user.User, error) { 127 // first try a local system files lookup using existing capabilities 128 usr, err := user.LookupUid(uid) 129 if err == nil { 130 return usr, nil 131 } 132 // local files lookup failed; attempt to call `getent` to query configured passwd dbs 133 return getentUser(strconv.Itoa(uid)) 134 } 135 136 func getentUser(name string) (user.User, error) { 137 reader, err := callGetent("passwd", name) 138 if err != nil { 139 return user.User{}, err 140 } 141 users, err := user.ParsePasswd(reader) 142 if err != nil { 143 return user.User{}, err 144 } 145 if len(users) == 0 { 146 return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", name) 147 } 148 return users[0], nil 149 } 150 151 // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, 152 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 153 func LookupGroup(name string) (user.Group, error) { 154 // first try a local system files lookup using existing capabilities 155 group, err := user.LookupGroup(name) 156 if err == nil { 157 return group, nil 158 } 159 // local files lookup failed; attempt to call `getent` to query configured group dbs 160 return getentGroup(name) 161 } 162 163 // LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID, 164 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 165 func LookupGID(gid int) (user.Group, error) { 166 // first try a local system files lookup using existing capabilities 167 group, err := user.LookupGid(gid) 168 if err == nil { 169 return group, nil 170 } 171 // local files lookup failed; attempt to call `getent` to query configured group dbs 172 return getentGroup(strconv.Itoa(gid)) 173 } 174 175 func getentGroup(name string) (user.Group, error) { 176 reader, err := callGetent("group", name) 177 if err != nil { 178 return user.Group{}, err 179 } 180 groups, err := user.ParseGroup(reader) 181 if err != nil { 182 return user.Group{}, err 183 } 184 if len(groups) == 0 { 185 return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", name) 186 } 187 return groups[0], nil 188 } 189 190 func callGetent(database, key string) (io.Reader, error) { 191 entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") }) 192 // if no `getent` command on host, can't do anything else 193 if getentCmd == "" { 194 return nil, fmt.Errorf("unable to find getent command") 195 } 196 out, err := execCmd(getentCmd, database, key) 197 if err != nil { 198 exitCode, errC := system.GetExitCode(err) 199 if errC != nil { 200 return nil, err 201 } 202 switch exitCode { 203 case 1: 204 return nil, fmt.Errorf("getent reported invalid parameters/database unknown") 205 case 2: 206 return nil, fmt.Errorf("getent unable to find entry %q in %s database", key, database) 207 case 3: 208 return nil, fmt.Errorf("getent database doesn't support enumeration") 209 default: 210 return nil, err 211 } 212 213 } 214 return bytes.NewReader(out), nil 215 } 216 217 // setPermissions performs a chown/chmod only if the uid/gid don't match what's requested 218 // Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the 219 // dir is on an NFS share, so don't call chown unless we absolutely must. 220 // Likewise for setting permissions. 221 func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT) error { 222 if stat == nil { 223 var err error 224 stat, err = system.Stat(p) 225 if err != nil { 226 return err 227 } 228 } 229 if os.FileMode(stat.Mode()).Perm() != mode.Perm() { 230 if err := os.Chmod(p, mode.Perm()); err != nil { 231 return err 232 } 233 } 234 if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) { 235 return nil 236 } 237 return os.Chown(p, uid, gid) 238 } 239 240 // NewIdentityMapping takes a requested username and 241 // using the data from /etc/sub{uid,gid} ranges, creates the 242 // proper uid and gid remapping ranges for that user/group pair 243 func NewIdentityMapping(name string) (*IdentityMapping, error) { 244 usr, err := LookupUser(name) 245 if err != nil { 246 return nil, fmt.Errorf("Could not get user for username %s: %v", name, err) 247 } 248 249 subuidRanges, err := lookupSubUIDRanges(usr) 250 if err != nil { 251 return nil, err 252 } 253 subgidRanges, err := lookupSubGIDRanges(usr) 254 if err != nil { 255 return nil, err 256 } 257 258 return &IdentityMapping{ 259 uids: subuidRanges, 260 gids: subgidRanges, 261 }, nil 262 } 263 264 func lookupSubUIDRanges(usr user.User) ([]IDMap, error) { 265 rangeList, err := parseSubuid(strconv.Itoa(usr.Uid)) 266 if err != nil { 267 return nil, err 268 } 269 if len(rangeList) == 0 { 270 rangeList, err = parseSubuid(usr.Name) 271 if err != nil { 272 return nil, err 273 } 274 } 275 if len(rangeList) == 0 { 276 return nil, errors.Errorf("no subuid ranges found for user %q", usr.Name) 277 } 278 return createIDMap(rangeList), nil 279 } 280 281 func lookupSubGIDRanges(usr user.User) ([]IDMap, error) { 282 rangeList, err := parseSubgid(strconv.Itoa(usr.Uid)) 283 if err != nil { 284 return nil, err 285 } 286 if len(rangeList) == 0 { 287 rangeList, err = parseSubgid(usr.Name) 288 if err != nil { 289 return nil, err 290 } 291 } 292 if len(rangeList) == 0 { 293 return nil, errors.Errorf("no subgid ranges found for user %q", usr.Name) 294 } 295 return createIDMap(rangeList), nil 296 }