diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index 9db792809..441e8f002 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -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 diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index 7ebdefd1c..6c537dc70 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -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", diff --git a/weed/s3api/auth_sts_identity_test.go b/weed/s3api/auth_sts_identity_test.go index 5120bbccf..808f04e67 100644 --- a/weed/s3api/auth_sts_identity_test.go +++ b/weed/s3api/auth_sts_identity_test.go @@ -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) {