Fix races in IAM cache lazy loading (#19346)

Fix races in IAM cache

Fixes #19344

On the top level we only grab a read lock, but we write to the cache if we manage to fetch it.

a03dac41eb/cmd/iam-store.go (L446) is also flipped to what it should be AFAICT.

Change the internal cache structure to a concurrency safe implementation.

Bonus: Also switch grid implementation.
This commit is contained in:
Klaus Post 2024-03-26 19:12:57 +01:00 committed by GitHub
parent 53a14c7301
commit 7ff4164d65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 214 additions and 328 deletions

View File

@ -38,6 +38,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v2/policy"
"github.com/puzpuzpuz/xsync/v3"
) )
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key> // RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
@ -1936,13 +1937,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case userPolicyMappingsFile: case userPolicyMappingsFile:
userPolicyMap := make(map[string]MappedPolicy) userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap) err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
userPolData, err := json.Marshal(userPolicyMap) userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
@ -1953,13 +1954,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case groupPolicyMappingsFile: case groupPolicyMappingsFile:
groupPolicyMap := make(map[string]MappedPolicy) groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap) err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
grpPolData, err := json.Marshal(groupPolicyMap) grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
@ -1970,13 +1971,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case stsUserPolicyMappingsFile: case stsUserPolicyMappingsFile:
userPolicyMap := make(map[string]MappedPolicy) userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap) err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
userPolData, err := json.Marshal(userPolicyMap) userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
@ -1986,13 +1987,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
return return
} }
case stsGroupPolicyMappingsFile: case stsGroupPolicyMappingsFile:
groupPolicyMap := make(map[string]MappedPolicy) groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap) err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return
} }
grpPolData, err := json.Marshal(groupPolicyMap) grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL) writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
return return

View File

@ -31,6 +31,7 @@ import (
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/puzpuzpuz/xsync/v3"
"go.etcd.io/etcd/api/v3/mvccpb" "go.etcd.io/etcd/api/v3/mvccpb"
etcd "go.etcd.io/etcd/client/v3" etcd "go.etcd.io/etcd/client/v3"
) )
@ -325,11 +326,11 @@ func (ies *IAMEtcdStore) loadGroups(ctx context.Context, m map[string]GroupInfo)
return nil return nil
} }
func (ies *IAMEtcdStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, _ int) error { func (ies *IAMEtcdStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error {
return ies.loadMappedPolicy(ctx, name, userType, isGroup, m) return ies.loadMappedPolicy(ctx, name, userType, isGroup, m)
} }
func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error { func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
var p MappedPolicy var p MappedPolicy
err := ies.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup)) err := ies.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
if err != nil { if err != nil {
@ -338,11 +339,11 @@ func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, user
} }
return err return err
} }
m[name] = p m.Store(name, p)
return nil return nil
} }
func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, basePrefix string) error { func getMappedPolicy(kv *mvccpb.KeyValue, m *xsync.MapOf[string, MappedPolicy], basePrefix string) error {
var p MappedPolicy var p MappedPolicy
err := getIAMConfig(&p, kv.Value, string(kv.Key)) err := getIAMConfig(&p, kv.Value, string(kv.Key))
if err != nil { if err != nil {
@ -352,11 +353,11 @@ func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserT
return err return err
} }
name := extractPathPrefixAndSuffix(string(kv.Key), basePrefix, ".json") name := extractPathPrefixAndSuffix(string(kv.Key), basePrefix, ".json")
m[name] = p m.Store(name, p)
return nil return nil
} }
func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error { func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout) cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
defer cancel() defer cancel()
var basePrefix string var basePrefix string
@ -381,7 +382,7 @@ func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUse
// Parse all policies mapping to create the proper data model // Parse all policies mapping to create the proper data model
for _, kv := range r.Kvs { for _, kv := range r.Kvs {
if err = getMappedPolicy(ctx, kv, userType, isGroup, m, basePrefix); err != nil && !errors.Is(err, errNoSuchPolicy) { if err = getMappedPolicy(kv, m, basePrefix); err != nil && !errors.Is(err, errNoSuchPolicy) {
return err return err
} }
} }

View File

