github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/iam-store.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "encoding/base64" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "sort" 27 "strings" 28 "time" 29 30 jsoniter "github.com/json-iterator/go" 31 "github.com/minio/madmin-go/v3" 32 "github.com/minio/minio-go/v7/pkg/set" 33 "github.com/minio/minio/internal/auth" 34 "github.com/minio/minio/internal/config/identity/openid" 35 "github.com/minio/minio/internal/jwt" 36 "github.com/minio/minio/internal/logger" 37 "github.com/minio/pkg/v2/policy" 38 "github.com/puzpuzpuz/xsync/v3" 39 ) 40 41 const ( 42 // IAM configuration directory. 43 iamConfigPrefix = minioConfigPrefix + "/iam" 44 45 // IAM users directory. 46 iamConfigUsersPrefix = iamConfigPrefix + "/users/" 47 48 // IAM service accounts directory. 49 iamConfigServiceAccountsPrefix = iamConfigPrefix + "/service-accounts/" 50 51 // IAM groups directory. 52 iamConfigGroupsPrefix = iamConfigPrefix + "/groups/" 53 54 // IAM policies directory. 55 iamConfigPoliciesPrefix = iamConfigPrefix + "/policies/" 56 57 // IAM sts directory. 58 iamConfigSTSPrefix = iamConfigPrefix + "/sts/" 59 60 // IAM Policy DB prefixes. 61 iamConfigPolicyDBPrefix = iamConfigPrefix + "/policydb/" 62 iamConfigPolicyDBUsersPrefix = iamConfigPolicyDBPrefix + "users/" 63 iamConfigPolicyDBSTSUsersPrefix = iamConfigPolicyDBPrefix + "sts-users/" 64 iamConfigPolicyDBServiceAccountsPrefix = iamConfigPolicyDBPrefix + "service-accounts/" 65 iamConfigPolicyDBGroupsPrefix = iamConfigPolicyDBPrefix + "groups/" 66 67 // IAM identity file which captures identity credentials. 68 iamIdentityFile = "identity.json" 69 70 // IAM policy file which provides policies for each users. 71 iamPolicyFile = "policy.json" 72 73 // IAM group members file 74 iamGroupMembersFile = "members.json" 75 76 // IAM format file 77 iamFormatFile = "format.json" 78 79 iamFormatVersion1 = 1 80 81 minServiceAccountExpiry time.Duration = 15 * time.Minute 82 maxServiceAccountExpiry time.Duration = 365 * 24 * time.Hour 83 ) 84 85 var errInvalidSvcAcctExpiration = errors.New("invalid service account expiration") 86 87 type iamFormat struct { 88 Version int `json:"version"` 89 } 90 91 func newIAMFormatVersion1() iamFormat { 92 return iamFormat{Version: iamFormatVersion1} 93 } 94 95 func getIAMFormatFilePath() string { 96 return iamConfigPrefix + SlashSeparator + iamFormatFile 97 } 98 99 func getUserIdentityPath(user string, userType IAMUserType) string { 100 var basePath string 101 switch userType { 102 case svcUser: 103 basePath = iamConfigServiceAccountsPrefix 104 case stsUser: 105 basePath = iamConfigSTSPrefix 106 default: 107 basePath = iamConfigUsersPrefix 108 } 109 return pathJoin(basePath, user, iamIdentityFile) 110 } 111 112 func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error { 113 bootstrapTraceMsg("Load IAM format file") 114 var iamFmt iamFormat 115 path := getIAMFormatFilePath() 116 if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil && !errors.Is(err, errConfigNotFound) { 117 // if IAM format 118 return err 119 } 120 121 if iamFmt.Version >= iamFormatVersion1 { 122 // Nothing to do. 123 return nil 124 } 125 126 bootstrapTraceMsg("Write IAM format file") 127 // Save iam format to version 1. 128 return store.saveIAMConfig(ctx, newIAMFormatVersion1(), path) 129 } 130 131 func getGroupInfoPath(group string) string { 132 return pathJoin(iamConfigGroupsPrefix, group, iamGroupMembersFile) 133 } 134 135 func getPolicyDocPath(name string) string { 136 return pathJoin(iamConfigPoliciesPrefix, name, iamPolicyFile) 137 } 138 139 func getMappedPolicyPath(name string, userType IAMUserType, isGroup bool) string { 140 if isGroup { 141 return pathJoin(iamConfigPolicyDBGroupsPrefix, name+".json") 142 } 143 switch userType { 144 case svcUser: 145 return pathJoin(iamConfigPolicyDBServiceAccountsPrefix, name+".json") 146 case stsUser: 147 return pathJoin(iamConfigPolicyDBSTSUsersPrefix, name+".json") 148 default: 149 return pathJoin(iamConfigPolicyDBUsersPrefix, name+".json") 150 } 151 } 152 153 // UserIdentity represents a user's secret key and their status 154 type UserIdentity struct { 155 Version int `json:"version"` 156 Credentials auth.Credentials `json:"credentials"` 157 UpdatedAt time.Time `json:"updatedAt,omitempty"` 158 } 159 160 func newUserIdentity(cred auth.Credentials) UserIdentity { 161 return UserIdentity{Version: 1, Credentials: cred, UpdatedAt: UTCNow()} 162 } 163 164 // GroupInfo contains info about a group 165 type GroupInfo struct { 166 Version int `json:"version"` 167 Status string `json:"status"` 168 Members []string `json:"members"` 169 UpdatedAt time.Time `json:"updatedAt,omitempty"` 170 } 171 172 func newGroupInfo(members []string) GroupInfo { 173 return GroupInfo{Version: 1, Status: statusEnabled, Members: members, UpdatedAt: UTCNow()} 174 } 175 176 // MappedPolicy represents a policy name mapped to a user or group 177 type MappedPolicy struct { 178 Version int `json:"version"` 179 Policies string `json:"policy"` 180 UpdatedAt time.Time `json:"updatedAt,omitempty"` 181 } 182 183 // mappedPoliciesToMap copies the map of mapped policies to a regular map. 184 func mappedPoliciesToMap(m *xsync.MapOf[string, MappedPolicy]) map[string]MappedPolicy { 185 policies := make(map[string]MappedPolicy, m.Size()) 186 m.Range(func(k string, v MappedPolicy) bool { 187 policies[k] = v 188 return true 189 }) 190 return policies 191 } 192 193 // converts a mapped policy into a slice of distinct policies 194 func (mp MappedPolicy) toSlice() []string { 195 var policies []string 196 for _, policy := range strings.Split(mp.Policies, ",") { 197 if strings.TrimSpace(policy) == "" { 198 continue 199 } 200 policies = append(policies, policy) 201 } 202 return policies 203 } 204 205 func (mp MappedPolicy) policySet() set.StringSet { 206 return set.CreateStringSet(mp.toSlice()...) 207 } 208 209 func newMappedPolicy(policy string) MappedPolicy { 210 return MappedPolicy{Version: 1, Policies: policy, UpdatedAt: UTCNow()} 211 } 212 213 // PolicyDoc represents an IAM policy with some metadata. 214 type PolicyDoc struct { 215 Version int `json:",omitempty"` 216 Policy policy.Policy 217 CreateDate time.Time `json:",omitempty"` 218 UpdateDate time.Time `json:",omitempty"` 219 } 220 221 func newPolicyDoc(p policy.Policy) PolicyDoc { 222 now := UTCNow().Round(time.Millisecond) 223 return PolicyDoc{ 224 Version: 1, 225 Policy: p, 226 CreateDate: now, 227 UpdateDate: now, 228 } 229 } 230 231 // defaultPolicyDoc - used to wrap a default policy as PolicyDoc. 232 func defaultPolicyDoc(p policy.Policy) PolicyDoc { 233 return PolicyDoc{ 234 Version: 1, 235 Policy: p, 236 } 237 } 238 239 func (d *PolicyDoc) update(p policy.Policy) { 240 now := UTCNow().Round(time.Millisecond) 241 d.UpdateDate = now 242 if d.CreateDate.IsZero() { 243 d.CreateDate = now 244 } 245 d.Policy = p 246 } 247 248 // parseJSON parses both the old and the new format for storing policy 249 // definitions. 250 // 251 // The on-disk format of policy definitions has changed (around early 12/2021) 252 // from policy.Policy to PolicyDoc. To avoid a migration, loading supports 253 // both the old and the new formats. 254 func (d *PolicyDoc) parseJSON(data []byte) error { 255 json := jsoniter.ConfigCompatibleWithStandardLibrary 256 var doc PolicyDoc 257 err := json.Unmarshal(data, &doc) 258 if err != nil { 259 err2 := json.Unmarshal(data, &doc.Policy) 260 if err2 != nil { 261 // Just return the first error. 262 return err 263 } 264 d.Policy = doc.Policy 265 return nil 266 } 267 *d = doc 268 return nil 269 } 270 271 // key options 272 type options struct { 273 ttl int64 // expiry in seconds 274 } 275 276 type iamWatchEvent struct { 277 isCreated bool // !isCreated implies a delete event. 278 keyPath string 279 } 280 281 // iamCache contains in-memory cache of IAM data. 282 type iamCache struct { 283 updatedAt time.Time 284 285 // map of policy names to policy definitions 286 iamPolicyDocsMap map[string]PolicyDoc 287 288 // map of regular username to credentials 289 iamUsersMap map[string]UserIdentity 290 // map of regular username to policy names 291 iamUserPolicyMap *xsync.MapOf[string, MappedPolicy] 292 293 // STS accounts are loaded on demand and not via the periodic IAM reload. 294 // map of STS access key to credentials 295 iamSTSAccountsMap map[string]UserIdentity 296 // map of STS access key to policy names 297 iamSTSPolicyMap *xsync.MapOf[string, MappedPolicy] 298 299 // map of group names to group info 300 iamGroupsMap map[string]GroupInfo 301 // map of user names to groups they are a member of 302 iamUserGroupMemberships map[string]set.StringSet 303 // map of group names to policy names 304 iamGroupPolicyMap *xsync.MapOf[string, MappedPolicy] 305 } 306 307 func newIamCache() *iamCache { 308 return &iamCache{ 309 iamPolicyDocsMap: map[string]PolicyDoc{}, 310 iamUsersMap: map[string]UserIdentity{}, 311 iamUserPolicyMap: xsync.NewMapOf[string, MappedPolicy](), 312 iamSTSAccountsMap: map[string]UserIdentity{}, 313 iamSTSPolicyMap: xsync.NewMapOf[string, MappedPolicy](), 314 iamGroupsMap: map[string]GroupInfo{}, 315 iamUserGroupMemberships: map[string]set.StringSet{}, 316 iamGroupPolicyMap: xsync.NewMapOf[string, MappedPolicy](), 317 } 318 } 319 320 // buildUserGroupMemberships - builds the memberships map. IMPORTANT: 321 // Assumes that c.Lock is held by caller. 322 func (c *iamCache) buildUserGroupMemberships() { 323 for group, gi := range c.iamGroupsMap { 324 c.updateGroupMembershipsMap(group, &gi) 325 } 326 } 327 328 // updateGroupMembershipsMap - updates the memberships map for a 329 // group. IMPORTANT: Assumes c.Lock() is held by caller. 330 func (c *iamCache) updateGroupMembershipsMap(group string, gi *GroupInfo) { 331 if gi == nil { 332 return 333 } 334 for _, member := range gi.Members { 335 v := c.iamUserGroupMemberships[member] 336 if v == nil { 337 v = set.CreateStringSet(group) 338 } else { 339 v.Add(group) 340 } 341 c.iamUserGroupMemberships[member] = v 342 } 343 } 344 345 // removeGroupFromMembershipsMap - removes the group from every member 346 // in the cache. IMPORTANT: Assumes c.Lock() is held by caller. 347 func (c *iamCache) removeGroupFromMembershipsMap(group string) { 348 for member, groups := range c.iamUserGroupMemberships { 349 if !groups.Contains(group) { 350 continue 351 } 352 groups.Remove(group) 353 c.iamUserGroupMemberships[member] = groups 354 } 355 } 356 357 // policyDBGet - lower-level helper; does not take locks. 358 // 359 // If a group is passed, it returns policies associated with the group. 360 // 361 // If a user is passed, it returns policies of the user along with any groups 362 // that the server knows the user is a member of. 363 // 364 // In LDAP users mode, the server does not store any group membership 365 // information in IAM (i.e sys.iam*Map) - this info is stored only in the STS 366 // generated credentials. Thus we skip looking up group memberships, user map, 367 // and group map and check the appropriate policy maps directly. 368 func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([]string, time.Time, error) { 369 if isGroup { 370 if store.getUsersSysType() == MinIOUsersSysType { 371 g, ok := c.iamGroupsMap[name] 372 if !ok { 373 if err := store.loadGroup(context.Background(), name, c.iamGroupsMap); err != nil { 374 return nil, time.Time{}, err 375 } 376 g, ok = c.iamGroupsMap[name] 377 if !ok { 378 return nil, time.Time{}, errNoSuchGroup 379 } 380 } 381 382 // Group is disabled, so we return no policy - this 383 // ensures the request is denied. 384 if g.Status == statusDisabled { 385 return nil, time.Time{}, nil 386 } 387 } 388 389 policy, ok := c.iamGroupPolicyMap.Load(name) 390 if ok { 391 return policy.toSlice(), policy.UpdatedAt, nil 392 } 393 if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { 394 return nil, time.Time{}, err 395 } 396 policy, _ = c.iamGroupPolicyMap.Load(name) 397 return policy.toSlice(), policy.UpdatedAt, nil 398 } 399 400 // When looking for a user's policies, we also check if the user 401 // and the groups they are member of are enabled. 402 u, ok := c.iamUsersMap[name] 403 if ok { 404 if !u.Credentials.IsValid() { 405 return nil, time.Time{}, nil 406 } 407 } 408 409 // For internal IDP regular/service account user accounts, the policy 410 // mapping is iamUserPolicyMap. For STS accounts, the parent user would be 411 // passed here and we lookup the mapping in iamSTSPolicyMap. 412 mp, ok := c.iamUserPolicyMap.Load(name) 413 if !ok { 414 if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, false, c.iamUserPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { 415 return nil, time.Time{}, err 416 } 417 418 mp, ok = c.iamUserPolicyMap.Load(name) 419 if !ok { 420 // Since user "name" could be a parent user of an STS account, we look up 421 // mappings for those too. 422 mp, ok = c.iamSTSPolicyMap.Load(name) 423 if !ok { 424 // Attempt to load parent user mapping for STS accounts 425 if err := store.loadMappedPolicyWithRetry(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { 426 return nil, time.Time{}, err 427 } 428 mp, _ = c.iamSTSPolicyMap.Load(name) 429 } 430 } 431 } 432 433 // returned policy could be empty 434 policies := mp.toSlice() 435 436 for _, group := range c.iamUserGroupMemberships[name].ToSlice() { 437 if store.getUsersSysType() == MinIOUsersSysType { 438 g, ok := c.iamGroupsMap[group] 439 if !ok { 440 if err := store.loadGroup(context.Background(), group, c.iamGroupsMap); err != nil { 441 return nil, time.Time{}, err 442 } 443 g, ok = c.iamGroupsMap[group] 444 if !ok { 445 return nil, time.Time{}, errNoSuchGroup 446 } 447 } 448 449 // Group is disabled, so we return no policy - this 450 // ensures the request is denied. 451 if g.Status == statusDisabled { 452 return nil, time.Time{}, nil 453 } 454 } 455 456 policy, ok := c.iamGroupPolicyMap.Load(group) 457 if !ok { 458 if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { 459 return nil, time.Time{}, err 460 } 461 policy, _ = c.iamGroupPolicyMap.Load(group) 462 } 463 464 policies = append(policies, policy.toSlice()...) 465 } 466 467 return policies, mp.UpdatedAt, nil 468 } 469 470 func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error { 471 if u.Credentials.SessionToken != "" { 472 jwtClaims, err := extractJWTClaims(u) 473 if err != nil { 474 return err 475 } 476 u.Credentials.Claims = jwtClaims.Map() 477 } 478 if u.Credentials.IsTemp() && !u.Credentials.IsServiceAccount() { 479 c.iamSTSAccountsMap[key] = u 480 } else { 481 c.iamUsersMap[key] = u 482 } 483 c.updatedAt = time.Now() 484 return nil 485 } 486 487 // IAMStorageAPI defines an interface for the IAM persistence layer 488 type IAMStorageAPI interface { 489 // The role of the read-write lock is to prevent go routines from 490 // concurrently reading and writing the IAM storage. The (r)lock() 491 // functions return the iamCache. The cache can be safely written to 492 // only when returned by `lock()`. 493 lock() *iamCache 494 unlock() 495 rlock() *iamCache 496 runlock() 497 getUsersSysType() UsersSysType 498 loadPolicyDoc(ctx context.Context, policy string, m map[string]PolicyDoc) error 499 loadPolicyDocWithRetry(ctx context.Context, policy string, m map[string]PolicyDoc, retries int) error 500 loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error 501 loadUser(ctx context.Context, user string, userType IAMUserType, m map[string]UserIdentity) error 502 loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error 503 loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error 504 loadGroups(ctx context.Context, m map[string]GroupInfo) error 505 loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error 506 loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error 507 loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error 508 saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error 509 loadIAMConfig(ctx context.Context, item interface{}, path string) error 510 deleteIAMConfig(ctx context.Context, path string) error 511 savePolicyDoc(ctx context.Context, policyName string, p PolicyDoc) error 512 saveMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, mp MappedPolicy, opts ...options) error 513 saveUserIdentity(ctx context.Context, name string, userType IAMUserType, u UserIdentity, opts ...options) error 514 saveGroupInfo(ctx context.Context, group string, gi GroupInfo) error 515 deletePolicyDoc(ctx context.Context, policyName string) error 516 deleteMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool) error 517 deleteUserIdentity(ctx context.Context, name string, userType IAMUserType) error 518 deleteGroupInfo(ctx context.Context, name string) error 519 } 520 521 // iamStorageWatcher is implemented by `IAMStorageAPI` implementers that 522 // additionally support watching storage for changes. 523 type iamStorageWatcher interface { 524 watch(ctx context.Context, keyPath string) <-chan iamWatchEvent 525 } 526 527 // Set default canned policies only if not already overridden by users. 528 func setDefaultCannedPolicies(policies map[string]PolicyDoc) { 529 for _, v := range policy.DefaultPolicies { 530 if _, ok := policies[v.Name]; !ok { 531 policies[v.Name] = defaultPolicyDoc(v.Definition) 532 } 533 } 534 } 535 536 // PurgeExpiredSTS - purges expired STS credentials. 537 func (store *IAMStoreSys) PurgeExpiredSTS(ctx context.Context) error { 538 iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore) 539 if !ok { 540 // No purging is done for non-object storage. 541 return nil 542 } 543 return iamOS.PurgeExpiredSTS(ctx) 544 } 545 546 // LoadIAMCache reads all IAM items and populates a new iamCache object and 547 // replaces the in-memory cache object. 548 func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) error { 549 bootstrapTraceMsg := func(s string) { 550 if firstTime { 551 bootstrapTraceMsg(s) 552 } 553 } 554 bootstrapTraceMsg("loading IAM data") 555 556 newCache := newIamCache() 557 558 loadedAt := time.Now() 559 560 if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok { 561 err := iamOS.loadAllFromObjStore(ctx, newCache) 562 if err != nil { 563 return err 564 } 565 } else { 566 567 bootstrapTraceMsg("loading policy documents") 568 if err := store.loadPolicyDocs(ctx, newCache.iamPolicyDocsMap); err != nil { 569 return err 570 } 571 572 // Sets default canned policies, if none are set. 573 setDefaultCannedPolicies(newCache.iamPolicyDocsMap) 574 575 if store.getUsersSysType() == MinIOUsersSysType { 576 bootstrapTraceMsg("loading regular users") 577 if err := store.loadUsers(ctx, regUser, newCache.iamUsersMap); err != nil { 578 return err 579 } 580 bootstrapTraceMsg("loading regular groups") 581 if err := store.loadGroups(ctx, newCache.iamGroupsMap); err != nil { 582 return err 583 } 584 } 585 586 bootstrapTraceMsg("loading user policy mapping") 587 // load polices mapped to users 588 if err := store.loadMappedPolicies(ctx, regUser, false, newCache.iamUserPolicyMap); err != nil { 589 return err 590 } 591 592 bootstrapTraceMsg("loading group policy mapping") 593 // load policies mapped to groups 594 if err := store.loadMappedPolicies(ctx, regUser, true, newCache.iamGroupPolicyMap); err != nil { 595 return err 596 } 597 598 bootstrapTraceMsg("loading service accounts") 599 // load service accounts 600 if err := store.loadUsers(ctx, svcUser, newCache.iamUsersMap); err != nil { 601 return err 602 } 603 604 newCache.buildUserGroupMemberships() 605 } 606 607 cache := store.lock() 608 defer store.unlock() 609 610 // We should only update the in-memory cache if there were no changes 611 // to the in-memory cache since the disk loading began. If there 612 // were changes to the in-memory cache we should wait for the next 613 // cycle until we can safely update the in-memory cache. 614 // 615 // An in-memory cache must be replaced only if we know for sure that the 616 // values loaded from disk are not stale. They might be stale if the 617 // cached.updatedAt is more recent than the refresh cycle began. 618 if cache.updatedAt.Before(loadedAt) { 619 // No one has updated anything since the config was loaded, 620 // so we just replace whatever is on the disk into memory. 621 cache.iamGroupPolicyMap = newCache.iamGroupPolicyMap 622 cache.iamGroupsMap = newCache.iamGroupsMap 623 cache.iamPolicyDocsMap = newCache.iamPolicyDocsMap 624 cache.iamUserGroupMemberships = newCache.iamUserGroupMemberships 625 cache.iamUserPolicyMap = newCache.iamUserPolicyMap 626 cache.iamUsersMap = newCache.iamUsersMap 627 // For STS policy map, we need to merge the new cache with the existing 628 // cache because the periodic IAM reload is partial. The periodic load 629 // here is to account for STS policy mapping changes that should apply 630 // for service accounts derived from such STS accounts (i.e. LDAP STS 631 // accounts). 632 newCache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool { 633 cache.iamSTSPolicyMap.Store(k, v) 634 return true 635 }) 636 637 cache.updatedAt = time.Now() 638 } 639 640 return nil 641 } 642 643 // IAMStoreSys contains IAMStorageAPI to add higher-level methods on the storage 644 // layer. 645 type IAMStoreSys struct { 646 IAMStorageAPI 647 } 648 649 // HasWatcher - returns if the storage system has a watcher. 650 func (store *IAMStoreSys) HasWatcher() bool { 651 _, ok := store.IAMStorageAPI.(iamStorageWatcher) 652 return ok 653 } 654 655 // GetUser - fetches credential from memory. 656 func (store *IAMStoreSys) GetUser(user string) (UserIdentity, bool) { 657 cache := store.rlock() 658 defer store.runlock() 659 660 u, ok := cache.iamUsersMap[user] 661 if !ok { 662 // Check the sts map 663 u, ok = cache.iamSTSAccountsMap[user] 664 } 665 return u, ok 666 } 667 668 // GetMappedPolicy - fetches mapped policy from memory. 669 func (store *IAMStoreSys) GetMappedPolicy(name string, isGroup bool) (MappedPolicy, bool) { 670 cache := store.rlock() 671 defer store.runlock() 672 673 if isGroup { 674 v, ok := cache.iamGroupPolicyMap.Load(name) 675 return v, ok 676 } 677 return cache.iamUserPolicyMap.Load(name) 678 } 679 680 // GroupNotificationHandler - updates in-memory cache on notification of 681 // change (e.g. peer notification for object storage and etcd watch 682 // notification). 683 func (store *IAMStoreSys) GroupNotificationHandler(ctx context.Context, group string) error { 684 cache := store.lock() 685 defer store.unlock() 686 687 err := store.loadGroup(ctx, group, cache.iamGroupsMap) 688 if err != nil && err != errNoSuchGroup { 689 return err 690 } 691 692 if err == errNoSuchGroup { 693 // group does not exist - so remove from memory. 694 cache.removeGroupFromMembershipsMap(group) 695 delete(cache.iamGroupsMap, group) 696 cache.iamGroupPolicyMap.Delete(group) 697 698 cache.updatedAt = time.Now() 699 return nil 700 } 701 702 gi := cache.iamGroupsMap[group] 703 704 // Updating the group memberships cache happens in two steps: 705 // 706 // 1. Remove the group from each user's list of memberships. 707 // 2. Add the group to each member's list of memberships. 708 // 709 // This ensures that regardless of members being added or 710 // removed, the cache stays current. 711 cache.removeGroupFromMembershipsMap(group) 712 cache.updateGroupMembershipsMap(group, &gi) 713 cache.updatedAt = time.Now() 714 return nil 715 } 716 717 // PolicyDBGet - fetches policies associated with the given user or group, and 718 // additional groups if provided. 719 func (store *IAMStoreSys) PolicyDBGet(name string, groups ...string) ([]string, error) { 720 if name == "" { 721 return nil, errInvalidArgument 722 } 723 724 cache := store.rlock() 725 defer store.runlock() 726 727 policies, _, err := cache.policyDBGet(store, name, false) 728 if err != nil { 729 return nil, err 730 } 731 732 for _, group := range groups { 733 ps, _, err := cache.policyDBGet(store, group, true) 734 if err != nil { 735 return nil, err 736 } 737 policies = append(policies, ps...) 738 } 739 740 return policies, nil 741 } 742 743 // AddUsersToGroup - adds users to group, creating the group if needed. 744 func (store *IAMStoreSys) AddUsersToGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { 745 if group == "" { 746 return updatedAt, errInvalidArgument 747 } 748 749 cache := store.lock() 750 defer store.unlock() 751 752 // Validate that all members exist. 753 for _, member := range members { 754 u, ok := cache.iamUsersMap[member] 755 if !ok { 756 return updatedAt, errNoSuchUser 757 } 758 cr := u.Credentials 759 if cr.IsTemp() || cr.IsServiceAccount() { 760 return updatedAt, errIAMActionNotAllowed 761 } 762 } 763 764 gi, ok := cache.iamGroupsMap[group] 765 if !ok { 766 // Set group as enabled by default when it doesn't 767 // exist. 768 gi = newGroupInfo(members) 769 } else { 770 gi.Members = set.CreateStringSet(append(gi.Members, members...)...).ToSlice() 771 gi.UpdatedAt = UTCNow() 772 } 773 774 if err := store.saveGroupInfo(ctx, group, gi); err != nil { 775 return updatedAt, err 776 } 777 778 cache.iamGroupsMap[group] = gi 779 780 // update user-group membership map 781 for _, member := range members { 782 gset := cache.iamUserGroupMemberships[member] 783 if gset == nil { 784 gset = set.CreateStringSet(group) 785 } else { 786 gset.Add(group) 787 } 788 cache.iamUserGroupMemberships[member] = gset 789 } 790 791 cache.updatedAt = time.Now() 792 return gi.UpdatedAt, nil 793 } 794 795 // helper function - does not take any locks. Updates only cache if 796 // updateCacheOnly is set. 797 func removeMembersFromGroup(ctx context.Context, store *IAMStoreSys, cache *iamCache, group string, members []string, updateCacheOnly bool) (updatedAt time.Time, err error) { 798 gi, ok := cache.iamGroupsMap[group] 799 if !ok { 800 return updatedAt, errNoSuchGroup 801 } 802 803 s := set.CreateStringSet(gi.Members...) 804 d := set.CreateStringSet(members...) 805 gi.Members = s.Difference(d).ToSlice() 806 807 if !updateCacheOnly { 808 err := store.saveGroupInfo(ctx, group, gi) 809 if err != nil { 810 return updatedAt, err 811 } 812 } 813 gi.UpdatedAt = UTCNow() 814 cache.iamGroupsMap[group] = gi 815 816 // update user-group membership map 817 for _, member := range members { 818 gset := cache.iamUserGroupMemberships[member] 819 if gset == nil { 820 continue 821 } 822 gset.Remove(group) 823 cache.iamUserGroupMemberships[member] = gset 824 } 825 826 cache.updatedAt = time.Now() 827 return gi.UpdatedAt, nil 828 } 829 830 // RemoveUsersFromGroup - removes users from group, deleting it if it is empty. 831 func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string, members []string) (updatedAt time.Time, err error) { 832 if group == "" { 833 return updatedAt, errInvalidArgument 834 } 835 836 cache := store.lock() 837 defer store.unlock() 838 839 // Validate that all members exist. 840 for _, member := range members { 841 u, ok := cache.iamUsersMap[member] 842 if !ok { 843 return updatedAt, errNoSuchUser 844 } 845 cr := u.Credentials 846 if cr.IsTemp() || cr.IsServiceAccount() { 847 return updatedAt, errIAMActionNotAllowed 848 } 849 } 850 851 gi, ok := cache.iamGroupsMap[group] 852 if !ok { 853 return updatedAt, errNoSuchGroup 854 } 855 856 // Check if attempting to delete a non-empty group. 857 if len(members) == 0 && len(gi.Members) != 0 { 858 return updatedAt, errGroupNotEmpty 859 } 860 861 if len(members) == 0 { 862 // len(gi.Members) == 0 here. 863 864 // Remove the group from storage. First delete the 865 // mapped policy. No-mapped-policy case is ignored. 866 if err := store.deleteMappedPolicy(ctx, group, regUser, true); err != nil && !errors.Is(err, errNoSuchPolicy) { 867 return updatedAt, err 868 } 869 if err := store.deleteGroupInfo(ctx, group); err != nil && err != errNoSuchGroup { 870 return updatedAt, err 871 } 872 873 // Delete from server memory 874 delete(cache.iamGroupsMap, group) 875 cache.iamGroupPolicyMap.Delete(group) 876 cache.updatedAt = time.Now() 877 return cache.updatedAt, nil 878 } 879 880 return removeMembersFromGroup(ctx, store, cache, group, members, false) 881 } 882 883 // SetGroupStatus - updates group status 884 func (store *IAMStoreSys) SetGroupStatus(ctx context.Context, group string, enabled bool) (updatedAt time.Time, err error) { 885 if group == "" { 886 return updatedAt, errInvalidArgument 887 } 888 889 cache := store.lock() 890 defer store.unlock() 891 892 gi, ok := cache.iamGroupsMap[group] 893 if !ok { 894 return updatedAt, errNoSuchGroup 895 } 896 897 if enabled { 898 gi.Status = statusEnabled 899 } else { 900 gi.Status = statusDisabled 901 } 902 gi.UpdatedAt = UTCNow() 903 if err := store.saveGroupInfo(ctx, group, gi); err != nil { 904 return gi.UpdatedAt, err 905 } 906 907 cache.iamGroupsMap[group] = gi 908 cache.updatedAt = time.Now() 909 910 return gi.UpdatedAt, nil 911 } 912 913 // GetGroupDescription - builds up group description 914 func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc, err error) { 915 cache := store.rlock() 916 defer store.runlock() 917 918 ps, updatedAt, err := cache.policyDBGet(store, group, true) 919 if err != nil { 920 return gd, err 921 } 922 923 policy := strings.Join(ps, ",") 924 925 if store.getUsersSysType() != MinIOUsersSysType { 926 return madmin.GroupDesc{ 927 Name: group, 928 Policy: policy, 929 UpdatedAt: updatedAt, 930 }, nil 931 } 932 933 gi, ok := cache.iamGroupsMap[group] 934 if !ok { 935 return gd, errNoSuchGroup 936 } 937 938 return madmin.GroupDesc{ 939 Name: group, 940 Status: gi.Status, 941 Members: gi.Members, 942 Policy: policy, 943 UpdatedAt: gi.UpdatedAt, 944 }, nil 945 } 946 947 // ListGroups - lists groups. Since this is not going to be a frequent 948 // operation, we fetch this info from storage, and refresh the cache as well. 949 func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) { 950 cache := store.lock() 951 defer store.unlock() 952 953 if store.getUsersSysType() == MinIOUsersSysType { 954 m := map[string]GroupInfo{} 955 err = store.loadGroups(ctx, m) 956 if err != nil { 957 return 958 } 959 cache.iamGroupsMap = m 960 cache.updatedAt = time.Now() 961 for k := range cache.iamGroupsMap { 962 res = append(res, k) 963 } 964 } 965 966 if store.getUsersSysType() == LDAPUsersSysType { 967 m := xsync.NewMapOf[string, MappedPolicy]() 968 err = store.loadMappedPolicies(ctx, stsUser, true, m) 969 if err != nil { 970 return 971 } 972 cache.iamGroupPolicyMap = m 973 cache.updatedAt = time.Now() 974 cache.iamGroupPolicyMap.Range(func(k string, v MappedPolicy) bool { 975 res = append(res, k) 976 return true 977 }) 978 } 979 980 return 981 } 982 983 // listGroups - lists groups - fetch groups from cache 984 func (store *IAMStoreSys) listGroups(ctx context.Context) (res []string, err error) { 985 cache := store.rlock() 986 defer store.runlock() 987 988 if store.getUsersSysType() == MinIOUsersSysType { 989 for k := range cache.iamGroupsMap { 990 res = append(res, k) 991 } 992 } 993 994 if store.getUsersSysType() == LDAPUsersSysType { 995 cache.iamGroupPolicyMap.Range(func(k string, _ MappedPolicy) bool { 996 res = append(res, k) 997 return true 998 }) 999 } 1000 return 1001 } 1002 1003 // PolicyDBUpdate - adds or removes given policies to/from the user or group's 1004 // policy associations. 1005 func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGroup bool, 1006 userType IAMUserType, policies []string, isAttach bool) (updatedAt time.Time, 1007 addedOrRemoved, effectivePolicies []string, err error, 1008 ) { 1009 if name == "" { 1010 err = errInvalidArgument 1011 return 1012 } 1013 1014 cache := store.lock() 1015 defer store.unlock() 1016 1017 // Load existing policy mapping 1018 var mp MappedPolicy 1019 if !isGroup { 1020 if userType == stsUser { 1021 stsMap := xsync.NewMapOf[string, MappedPolicy]() 1022 1023 // Attempt to load parent user mapping for STS accounts 1024 store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap) 1025 1026 mp, _ = stsMap.Load(name) 1027 } else { 1028 mp, _ = cache.iamUserPolicyMap.Load(name) 1029 } 1030 } else { 1031 if store.getUsersSysType() == MinIOUsersSysType { 1032 g, ok := cache.iamGroupsMap[name] 1033 if !ok { 1034 err = errNoSuchGroup 1035 return 1036 } 1037 1038 if g.Status == statusDisabled { 1039 err = errGroupDisabled 1040 return 1041 } 1042 } 1043 mp, _ = cache.iamGroupPolicyMap.Load(name) 1044 } 1045 1046 // Compute net policy change effect and updated policy mapping 1047 existingPolicySet := mp.policySet() 1048 policiesToUpdate := set.CreateStringSet(policies...) 1049 var newPolicySet set.StringSet 1050 newPolicyMapping := mp 1051 if isAttach { 1052 // new policies to attach => inputPolicies - existing (set difference) 1053 policiesToUpdate = policiesToUpdate.Difference(existingPolicySet) 1054 // validate that new policies to add are defined. 1055 for _, p := range policiesToUpdate.ToSlice() { 1056 if _, found := cache.iamPolicyDocsMap[p]; !found { 1057 err = errNoSuchPolicy 1058 return 1059 } 1060 } 1061 newPolicySet = existingPolicySet.Union(policiesToUpdate) 1062 } else { 1063 // policies to detach => inputPolicies ∩ existing (intersection) 1064 policiesToUpdate = policiesToUpdate.Intersection(existingPolicySet) 1065 newPolicySet = existingPolicySet.Difference(policiesToUpdate) 1066 } 1067 // We return an error if the requested policy update will have no effect. 1068 if policiesToUpdate.IsEmpty() { 1069 err = errNoPolicyToAttachOrDetach 1070 return 1071 } 1072 1073 newPolicies := newPolicySet.ToSlice() 1074 newPolicyMapping.Policies = strings.Join(newPolicies, ",") 1075 newPolicyMapping.UpdatedAt = UTCNow() 1076 addedOrRemoved = policiesToUpdate.ToSlice() 1077 1078 // In case of detach operation, it is possible that no policies are mapped - 1079 // in this case, we delete the mapping from the store. 1080 if len(newPolicies) == 0 { 1081 if err = store.deleteMappedPolicy(ctx, name, userType, isGroup); err != nil && !errors.Is(err, errNoSuchPolicy) { 1082 return 1083 } 1084 if !isGroup { 1085 if userType == stsUser { 1086 cache.iamSTSPolicyMap.Delete(name) 1087 } else { 1088 cache.iamUserPolicyMap.Delete(name) 1089 } 1090 } else { 1091 cache.iamGroupPolicyMap.Delete(name) 1092 } 1093 } else { 1094 1095 if err = store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil { 1096 return 1097 } 1098 if !isGroup { 1099 if userType == stsUser { 1100 cache.iamSTSPolicyMap.Store(name, newPolicyMapping) 1101 } else { 1102 cache.iamUserPolicyMap.Store(name, newPolicyMapping) 1103 } 1104 } else { 1105 cache.iamGroupPolicyMap.Store(name, newPolicyMapping) 1106 } 1107 } 1108 1109 cache.updatedAt = UTCNow() 1110 return cache.updatedAt, addedOrRemoved, newPolicies, nil 1111 } 1112 1113 // PolicyDBSet - update the policy mapping for the given user or group in 1114 // storage and in cache. We do not check for the existence of the user here 1115 // since users can be virtual, such as for: 1116 // - LDAP users 1117 // - CommonName for STS accounts generated by AssumeRoleWithCertificate 1118 func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string, userType IAMUserType, isGroup bool) (updatedAt time.Time, err error) { 1119 if name == "" { 1120 return updatedAt, errInvalidArgument 1121 } 1122 1123 cache := store.lock() 1124 defer store.unlock() 1125 1126 // Handle policy mapping removal. 1127 if policy == "" { 1128 if store.getUsersSysType() == LDAPUsersSysType { 1129 // Add a fallback removal towards previous content that may come back 1130 // as a ghost user due to lack of delete, this change occurred 1131 // introduced in PR #11840 1132 store.deleteMappedPolicy(ctx, name, regUser, false) 1133 } 1134 err := store.deleteMappedPolicy(ctx, name, userType, isGroup) 1135 if err != nil && !errors.Is(err, errNoSuchPolicy) { 1136 return updatedAt, err 1137 } 1138 if !isGroup { 1139 if userType == stsUser { 1140 cache.iamSTSPolicyMap.Delete(name) 1141 } else { 1142 cache.iamUserPolicyMap.Delete(name) 1143 } 1144 } else { 1145 cache.iamGroupPolicyMap.Delete(name) 1146 } 1147 cache.updatedAt = time.Now() 1148 return cache.updatedAt, nil 1149 } 1150 1151 // Handle policy mapping set/update 1152 mp := newMappedPolicy(policy) 1153 for _, p := range mp.toSlice() { 1154 if _, found := cache.iamPolicyDocsMap[p]; !found { 1155 return updatedAt, errNoSuchPolicy 1156 } 1157 } 1158 1159 if err := store.saveMappedPolicy(ctx, name, userType, isGroup, mp); err != nil { 1160 return updatedAt, err 1161 } 1162 if !isGroup { 1163 if userType == stsUser { 1164 cache.iamSTSPolicyMap.Store(name, mp) 1165 } else { 1166 cache.iamUserPolicyMap.Store(name, mp) 1167 } 1168 } else { 1169 cache.iamGroupPolicyMap.Store(name, mp) 1170 } 1171 cache.updatedAt = time.Now() 1172 return mp.UpdatedAt, nil 1173 } 1174 1175 // PolicyNotificationHandler - loads given policy from storage. If not present, 1176 // deletes from cache. This notification only reads from storage, and updates 1177 // cache. When the notification is for a policy deletion, it updates the 1178 // user-policy and group-policy maps as well. 1179 func (store *IAMStoreSys) PolicyNotificationHandler(ctx context.Context, policy string) error { 1180 if policy == "" { 1181 return errInvalidArgument 1182 } 1183 1184 cache := store.lock() 1185 defer store.unlock() 1186 1187 err := store.loadPolicyDoc(ctx, policy, cache.iamPolicyDocsMap) 1188 if errors.Is(err, errNoSuchPolicy) { 1189 // policy was deleted, update cache. 1190 delete(cache.iamPolicyDocsMap, policy) 1191 1192 // update user policy map 1193 cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool { 1194 pset := mp.policySet() 1195 if !pset.Contains(policy) { 1196 return true 1197 } 1198 if store.getUsersSysType() == MinIOUsersSysType { 1199 _, ok := cache.iamUsersMap[u] 1200 if !ok { 1201 // happens when account is deleted or 1202 // expired. 1203 cache.iamUserPolicyMap.Delete(u) 1204 return true 1205 } 1206 } 1207 pset.Remove(policy) 1208 cache.iamUserPolicyMap.Store(u, newMappedPolicy(strings.Join(pset.ToSlice(), ","))) 1209 return true 1210 }) 1211 1212 // update group policy map 1213 cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool { 1214 pset := mp.policySet() 1215 if !pset.Contains(policy) { 1216 return true 1217 } 1218 pset.Remove(policy) 1219 cache.iamGroupPolicyMap.Store(g, newMappedPolicy(strings.Join(pset.ToSlice(), ","))) 1220 return true 1221 }) 1222 1223 cache.updatedAt = time.Now() 1224 return nil 1225 } 1226 return err 1227 } 1228 1229 // DeletePolicy - deletes policy from storage and cache. When this called in 1230 // response to a notification (i.e. isFromNotification = true), it skips the 1231 // validation of policy usage and the attempt to delete in the backend as well 1232 // (as this is already done by the notifying node). 1233 func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string, isFromNotification bool) error { 1234 if policy == "" { 1235 return errInvalidArgument 1236 } 1237 1238 cache := store.lock() 1239 defer store.unlock() 1240 1241 if !isFromNotification { 1242 // Check if policy is mapped to any existing user or group. If so, we do not 1243 // allow deletion of the policy. If the policy is mapped to an STS account, 1244 // we do allow deletion. 1245 users := []string{} 1246 groups := []string{} 1247 cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool { 1248 pset := mp.policySet() 1249 if store.getUsersSysType() == MinIOUsersSysType { 1250 if _, ok := cache.iamUsersMap[u]; !ok { 1251 // This case can happen when a temporary account is 1252 // deleted or expired - remove it from userPolicyMap. 1253 cache.iamUserPolicyMap.Delete(u) 1254 return true 1255 } 1256 } 1257 if pset.Contains(policy) { 1258 users = append(users, u) 1259 } 1260 return true 1261 }) 1262 cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool { 1263 pset := mp.policySet() 1264 if pset.Contains(policy) { 1265 groups = append(groups, g) 1266 } 1267 return true 1268 }) 1269 if len(users) != 0 || len(groups) != 0 { 1270 return errPolicyInUse 1271 } 1272 1273 err := store.deletePolicyDoc(ctx, policy) 1274 if errors.Is(err, errNoSuchPolicy) { 1275 // Ignore error if policy is already deleted. 1276 err = nil 1277 } 1278 if err != nil { 1279 return err 1280 } 1281 } 1282 1283 delete(cache.iamPolicyDocsMap, policy) 1284 cache.updatedAt = time.Now() 1285 1286 return nil 1287 } 1288 1289 // GetPolicy - gets the policy definition. Allows specifying multiple comma 1290 // separated policies - returns a combined policy. 1291 func (store *IAMStoreSys) GetPolicy(name string) (policy.Policy, error) { 1292 if name == "" { 1293 return policy.Policy{}, errInvalidArgument 1294 } 1295 1296 cache := store.rlock() 1297 defer store.runlock() 1298 1299 policies := newMappedPolicy(name).toSlice() 1300 var toMerge []policy.Policy 1301 for _, policy := range policies { 1302 if policy == "" { 1303 continue 1304 } 1305 v, ok := cache.iamPolicyDocsMap[policy] 1306 if !ok { 1307 return v.Policy, errNoSuchPolicy 1308 } 1309 toMerge = append(toMerge, v.Policy) 1310 } 1311 if len(toMerge) == 0 { 1312 return policy.Policy{}, errNoSuchPolicy 1313 } 1314 return policy.MergePolicies(toMerge...), nil 1315 } 1316 1317 // GetPolicyDoc - gets the policy doc which has the policy and some metadata. 1318 // Exactly one policy must be specified here. 1319 func (store *IAMStoreSys) GetPolicyDoc(name string) (r PolicyDoc, err error) { 1320 name = strings.TrimSpace(name) 1321 if name == "" { 1322 return r, errInvalidArgument 1323 } 1324 1325 cache := store.rlock() 1326 defer store.runlock() 1327 1328 v, ok := cache.iamPolicyDocsMap[name] 1329 if !ok { 1330 return r, errNoSuchPolicy 1331 } 1332 return v, nil 1333 } 1334 1335 // SetPolicy - creates a policy with name. 1336 func (store *IAMStoreSys) SetPolicy(ctx context.Context, name string, policy policy.Policy) (time.Time, error) { 1337 if policy.IsEmpty() || name == "" { 1338 return time.Time{}, errInvalidArgument 1339 } 1340 1341 cache := store.lock() 1342 defer store.unlock() 1343 1344 var ( 1345 d PolicyDoc 1346 ok bool 1347 ) 1348 if d, ok = cache.iamPolicyDocsMap[name]; ok { 1349 d.update(policy) 1350 } else { 1351 d = newPolicyDoc(policy) 1352 } 1353 1354 if err := store.savePolicyDoc(ctx, name, d); err != nil { 1355 return d.UpdateDate, err 1356 } 1357 1358 cache.iamPolicyDocsMap[name] = d 1359 cache.updatedAt = time.Now() 1360 1361 return d.UpdateDate, nil 1362 } 1363 1364 // ListPolicies - fetches all policies from storage and updates cache as well. 1365 // If bucketName is non-empty, returns policies matching the bucket. 1366 func (store *IAMStoreSys) ListPolicies(ctx context.Context, bucketName string) (map[string]policy.Policy, error) { 1367 cache := store.lock() 1368 defer store.unlock() 1369 1370 m := map[string]PolicyDoc{} 1371 err := store.loadPolicyDocs(ctx, m) 1372 if err != nil { 1373 return nil, err 1374 } 1375 1376 // Sets default canned policies 1377 setDefaultCannedPolicies(m) 1378 1379 cache.iamPolicyDocsMap = m 1380 cache.updatedAt = time.Now() 1381 1382 ret := map[string]policy.Policy{} 1383 for k, v := range m { 1384 if bucketName == "" || v.Policy.MatchResource(bucketName) { 1385 ret[k] = v.Policy 1386 } 1387 } 1388 1389 return ret, nil 1390 } 1391 1392 // ListPolicyDocs - fetches all policy docs from storage and updates cache as well. 1393 // If bucketName is non-empty, returns policy docs matching the bucket. 1394 func (store *IAMStoreSys) ListPolicyDocs(ctx context.Context, bucketName string) (map[string]PolicyDoc, error) { 1395 cache := store.lock() 1396 defer store.unlock() 1397 1398 m := map[string]PolicyDoc{} 1399 err := store.loadPolicyDocs(ctx, m) 1400 if err != nil { 1401 return nil, err 1402 } 1403 1404 // Sets default canned policies 1405 setDefaultCannedPolicies(m) 1406 1407 cache.iamPolicyDocsMap = m 1408 cache.updatedAt = time.Now() 1409 1410 ret := map[string]PolicyDoc{} 1411 for k, v := range m { 1412 if bucketName == "" || v.Policy.MatchResource(bucketName) { 1413 ret[k] = v 1414 } 1415 } 1416 1417 return ret, nil 1418 } 1419 1420 // fetches all policy docs from cache. 1421 // If bucketName is non-empty, returns policy docs matching the bucket. 1422 func (store *IAMStoreSys) listPolicyDocs(ctx context.Context, bucketName string) (map[string]PolicyDoc, error) { 1423 cache := store.rlock() 1424 defer store.runlock() 1425 ret := map[string]PolicyDoc{} 1426 for k, v := range cache.iamPolicyDocsMap { 1427 if bucketName == "" || v.Policy.MatchResource(bucketName) { 1428 ret[k] = v 1429 } 1430 } 1431 return ret, nil 1432 } 1433 1434 // helper function - does not take locks. 1435 func filterPolicies(cache *iamCache, policyName string, bucketName string) (string, policy.Policy) { 1436 var policies []string 1437 mp := newMappedPolicy(policyName) 1438 var toMerge []policy.Policy 1439 for _, policy := range mp.toSlice() { 1440 if policy == "" { 1441 continue 1442 } 1443 p, found := cache.iamPolicyDocsMap[policy] 1444 if !found { 1445 continue 1446 } 1447 if bucketName == "" || p.Policy.MatchResource(bucketName) { 1448 policies = append(policies, policy) 1449 toMerge = append(toMerge, p.Policy) 1450 } 1451 } 1452 return strings.Join(policies, ","), policy.MergePolicies(toMerge...) 1453 } 1454 1455 // FilterPolicies - accepts a comma separated list of policy names as a string 1456 // and bucket and returns only policies that currently exist in MinIO. If 1457 // bucketName is non-empty, additionally filters policies matching the bucket. 1458 // The first returned value is the list of currently existing policies, and the 1459 // second is their combined policy definition. 1460 func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) { 1461 cache := store.rlock() 1462 defer store.runlock() 1463 1464 return filterPolicies(cache, policyName, bucketName) 1465 } 1466 1467 // GetBucketUsers - returns users (not STS or service accounts) that have access 1468 // to the bucket. User is included even if a group policy that grants access to 1469 // the bucket is disabled. 1470 func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserInfo, error) { 1471 if bucket == "" { 1472 return nil, errInvalidArgument 1473 } 1474 1475 cache := store.rlock() 1476 defer store.runlock() 1477 1478 result := map[string]madmin.UserInfo{} 1479 for k, v := range cache.iamUsersMap { 1480 c := v.Credentials 1481 if c.IsTemp() || c.IsServiceAccount() { 1482 continue 1483 } 1484 var policies []string 1485 mp, ok := cache.iamUserPolicyMap.Load(k) 1486 if ok { 1487 policies = append(policies, mp.Policies) 1488 for _, group := range cache.iamUserGroupMemberships[k].ToSlice() { 1489 if nmp, ok := cache.iamGroupPolicyMap.Load(group); ok { 1490 policies = append(policies, nmp.Policies) 1491 } 1492 } 1493 } 1494 matchedPolicies, _ := filterPolicies(cache, strings.Join(policies, ","), bucket) 1495 if len(matchedPolicies) > 0 { 1496 result[k] = madmin.UserInfo{ 1497 PolicyName: matchedPolicies, 1498 Status: func() madmin.AccountStatus { 1499 if c.IsValid() { 1500 return madmin.AccountEnabled 1501 } 1502 return madmin.AccountDisabled 1503 }(), 1504 MemberOf: cache.iamUserGroupMemberships[k].ToSlice(), 1505 } 1506 } 1507 } 1508 1509 return result, nil 1510 } 1511 1512 // GetUsers - returns all users (not STS or service accounts). 1513 func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo { 1514 cache := store.rlock() 1515 defer store.runlock() 1516 1517 result := map[string]madmin.UserInfo{} 1518 for k, u := range cache.iamUsersMap { 1519 v := u.Credentials 1520 1521 if v.IsTemp() || v.IsServiceAccount() { 1522 continue 1523 } 1524 pl, _ := cache.iamUserPolicyMap.Load(k) 1525 result[k] = madmin.UserInfo{ 1526 PolicyName: pl.Policies, 1527 Status: func() madmin.AccountStatus { 1528 if v.IsValid() { 1529 return madmin.AccountEnabled 1530 } 1531 return madmin.AccountDisabled 1532 }(), 1533 MemberOf: cache.iamUserGroupMemberships[k].ToSlice(), 1534 UpdatedAt: pl.UpdatedAt, 1535 } 1536 } 1537 1538 return result 1539 } 1540 1541 // GetUsersWithMappedPolicies - safely returns the name of access keys with associated policies 1542 func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string { 1543 cache := store.rlock() 1544 defer store.runlock() 1545 1546 result := make(map[string]string) 1547 cache.iamUserPolicyMap.Range(func(k string, v MappedPolicy) bool { 1548 result[k] = v.Policies 1549 return true 1550 }) 1551 cache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool { 1552 result[k] = v.Policies 1553 return true 1554 }) 1555 return result 1556 } 1557 1558 // GetUserInfo - get info on a user. 1559 func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error) { 1560 if name == "" { 1561 return u, errInvalidArgument 1562 } 1563 1564 cache := store.rlock() 1565 defer store.runlock() 1566 1567 if store.getUsersSysType() != MinIOUsersSysType { 1568 // If the user has a mapped policy or is a member of a group, we 1569 // return that info. Otherwise we return error. 1570 var groups []string 1571 for _, v := range cache.iamUsersMap { 1572 if v.Credentials.ParentUser == name { 1573 groups = v.Credentials.Groups 1574 break 1575 } 1576 } 1577 for _, v := range cache.iamSTSAccountsMap { 1578 if v.Credentials.ParentUser == name { 1579 groups = v.Credentials.Groups 1580 break 1581 } 1582 } 1583 mappedPolicy, ok := cache.iamUserPolicyMap.Load(name) 1584 if !ok { 1585 mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name) 1586 } 1587 if !ok { 1588 // Attempt to load parent user mapping for STS accounts 1589 store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap) 1590 mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name) 1591 if !ok { 1592 return u, errNoSuchUser 1593 } 1594 } 1595 1596 return madmin.UserInfo{ 1597 PolicyName: mappedPolicy.Policies, 1598 MemberOf: groups, 1599 UpdatedAt: mappedPolicy.UpdatedAt, 1600 }, nil 1601 } 1602 1603 ui, found := cache.iamUsersMap[name] 1604 if !found { 1605 return u, errNoSuchUser 1606 } 1607 cred := ui.Credentials 1608 if cred.IsTemp() || cred.IsServiceAccount() { 1609 return u, errIAMActionNotAllowed 1610 } 1611 pl, _ := cache.iamUserPolicyMap.Load(name) 1612 return madmin.UserInfo{ 1613 PolicyName: pl.Policies, 1614 Status: func() madmin.AccountStatus { 1615 if cred.IsValid() { 1616 return madmin.AccountEnabled 1617 } 1618 return madmin.AccountDisabled 1619 }(), 1620 MemberOf: cache.iamUserGroupMemberships[name].ToSlice(), 1621 UpdatedAt: pl.UpdatedAt, 1622 }, nil 1623 } 1624 1625 // PolicyMappingNotificationHandler - handles updating a policy mapping from storage. 1626 func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context, userOrGroup string, isGroup bool, userType IAMUserType) error { 1627 if userOrGroup == "" { 1628 return errInvalidArgument 1629 } 1630 1631 cache := store.lock() 1632 defer store.unlock() 1633 1634 var m *xsync.MapOf[string, MappedPolicy] 1635 switch { 1636 case isGroup: 1637 m = cache.iamGroupPolicyMap 1638 default: 1639 m = cache.iamUserPolicyMap 1640 } 1641 err := store.loadMappedPolicy(ctx, userOrGroup, userType, isGroup, m) 1642 if errors.Is(err, errNoSuchPolicy) { 1643 // This means that the policy mapping was deleted, so we update 1644 // the cache. 1645 m.Delete(userOrGroup) 1646 cache.updatedAt = time.Now() 1647 1648 err = nil 1649 } 1650 return err 1651 } 1652 1653 // UserNotificationHandler - handles updating a user/STS account/service account 1654 // from storage. 1655 func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey string, userType IAMUserType) error { 1656 if accessKey == "" { 1657 return errInvalidArgument 1658 } 1659 1660 cache := store.lock() 1661 defer store.unlock() 1662 1663 var m map[string]UserIdentity 1664 switch userType { 1665 case stsUser: 1666 m = cache.iamSTSAccountsMap 1667 default: 1668 m = cache.iamUsersMap 1669 } 1670 err := store.loadUser(ctx, accessKey, userType, m) 1671 1672 if err == errNoSuchUser { 1673 // User was deleted - we update the cache. 1674 delete(m, accessKey) 1675 1676 // Since cache was updated, we update the timestamp. 1677 defer func() { 1678 cache.updatedAt = time.Now() 1679 }() 1680 1681 // 1. Start with updating user-group memberships 1682 if store.getUsersSysType() == MinIOUsersSysType { 1683 memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() 1684 for _, group := range memberOf { 1685 _, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, true) 1686 if removeErr == errNoSuchGroup { 1687 removeErr = nil 1688 } 1689 if removeErr != nil { 1690 return removeErr 1691 } 1692 } 1693 } 1694 1695 // 2. Remove any derived credentials from memory 1696 if userType == regUser { 1697 for k, u := range cache.iamUsersMap { 1698 if u.Credentials.IsServiceAccount() && u.Credentials.ParentUser == accessKey { 1699 delete(cache.iamUsersMap, k) 1700 } 1701 } 1702 for k, u := range cache.iamSTSAccountsMap { 1703 if u.Credentials.ParentUser == accessKey { 1704 delete(cache.iamSTSAccountsMap, k) 1705 } 1706 } 1707 } 1708 1709 // 3. Delete any mapped policy 1710 cache.iamUserPolicyMap.Delete(accessKey) 1711 1712 return nil 1713 } 1714 1715 if err != nil { 1716 return err 1717 } 1718 1719 // Since cache was updated, we update the timestamp. 1720 defer func() { 1721 cache.updatedAt = time.Now() 1722 }() 1723 1724 cred := m[accessKey].Credentials 1725 switch userType { 1726 case stsUser: 1727 // For STS accounts a policy is mapped to the parent user (if a mapping exists). 1728 err = store.loadMappedPolicy(ctx, cred.ParentUser, userType, false, cache.iamSTSPolicyMap) 1729 case svcUser: 1730 // For service accounts, the parent may be a regular (internal) IDP 1731 // user or a "virtual" user (parent of an STS account). 1732 // 1733 // If parent is a regular user => policy mapping is done on that parent itself. 1734 // 1735 // If parent is "virtual" => policy mapping is done on the virtual 1736 // parent and that virtual parent is an stsUser. 1737 // 1738 // To load the appropriate mapping, we check the parent user type. 1739 _, parentIsRegularUser := cache.iamUsersMap[cred.ParentUser] 1740 if parentIsRegularUser { 1741 err = store.loadMappedPolicy(ctx, cred.ParentUser, regUser, false, cache.iamUserPolicyMap) 1742 } else { 1743 err = store.loadMappedPolicy(ctx, cred.ParentUser, stsUser, false, cache.iamSTSPolicyMap) 1744 } 1745 case regUser: 1746 // For regular users, we load the mapped policy. 1747 err = store.loadMappedPolicy(ctx, accessKey, userType, false, cache.iamUserPolicyMap) 1748 default: 1749 // This is just to ensure that we have covered all cases for new 1750 // code in future. 1751 panic("unknown user type") 1752 } 1753 // Ignore policy not mapped error 1754 if err != nil && !errors.Is(err, errNoSuchPolicy) { 1755 return err 1756 } 1757 1758 return nil 1759 } 1760 1761 // DeleteUser - deletes a user from storage and cache. This only used with 1762 // long-term users and service accounts, not STS. 1763 func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, userType IAMUserType) error { 1764 if accessKey == "" { 1765 return errInvalidArgument 1766 } 1767 1768 cache := store.lock() 1769 defer store.unlock() 1770 1771 // first we remove the user from their groups. 1772 if store.getUsersSysType() == MinIOUsersSysType && userType == regUser { 1773 memberOf := cache.iamUserGroupMemberships[accessKey].ToSlice() 1774 for _, group := range memberOf { 1775 _, removeErr := removeMembersFromGroup(ctx, store, cache, group, []string{accessKey}, false) 1776 if removeErr != nil { 1777 return removeErr 1778 } 1779 } 1780 } 1781 1782 // Now we can remove the user from memory and IAM store 1783 1784 // Delete any STS and service account derived from this credential 1785 // first. 1786 if userType == regUser { 1787 for _, ui := range cache.iamUsersMap { 1788 u := ui.Credentials 1789 if u.ParentUser == accessKey { 1790 switch { 1791 case u.IsServiceAccount(): 1792 _ = store.deleteUserIdentity(ctx, u.AccessKey, svcUser) 1793 delete(cache.iamUsersMap, u.AccessKey) 1794 case u.IsTemp(): 1795 _ = store.deleteUserIdentity(ctx, u.AccessKey, stsUser) 1796 delete(cache.iamUsersMap, u.AccessKey) 1797 } 1798 } 1799 } 1800 } 1801 1802 // It is ok to ignore deletion error on the mapped policy 1803 store.deleteMappedPolicy(ctx, accessKey, userType, false) 1804 cache.iamUserPolicyMap.Delete(accessKey) 1805 1806 err := store.deleteUserIdentity(ctx, accessKey, userType) 1807 if err == errNoSuchUser { 1808 // ignore if user is already deleted. 1809 err = nil 1810 } 1811 delete(cache.iamUsersMap, accessKey) 1812 1813 cache.updatedAt = time.Now() 1814 1815 return err 1816 } 1817 1818 // SetTempUser - saves temporary (STS) credential to storage and cache. If a 1819 // policy name is given, it is associated with the parent user specified in the 1820 // credential. 1821 func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cred auth.Credentials, policyName string) (time.Time, error) { 1822 if accessKey == "" || !cred.IsTemp() || cred.IsExpired() || cred.ParentUser == "" { 1823 return time.Time{}, errInvalidArgument 1824 } 1825 1826 ttl := int64(cred.Expiration.Sub(UTCNow()).Seconds()) 1827 1828 cache := store.lock() 1829 defer store.unlock() 1830 1831 if policyName != "" { 1832 mp := newMappedPolicy(policyName) 1833 _, combinedPolicyStmt := filterPolicies(cache, mp.Policies, "") 1834 1835 if combinedPolicyStmt.IsEmpty() { 1836 return time.Time{}, fmt.Errorf("specified policy %s, not found %w", policyName, errNoSuchPolicy) 1837 } 1838 1839 err := store.saveMappedPolicy(ctx, cred.ParentUser, stsUser, false, mp, options{ttl: ttl}) 1840 if err != nil { 1841 return time.Time{}, err 1842 } 1843 1844 cache.iamSTSPolicyMap.Store(cred.ParentUser, mp) 1845 } 1846 1847 u := newUserIdentity(cred) 1848 err := store.saveUserIdentity(ctx, accessKey, stsUser, u, options{ttl: ttl}) 1849 if err != nil { 1850 return time.Time{}, err 1851 } 1852 1853 cache.iamSTSAccountsMap[accessKey] = u 1854 cache.updatedAt = time.Now() 1855 1856 return u.UpdatedAt, nil 1857 } 1858 1859 // DeleteUsers - given a set of users or access keys, deletes them along with 1860 // any derived credentials (STS or service accounts) and any associated policy 1861 // mappings. 1862 func (store *IAMStoreSys) DeleteUsers(ctx context.Context, users []string) error { 1863 cache := store.lock() 1864 defer store.unlock() 1865 1866 var deleted bool 1867 usersToDelete := set.CreateStringSet(users...) 1868 for user, ui := range cache.iamUsersMap { 1869 userType := regUser 1870 cred := ui.Credentials 1871 1872 if cred.IsServiceAccount() { 1873 userType = svcUser 1874 } else if cred.IsTemp() { 1875 userType = stsUser 1876 } 1877 1878 if usersToDelete.Contains(user) || usersToDelete.Contains(cred.ParentUser) { 1879 // Delete this user account and its policy mapping 1880 store.deleteMappedPolicy(ctx, user, userType, false) 1881 cache.iamUserPolicyMap.Delete(user) 1882 1883 // we are only logging errors, not handling them. 1884 err := store.deleteUserIdentity(ctx, user, userType) 1885 logger.LogIf(GlobalContext, err) 1886 delete(cache.iamUsersMap, user) 1887 1888 deleted = true 1889 } 1890 } 1891 1892 if deleted { 1893 cache.updatedAt = time.Now() 1894 } 1895 1896 return nil 1897 } 1898 1899 // ParentUserInfo contains extra info about a the parent user. 1900 type ParentUserInfo struct { 1901 subClaimValue string 1902 roleArns set.StringSet 1903 } 1904 1905 // GetAllParentUsers - returns all distinct "parent-users" associated with STS 1906 // or service credentials, mapped to all distinct roleARNs associated with the 1907 // parent user. The dummy role ARN is associated with parent users from 1908 // policy-claim based OpenID providers. 1909 func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo { 1910 cache := store.rlock() 1911 defer store.runlock() 1912 1913 res := map[string]ParentUserInfo{} 1914 for _, ui := range cache.iamUsersMap { 1915 cred := ui.Credentials 1916 // Only consider service account or STS credentials with 1917 // non-empty session tokens. 1918 if !(cred.IsServiceAccount() || cred.IsTemp()) || 1919 cred.SessionToken == "" { 1920 continue 1921 } 1922 1923 var ( 1924 err error 1925 claims map[string]interface{} = cred.Claims 1926 ) 1927 1928 if cred.IsServiceAccount() { 1929 claims, err = getClaimsFromTokenWithSecret(cred.SessionToken, cred.SecretKey) 1930 } else if cred.IsTemp() { 1931 var secretKey string 1932 secretKey, err = getTokenSigningKey() 1933 if err != nil { 1934 continue 1935 } 1936 claims, err = getClaimsFromTokenWithSecret(cred.SessionToken, secretKey) 1937 } 1938 1939 if err != nil { 1940 continue 1941 } 1942 if cred.ParentUser == "" { 1943 continue 1944 } 1945 1946 subClaimValue := cred.ParentUser 1947 if v, ok := claims[subClaim]; ok { 1948 subFromToken, ok := v.(string) 1949 if ok { 1950 subClaimValue = subFromToken 1951 } 1952 } 1953 1954 roleArn := openid.DummyRoleARN.String() 1955 s, ok := claims[roleArnClaim] 1956 val, ok2 := s.(string) 1957 if ok && ok2 { 1958 roleArn = val 1959 } 1960 v, ok := res[cred.ParentUser] 1961 if ok { 1962 res[cred.ParentUser] = ParentUserInfo{ 1963 subClaimValue: subClaimValue, 1964 roleArns: v.roleArns.Union(set.CreateStringSet(roleArn)), 1965 } 1966 } else { 1967 res[cred.ParentUser] = ParentUserInfo{ 1968 subClaimValue: subClaimValue, 1969 roleArns: set.CreateStringSet(roleArn), 1970 } 1971 } 1972 } 1973 1974 return res 1975 } 1976 1977 // Assumes store is locked by caller. If users is empty, returns all user mappings. 1978 func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string, 1979 userPredicate func(string) bool, 1980 ) []madmin.UserPolicyEntities { 1981 var r []madmin.UserPolicyEntities 1982 usersSet := set.CreateStringSet(users...) 1983 cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool { 1984 if userPredicate != nil && !userPredicate(user) { 1985 return true 1986 } 1987 1988 if !usersSet.IsEmpty() && !usersSet.Contains(user) { 1989 return true 1990 } 1991 1992 ps := mappedPolicy.toSlice() 1993 sort.Strings(ps) 1994 r = append(r, madmin.UserPolicyEntities{ 1995 User: user, 1996 Policies: ps, 1997 }) 1998 return true 1999 }) 2000 2001 stsMap := xsync.NewMapOf[string, MappedPolicy]() 2002 for _, user := range users { 2003 // Attempt to load parent user mapping for STS accounts 2004 store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap) 2005 } 2006 2007 stsMap.Range(func(user string, mappedPolicy MappedPolicy) bool { 2008 if userPredicate != nil && !userPredicate(user) { 2009 return true 2010 } 2011 2012 ps := mappedPolicy.toSlice() 2013 sort.Strings(ps) 2014 r = append(r, madmin.UserPolicyEntities{ 2015 User: user, 2016 Policies: ps, 2017 }) 2018 return true 2019 }) 2020 2021 sort.Slice(r, func(i, j int) bool { 2022 return r[i].User < r[j].User 2023 }) 2024 2025 return r 2026 } 2027 2028 // Assumes store is locked by caller. If groups is empty, returns all group mappings. 2029 func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []string, 2030 groupPredicate func(string) bool, 2031 ) []madmin.GroupPolicyEntities { 2032 var r []madmin.GroupPolicyEntities 2033 groupsSet := set.CreateStringSet(groups...) 2034 cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool { 2035 if groupPredicate != nil && !groupPredicate(group) { 2036 return true 2037 } 2038 2039 if !groupsSet.IsEmpty() && !groupsSet.Contains(group) { 2040 return true 2041 } 2042 2043 ps := mappedPolicy.toSlice() 2044 sort.Strings(ps) 2045 r = append(r, madmin.GroupPolicyEntities{ 2046 Group: group, 2047 Policies: ps, 2048 }) 2049 return true 2050 }) 2051 2052 sort.Slice(r, func(i, j int) bool { 2053 return r[i].Group < r[j].Group 2054 }) 2055 2056 return r 2057 } 2058 2059 // Assumes store is locked by caller. If policies is empty, returns all policy mappings. 2060 func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string, 2061 userPredicate, groupPredicate func(string) bool, 2062 ) []madmin.PolicyEntities { 2063 queryPolSet := set.CreateStringSet(policies...) 2064 2065 policyToUsersMap := make(map[string]set.StringSet) 2066 cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool { 2067 if userPredicate != nil && !userPredicate(user) { 2068 return true 2069 } 2070 2071 commonPolicySet := mappedPolicy.policySet() 2072 if !queryPolSet.IsEmpty() { 2073 commonPolicySet = commonPolicySet.Intersection(queryPolSet) 2074 } 2075 for _, policy := range commonPolicySet.ToSlice() { 2076 s, ok := policyToUsersMap[policy] 2077 if !ok { 2078 policyToUsersMap[policy] = set.CreateStringSet(user) 2079 } else { 2080 s.Add(user) 2081 policyToUsersMap[policy] = s 2082 } 2083 } 2084 return true 2085 }) 2086 2087 if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok { 2088 for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) { 2089 user := strings.TrimSuffix(item.Item, ".json") 2090 if userPredicate != nil && !userPredicate(user) { 2091 continue 2092 } 2093 2094 var mappedPolicy MappedPolicy 2095 store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false)) 2096 2097 commonPolicySet := mappedPolicy.policySet() 2098 if !queryPolSet.IsEmpty() { 2099 commonPolicySet = commonPolicySet.Intersection(queryPolSet) 2100 } 2101 for _, policy := range commonPolicySet.ToSlice() { 2102 s, ok := policyToUsersMap[policy] 2103 if !ok { 2104 policyToUsersMap[policy] = set.CreateStringSet(user) 2105 } else { 2106 s.Add(user) 2107 policyToUsersMap[policy] = s 2108 } 2109 } 2110 } 2111 } 2112 2113 policyToGroupsMap := make(map[string]set.StringSet) 2114 cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool { 2115 if groupPredicate != nil && !groupPredicate(group) { 2116 return true 2117 } 2118 2119 commonPolicySet := mappedPolicy.policySet() 2120 if !queryPolSet.IsEmpty() { 2121 commonPolicySet = commonPolicySet.Intersection(queryPolSet) 2122 } 2123 for _, policy := range commonPolicySet.ToSlice() { 2124 s, ok := policyToGroupsMap[policy] 2125 if !ok { 2126 policyToGroupsMap[policy] = set.CreateStringSet(group) 2127 } else { 2128 s.Add(group) 2129 policyToGroupsMap[policy] = s 2130 } 2131 } 2132 return true 2133 }) 2134 2135 m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap)) 2136 for policy, groups := range policyToGroupsMap { 2137 s := groups.ToSlice() 2138 sort.Strings(s) 2139 m[policy] = madmin.PolicyEntities{ 2140 Policy: policy, 2141 Groups: s, 2142 } 2143 } 2144 for policy, users := range policyToUsersMap { 2145 s := users.ToSlice() 2146 sort.Strings(s) 2147 2148 // Update existing value in map 2149 pe := m[policy] 2150 pe.Policy = policy 2151 pe.Users = s 2152 m[policy] = pe 2153 } 2154 2155 policyEntities := make([]madmin.PolicyEntities, 0, len(m)) 2156 for _, v := range m { 2157 policyEntities = append(policyEntities, v) 2158 } 2159 2160 sort.Slice(policyEntities, func(i, j int) bool { 2161 return policyEntities[i].Policy < policyEntities[j].Policy 2162 }) 2163 2164 return policyEntities 2165 } 2166 2167 // ListPolicyMappings - return users/groups mapped to policies. 2168 func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery, 2169 userPredicate, groupPredicate func(string) bool, 2170 ) madmin.PolicyEntitiesResult { 2171 cache := store.rlock() 2172 defer store.runlock() 2173 2174 var result madmin.PolicyEntitiesResult 2175 2176 isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policy) == 0 2177 2178 if len(q.Users) > 0 { 2179 result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate) 2180 } 2181 if len(q.Groups) > 0 { 2182 result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate) 2183 } 2184 if len(q.Policy) > 0 || isAllPoliciesQuery { 2185 result.PolicyMappings = store.listPolicyMappings(cache, q.Policy, userPredicate, groupPredicate) 2186 } 2187 return result 2188 } 2189 2190 // SetUserStatus - sets current user status. 2191 func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) { 2192 if accessKey != "" && status != madmin.AccountEnabled && status != madmin.AccountDisabled { 2193 return updatedAt, errInvalidArgument 2194 } 2195 2196 cache := store.lock() 2197 defer store.unlock() 2198 2199 ui, ok := cache.iamUsersMap[accessKey] 2200 if !ok { 2201 return updatedAt, errNoSuchUser 2202 } 2203 cred := ui.Credentials 2204 2205 if cred.IsTemp() || cred.IsServiceAccount() { 2206 return updatedAt, errIAMActionNotAllowed 2207 } 2208 2209 uinfo := newUserIdentity(auth.Credentials{ 2210 AccessKey: accessKey, 2211 SecretKey: cred.SecretKey, 2212 Status: func() string { 2213 switch string(status) { 2214 case string(madmin.AccountEnabled), string(auth.AccountOn): 2215 return auth.AccountOn 2216 } 2217 return auth.AccountOff 2218 }(), 2219 }) 2220 2221 if err := store.saveUserIdentity(ctx, accessKey, regUser, uinfo); err != nil { 2222 return updatedAt, err 2223 } 2224 2225 if err := cache.updateUserWithClaims(accessKey, uinfo); err != nil { 2226 return updatedAt, err 2227 } 2228 2229 return uinfo.UpdatedAt, nil 2230 } 2231 2232 // AddServiceAccount - add a new service account 2233 func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Credentials) (updatedAt time.Time, err error) { 2234 cache := store.lock() 2235 defer store.unlock() 2236 2237 accessKey := cred.AccessKey 2238 parentUser := cred.ParentUser 2239 2240 // Found newly requested service account, to be an existing account - 2241 // reject such operation (updates to the service account are handled in 2242 // a different API). 2243 if su, found := cache.iamUsersMap[accessKey]; found { 2244 scred := su.Credentials 2245 if scred.ParentUser != parentUser { 2246 return updatedAt, fmt.Errorf("%w: the service account access key is taken by another user", errIAMServiceAccountNotAllowed) 2247 } 2248 return updatedAt, fmt.Errorf("%w: the service account access key already taken", errIAMServiceAccountNotAllowed) 2249 } 2250 2251 // Parent user must not be a service account. 2252 if u, found := cache.iamUsersMap[parentUser]; found && u.Credentials.IsServiceAccount() { 2253 return updatedAt, fmt.Errorf("%w: unable to create a service account for another service account", errIAMServiceAccountNotAllowed) 2254 } 2255 2256 u := newUserIdentity(cred) 2257 err = store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u) 2258 if err != nil { 2259 return updatedAt, err 2260 } 2261 2262 cache.updateUserWithClaims(u.Credentials.AccessKey, u) 2263 2264 return u.UpdatedAt, nil 2265 } 2266 2267 // UpdateServiceAccount - updates a service account on storage. 2268 func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey string, opts updateServiceAccountOpts) (updatedAt time.Time, err error) { 2269 cache := store.lock() 2270 defer store.unlock() 2271 2272 ui, ok := cache.iamUsersMap[accessKey] 2273 if !ok || !ui.Credentials.IsServiceAccount() { 2274 return updatedAt, errNoSuchServiceAccount 2275 } 2276 cr := ui.Credentials 2277 currentSecretKey := cr.SecretKey 2278 if opts.secretKey != "" { 2279 if !auth.IsSecretKeyValid(opts.secretKey) { 2280 return updatedAt, auth.ErrInvalidSecretKeyLength 2281 } 2282 cr.SecretKey = opts.secretKey 2283 } 2284 2285 if opts.name != "" { 2286 cr.Name = opts.name 2287 } 2288 2289 if opts.description != "" { 2290 cr.Description = opts.description 2291 } 2292 2293 if opts.expiration != nil { 2294 expirationInUTC := opts.expiration.UTC() 2295 if err := validateSvcExpirationInUTC(expirationInUTC); err != nil { 2296 return updatedAt, err 2297 } 2298 cr.Expiration = expirationInUTC 2299 } 2300 2301 switch opts.status { 2302 // The caller did not ask to update status account, do nothing 2303 case "": 2304 case string(madmin.AccountEnabled): 2305 cr.Status = auth.AccountOn 2306 case string(madmin.AccountDisabled): 2307 cr.Status = auth.AccountOff 2308 // Update account status 2309 case auth.AccountOn, auth.AccountOff: 2310 cr.Status = opts.status 2311 default: 2312 return updatedAt, errors.New("unknown account status value") 2313 } 2314 2315 m, err := getClaimsFromTokenWithSecret(cr.SessionToken, currentSecretKey) 2316 if err != nil { 2317 return updatedAt, fmt.Errorf("unable to get svc acc claims: %v", err) 2318 } 2319 2320 // Extracted session policy name string can be removed as its not useful 2321 // at this point. 2322 delete(m, sessionPolicyNameExtracted) 2323 2324 // sessionPolicy is nil and there is embedded policy attached we remove 2325 // embedded policy at that point. 2326 if _, ok := m[policy.SessionPolicyName]; ok && opts.sessionPolicy == nil { 2327 delete(m, policy.SessionPolicyName) 2328 m[iamPolicyClaimNameSA()] = inheritedPolicyType 2329 } 2330 2331 if opts.sessionPolicy != nil { // session policies is being updated 2332 if err := opts.sessionPolicy.Validate(); err != nil { 2333 return updatedAt, err 2334 } 2335 2336 policyBuf, err := json.Marshal(opts.sessionPolicy) 2337 if err != nil { 2338 return updatedAt, err 2339 } 2340 2341 if len(policyBuf) > 2048 { 2342 return updatedAt, errSessionPolicyTooLarge 2343 } 2344 2345 // Overwrite session policy claims. 2346 m[policy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf) 2347 m[iamPolicyClaimNameSA()] = embeddedPolicyType 2348 } 2349 2350 cr.SessionToken, err = auth.JWTSignWithAccessKey(accessKey, m, cr.SecretKey) 2351 if err != nil { 2352 return updatedAt, err 2353 } 2354 2355 u := newUserIdentity(cr) 2356 if err := store.saveUserIdentity(ctx, u.Credentials.AccessKey, svcUser, u); err != nil { 2357 return updatedAt, err 2358 } 2359 2360 if err := cache.updateUserWithClaims(u.Credentials.AccessKey, u); err != nil { 2361 return updatedAt, err 2362 } 2363 2364 return u.UpdatedAt, nil 2365 } 2366 2367 // ListTempAccounts - lists only temporary accounts from the cache. 2368 func (store *IAMStoreSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) { 2369 cache := store.rlock() 2370 defer store.runlock() 2371 2372 userExists := false 2373 var tempAccounts []UserIdentity 2374 for _, v := range cache.iamUsersMap { 2375 isDerived := false 2376 if v.Credentials.IsServiceAccount() || v.Credentials.IsTemp() { 2377 isDerived = true 2378 } 2379 2380 if !isDerived && v.Credentials.AccessKey == accessKey { 2381 userExists = true 2382 } else if isDerived && v.Credentials.ParentUser == accessKey { 2383 userExists = true 2384 if v.Credentials.IsTemp() { 2385 // Hide secret key & session key here 2386 v.Credentials.SecretKey = "" 2387 v.Credentials.SessionToken = "" 2388 tempAccounts = append(tempAccounts, v) 2389 } 2390 } 2391 } 2392 2393 if !userExists { 2394 return nil, errNoSuchUser 2395 } 2396 2397 return tempAccounts, nil 2398 } 2399 2400 // ListServiceAccounts - lists only service accounts from the cache. 2401 func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) { 2402 cache := store.rlock() 2403 defer store.runlock() 2404 2405 var serviceAccounts []auth.Credentials 2406 for _, u := range cache.iamUsersMap { 2407 v := u.Credentials 2408 if accessKey != "" && v.ParentUser == accessKey { 2409 if v.IsServiceAccount() { 2410 // Hide secret key & session key here 2411 v.SecretKey = "" 2412 v.SessionToken = "" 2413 serviceAccounts = append(serviceAccounts, v) 2414 } 2415 } 2416 } 2417 2418 return serviceAccounts, nil 2419 } 2420 2421 // ListSTSAccounts - lists only STS accounts from the cache. 2422 func (store *IAMStoreSys) ListSTSAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) { 2423 cache := store.rlock() 2424 defer store.runlock() 2425 2426 var stsAccounts []auth.Credentials 2427 for _, u := range cache.iamSTSAccountsMap { 2428 v := u.Credentials 2429 if accessKey != "" && v.ParentUser == accessKey { 2430 if v.IsTemp() { 2431 // Hide secret key & session key here 2432 v.SecretKey = "" 2433 v.SessionToken = "" 2434 stsAccounts = append(stsAccounts, v) 2435 } 2436 } 2437 } 2438 2439 return stsAccounts, nil 2440 } 2441 2442 // AddUser - adds/updates long term user account to storage. 2443 func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) { 2444 cache := store.lock() 2445 defer store.unlock() 2446 2447 cache.updatedAt = time.Now() 2448 2449 ui, ok := cache.iamUsersMap[accessKey] 2450 2451 // It is not possible to update an STS account. 2452 if ok && ui.Credentials.IsTemp() { 2453 return updatedAt, errIAMActionNotAllowed 2454 } 2455 2456 u := newUserIdentity(auth.Credentials{ 2457 AccessKey: accessKey, 2458 SecretKey: ureq.SecretKey, 2459 Status: func() string { 2460 switch string(ureq.Status) { 2461 case string(madmin.AccountEnabled), string(auth.AccountOn): 2462 return auth.AccountOn 2463 } 2464 return auth.AccountOff 2465 }(), 2466 }) 2467 2468 if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil { 2469 return updatedAt, err 2470 } 2471 if err := cache.updateUserWithClaims(accessKey, u); err != nil { 2472 return updatedAt, err 2473 } 2474 2475 return u.UpdatedAt, nil 2476 } 2477 2478 // UpdateUserSecretKey - sets user secret key to storage. 2479 func (store *IAMStoreSys) UpdateUserSecretKey(ctx context.Context, accessKey, secretKey string) error { 2480 cache := store.lock() 2481 defer store.unlock() 2482 2483 cache.updatedAt = time.Now() 2484 2485 ui, ok := cache.iamUsersMap[accessKey] 2486 if !ok { 2487 return errNoSuchUser 2488 } 2489 cred := ui.Credentials 2490 cred.SecretKey = secretKey 2491 u := newUserIdentity(cred) 2492 if err := store.saveUserIdentity(ctx, accessKey, regUser, u); err != nil { 2493 return err 2494 } 2495 2496 return cache.updateUserWithClaims(accessKey, u) 2497 } 2498 2499 // GetSTSAndServiceAccounts - returns all STS and Service account credentials. 2500 func (store *IAMStoreSys) GetSTSAndServiceAccounts() []auth.Credentials { 2501 cache := store.rlock() 2502 defer store.runlock() 2503 2504 var res []auth.Credentials 2505 for _, u := range cache.iamUsersMap { 2506 cred := u.Credentials 2507 if cred.IsServiceAccount() { 2508 res = append(res, cred) 2509 } 2510 } 2511 for _, u := range cache.iamSTSAccountsMap { 2512 res = append(res, u.Credentials) 2513 } 2514 2515 return res 2516 } 2517 2518 // UpdateUserIdentity - updates a user credential. 2519 func (store *IAMStoreSys) UpdateUserIdentity(ctx context.Context, cred auth.Credentials) error { 2520 cache := store.lock() 2521 defer store.unlock() 2522 2523 cache.updatedAt = time.Now() 2524 2525 userType := regUser 2526 if cred.IsServiceAccount() { 2527 userType = svcUser 2528 } else if cred.IsTemp() { 2529 userType = stsUser 2530 } 2531 ui := newUserIdentity(cred) 2532 // Overwrite the user identity here. As store should be 2533 // atomic, it shouldn't cause any corruption. 2534 if err := store.saveUserIdentity(ctx, cred.AccessKey, userType, ui); err != nil { 2535 return err 2536 } 2537 2538 return cache.updateUserWithClaims(cred.AccessKey, ui) 2539 } 2540 2541 // LoadUser - attempts to load user info from storage and updates cache. 2542 func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) { 2543 cache := store.lock() 2544 defer store.unlock() 2545 2546 cache.updatedAt = time.Now() 2547 2548 _, found := cache.iamUsersMap[accessKey] 2549 2550 // Check for regular user access key 2551 if !found { 2552 store.loadUser(ctx, accessKey, regUser, cache.iamUsersMap) 2553 if _, found = cache.iamUsersMap[accessKey]; found { 2554 // load mapped policies 2555 store.loadMappedPolicyWithRetry(ctx, accessKey, regUser, false, cache.iamUserPolicyMap, 3) 2556 } 2557 } 2558 2559 // Check for service account 2560 if !found { 2561 store.loadUser(ctx, accessKey, svcUser, cache.iamUsersMap) 2562 var svc UserIdentity 2563 svc, found = cache.iamUsersMap[accessKey] 2564 if found { 2565 // Load parent user and mapped policies. 2566 if store.getUsersSysType() == MinIOUsersSysType { 2567 store.loadUser(ctx, svc.Credentials.ParentUser, regUser, cache.iamUsersMap) 2568 store.loadMappedPolicyWithRetry(ctx, svc.Credentials.ParentUser, regUser, false, cache.iamUserPolicyMap, 3) 2569 } else { 2570 // In case of LDAP the parent user's policy mapping needs to be 2571 // loaded into sts map 2572 store.loadMappedPolicyWithRetry(ctx, svc.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap, 3) 2573 } 2574 } 2575 } 2576 2577 // Check for STS account 2578 stsAccountFound := false 2579 var stsUserCred UserIdentity 2580 if !found { 2581 store.loadUser(ctx, accessKey, stsUser, cache.iamSTSAccountsMap) 2582 if stsUserCred, found = cache.iamSTSAccountsMap[accessKey]; found { 2583 // Load mapped policy 2584 store.loadMappedPolicyWithRetry(ctx, stsUserCred.Credentials.ParentUser, stsUser, false, cache.iamSTSPolicyMap, 3) 2585 stsAccountFound = true 2586 } 2587 } 2588 2589 // Load any associated policy definitions 2590 if !stsAccountFound { 2591 pols, _ := cache.iamUserPolicyMap.Load(accessKey) 2592 for _, policy := range pols.toSlice() { 2593 if _, found = cache.iamPolicyDocsMap[policy]; !found { 2594 store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3) 2595 } 2596 } 2597 } else { 2598 pols, _ := cache.iamSTSPolicyMap.Load(stsUserCred.Credentials.AccessKey) 2599 for _, policy := range pols.toSlice() { 2600 if _, found = cache.iamPolicyDocsMap[policy]; !found { 2601 store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3) 2602 } 2603 } 2604 } 2605 } 2606 2607 func extractJWTClaims(u UserIdentity) (*jwt.MapClaims, error) { 2608 jwtClaims, err := auth.ExtractClaims(u.Credentials.SessionToken, u.Credentials.SecretKey) 2609 if err != nil { 2610 secretKey, err := getTokenSigningKey() 2611 if err != nil { 2612 return nil, err 2613 } 2614 // Session tokens for STS creds will be generated with root secret or site-replicator-0 secret 2615 jwtClaims, err = auth.ExtractClaims(u.Credentials.SessionToken, secretKey) 2616 if err != nil { 2617 return nil, err 2618 } 2619 } 2620 return jwtClaims, nil 2621 } 2622 2623 func validateSvcExpirationInUTC(expirationInUTC time.Time) error { 2624 if expirationInUTC.IsZero() || expirationInUTC.Equal(timeSentinel) { 2625 // Service accounts might not have expiration in older releases. 2626 return nil 2627 } 2628 2629 currentTime := time.Now().UTC() 2630 minExpiration := currentTime.Add(minServiceAccountExpiry) 2631 maxExpiration := currentTime.Add(maxServiceAccountExpiry) 2632 if expirationInUTC.Before(minExpiration) || expirationInUTC.After(maxExpiration) { 2633 return errInvalidSvcAcctExpiration 2634 } 2635 2636 return nil 2637 }