diff --git a/cmd/iam-object-store.go b/cmd/iam-object-store.go index b0fc2dcc8..6a1bf567c 100644 --- a/cmd/iam-object-store.go +++ b/cmd/iam-object-store.go @@ -457,6 +457,8 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam bootstrapTraceMsgFirstTime("loading all IAM items") + setDefaultCannedPolicies(cache.iamPolicyDocsMap) + listStartTime := UTCNow() listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx) if err != nil { @@ -485,7 +487,6 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam if took := time.Since(policyLoadStartTime); took > maxIAMLoadOpTime { logger.Info("Policy docs load took %.2fs (for %d items)", took.Seconds(), len(policiesList)) } - setDefaultCannedPolicies(cache.iamPolicyDocsMap) if iamOS.usersSysType == MinIOUsersSysType { bootstrapTraceMsgFirstTime("loading regular IAM users") diff --git a/cmd/iam-store.go b/cmd/iam-store.go index 658f42563..729904165 100644 --- a/cmd/iam-store.go +++ b/cmd/iam-store.go @@ -431,8 +431,41 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([ } } - // returned policy could be empty - policies := mp.toSlice() + // returned policy could be empty, we use set to de-duplicate. + 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() { if store.getUsersSysType() == MinIOUsersSysType { @@ -462,10 +495,12 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([ 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 { @@ -937,12 +972,7 @@ func (store *IAMStoreSys) GetGroupDescription(group string) (gd madmin.GroupDesc }, 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() - +func (store *IAMStoreSys) updateGroups(ctx context.Context, cache *iamCache) (res []string, err error) { if store.getUsersSysType() == MinIOUsersSysType { m := map[string]GroupInfo{} 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 @@ -1445,16 +1484,51 @@ func filterPolicies(cache *iamCache, policyName string, bucketName string) (stri return strings.Join(policies, ","), policy.MergePolicies(toMerge...) } -// FilterPolicies - accepts a comma separated list of policy names as a string -// and bucket and returns only policies that currently exist in MinIO. If -// bucketName is non-empty, additionally filters policies matching the bucket. -// The first returned value is the list of currently existing policies, and the -// second is their combined policy definition. -func (store *IAMStoreSys) FilterPolicies(policyName string, bucketName string) (string, policy.Policy) { - cache := store.rlock() - defer store.runlock() +// MergePolicies - accepts a comma separated list of policy names as a string +// and returns only policies that currently exist in MinIO. It includes hot loading +// of policies if not in the memory +func (store *IAMStoreSys) MergePolicies(policyName string) (string, policy.Policy) { + var policies []string + var missingPolicies []string + var toMerge []policy.Policy - 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 @@ -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 }) diff --git a/cmd/iam.go b/cmd/iam.go index 50e436a10..17eb03b64 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -436,7 +436,7 @@ func (sys *IAMSys) validateAndAddRolePolicyMappings(ctx context.Context, m map[a // running server by creating the policies after start up. for arn, rolePolicies := range m { specifiedPoliciesSet := newMappedPolicy(rolePolicies).policySet() - validPolicies, _ := sys.store.FilterPolicies(rolePolicies, "") + validPolicies, _ := sys.store.MergePolicies(rolePolicies) knownPoliciesSet := newMappedPolicy(validPolicies).policySet() unknownPoliciesSet := specifiedPoliciesSet.Difference(knownPoliciesSet) if len(unknownPoliciesSet) > 0 { @@ -672,7 +672,7 @@ func (sys *IAMSys) CurrentPolicies(policyName string) string { return "" } - policies, _ := sys.store.FilterPolicies(policyName, "") + policies, _ := sys.store.MergePolicies(policyName) return policies } @@ -2122,7 +2122,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string) var combinedPolicy policy.Policy // Policies were found, evaluate all of them. if !isOwnerDerived { - availablePoliciesStr, c := sys.store.FilterPolicies(strings.Join(svcPolicies, ","), "") + availablePoliciesStr, c := sys.store.MergePolicies(strings.Join(svcPolicies, ",")) if availablePoliciesStr == "" { return false } @@ -2350,7 +2350,7 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe // GetCombinedPolicy returns a combined policy combining all policies func (sys *IAMSys) GetCombinedPolicy(policies ...string) policy.Policy { - _, policy := sys.store.FilterPolicies(strings.Join(policies, ","), "") + _, policy := sys.store.MergePolicies(strings.Join(policies, ",")) return policy }