diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index ae7b48be3..e412008f0 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -49,6 +49,8 @@ type IdentityAccessManagement struct { accessKeyIdent map[string]*Identity nameToIdentity map[string]*Identity // O(1) lookup by identity name policies map[string]*iam_pb.Policy + groups map[string]*iam_pb.Group // group name -> group + userGroups map[string][]string // user name -> group names (reverse index) accounts map[string]*Account emailAccount map[string]*Account hashes map[string]*sync.Pool @@ -563,6 +565,16 @@ func (iam *IdentityAccessManagement) ReplaceS3ApiConfiguration(config *iam_pb.S3 for _, policy := range config.Policies { policies[policy.Name] = policy } + groups := make(map[string]*iam_pb.Group) + userGroupsMap := make(map[string][]string) + for _, g := range config.Groups { + groups[g.Name] = g + if !g.Disabled { + for _, member := range g.Members { + userGroupsMap[member] = append(userGroupsMap[member], g.Name) + } + } + } for _, ident := range config.Identities { glog.V(3).Infof("loading identity %s (disabled=%v)", ident.Name, ident.Disabled) t := &Identity{ @@ -663,6 +675,8 @@ func (iam *IdentityAccessManagement) ReplaceS3ApiConfiguration(config *iam_pb.S3 iam.nameToIdentity = nameToIdentity iam.accessKeyIdent = accessKeyIdent iam.policies = policies + iam.groups = groups + iam.userGroups = userGroupsMap iam.rebuildIAMPolicyEngineLocked() // Re-add environment-based identities that were preserved @@ -911,6 +925,18 @@ func (iam *IdentityAccessManagement) MergeS3ApiConfiguration(config *iam_pb.S3Ap policies[policy.Name] = policy } + // Process groups from dynamic config + mergedGroups := make(map[string]*iam_pb.Group) + mergedUserGroups := make(map[string][]string) + for _, g := range config.Groups { + mergedGroups[g.Name] = g + if !g.Disabled { + for _, member := range g.Members { + mergedUserGroups[member] = append(mergedUserGroups[member], g.Name) + } + } + } + iam.m.Lock() // atomically switch iam.identities = identities @@ -920,6 +946,8 @@ func (iam *IdentityAccessManagement) MergeS3ApiConfiguration(config *iam_pb.S3Ap iam.nameToIdentity = nameToIdentity iam.accessKeyIdent = accessKeyIdent iam.policies = policies + iam.groups = mergedGroups + iam.userGroups = mergedUserGroups iam.rebuildIAMPolicyEngineLocked() // Update authentication state based on whether identities exist // Once enabled, keep it enabled (one-way toggle) @@ -1837,14 +1865,18 @@ func determineIAMAuthPath(sessionToken, principal, principalArn string) iamAuthP // Returns true if any matching statement explicitly allows the action. // Uses the cached iamPolicyEngine to avoid re-parsing policy JSON on every request. func (iam *IdentityAccessManagement) evaluateIAMPolicies(r *http.Request, identity *Identity, action Action, bucket, object string) bool { - if identity == nil || len(identity.PolicyNames) == 0 { - return false - } - iam.m.RLock() engine := iam.iamPolicyEngine + groupNames := iam.userGroups[identity.Name] + groupMap := iam.groups iam.m.RUnlock() + // Collect all policy names: user policies + group policies + hasPolicies := len(identity.PolicyNames) > 0 || len(groupNames) > 0 + if identity == nil || !hasPolicies { + return false + } + if engine == nil { return false } @@ -1858,15 +1890,17 @@ func (iam *IdentityAccessManagement) evaluateIAMPolicies(r *http.Request, identi conditions[k] = v } - for _, policyName := range identity.PolicyNames { - result := engine.EvaluatePolicy(policyName, &policy_engine.PolicyEvaluationArgs{ - Action: s3Action, - Resource: resource, - Principal: principal, - Conditions: conditions, - Claims: identity.Claims, - }) + evalArgs := &policy_engine.PolicyEvaluationArgs{ + Action: s3Action, + Resource: resource, + Principal: principal, + Conditions: conditions, + Claims: identity.Claims, + } + // Evaluate user's own policies + for _, policyName := range identity.PolicyNames { + result := engine.EvaluatePolicy(policyName, evalArgs) if result == policy_engine.PolicyResultDeny { return false } @@ -1875,6 +1909,23 @@ func (iam *IdentityAccessManagement) evaluateIAMPolicies(r *http.Request, identi } } + // Evaluate policies from user's groups (skip disabled groups) + for _, gName := range groupNames { + g, ok := groupMap[gName] + if !ok || g.Disabled { + continue + } + for _, policyName := range g.PolicyNames { + result := engine.EvaluatePolicy(policyName, evalArgs) + if result == policy_engine.PolicyResultDeny { + return false + } + if result == policy_engine.PolicyResultAllow { + explicitAllow = true + } + } + } + return explicitAllow } @@ -1894,7 +1945,10 @@ func (iam *IdentityAccessManagement) VerifyActionPermission(r *http.Request, ide hasSessionToken := r.Header.Get("X-SeaweedFS-Session-Token") != "" || r.Header.Get("X-Amz-Security-Token") != "" || r.URL.Query().Get("X-Amz-Security-Token") != "" - hasAttachedPolicies := len(identity.PolicyNames) > 0 + iam.m.RLock() + userGroupNames := iam.userGroups[identity.Name] + iam.m.RUnlock() + hasAttachedPolicies := len(identity.PolicyNames) > 0 || len(userGroupNames) > 0 if (len(identity.Actions) == 0 || hasSessionToken || hasAttachedPolicies) && iam.iamIntegration != nil { return iam.authorizeWithIAM(r, identity, action, bucket, object)