iam: Hot load of the policy during request authorization (#20007)

Hot load a policy document when during account authorization evaluation
to avoid returning 403 during server startup, when not all policies are
already loaded.

Add this support for group policies as well.
This commit is contained in:
Anis Eleuch 2024-06-28 01:03:07 +01:00 committed by GitHub
parent 709612cb37
commit 722118386d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 112 additions and 25 deletions

View File

@ -457,6 +457,8 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
bootstrapTraceMsgFirstTime("loading all IAM items") bootstrapTraceMsgFirstTime("loading all IAM items")
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
listStartTime := UTCNow() listStartTime := UTCNow()
listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx) listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx)
if err != nil { if err != nil {
@ -485,7 +487,6 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
if took := time.Since(policyLoadStartTime); took > maxIAMLoadOpTime { if took := time.Since(policyLoadStartTime); took > maxIAMLoadOpTime {
logger.Info("Policy docs load took %.2fs (for %d items)", took.Seconds(), len(policiesList)) logger.Info("Policy docs load took %.2fs (for %d items)", took.Seconds(), len(policiesList))
} }
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
if iamOS.usersSysType == MinIOUsersSysType { if iamOS.usersSysType == MinIOUsersSysType {
bootstrapTraceMsgFirstTime("loading regular IAM users") bootstrapTraceMsgFirstTime("loading regular IAM users")

View File

@ -431,8 +431,41 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
} }
} }
// returned policy could be empty // returned policy could be empty, we use set to de-duplicate.
policies := mp.toSlice() policies := set.CreateStringSet(mp.toSlice()...)
for _, group := range u.Credentials.Groups {
if store.getUsersSysType() == MinIOUsersSysType {
g, ok := c.iamGroupsMap[group]
if !ok {
if err := store.loadGroup(context.Background(), group, c.iamGroupsMap); err != nil {
return nil, time.Time{}, err
}
g, ok = c.iamGroupsMap[group]
if !ok {
return nil, time.Time{}, errNoSuchGroup
}
}
// Group is disabled, so we return no policy - this
// ensures the request is denied.
if g.Status == statusDisabled {
return nil, time.Time{}, nil
}
}
policy, ok := c.iamGroupPolicyMap.Load(group)
if !ok {
if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
return nil, time.Time{}, err
}
policy, _ = c.iamGroupPolicyMap.Load(group)
}
for _, p := range policy.toSlice() {
policies.Add(p)
}
}
for _, group := range c.iamUserGroupMemberships[name].ToSlice() { for _, group := range c.iamUserGroupMemberships[name].ToSlice() {
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
@ -462,10 +495,12 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
policy, _ = c.iamGroupPolicyMap.Load(group) policy, _ = c.iamGroupPolicyMap.Load(group)
} }
policies = append(policies, policy.toSlice()...) for _, p := range policy.toSlice() {
policies.Add(p)
}
} }
return policies, mp.UpdatedAt, nil return policies.ToSlice(), mp.UpdatedAt, nil
} }
func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error { func (c *iamCache) updateUserWithClaims(key string, u UserIdentity) error {
@ -937,12 +972,7 @@ func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc
}, nil }, nil
} }
// ListGroups - lists groups. Since this is not going to be a frequent func (store *IAMStoreSys) updateGroups(ctx context.Context, cache *iamCache) (res []string, err error) {
// operation, we fetch this info from storage, and refresh the cache as well.
func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) {
cache := store.lock()
defer store.unlock()
if store.getUsersSysType() == MinIOUsersSysType { if store.getUsersSysType() == MinIOUsersSysType {
m := map[string]GroupInfo{} m := map[string]GroupInfo{}
err = store.loadGroups(ctx, m) err = store.loadGroups(ctx, m)
@ -970,7 +1000,16 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err
}) })
} }
return return res, nil
}
// ListGroups - lists groups. Since this is not going to be a frequent
// operation, we fetch this info from storage, and refresh the cache as well.
func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err error) {
cache := store.lock()
defer store.unlock()
return store.updateGroups(ctx, cache)
} }
// listGroups - lists groups - fetch groups from cache // listGroups - lists groups - fetch groups from cache
@ -1445,16 +1484,51 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri
return strings.Join(policies, ","), policy.MergePolicies(toMerge...) return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
} }
// FilterPolicies - accepts a comma separated list of policy names as a string // MergePolicies - accepts a comma separated list of policy names as a string
// and bucket and returns only policies that currently exist in MinIO. If // and returns only policies that currently exist in MinIO. It includes hot loading
// bucketName is non-empty, additionally filters policies matching the bucket. // of policies if not in the memory
// The first returned value is the list of currently existing policies, and the func (store *IAMStoreSys) MergePolicies(policyName string) (string, policy.Policy) {
// second is their combined policy definition. var policies []string
func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) { var missingPolicies []string
cache := store.rlock() var toMerge []policy.Policy
defer store.runlock()
return filterPolicies(cache, policyName, bucketName) cache := store.rlock()
for _, policy := range newMappedPolicy(policyName).toSlice() {
if policy == "" {
continue
}
p, found := cache.iamPolicyDocsMap[policy]
if !found {
missingPolicies = append(missingPolicies, policy)
continue
}
policies = append(policies, policy)
toMerge = append(toMerge, p.Policy)
}
store.runlock()
if len(missingPolicies) > 0 {
m := make(map[string]PolicyDoc)
for _, policy := range missingPolicies {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_ = store.loadPolicyDoc(ctx, policy, m)
cancel()
}
cache := store.lock()
for policy, p := range m {
cache.iamPolicyDocsMap[policy] = p
}
store.unlock()
for policy, p := range m {
policies = append(policies, policy)
toMerge = append(toMerge, p.Policy)
}
}
return strings.Join(policies, ","), policy.MergePolicies(toMerge...)
} }
// GetBucketUsers - returns users (not STS or service accounts) that have access // GetBucketUsers - returns users (not STS or service accounts) that have access
@ -2675,6 +2749,18 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) error
} }
} }
load := len(cache.iamGroupsMap) == 0
if store.getUsersSysType() == LDAPUsersSysType && cache.iamGroupPolicyMap.Size() == 0 {
load = true
}
if load {
if _, err = store.updateGroups(ctx, cache); err != nil {
return "done", err
}
}
cache.buildUserGroupMemberships()
return "done", err return "done", err
}) })

View File

@ -436,7 +436,7 @@ func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[a
// running server by creating the policies after start up. // running server by creating the policies after start up.
for arn, rolePolicies := range m { for arn, rolePolicies := range m {
specifiedPoliciesSet := newMappedPolicy(rolePolicies).policySet() specifiedPoliciesSet := newMappedPolicy(rolePolicies).policySet()
validPolicies, _ := sys.store.FilterPolicies(rolePolicies, "") validPolicies, _ := sys.store.MergePolicies(rolePolicies)
knownPoliciesSet := newMappedPolicy(validPolicies).policySet() knownPoliciesSet := newMappedPolicy(validPolicies).policySet()
unknownPoliciesSet := specifiedPoliciesSet.Difference(knownPoliciesSet) unknownPoliciesSet := specifiedPoliciesSet.Difference(knownPoliciesSet)
if len(unknownPoliciesSet) > 0 { if len(unknownPoliciesSet) > 0 {
@ -672,7 +672,7 @@ func (sys *IAMSys) CurrentPolicies(policyName string) string {
return "" return ""
} }
policies, _ := sys.store.FilterPolicies(policyName, "") policies, _ := sys.store.MergePolicies(policyName)
return policies return policies
} }
@ -2122,7 +2122,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string)
var combinedPolicy policy.Policy var combinedPolicy policy.Policy
// Policies were found, evaluate all of them. // Policies were found, evaluate all of them.
if !isOwnerDerived { if !isOwnerDerived {
availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "") availablePoliciesStr, c := sys.store.MergePolicies(strings.Join(svcPolicies, ","))
if availablePoliciesStr == "" { if availablePoliciesStr == "" {
return false return false
} }
@ -2350,7 +2350,7 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
// GetCombinedPolicy returns a combined policy combining all policies // GetCombinedPolicy returns a combined policy combining all policies
func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy { func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy {
_, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "") _, policy := sys.store.MergePolicies(strings.Join(policies, ","))
return policy return policy
} }