Browse Source

STS: Fix is_admin propagation and optimize IAM policy evaluation for assumed roles

- Restore is_admin propagation via JWT req_ctx
- Optimize IsActionAllowed to skip role lookups for admin sessions
- Ensure session policies are still applied for downscoping
- Remove debug logging
- Fix syntax errors in cleanup
pull/8345/head
Chris Lu 3 weeks ago
parent
commit
14dfee9cdc
  1. 94
      weed/iam/integration/iam_manager.go
  2. 2
      weed/iam/sts/token_utils.go
  3. 4
      weed/s3api/s3_iam_middleware.go
  4. 3
      weed/s3api/s3api_sts.go

94
weed/iam/integration/iam_manager.go

@ -349,7 +349,17 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
// Add principal to context for policy matching // Add principal to context for policy matching
// The PolicyEngine checks RequestContext["principal"] or RequestContext["aws:PrincipalArn"] // The PolicyEngine checks RequestContext["principal"] or RequestContext["aws:PrincipalArn"]
evalCtx.RequestContext["principal"] = request.Principal evalCtx.RequestContext["principal"] = request.Principal
evalCtx.RequestContext["aws:PrincipalArn"] = request.Principal
evalCtx.RequestContext["aws:PrincipalArn"] = request.Principal // AWS standard key
// Check if this is an admin request - bypass policy evaluation if so
// This mirrors the logic in auth_signature_v4.go but applies it at authorization time
isAdmin := false
if request.RequestContext != nil {
if val, ok := request.RequestContext["is_admin"].(bool); ok && val {
isAdmin = true
}
// Print full request context for debugging
}
// Parse principal ARN to extract details for context variables (e.g. ${aws:username}) // Parse principal ARN to extract details for context variables (e.g. ${aws:username})
arnInfo := utils.ParsePrincipalARN(request.Principal) arnInfo := utils.ParsePrincipalARN(request.Principal)
@ -382,48 +392,56 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
} }
} }
policies := request.PolicyNames
if len(policies) == 0 {
// Extract role name from principal ARN
roleName := utils.ExtractRoleNameFromPrincipal(request.Principal)
if roleName == "" {
userName := utils.ExtractUserNameFromPrincipal(request.Principal)
if userName == "" {
return false, fmt.Errorf("could not extract role from principal: %s", request.Principal)
}
if m.userStore == nil {
return false, fmt.Errorf("user store unavailable for principal: %s", request.Principal)
}
user, err := m.userStore.GetUser(ctx, userName)
if err != nil || user == nil {
return false, fmt.Errorf("user not found for principal: %s (user=%s)", request.Principal, userName)
}
policies = user.GetPolicyNames()
} else {
// Get role definition
roleDef, err := m.roleStore.GetRole(ctx, m.getFilerAddress(), roleName)
if err != nil {
return false, fmt.Errorf("role not found: %s", roleName)
}
var baseResult *policy.EvaluationResult
var err error
policies = roleDef.AttachedPolicies
if isAdmin {
// Admin always has base access allowed
baseResult = &policy.EvaluationResult{Effect: policy.EffectAllow}
} else {
policies := request.PolicyNames
if len(policies) == 0 {
// Extract role name from principal ARN
roleName := utils.ExtractRoleNameFromPrincipal(request.Principal)
if roleName == "" {
userName := utils.ExtractUserNameFromPrincipal(request.Principal)
if userName == "" {
return false, fmt.Errorf("could not extract role from principal: %s", request.Principal)
}
if m.userStore == nil {
return false, fmt.Errorf("user store unavailable for principal: %s", request.Principal)
}
user, err := m.userStore.GetUser(ctx, userName)
if err != nil || user == nil {
return false, fmt.Errorf("user not found for principal: %s (user=%s)", request.Principal, userName)
}
policies = user.GetPolicyNames()
} else {
// Get role definition
roleDef, err := m.roleStore.GetRole(ctx, m.getFilerAddress(), roleName)
if err != nil {
return false, fmt.Errorf("role not found: %s", roleName)
}
policies = roleDef.AttachedPolicies
}
} }
}
if bucketPolicyName != "" {
// Enforce an upper bound on the number of policies to avoid excessive allocations
if len(policies) >= maxPoliciesForEvaluation {
return false, fmt.Errorf("too many policies for evaluation: %d >= %d", len(policies), maxPoliciesForEvaluation)
if bucketPolicyName != "" {
// Enforce an upper bound on the number of policies to avoid excessive allocations
if len(policies) >= maxPoliciesForEvaluation {
return false, fmt.Errorf("too many policies for evaluation: %d >= %d", len(policies), maxPoliciesForEvaluation)
}
// Create a new slice to avoid modifying the original and append the bucket policy
copied := make([]string, len(policies))
copy(copied, policies)
policies = append(copied, bucketPolicyName)
} }
// Create a new slice to avoid modifying the original and append the bucket policy
copied := make([]string, len(policies))
copy(copied, policies)
policies = append(copied, bucketPolicyName)
}
baseResult, err := m.policyEngine.Evaluate(ctx, "", evalCtx, policies)
if err != nil {
return false, fmt.Errorf("policy evaluation failed: %w", err)
baseResult, err = m.policyEngine.Evaluate(ctx, "", evalCtx, policies)
if err != nil {
return false, fmt.Errorf("policy evaluation failed: %w", err)
}
} }
// Base policy must allow; if it doesn't, deny immediately (session policy can only further restrict) // Base policy must allow; if it doesn't, deny immediately (session policy can only further restrict)

