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