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