Browse Source

iam: integrate group policies into authorization

Add groups and userGroups reverse index to IdentityAccessManagement.
Populate both maps during ReplaceS3ApiConfiguration and
MergeS3ApiConfiguration. Modify evaluateIAMPolicies to evaluate
policies from user's enabled groups in addition to user policies.
Update VerifyActionPermission to consider group policies when
checking hasAttachedPolicies.
pull/8560/head
Chris Lu 2 days ago
parent
commit
e660180438
  1. 80
      weed/s3api/auth_credentials.go

80
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)

Loading…
Cancel
Save