2
weed/iam/sts/token_utils.go

@ -44,6 +44,8 @@ func (t *TokenGenerator) GenerateJWTWithClaims(claims *STSSessionClaims) (string
claims.Issuer = t.issuer claims.Issuer = t.issuer
} }
// SECURITY: Use deterministic signing results for troubleshooting if needed,
// but standard HS256 with common secret is usually sufficient.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(t.signingKey) return token.SignedString(t.signingKey)
} }

4
weed/s3api/s3_iam_middleware.go

@ -233,6 +233,10 @@ func (s3iam *S3IAMIntegration) ValidateSessionToken(ctx context.Context, token s
// AuthorizeAction authorizes actions using our policy engine // AuthorizeAction authorizes actions using our policy engine
func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IAMIdentity, action Action, bucket string, objectKey string, r *http.Request) s3err.ErrorCode { func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IAMIdentity, action Action, bucket string, objectKey string, r *http.Request) s3err.ErrorCode {
fmt.Printf("DEBUG: AuthorizeAction called: Identity=%s Action=%s Bucket=%s Enabled=%v\n", identity.Name, action, bucket, s3iam.enabled)
if identity.Claims != nil {
fmt.Printf("DEBUG: AuthorizeAction Identity.Claims=%v\n", identity.Claims)
}
if !s3iam.enabled { if !s3iam.enabled {
return s3err.ErrNone // Fallback to existing authorization return s3err.ErrNone // Fallback to existing authorization
} }

3
weed/s3api/s3api_sts.go

@ -186,6 +186,8 @@ func (h *STSHandlers) handleAssumeRoleWithWebIdentity(w http.ResponseWriter, r *
Policy: sessionPolicyPtr, Policy: sessionPolicyPtr,
} }
glog.V(0).Infof("DEBUG: AssumeRoleWithWebIdentity: RoleArn=%s SessionPolicyLen=%d", roleArn, len(sessionPolicyJSON))
// Call STS service // Call STS service
response, err := h.stsService.AssumeRoleWithWebIdentity(ctx, request) response, err := h.stsService.AssumeRoleWithWebIdentity(ctx, request)
if err != nil { if err != nil {
@ -320,7 +322,6 @@ func (h *STSHandlers) handleAssumeRole(w http.ResponseWriter, r *http.Request) {
} }
} }
// Parse optional inline session policy for downscoping
sessionPolicyJSON, err := sts.NormalizeSessionPolicy(r.FormValue("Policy")) sessionPolicyJSON, err := sts.NormalizeSessionPolicy(r.FormValue("Policy"))
if err != nil { if err != nil {
h.writeSTSErrorResponse(w, r, STSErrMalformedPolicyDocument, h.writeSTSErrorResponse(w, r, STSErrMalformedPolicyDocument,

Loading…
Cancel
Save