Fix: Populate Claims from STS session RequestContext for policy variable substitution (#8082)
* Fix: Populate Claims from STS session RequestContext for policy variable substitution
When using STS temporary credentials (from AssumeRoleWithWebIdentity) with
AWS Signature V4 authentication, JWT claims like preferred_username were
not available for bucket policy variable substitution (e.g., ${jwt:preferred_username}).
Root Cause:
- STS session tokens store user claims in the req_ctx field (added in PR #8079)
- validateSTSSessionToken() created Identity but didn't populate Claims field
- authorizeWithIAM() created IAMIdentity but didn't copy Claims
- Policy engine couldn't resolve ${jwt:*} variables without claims
Changes:
1. auth_signature_v4.go: Extract claims from sessionInfo.RequestContext
and populate Identity.Claims in validateSTSSessionToken()
2. auth_credentials.go: Copy Claims when creating IAMIdentity in
authorizeWithIAM()
3. auth_sts_identity_test.go: Add TestSTSIdentityClaimsPopulation to
verify claims are properly populated from RequestContext
This enables bucket policies with JWT claim variables to work correctly
with STS temporary credentials obtained via AssumeRoleWithWebIdentity.
Fixes #8037
* Refactor: Idiomatic map population for STS claims
This commit is contained in:
@@ -1408,6 +1408,7 @@ func (iam *IdentityAccessManagement) authorizeWithIAM(r *http.Request, identity
|
||||
Name: identity.Name,
|
||||
Account: identity.Account,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
Claims: identity.Claims, // Copy claims for policy variable substitution
|
||||
}
|
||||
|
||||
// Determine authorization path and configure identity
|
||||
|
||||
@@ -382,6 +382,14 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
|
||||
Expiration: sessionInfo.ExpiresAt.Unix(),
|
||||
}
|
||||
|
||||
// Create claims map from request context
|
||||
// The request context contains user information from the original OIDC token
|
||||
// that was used in AssumeRoleWithWebIdentity (e.g., preferred_username, email, etc.)
|
||||
claims := make(map[string]interface{}, len(sessionInfo.RequestContext))
|
||||
for k, v := range sessionInfo.RequestContext {
|
||||
claims[k] = v
|
||||
}
|
||||
|
||||
// Create an identity for the STS session
|
||||
// The identity represents the assumed role user
|
||||
identity := &Identity{
|
||||
@@ -390,6 +398,7 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
|
||||
Credentials: []*Credential{cred},
|
||||
PrincipalArn: sessionInfo.Principal,
|
||||
PolicyNames: sessionInfo.Policies, // Populate PolicyNames for IAM authorization
|
||||
Claims: claims, // Populate Claims for policy variable substitution
|
||||
}
|
||||
|
||||
glog.V(2).Infof("Successfully validated STS session token for principal: %s, assumed role user: %s",
|
||||
|
||||
@@ -304,6 +304,71 @@ func TestValidateSTSSessionTokenIntegration(t *testing.T) {
|
||||
t.Log("✓ Integration test passed: STS identity properly configured for IAM authorization")
|
||||
}
|
||||
|
||||
// TestSTSIdentityClaimsPopulation tests that Claims are properly populated from RequestContext
|
||||
// This is critical for policy variable substitution like ${jwt:preferred_username}
|
||||
func TestSTSIdentityClaimsPopulation(t *testing.T) {
|
||||
// Setup STS service
|
||||
stsService, config := setupTestSTSService(t)
|
||||
|
||||
// Create IAM with STS integration
|
||||
iam := NewIdentityAccessManagementWithStore(&S3ApiServerOption{}, "memory")
|
||||
s3iam := &S3IAMIntegration{
|
||||
stsService: stsService,
|
||||
}
|
||||
iam.SetIAMIntegration(s3iam)
|
||||
|
||||
// Create a mock HTTP request
|
||||
req, err := http.NewRequest("PUT", "/test-bucket/test-object.txt", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate session token with RequestContext containing user claims
|
||||
sessionId := "claims-test-session"
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
sessionClaims := sts.NewSTSSessionClaims(sessionId, config.Issuer, expiresAt).
|
||||
WithSessionName("claims-test").
|
||||
WithRoleInfo("arn:aws:iam::role/S3UserRole", "arn:aws:sts::assumed-role/S3UserRole/claims-test", "arn:aws:sts::assumed-role/S3UserRole/claims-test")
|
||||
|
||||
// Add RequestContext with user claims (simulating AssumeRoleWithWebIdentity)
|
||||
sessionClaims.RequestContext = map[string]interface{}{
|
||||
"preferred_username": "f2wbnp",
|
||||
"email": "user@example.com",
|
||||
"name": "Test User",
|
||||
"groups": []string{"developers", "users"},
|
||||
}
|
||||
|
||||
sessionClaims.Policies = []string{"S3UserPolicy"}
|
||||
|
||||
tokenGen := sts.NewTokenGenerator(config.SigningKey, config.Issuer)
|
||||
sessionToken, err := tokenGen.GenerateJWTWithClaims(sessionClaims)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Validate session token
|
||||
sessionInfo, err := stsService.ValidateSessionToken(context.Background(), sessionToken)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sessionInfo)
|
||||
require.NotNil(t, sessionInfo.RequestContext, "RequestContext should be populated")
|
||||
|
||||
// Verify RequestContext has the claims
|
||||
assert.Equal(t, "f2wbnp", sessionInfo.RequestContext["preferred_username"])
|
||||
assert.Equal(t, "user@example.com", sessionInfo.RequestContext["email"])
|
||||
|
||||
// Validate session token and check identity creation
|
||||
identity, _, errCode := iam.validateSTSSessionToken(req, sessionToken, sessionInfo.Credentials.AccessKeyId)
|
||||
require.Equal(t, s3err.ErrNone, errCode)
|
||||
require.NotNil(t, identity)
|
||||
|
||||
// Verify Claims are populated from RequestContext
|
||||
assert.NotNil(t, identity.Claims, "Claims should be populated")
|
||||
assert.Equal(t, "f2wbnp", identity.Claims["preferred_username"], "preferred_username should be in Claims")
|
||||
assert.Equal(t, "user@example.com", identity.Claims["email"], "email should be in Claims")
|
||||
assert.Equal(t, "Test User", identity.Claims["name"], "name should be in Claims")
|
||||
|
||||
// Verify PolicyNames are also populated
|
||||
assert.Equal(t, []string{"S3UserPolicy"}, identity.PolicyNames)
|
||||
|
||||
t.Log("✓ Claims properly populated from RequestContext for policy variable substitution")
|
||||
}
|
||||
|
||||
// Helper functions for tests
|
||||
|
||||
func setupTestSTSService(t *testing.T) (*sts.STSService, *sts.STSConfig) {
|
||||
|
||||
Reference in New Issue
Block a user