mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-04 23:27:09 +00:00
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.
This commit is contained in:
@@ -323,14 +323,30 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1563,14 +1563,22 @@ func (iam *IdentityAccessManagement) VerifyActionPermission(r *http.Request, ide
|
||||
}
|
||||
|
||||
// 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 !identity.CanDo(action, bucket, object) {
|
||||
return s3err.ErrAccessDenied
|
||||
}
|
||||
return s3err.ErrNone
|
||||
} else if iam.iamIntegration != nil {
|
||||
return iam.authorizeWithIAM(r, identity, action, bucket, object)
|
||||
}
|
||||
|
||||
return s3err.ErrAccessDenied
|
||||
|
||||
@@ -435,9 +435,9 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
|
||||
}
|
||||
|
||||
// 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",
|
||||
sessionInfo.Principal, sessionInfo.AssumedRoleUser)
|
||||
|
||||
@@ -852,6 +852,12 @@ func loadIAMManagerFromConfig(configPath string, filerAddressProvider func() str
|
||||
if err := json.Unmarshal(configData, &configRoot); err != nil {
|
||||
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
|
||||
if configRoot.Policy == nil {
|
||||
|
||||
Reference in New Issue
Block a user