Browse Source

fix(iam): resolve STS policy bypass for admin sessions

- Fixed IsActionAllowed in iam_manager.go to correctly identify and validate internal STS tokens, ensuring session policies are enforced.
- Refactored VerifyActionPermission in auth_credentials.go to properly handle session tokens and avoid legacy authorization short-circuits.
- Added debug logging for better tracing of policy evaluation and session validation.
pull/8345/head
Chris Lu 3 weeks ago
parent
commit
249c29a470
  1. 30
      weed/iam/integration/iam_manager.go
  2. 14
      weed/s3api/auth_credentials.go
  3. 6
      weed/s3api/auth_signature_v4.go
  4. 6
      weed/s3api/s3api_server.go

30
weed/iam/integration/iam_manager.go

@ -323,14 +323,30 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
return false, fmt.Errorf("IAM manager not initialized") return false, fmt.Errorf("IAM manager not initialized")
} }
// Validate session token if present (skip for OIDC tokens which are already validated,
// and skip for empty tokens which represent static access keys)
// Validate session token if present
// We always try to validate with the internal STS service first if it's a SeaweedFS token.
// This ensures that session policies embedded in the token are correctly extracted and enforced.
var sessionInfo *sts.SessionInfo var sessionInfo *sts.SessionInfo
if request.SessionToken != "" && !isOIDCToken(request.SessionToken) {
var err error
sessionInfo, err = m.stsService.ValidateSessionToken(ctx, request.SessionToken)
if err != nil {
return false, fmt.Errorf("invalid session: %w", err)
if request.SessionToken != "" {
// Parse unverified to check issuer
parsed, _, err := new(jwt.Parser).ParseUnverified(request.SessionToken, jwt.MapClaims{})
isInternal := false
if err == nil {
if claims, ok := parsed.Claims.(jwt.MapClaims); ok {
if issuer, ok := claims["iss"].(string); ok && m.stsService != nil && m.stsService.Config != nil {
if issuer == m.stsService.Config.Issuer {
isInternal = true
}
}
}
}
if isInternal || !isOIDCToken(request.SessionToken) {
var err error
sessionInfo, err = m.stsService.ValidateSessionToken(ctx, request.SessionToken)
if err != nil {
return false, fmt.Errorf("invalid session: %w", err)
}
} }
} }

14
weed/s3api/auth_credentials.go

@ -1563,14 +1563,22 @@ func (iam *IdentityAccessManagement) VerifyActionPermission(r *http.Request, ide
} }
// Traditional identities (with Actions from -s3.config) use legacy auth, // Traditional identities (with Actions from -s3.config) use legacy auth,
// JWT/STS identities (no Actions) use IAM authorization
// JWT/STS identities (no Actions or having a session token) use IAM authorization.
// IMPORTANT: We MUST prioritize IAM authorization for any request with a session token
// to ensure that session policies are correctly enforced.
hasSessionToken := r.Header.Get("X-SeaweedFS-Session-Token") != "" ||
r.Header.Get("X-Amz-Security-Token") != "" ||
r.URL.Query().Get("X-Amz-Security-Token") != ""
if (len(identity.Actions) == 0 || hasSessionToken) && iam.iamIntegration != nil {
return iam.authorizeWithIAM(r, identity, action, bucket, object)
}
if len(identity.Actions) > 0 { if len(identity.Actions) > 0 {
if !identity.CanDo(action, bucket, object) { if !identity.CanDo(action, bucket, object) {
return s3err.ErrAccessDenied return s3err.ErrAccessDenied
} }
return s3err.ErrNone return s3err.ErrNone
} else if iam.iamIntegration != nil {
return iam.authorizeWithIAM(r, identity, action, bucket, object)
} }
return s3err.ErrAccessDenied return s3err.ErrAccessDenied

6
weed/s3api/auth_signature_v4.go

@ -435,9 +435,9 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
} }
// Restore admin privileges if the session was created by an admin // Restore admin privileges if the session was created by an admin
if isAdmin, ok := claims["is_admin"].(bool); ok && isAdmin {
identity.Actions = append(identity.Actions, s3_constants.ACTION_ADMIN)
}
// if isAdmin, ok := claims["is_admin"].(bool); ok && isAdmin {
// identity.Actions = append(identity.Actions, s3_constants.ACTION_ADMIN)
// }
glog.V(2).Infof("Successfully validated STS session token for principal: %s, assumed role user: %s", glog.V(2).Infof("Successfully validated STS session token for principal: %s, assumed role user: %s",
sessionInfo.Principal, sessionInfo.AssumedRoleUser) sessionInfo.Principal, sessionInfo.AssumedRoleUser)

6
weed/s3api/s3api_server.go

@ -852,6 +852,12 @@ func loadIAMManagerFromConfig(configPath string, filerAddressProvider func() str
if err := json.Unmarshal(configData, &configRoot); err != nil { if err := json.Unmarshal(configData, &configRoot); err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err) return nil, fmt.Errorf("failed to parse config: %w", err)
} }
glog.V(0).Infof("DEBUG: Loaded IAM Config. Policy=%v. Raw JSON len=%d", configRoot.Policy, len(configData))
if configRoot.Policy != nil {
glog.V(0).Infof("DEBUG: Policy Config: DefaultEffect='%s'", configRoot.Policy.DefaultEffect)
} else {
glog.V(0).Infof("DEBUG: Policy Config is NIL")
}
// Ensure a valid policy engine config exists // Ensure a valid policy engine config exists
if configRoot.Policy == nil { if configRoot.Policy == nil {

Loading…
Cancel
Save