@ -35,6 +35,7 @@ import (
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/puzpuzpuz/xsync/v3"
) )
// IAMObjectStore implements IAMStorageAPI // IAMObjectStore implements IAMStorageAPI
@ -325,9 +326,7 @@ func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupI
return nil return nil
} }
func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error {
m map[string]MappedPolicy, retries int,
) error {
for { for {
retry: retry:
var p MappedPolicy var p MappedPolicy
@ -344,14 +343,12 @@ func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name
goto retry goto retry
} }
m[name] = p m.Store(name, p)
return nil return nil
} }
} }
func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
m map[string]MappedPolicy,
) error {
var p MappedPolicy var p MappedPolicy
err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup)) err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
if err != nil { if err != nil {
@ -361,11 +358,11 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string,
return err return err
} }
m[name] = p m.Store(name, p)
return nil return nil
} }
func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error { func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
var basePath string var basePath string
if isGroup { if isGroup {
basePath = iamConfigPolicyDBGroupsPrefix basePath = iamConfigPolicyDBGroupsPrefix

View File

@ -35,6 +35,7 @@ import (
"github.com/minio/minio/internal/jwt" "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v2/policy"
"github.com/puzpuzpuz/xsync/v3"
) )
const ( const (
@ -179,6 +180,16 @@ type MappedPolicy struct {
UpdatedAt time.Time `json:"updatedAt,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty"`
} }
// mappedPoliciesToMap copies the map of mapped policies to a regular map.
func mappedPoliciesToMap(m *xsync.MapOf[string, MappedPolicy]) map[string]MappedPolicy {
policies := make(map[string]MappedPolicy, m.Size())
m.Range(func(k string, v MappedPolicy) bool {
policies[k] = v
return true
})
return policies
}
// converts a mapped policy into a slice of distinct policies // converts a mapped policy into a slice of distinct policies
func (mp MappedPolicy) toSlice() []string { func (mp MappedPolicy) toSlice() []string {
var policies []string var policies []string
@ -277,32 +288,32 @@ type iamCache struct {
// map of regular username to credentials // map of regular username to credentials
iamUsersMap map[string]UserIdentity iamUsersMap map[string]UserIdentity
// map of regular username to policy names // map of regular username to policy names
iamUserPolicyMap map[string]MappedPolicy iamUserPolicyMap *xsync.MapOf[string, MappedPolicy]
// STS accounts are loaded on demand and not via the periodic IAM reload. // STS accounts are loaded on demand and not via the periodic IAM reload.
// map of STS access key to credentials // map of STS access key to credentials
iamSTSAccountsMap map[string]UserIdentity iamSTSAccountsMap map[string]UserIdentity
// map of STS access key to policy names // map of STS access key to policy names
iamSTSPolicyMap map[string]MappedPolicy iamSTSPolicyMap *xsync.MapOf[string, MappedPolicy]
// map of group names to group info // map of group names to group info
iamGroupsMap map[string]GroupInfo iamGroupsMap map[string]GroupInfo
// map of user names to groups they are a member of // map of user names to groups they are a member of
iamUserGroupMemberships map[string]set.StringSet iamUserGroupMemberships map[string]set.StringSet
// map of group names to policy names // map of group names to policy names
iamGroupPolicyMap map[string]MappedPolicy iamGroupPolicyMap *xsync.MapOf[string, MappedPolicy]
} }
func newIamCache() *iamCache { func newIamCache() *iamCache {
return &iamCache{ return &iamCache{
iamPolicyDocsMap: map[string]PolicyDoc{}, iamPolicyDocsMap: map[string]PolicyDoc{},
iamUsersMap: map[string]UserIdentity{}, iamUsersMap: map[string]UserIdentity{},
iamUserPolicyMap: map[string]MappedPolicy{}, iamUserPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
iamSTSAccountsMap: map[string]UserIdentity{}, iamSTSAccountsMap: map[string]UserIdentity{},
iamSTSPolicyMap: map[string]MappedPolicy{}, iamSTSPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
iamGroupsMap: map[string]GroupInfo{}, iamGroupsMap: map[string]GroupInfo{},
iamUserGroupMemberships: map[string]set.StringSet{}, iamUserGroupMemberships: map[string]set.StringSet{},
iamGroupPolicyMap: map[string]MappedPolicy{}, iamGroupPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
} }
} }
@ -375,14 +386,14 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
} }
} }
policy, ok := c.iamGroupPolicyMap[name] policy, ok := c.iamGroupPolicyMap.Load(name)
if ok { if ok {
return policy.toSlice(), policy.UpdatedAt, nil return policy.toSlice(), policy.UpdatedAt, nil
} }
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
return nil, time.Time{}, err return nil, time.Time{}, err
} }
policy = c.iamGroupPolicyMap[name] policy, _ = c.iamGroupPolicyMap.Load(name)
return policy.toSlice(), policy.UpdatedAt, nil return policy.toSlice(), policy.UpdatedAt, nil
} }
@ -398,23 +409,23 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
// For internal IDP regular/service account user accounts, the policy // For internal IDP regular/service account user accounts, the policy
// mapping is iamUserPolicyMap. For STS accounts, the parent user would be // mapping is iamUserPolicyMap. For STS accounts, the parent user would be
// passed here and we lookup the mapping in iamSTSPolicyMap. // passed here and we lookup the mapping in iamSTSPolicyMap.
mp, ok := c.iamUserPolicyMap[name] mp, ok := c.iamUserPolicyMap.Load(name)
if !ok { if !ok {
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, false, c.iamUserPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, false, c.iamUserPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
return nil, time.Time{}, err return nil, time.Time{}, err
} }
mp, ok = c.iamUserPolicyMap[name] mp, ok = c.iamUserPolicyMap.Load(name)
if !ok { if !ok {
// Since user "name" could be a parent user of an STS account, we look up // Since user "name" could be a parent user of an STS account, we look up
// mappings for those too. // mappings for those too.
mp, ok = c.iamSTSPolicyMap[name] mp, ok = c.iamSTSPolicyMap.Load(name)
if !ok { if !ok {
// Attempt to load parent user mapping for STS accounts // Attempt to load parent user mapping for STS accounts
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { if err := store.loadMappedPolicyWithRetry(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
return nil, time.Time{}, err return nil, time.Time{}, err
} }
mp = c.iamSTSPolicyMap[name] mp, _ = c.iamSTSPolicyMap.Load(name)
} }
} }
} }
@ -442,12 +453,12 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
} }
} }
policy, ok := c.iamGroupPolicyMap[group] policy, ok := c.iamGroupPolicyMap.Load(group)
if ok { if !ok {
if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) { if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
return nil, time.Time{}, err return nil, time.Time{}, err
} }
policy = c.iamGroupPolicyMap[group] policy, _ = c.iamGroupPolicyMap.Load(group)
} }
policies = append(policies, policy.toSlice()...) policies = append(policies, policy.toSlice()...)
@ -491,9 +502,9 @@ type IAMStorageAPI interface {
loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
loadGroups(ctx context.Context, m map[string]GroupInfo) error loadGroups(ctx context.Context, m map[string]GroupInfo) error
loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error
loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, retries int) error loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error
loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error
saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error
loadIAMConfig(ctx context.Context, item interface{}, path string) error loadIAMConfig(ctx context.Context, item interface{}, path string) error
deleteIAMConfig(ctx context.Context, path string) error deleteIAMConfig(ctx context.Context, path string) error
@ -618,9 +629,10 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
// here is to account for STS policy mapping changes that should apply // here is to account for STS policy mapping changes that should apply
// for service accounts derived from such STS accounts (i.e. LDAP STS // for service accounts derived from such STS accounts (i.e. LDAP STS
// accounts). // accounts).
for k, v := range newCache.iamSTSPolicyMap { newCache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool {
cache.iamSTSPolicyMap[k] = v cache.iamSTSPolicyMap.Store(k, v)
} return true
})
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
} }
@ -659,12 +671,10 @@ func (store *IAMStoreSys) GetMappedPolicy(name string, isGroup bool) (MappedPoli
defer store.runlock() defer store.runlock()
if isGroup { if isGroup {
v, ok := cache.iamGroupPolicyMap[name] v, ok := cache.iamGroupPolicyMap.Load(name)
return v, ok return v, ok
} }
return cache.iamUserPolicyMap.Load(name)
v, ok := cache.iamUserPolicyMap[name]
return v, ok
} }
// GroupNotificationHandler - updates in-memory cache on notification of // GroupNotificationHandler - updates in-memory cache on notification of
@ -683,7 +693,7 @@ func (store *IAMStoreSys) GroupNotificationHandler(ctx context.Context, group st
// group does not exist - so remove from memory. // group does not exist - so remove from memory.
cache.removeGroupFromMembershipsMap(group) cache.removeGroupFromMembershipsMap(group)
delete(cache.iamGroupsMap, group) delete(cache.iamGroupsMap, group)
delete(cache.iamGroupPolicyMap, group) cache.iamGroupPolicyMap.Delete(group)
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return nil
@ -862,7 +872,7 @@ func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string
// Delete from server memory // Delete from server memory
delete(cache.iamGroupsMap, group) delete(cache.iamGroupsMap, group)
delete(cache.iamGroupPolicyMap, group) cache.iamGroupPolicyMap.Delete(group)
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return cache.updatedAt, nil return cache.updatedAt, nil
} }
@ -954,16 +964,17 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err
} }
if store.getUsersSysType() == LDAPUsersSysType { if store.getUsersSysType() == LDAPUsersSysType {
m := map[string]MappedPolicy{} m := xsync.NewMapOf[string, MappedPolicy]()
err = store.loadMappedPolicies(ctx, stsUser, true, m) err = store.loadMappedPolicies(ctx, stsUser, true, m)
if err != nil { if err != nil {
return return
} }
cache.iamGroupPolicyMap = m cache.iamGroupPolicyMap = m
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
for k := range cache.iamGroupPolicyMap { cache.iamGroupPolicyMap.Range(func(k string, v MappedPolicy) bool {
res = append(res, k) res = append(res, k)
} return true
})
} }
return return
@ -981,9 +992,10 @@ func (store *IAMStoreSys) listGroups(ctx context.Context) (res []string, err err
} }
if store.getUsersSysType() == LDAPUsersSysType { if store.getUsersSysType() == LDAPUsersSysType {
for k := range cache.iamGroupPolicyMap { cache.iamGroupPolicyMap.Range(func(k string, _ MappedPolicy) bool {
res = append(res, k) res = append(res, k)
} return true
})
} }
return return
} }
@ -1006,14 +1018,14 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
var mp MappedPolicy var mp MappedPolicy
if !isGroup { if !isGroup {
if userType == stsUser { if userType == stsUser {
stsMap := map[string]MappedPolicy{} stsMap := xsync.NewMapOf[string, MappedPolicy]()
// Attempt to load parent user mapping for STS accounts // Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap) store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap)
mp = stsMap[name] mp, _ = stsMap.Load(name)
} else { } else {
mp = cache.iamUserPolicyMap[name] mp, _ = cache.iamUserPolicyMap.Load(name)
} }
} else { } else {
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
@ -1028,7 +1040,7 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
return return
} }
} }
mp = cache.iamGroupPolicyMap[name] mp, _ = cache.iamGroupPolicyMap.Load(name)
} }
// Compute net policy change effect and updated policy mapping // Compute net policy change effect and updated policy mapping
@ -1071,12 +1083,12 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
} }
if !isGroup { if !isGroup {
if userType == stsUser { if userType == stsUser {
delete(cache.iamSTSPolicyMap, name) cache.iamSTSPolicyMap.Delete(name)
} else { } else {
delete(cache.iamUserPolicyMap, name) cache.iamUserPolicyMap.Delete(name)
} }
} else { } else {
delete(cache.iamGroupPolicyMap, name) cache.iamGroupPolicyMap.Delete(name)
} }
} else { } else {
@ -1085,12 +1097,12 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
} }
if !isGroup { if !isGroup {
if userType == stsUser { if userType == stsUser {
cache.iamSTSPolicyMap[name] = newPolicyMapping cache.iamSTSPolicyMap.Store(name, newPolicyMapping)
} else { } else {
cache.iamUserPolicyMap[name] = newPolicyMapping cache.iamUserPolicyMap.Store(name, newPolicyMapping)
} }
} else { } else {
cache.iamGroupPolicyMap[name] = newPolicyMapping cache.iamGroupPolicyMap.Store(name, newPolicyMapping)
} }
} }
@ -1125,12 +1137,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
} }
if !isGroup { if !isGroup {
if userType == stsUser { if userType == stsUser {
delete(cache.iamSTSPolicyMap, name) cache.iamSTSPolicyMap.Delete(name)
} else { } else {
delete(cache.iamUserPolicyMap, name) cache.iamUserPolicyMap.Delete(name)
} }
} else { } else {
delete(cache.iamGroupPolicyMap, name) cache.iamGroupPolicyMap.Delete(name)
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return cache.updatedAt, nil return cache.updatedAt, nil
@ -1149,12 +1161,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
} }
if !isGroup { if !isGroup {
if userType == stsUser { if userType == stsUser {
cache.iamSTSPolicyMap[name] = mp cache.iamSTSPolicyMap.Store(name, mp)
} else { } else {
cache.iamUserPolicyMap[name] = mp cache.iamUserPolicyMap.Store(name, mp)
} }
} else { } else {
cache.iamGroupPolicyMap[name] = mp cache.iamGroupPolicyMap.Store(name, mp)
} }
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return mp.UpdatedAt, nil return mp.UpdatedAt, nil
@ -1178,33 +1190,35 @@ func (store *IAMStoreSys) PolicyNotificationHandler(ctx context.Context, policy
delete(cache.iamPolicyDocsMap, policy) delete(cache.iamPolicyDocsMap, policy)
// update user policy map // update user policy map
for u, mp := range cache.iamUserPolicyMap { cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool {
pset := mp.policySet() pset := mp.policySet()
if !pset.Contains(policy) { if !pset.Contains(policy) {
continue return true
} }
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
_, ok := cache.iamUsersMap[u] _, ok := cache.iamUsersMap[u]
if !ok { if !ok {
// happens when account is deleted or // happens when account is deleted or
// expired. // expired.
delete(cache.iamUserPolicyMap, u) cache.iamUserPolicyMap.Delete(u)
continue return true
} }
} }
pset.Remove(policy) pset.Remove(policy)
cache.iamUserPolicyMap[u] = newMappedPolicy(strings.Join(pset.ToSlice(), ",")) cache.iamUserPolicyMap.Store(u, newMappedPolicy(strings.Join(pset.ToSlice(), ",")))
} return true
})
// update group policy map // update group policy map
for g, mp := range cache.iamGroupPolicyMap { cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool {
pset := mp.policySet() pset := mp.policySet()
if !pset.Contains(policy) { if !pset.Contains(policy) {
continue return true
} }
pset.Remove(policy) pset.Remove(policy)
cache.iamGroupPolicyMap[g] = newMappedPolicy(strings.Join(pset.ToSlice(), ",")) cache.iamGroupPolicyMap.Store(g, newMappedPolicy(strings.Join(pset.ToSlice(), ",")))
} return true
})
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
return nil return nil
@ -1230,26 +1244,28 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string, isFro
// we do allow deletion. // we do allow deletion.
users := []string{} users := []string{}
groups := []string{} groups := []string{}
for u, mp := range cache.iamUserPolicyMap { cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool {
pset := mp.policySet() pset := mp.policySet()
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
if _, ok := cache.iamUsersMap[u]; !ok { if _, ok := cache.iamUsersMap[u]; !ok {
// This case can happen when a temporary account is // This case can happen when a temporary account is
// deleted or expired - remove it from userPolicyMap. // deleted or expired - remove it from userPolicyMap.
delete(cache.iamUserPolicyMap, u) cache.iamUserPolicyMap.Delete(u)
continue return true
} }
} }
if pset.Contains(policy) { if pset.Contains(policy) {
users = append(users, u) users = append(users, u)
} }
} return true
for g, mp := range cache.iamGroupPolicyMap { })
cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool {
pset := mp.policySet() pset := mp.policySet()
if pset.Contains(policy) { if pset.Contains(policy) {
groups = append(groups, g) groups = append(groups, g)
} }
} return true
})
if len(users) != 0 || len(groups) != 0 { if len(users) != 0 || len(groups) != 0 {
return errPolicyInUse return errPolicyInUse
} }
@ -1466,11 +1482,11 @@ func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserI
continue continue
} }
var policies []string var policies []string
mp, ok := cache.iamUserPolicyMap[k] mp, ok := cache.iamUserPolicyMap.Load(k)
if ok { if ok {
policies = append(policies, mp.Policies) policies = append(policies, mp.Policies)
for _, group := range cache.iamUserGroupMemberships[k].ToSlice() { for _, group := range cache.iamUserGroupMemberships[k].ToSlice() {
if nmp, ok := cache.iamGroupPolicyMap[group]; ok { if nmp, ok := cache.iamGroupPolicyMap.Load(group); ok {
policies = append(policies, nmp.Policies) policies = append(policies, nmp.Policies)
} }
} }
@ -1505,8 +1521,9 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
if v.IsTemp() || v.IsServiceAccount() { if v.IsTemp() || v.IsServiceAccount() {
continue continue
} }
pl, _ := cache.iamUserPolicyMap.Load(k)
result[k] = madmin.UserInfo{ result[k] = madmin.UserInfo{
PolicyName: cache.iamUserPolicyMap[k].Policies, PolicyName: pl.Policies,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
if v.IsValid() { if v.IsValid() {
return madmin.AccountEnabled return madmin.AccountEnabled
@ -1514,7 +1531,7 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
return madmin.AccountDisabled return madmin.AccountDisabled
}(), }(),
MemberOf: cache.iamUserGroupMemberships[k].ToSlice(), MemberOf: cache.iamUserGroupMemberships[k].ToSlice(),
UpdatedAt: cache.iamUserPolicyMap[k].UpdatedAt, UpdatedAt: pl.UpdatedAt,
} }
} }
@ -1527,12 +1544,14 @@ func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string {
defer store.runlock() defer store.runlock()
result := make(map[string]string) result := make(map[string]string)
for k, v := range cache.iamUserPolicyMap { cache.iamUserPolicyMap.Range(func(k string, v MappedPolicy) bool {
result[k] = v.Policies result[k] = v.Policies
} return true
for k, v := range cache.iamSTSPolicyMap { })
cache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool {
result[k] = v.Policies result[k] = v.Policies
} return true
})
return result return result
} }
@ -1561,14 +1580,14 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
break break
} }
} }
mappedPolicy, ok := cache.iamUserPolicyMap[name] mappedPolicy, ok := cache.iamUserPolicyMap.Load(name)
if !ok { if !ok {
mappedPolicy, ok = cache.iamSTSPolicyMap[name] mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name)
} }
if !ok { if !ok {
// Attempt to load parent user mapping for STS accounts // Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap) store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap)
mappedPolicy, ok = cache.iamSTSPolicyMap[name] mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name)
if !ok { if !ok {
return u, errNoSuchUser return u, errNoSuchUser
} }
@ -1589,9 +1608,9 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
if cred.IsTemp() || cred.IsServiceAccount() { if cred.IsTemp() || cred.IsServiceAccount() {
return u, errIAMActionNotAllowed return u, errIAMActionNotAllowed
} }
pl, _ := cache.iamUserPolicyMap.Load(name)
return madmin.UserInfo{ return madmin.UserInfo{
PolicyName: cache.iamUserPolicyMap[name].Policies, PolicyName: pl.Policies,
Status: func() madmin.AccountStatus { Status: func() madmin.AccountStatus {
if cred.IsValid() { if cred.IsValid() {
return madmin.AccountEnabled return madmin.AccountEnabled
@ -1599,7 +1618,7 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
return madmin.AccountDisabled return madmin.AccountDisabled
}(), }(),
MemberOf: cache.iamUserGroupMemberships[name].ToSlice(), MemberOf: cache.iamUserGroupMemberships[name].ToSlice(),
UpdatedAt: cache.iamUserPolicyMap[name].UpdatedAt, UpdatedAt: pl.UpdatedAt,
}, nil }, nil
} }
@ -1612,7 +1631,7 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
cache := store.lock() cache := store.lock()
defer store.unlock() defer store.unlock()
var m map[string]MappedPolicy var m *xsync.MapOf[string, MappedPolicy]
switch { switch {
case isGroup: case isGroup:
m = cache.iamGroupPolicyMap m = cache.iamGroupPolicyMap
@ -1623,7 +1642,7 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
if errors.Is(err, errNoSuchPolicy) { if errors.Is(err, errNoSuchPolicy) {
// This means that the policy mapping was deleted, so we update // This means that the policy mapping was deleted, so we update
// the cache. // the cache.
delete(m, userOrGroup) m.Delete(userOrGroup)
cache.updatedAt = time.Now() cache.updatedAt = time.Now()
err = nil err = nil
@ -1688,7 +1707,7 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
} }
// 3. Delete any mapped policy // 3. Delete any mapped policy
delete(cache.iamUserPolicyMap, accessKey) cache.iamUserPolicyMap.Delete(accessKey)
return nil return nil
} }
@ -1782,7 +1801,7 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user
// It is ok to ignore deletion error on the mapped policy // It is ok to ignore deletion error on the mapped policy
store.deleteMappedPolicy(ctx, accessKey, userType, false) store.deleteMappedPolicy(ctx, accessKey, userType, false)
delete(cache.iamUserPolicyMap, accessKey) cache.iamUserPolicyMap.Delete(accessKey)
err := store.deleteUserIdentity(ctx, accessKey, userType) err := store.deleteUserIdentity(ctx, accessKey, userType)
if err == errNoSuchUser { if err == errNoSuchUser {
@ -1822,7 +1841,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
return time.Time{}, err return time.Time{}, err
} }
cache.iamSTSPolicyMap[cred.ParentUser] = mp cache.iamSTSPolicyMap.Store(cred.ParentUser, mp)
} }
u := newUserIdentity(cred) u := newUserIdentity(cred)
@ -1859,7 +1878,7 @@ func (store *IAMStoreSys) DeleteUsers(ctx context.Context, users []string) error
if usersToDelete.Contains(user) || usersToDelete.Contains(cred.ParentUser) { if usersToDelete.Contains(user) || usersToDelete.Contains(cred.ParentUser) {
// Delete this user account and its policy mapping // Delete this user account and its policy mapping
store.deleteMappedPolicy(ctx, user, userType, false) store.deleteMappedPolicy(ctx, user, userType, false)
delete(cache.iamUserPolicyMap, user) cache.iamUserPolicyMap.Delete(user)
// we are only logging errors, not handling them. // we are only logging errors, not handling them.
err := store.deleteUserIdentity(ctx, user, userType) err := store.deleteUserIdentity(ctx, user, userType)
@ -1961,13 +1980,13 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
) []madmin.UserPolicyEntities { ) []madmin.UserPolicyEntities {
var r []madmin.UserPolicyEntities var r []madmin.UserPolicyEntities
usersSet := set.CreateStringSet(users...) usersSet := set.CreateStringSet(users...)
for user, mappedPolicy := range cache.iamUserPolicyMap { cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
if userPredicate != nil && !userPredicate(user) { if userPredicate != nil && !userPredicate(user) {
continue return true
} }
if !usersSet.IsEmpty() && !usersSet.Contains(user) { if !usersSet.IsEmpty() && !usersSet.Contains(user) {
continue return true
} }
ps := mappedPolicy.toSlice() ps := mappedPolicy.toSlice()
@ -1976,17 +1995,18 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
User: user, User: user,
Policies: ps, Policies: ps,
}) })
} return true
})
stsMap := map[string]MappedPolicy{} stsMap := xsync.NewMapOf[string, MappedPolicy]()
for _, user := range users { for _, user := range users {
// Attempt to load parent user mapping for STS accounts // Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap) store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
} }
for user, mappedPolicy := range stsMap { stsMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
if userPredicate != nil && !userPredicate(user) { if userPredicate != nil && !userPredicate(user) {
continue return true
} }
ps := mappedPolicy.toSlice() ps := mappedPolicy.toSlice()
@ -1995,7 +2015,8 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
User: user, User: user,
Policies: ps, Policies: ps,
}) })
} return true
})
sort.Slice(r, func(i, j int) bool { sort.Slice(r, func(i, j int) bool {
return r[i].User < r[j].User return r[i].User < r[j].User
@ -2010,13 +2031,13 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
) []madmin.GroupPolicyEntities { ) []madmin.GroupPolicyEntities {
var r []madmin.GroupPolicyEntities var r []madmin.GroupPolicyEntities
groupsSet := set.CreateStringSet(groups...) groupsSet := set.CreateStringSet(groups...)
for group, mappedPolicy := range cache.iamGroupPolicyMap { cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
if groupPredicate != nil && !groupPredicate(group) { if groupPredicate != nil && !groupPredicate(group) {
continue return true
} }
if !groupsSet.IsEmpty() && !groupsSet.Contains(group) { if !groupsSet.IsEmpty() && !groupsSet.Contains(group) {
continue return true
} }
ps := mappedPolicy.toSlice() ps := mappedPolicy.toSlice()
@ -2025,7 +2046,8 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
Group: group, Group: group,
Policies: ps, Policies: ps,
}) })
} return true
})
sort.Slice(r, func(i, j int) bool { sort.Slice(r, func(i, j int) bool {
return r[i].Group < r[j].Group return r[i].Group < r[j].Group
@ -2041,9 +2063,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
queryPolSet := set.CreateStringSet(policies...) queryPolSet := set.CreateStringSet(policies...)
policyToUsersMap := make(map[string]set.StringSet) policyToUsersMap := make(map[string]set.StringSet)
for user, mappedPolicy := range cache.iamUserPolicyMap { cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
if userPredicate != nil && !userPredicate(user) { if userPredicate != nil && !userPredicate(user) {
continue return true
} }
commonPolicySet := mappedPolicy.policySet() commonPolicySet := mappedPolicy.policySet()
@ -2059,7 +2081,8 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
policyToUsersMap[policy] = s policyToUsersMap[policy] = s
} }
} }
} return true
})
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok { if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) { for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) {
@ -2088,9 +2111,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
} }
policyToGroupsMap := make(map[string]set.StringSet) policyToGroupsMap := make(map[string]set.StringSet)
for group, mappedPolicy := range cache.iamGroupPolicyMap { cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
if groupPredicate != nil && !groupPredicate(group) { if groupPredicate != nil && !groupPredicate(group) {
continue return true
} }
commonPolicySet := mappedPolicy.policySet() commonPolicySet := mappedPolicy.policySet()
@ -2106,7 +2129,8 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
policyToGroupsMap[policy] = s policyToGroupsMap[policy] = s
} }
} }
} return true
})
m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap)) m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap))
for policy, groups := range policyToGroupsMap { for policy, groups := range policyToGroupsMap {
@ -2564,13 +2588,15 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
// Load any associated policy definitions // Load any associated policy definitions
if !stsAccountFound { if !stsAccountFound {
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() { pols, _ := cache.iamUserPolicyMap.Load(accessKey)
for _, policy := range pols.toSlice() {
if _, found = cache.iamPolicyDocsMap[policy]; !found { if _, found = cache.iamPolicyDocsMap[policy]; !found {
store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3) store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3)
} }
} }
} else { } else {
for _, policy := range cache.iamSTSPolicyMap[stsUserCred.Credentials.AccessKey].toSlice() { pols, _ := cache.iamSTSPolicyMap.Load(stsUserCred.Credentials.AccessKey)
for _, policy := range pols.toSlice() {
if _, found = cache.iamPolicyDocsMap[policy]; !found { if _, found = cache.iamPolicyDocsMap[policy]; !found {
store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3) store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3)
} }

View File

@ -46,6 +46,7 @@ import (
sreplication "github.com/minio/minio/internal/bucket/replication" sreplication "github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v2/policy"
"github.com/puzpuzpuz/xsync/v3"
) )
const ( const (
@ -2044,28 +2045,30 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
// Followed by group policy mapping // Followed by group policy mapping
{ {
// Replicate policy mappings on local to all peers. // Replicate policy mappings on local to all peers.
groupPolicyMap := make(map[string]MappedPolicy) groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
errG := globalIAMSys.store.loadMappedPolicies(ctx, unknownIAMUserType, true, groupPolicyMap) errG := globalIAMSys.store.loadMappedPolicies(ctx, unknownIAMUserType, true, groupPolicyMap)
if errG != nil { if errG != nil {
return errSRBackendIssue(errG) return errSRBackendIssue(errG)
} }
for group, mp := range groupPolicyMap { var err error
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ groupPolicyMap.Range(func(k string, mp MappedPolicy) bool {
err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicyMapping, Type: madmin.SRIAMItemPolicyMapping,
PolicyMapping: &madmin.SRPolicyMapping{ PolicyMapping: &madmin.SRPolicyMapping{
UserOrGroup: group, UserOrGroup: k,
UserType: int(unknownIAMUserType), UserType: int(unknownIAMUserType),
IsGroup: true, IsGroup: true,
Policy: mp.Policies, Policy: mp.Policies,
}, },
UpdatedAt: mp.UpdatedAt, UpdatedAt: mp.UpdatedAt,
}) })
return err == nil
})
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
} }
} }
}
// Service accounts are the static accounts that should be synced with // Service accounts are the static accounts that should be synced with
// valid claims. // valid claims.
@ -2128,14 +2131,14 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
// Followed by policy mapping for the userAccounts we previously synced. // Followed by policy mapping for the userAccounts we previously synced.
{ {
// Replicate policy mappings on local to all peers. // Replicate policy mappings on local to all peers.
userPolicyMap := make(map[string]MappedPolicy) userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
errU := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap) errU := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
if errU != nil { if errU != nil {
return errSRBackendIssue(errU) return errSRBackendIssue(errU)
} }
var err error
for user, mp := range userPolicyMap { userPolicyMap.Range(func(user string, mp MappedPolicy) bool {
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicyMapping, Type: madmin.SRIAMItemPolicyMapping,
PolicyMapping: &madmin.SRPolicyMapping{ PolicyMapping: &madmin.SRPolicyMapping{
UserOrGroup: user, UserOrGroup: user,
@ -2145,23 +2148,25 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
}, },
UpdatedAt: mp.UpdatedAt, UpdatedAt: mp.UpdatedAt,
}) })
return err == nil
})
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
} }
} }
}
// and finally followed by policy mappings for for STS users. // and finally followed by policy mappings for for STS users.
{ {
// Replicate policy mappings on local to all peers. // Replicate policy mappings on local to all peers.
stsPolicyMap := make(map[string]MappedPolicy) stsPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
errU := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap) errU := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap)
if errU != nil { if errU != nil {
return errSRBackendIssue(errU) return errSRBackendIssue(errU)
} }
for user, mp := range stsPolicyMap { var err error
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{ stsPolicyMap.Range(func(user string, mp MappedPolicy) bool {
err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicyMapping, Type: madmin.SRIAMItemPolicyMapping,
PolicyMapping: &madmin.SRPolicyMapping{ PolicyMapping: &madmin.SRPolicyMapping{
UserOrGroup: user, UserOrGroup: user,
@ -2171,11 +2176,12 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
}, },
UpdatedAt: mp.UpdatedAt, UpdatedAt: mp.UpdatedAt,
}) })
return err == nil
})
if err != nil { if err != nil {
return errSRIAMError(err) return errSRIAMError(err)
} }
} }
}
return nil return nil
} }
@ -3783,12 +3789,12 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
if opts.Users || opts.Entity == madmin.SRUserEntity { if opts.Users || opts.Entity == madmin.SRUserEntity {
// Replicate policy mappings on local to all peers. // Replicate policy mappings on local to all peers.
userPolicyMap := make(map[string]MappedPolicy) userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
stsPolicyMap := make(map[string]MappedPolicy) stsPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
svcPolicyMap := make(map[string]MappedPolicy) svcPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
if opts.Entity == madmin.SRUserEntity { if opts.Entity == madmin.SRUserEntity {
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, false); ok { if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, false); ok {
userPolicyMap[opts.EntityValue] = mp userPolicyMap.Store(opts.EntityValue, mp)
} }
} else { } else {
stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap) stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap)
@ -3804,34 +3810,23 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
return info, errSRBackendIssue(svcErr) return info, errSRBackendIssue(svcErr)
} }
} }
info.UserPolicies = make(map[string]madmin.SRPolicyMapping, len(userPolicyMap)) info.UserPolicies = make(map[string]madmin.SRPolicyMapping, userPolicyMap.Size())
for user, mp := range userPolicyMap { addPolicy := func(t IAMUserType, mp *xsync.MapOf[string, MappedPolicy]) {
info.UserPolicies[user] = madmin.SRPolicyMapping{ mp.Range(func(k string, mp MappedPolicy) bool {
info.UserPolicies[k] = madmin.SRPolicyMapping{
IsGroup: false, IsGroup: false,
UserOrGroup: user, UserOrGroup: k,
UserType: int(regUser), UserType: int(t),
Policy: mp.Policies,
UpdatedAt: mp.UpdatedAt,
}
}
for stsU, mp := range stsPolicyMap {
info.UserPolicies[stsU] = madmin.SRPolicyMapping{
IsGroup: false,
UserOrGroup: stsU,
UserType: int(stsUser),
Policy: mp.Policies,
UpdatedAt: mp.UpdatedAt,
}
}
for svcU, mp := range svcPolicyMap {
info.UserPolicies[svcU] = madmin.SRPolicyMapping{
IsGroup: false,
UserOrGroup: svcU,
UserType: int(svcUser),
Policy: mp.Policies, Policy: mp.Policies,
UpdatedAt: mp.UpdatedAt, UpdatedAt: mp.UpdatedAt,
} }
return true
})
} }
addPolicy(regUser, userPolicyMap)
addPolicy(stsUser, stsPolicyMap)
addPolicy(svcUser, svcPolicyMap)
info.UserInfoMap = make(map[string]madmin.UserInfo) info.UserInfoMap = make(map[string]madmin.UserInfo)
if opts.Entity == madmin.SRUserEntity { if opts.Entity == madmin.SRUserEntity {
if ui, err := globalIAMSys.GetUserInfo(ctx, opts.EntityValue); err == nil { if ui, err := globalIAMSys.GetUserInfo(ctx, opts.EntityValue); err == nil {
@ -3875,10 +3870,10 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
if opts.Groups || opts.Entity == madmin.SRGroupEntity { if opts.Groups || opts.Entity == madmin.SRGroupEntity {
// Replicate policy mappings on local to all peers. // Replicate policy mappings on local to all peers.
groupPolicyMap := make(map[string]MappedPolicy) groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
if opts.Entity == madmin.SRGroupEntity { if opts.Entity == madmin.SRGroupEntity {
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, true); ok { if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, true); ok {
groupPolicyMap[opts.EntityValue] = mp groupPolicyMap.Store(opts.EntityValue, mp)
} }
} else { } else {
stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap) stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
@ -3891,15 +3886,16 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
} }
} }
info.GroupPolicies = make(map[string]madmin.SRPolicyMapping, len(c.state.Peers)) info.GroupPolicies = make(map[string]madmin.SRPolicyMapping, groupPolicyMap.Size())
for group, mp := range groupPolicyMap { groupPolicyMap.Range(func(group string, mp MappedPolicy) bool {
info.GroupPolicies[group] = madmin.SRPolicyMapping{ info.GroupPolicies[group] = madmin.SRPolicyMapping{
IsGroup: true, IsGroup: true,
UserOrGroup: group, UserOrGroup: group,
Policy: mp.Policies, Policy: mp.Policies,
UpdatedAt: mp.UpdatedAt, UpdatedAt: mp.UpdatedAt,
} }
} return true
})
info.GroupDescMap = make(map[string]madmin.GroupDesc) info.GroupDescMap = make(map[string]madmin.GroupDesc)
if opts.Entity == madmin.SRGroupEntity { if opts.Entity == madmin.SRGroupEntity {
if gd, err := globalIAMSys.GetGroupDescription(opts.EntityValue); err == nil { if gd, err := globalIAMSys.GetGroupDescription(opts.EntityValue); err == nil {

1
go.mod
View File

@ -219,6 +219,7 @@ require (
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/pquerna/cachecontrol v0.2.0 // indirect
github.com/prometheus/prom2json v1.3.3 // indirect github.com/prometheus/prom2json v1.3.3 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 // indirect github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rjeczalik/notify v0.9.3 // indirect github.com/rjeczalik/notify v0.9.3 // indirect

2
go.sum
View File

@ -588,6 +588,8 @@ github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGK
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo= github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo=
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc= github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=

View File

@ -42,6 +42,7 @@ import (
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/puzpuzpuz/xsync/v3"
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
"github.com/zeebo/xxh3" "github.com/zeebo/xxh3"
) )
@ -75,10 +76,10 @@ type Connection struct {
ctx context.Context ctx context.Context
// Active mux connections. // Active mux connections.
outgoing *lockedClientMap outgoing *xsync.MapOf[uint64, *muxClient]
// Incoming streams // Incoming streams
inStream *lockedServerMap inStream *xsync.MapOf[uint64, *muxServer]
// outQueue is the output queue // outQueue is the output queue
outQueue chan []byte outQueue chan []byte
@ -205,8 +206,8 @@ func newConnection(o connectionParams) *Connection {
Local: o.local, Local: o.local,
id: o.id, id: o.id,
ctx: o.ctx, ctx: o.ctx,
outgoing: &lockedClientMap{m: make(map[uint64]*muxClient, 1000)}, outgoing: xsync.NewMapOfPresized[uint64, *muxClient](1000),
inStream: &lockedServerMap{m: make(map[uint64]*muxServer, 1000)}, inStream: xsync.NewMapOfPresized[uint64, *muxServer](1000),
outQueue: make(chan []byte, defaultOutQueue), outQueue: make(chan []byte, defaultOutQueue),
dialer: o.dial, dialer: o.dial,
side: ws.StateServerSide, side: ws.StateServerSide,

View File

@ -146,142 +146,3 @@ func bytesOrLength(b []byte) string {
} }
return fmt.Sprint(b) return fmt.Sprint(b)
} }
type lockedClientMap struct {
m map[uint64]*muxClient
mu sync.Mutex
}
func (m *lockedClientMap) Load(id uint64) (*muxClient, bool) {
m.mu.Lock()
v, ok := m.m[id]
m.mu.Unlock()
return v, ok
}
func (m *lockedClientMap) LoadAndDelete(id uint64) (*muxClient, bool) {
m.mu.Lock()
v, ok := m.m[id]
if ok {
delete(m.m, id)
}
m.mu.Unlock()
return v, ok
}
func (m *lockedClientMap) Size() int {
m.mu.Lock()
v := len(m.m)
m.mu.Unlock()
return v
}
func (m *lockedClientMap) Delete(id uint64) {
m.mu.Lock()
delete(m.m, id)
m.mu.Unlock()
}
func (m *lockedClientMap) Range(fn func(key uint64, value *muxClient) bool) {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.m {
if !fn(k, v) {
break
}
}
}
func (m *lockedClientMap) Clear() {
m.mu.Lock()
m.m = map[uint64]*muxClient{}
m.mu.Unlock()
}
func (m *lockedClientMap) LoadOrStore(id uint64, v *muxClient) (*muxClient, bool) {
m.mu.Lock()
v2, ok := m.m[id]
if ok {
m.mu.Unlock()
return v2, true
}
m.m[id] = v
m.mu.Unlock()
return v, false
}
type lockedServerMap struct {
m map[uint64]*muxServer
mu sync.Mutex
}
func (m *lockedServerMap) Load(id uint64) (*muxServer, bool) {
m.mu.Lock()
v, ok := m.m[id]
m.mu.Unlock()
return v, ok
}
func (m *lockedServerMap) LoadAndDelete(id uint64) (*muxServer, bool) {
m.mu.Lock()
v, ok := m.m[id]
if ok {
delete(m.m, id)
}
m.mu.Unlock()
return v, ok
}
func (m *lockedServerMap) Size() int {
m.mu.Lock()
v := len(m.m)
m.mu.Unlock()
return v
}
func (m *lockedServerMap) Delete(id uint64) {
m.mu.Lock()
delete(m.m, id)
m.mu.Unlock()
}
func (m *lockedServerMap) Range(fn func(key uint64, value *muxServer) bool) {
m.mu.Lock()
for k, v := range m.m {
if !fn(k, v) {
break
}
}
m.mu.Unlock()
}
func (m *lockedServerMap) Clear() {
m.mu.Lock()
m.m = map[uint64]*muxServer{}
m.mu.Unlock()
}
func (m *lockedServerMap) LoadOrStore(id uint64, v *muxServer) (*muxServer, bool) {
m.mu.Lock()
v2, ok := m.m[id]
if ok {
m.mu.Unlock()
return v2, true
}
m.m[id] = v
m.mu.Unlock()
return v, false
}
func (m *lockedServerMap) LoadOrCompute(id uint64, fn func() *muxServer) (*muxServer, bool) {
m.mu.Lock()
v2, ok := m.m[id]
if ok {
m.mu.Unlock()
return v2, true
}
v := fn()
m.m[id] = v
m.mu.Unlock()
return v, false
}