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,
|
Name: identity.Name,
|
||||||
Account: identity.Account,
|
Account: identity.Account,
|
||||||
PolicyNames: identity.PolicyNames,
|
PolicyNames: identity.PolicyNames,
|
||||||
|
Claims: identity.Claims, // Copy claims for policy variable substitution
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine authorization path and configure identity
|
// Determine authorization path and configure identity
|
||||||
|
|||||||
@@ -382,6 +382,14 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
|
|||||||
Expiration: sessionInfo.ExpiresAt.Unix(),
|
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
|
// Create an identity for the STS session
|
||||||
// The identity represents the assumed role user
|
// The identity represents the assumed role user
|
||||||
identity := &Identity{
|
identity := &Identity{
|
||||||
@@ -390,6 +398,7 @@ func (iam *IdentityAccessManagement) validateSTSSessionToken(r *http.Request, se
|
|||||||
Credentials: []*Credential{cred},
|
Credentials: []*Credential{cred},
|
||||||
PrincipalArn: sessionInfo.Principal,
|
PrincipalArn: sessionInfo.Principal,
|
||||||
PolicyNames: sessionInfo.Policies, // Populate PolicyNames for IAM authorization
|
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",
|
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")
|
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
|
// Helper functions for tests
|
||||||
|
|
||||||
func setupTestSTSService(t *testing.T) (*sts.STSService, *sts.STSConfig) {
|
func setupTestSTSService(t *testing.T) (*sts.STSService, *sts.STSConfig) {
|
||||||
|
|||||||
Reference in New Issue
Block a user