Browse Source
STS: Fallback to Caller Identity when RoleArn is missing in AssumeRole (#8345)
STS: Fallback to Caller Identity when RoleArn is missing in AssumeRole (#8345)
* s3api: make RoleArn optional in AssumeRole * s3api: address PR feedback for optional RoleArn * iam: add configurable default role for AssumeRole * S3 STS: Use caller identity when RoleArn is missing - Fallback to PrincipalArn/Context in AssumeRole if RoleArn is empty - Handle User ARNs in prepareSTSCredentials - Fix PrincipalArn generation for env var credentials * Test: Add unit test for AssumeRole caller identity fallback * fix(s3api): propagate admin permissions to assumed role session when using caller identity fallback * 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 * 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/8347/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 322 additions and 70 deletions
-
124weed/iam/integration/iam_manager.go
-
2weed/iam/sts/token_utils.go
-
17weed/s3api/auth_credentials.go
-
5weed/s3api/auth_signature_v4.go
-
4weed/s3api/s3_iam_middleware.go
-
6weed/s3api/s3api_server.go
-
81weed/s3api/s3api_sts.go
-
153weed/s3api/s3api_sts_assume_role_test.go
@ -0,0 +1,153 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/http" |
|||
"net/url" |
|||
"testing" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/iam/sts" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" |
|||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" |
|||
"github.com/stretchr/testify/assert" |
|||
"github.com/stretchr/testify/require" |
|||
) |
|||
|
|||
// TestAssumeRole_CallerIdentityFallback tests the fallback logic when RoleArn is missing
|
|||
func TestAssumeRole_CallerIdentityFallback(t *testing.T) { |
|||
// Setup STS service
|
|||
stsService, _ := setupTestSTSService(t) |
|||
|
|||
// Create IAM integration mock
|
|||
iamMock := &MockIAMIntegration{ |
|||
authorizeFunc: func(ctx context.Context, identity *IAMIdentity, action Action, bucket, object string, r *http.Request) s3err.ErrorCode { |
|||
// Allow global sts:AssumeRole
|
|||
if action == "sts:AssumeRole" { |
|||
return s3err.ErrNone |
|||
} |
|||
return s3err.ErrAccessDenied |
|||
}, |
|||
validateTrustPolicyFunc: func(ctx context.Context, roleArn, principalArn string) error { |
|||
// Allow all trust policies for this test
|
|||
return nil |
|||
}, |
|||
} |
|||
|
|||
// Create IAM service with the mock integration
|
|||
iam := &IdentityAccessManagement{ |
|||
iamIntegration: iamMock, |
|||
} |
|||
|
|||
// Create STS handlers
|
|||
stsHandlers := NewSTSHandlers(stsService, iam) |
|||
|
|||
// Test case 1: Caller is an IAM User, RoleArn is missing
|
|||
t.Run("Caller is IAM User, No RoleArn", func(t *testing.T) { |
|||
// Mock request
|
|||
req, err := http.NewRequest("POST", "/", nil) |
|||
require.NoError(t, err) |
|||
req.Form = url.Values{} |
|||
req.Form.Set("Action", "AssumeRole") |
|||
req.Form.Set("RoleSessionName", "test-session") |
|||
req.Form.Set("Version", "2011-06-15") |
|||
|
|||
// Mock the authenticated identity (IAM User)
|
|||
callerIdentity := &Identity{ |
|||
Name: "alice", |
|||
Account: &AccountAdmin, |
|||
PrincipalArn: fmt.Sprintf("arn:aws:iam::%s:user/alice", defaultAccountID), |
|||
Actions: []Action{s3_constants.ACTION_ADMIN}, |
|||
} |
|||
|
|||
// 1. Test prepareSTSCredentials with NO RoleArn (simulating the fallback logic having passed PrincipalArn)
|
|||
// expected RoleArn passed to prepareSTSCredentials would be the caller's PrincipalArn
|
|||
fallbackRoleArn := callerIdentity.PrincipalArn |
|||
|
|||
// Prepare custom claims for the session (mimicking handleAssumeRole logic)
|
|||
var modifyClaims func(claims *sts.STSSessionClaims) |
|||
if callerIdentity.isAdmin() { |
|||
modifyClaims = func(claims *sts.STSSessionClaims) { |
|||
if claims.RequestContext == nil { |
|||
claims.RequestContext = make(map[string]interface{}) |
|||
} |
|||
claims.RequestContext["is_admin"] = true |
|||
} |
|||
} |
|||
|
|||
stsCreds, assumedUser, err := stsHandlers.prepareSTSCredentials(fallbackRoleArn, "test-session", nil, "", modifyClaims) |
|||
require.NoError(t, err) |
|||
|
|||
// Assertions
|
|||
// The role name should be extracted from the user ARN ("alice")
|
|||
assert.Contains(t, assumedUser.Arn, fmt.Sprintf("assumed-role/alice/test-session")) |
|||
assert.Contains(t, assumedUser.AssumedRoleId, "alice:test-session") |
|||
|
|||
// Verify token claims using ValidateSessionToken
|
|||
sessionInfo, err := stsService.ValidateSessionToken(context.Background(), stsCreds.SessionToken) |
|||
require.NoError(t, err) |
|||
|
|||
// The RoleArn in session info should match the fallback ARN (user ARN)
|
|||
assert.Equal(t, fallbackRoleArn, sessionInfo.RoleArn) |
|||
|
|||
// Verify is_admin claim is present
|
|||
isAdmin, ok := sessionInfo.RequestContext["is_admin"].(bool) |
|||
assert.True(t, ok, "is_admin claim should be present") |
|||
assert.True(t, isAdmin, "is_admin claim should be true") |
|||
}) |
|||
|
|||
// Test case 2: Caller is an STS Assumed Role, No RoleArn
|
|||
t.Run("Caller is STS Assumed Role, No RoleArn", func(t *testing.T) { |
|||
// Mock identity
|
|||
callerIdentity := &Identity{ |
|||
Name: "arn:aws:sts::111122223333:assumed-role/admin/session1", |
|||
Account: &AccountAdmin, |
|||
PrincipalArn: "arn:aws:sts::111122223333:assumed-role/admin/session1", |
|||
} |
|||
|
|||
fallbackRoleArn := callerIdentity.PrincipalArn |
|||
|
|||
stsCreds, assumedUser, err := stsHandlers.prepareSTSCredentials(fallbackRoleArn, "nested-session", nil, "", nil) |
|||
require.NoError(t, err) |
|||
|
|||
// The role name should be extracted from the assumed role ARN ("admin")
|
|||
assert.Contains(t, assumedUser.Arn, "assumed-role/admin/nested-session") |
|||
assert.Contains(t, assumedUser.AssumedRoleId, "admin:nested-session") |
|||
|
|||
// Check claims
|
|||
sessionInfo, err := stsService.ValidateSessionToken(context.Background(), stsCreds.SessionToken) |
|||
require.NoError(t, err) |
|||
assert.Equal(t, fallbackRoleArn, sessionInfo.RoleArn) |
|||
}) |
|||
|
|||
// Test case 3: Explicit RoleArn provided (Standard AssumeRole)
|
|||
t.Run("Explicit RoleArn Provided", func(t *testing.T) { |
|||
explicitRoleArn := "arn:aws:iam::111122223333:role/TargetRole" |
|||
|
|||
stsCreds, assumedUser, err := stsHandlers.prepareSTSCredentials(explicitRoleArn, "explicit-session", nil, "", nil) |
|||
require.NoError(t, err) |
|||
|
|||
// Role name should be "TargetRole"
|
|||
assert.Contains(t, assumedUser.Arn, "assumed-role/TargetRole/explicit-session") |
|||
|
|||
// Check claims
|
|||
sessionInfo, err := stsService.ValidateSessionToken(context.Background(), stsCreds.SessionToken) |
|||
require.NoError(t, err) |
|||
assert.Equal(t, explicitRoleArn, sessionInfo.RoleArn) |
|||
}) |
|||
|
|||
// Test case 4: Malformed ARN (Edge case)
|
|||
t.Run("Malformed ARN", func(t *testing.T) { |
|||
malformedArn := "invalid-arn" |
|||
|
|||
stsCreds, assumedUser, err := stsHandlers.prepareSTSCredentials(malformedArn, "bad-session", nil, "", nil) |
|||
require.NoError(t, err) |
|||
|
|||
// Fallback behavior: use full string as role name if extraction fails
|
|||
assert.Contains(t, assumedUser.Arn, "assumed-role/invalid-arn/bad-session") |
|||
|
|||
sessionInfo, err := stsService.ValidateSessionToken(context.Background(), stsCreds.SessionToken) |
|||
require.NoError(t, err) |
|||
assert.Equal(t, malformedArn, sessionInfo.RoleArn) |
|||
}) |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue