* Add session policy support to IAM - Implement policy evaluation for session tokens in policy_engine.go - Add session_policy field to session claims for tracking applied policies - Update STS service to include session policies in token generation - Add IAM integration tests for session policy validation - Update IAM manager to support policy attachment to sessions - Extend S3 API STS endpoint to handle session policy restrictions * fix: optimize session policy evaluation and add documentation * sts: add NormalizeSessionPolicy helper for inline session policies * sts: support inline session policies for AssumeRoleWithWebIdentity and credential-based flows * s3api: parse and normalize Policy parameter for STS HTTP handlers * tests: add session policy unit tests and integration tests for inline policy downscoping * tests: add s3tables STS inline policy integration * iam: handle user principals and validate tokens * sts: enforce inline session policy size limit * tests: harden s3tables STS integration config * iam: clarify principal policy resolution errors * tests: improve STS integration endpoint selection
169 lines
5.9 KiB
Go
169 lines
5.9 KiB
Go
package sts
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createSessionPolicyTestJWT creates a test JWT token for session policy tests
|
|
func createSessionPolicyTestJWT(t *testing.T, issuer, subject string) string {
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"iss": issuer,
|
|
"sub": subject,
|
|
"aud": "test-client",
|
|
"exp": time.Now().Add(time.Hour).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
})
|
|
|
|
tokenString, err := token.SignedString([]byte("test-signing-key"))
|
|
require.NoError(t, err)
|
|
return tokenString
|
|
}
|
|
|
|
// TestAssumeRoleWithWebIdentity_SessionPolicy verifies inline session policies are preserved in tokens.
|
|
func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
|
|
service := setupTestSTSService(t)
|
|
ctx := context.Background()
|
|
|
|
sessionPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::example-bucket/*"}]}`
|
|
testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
|
|
|
|
request := &AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
WebIdentityToken: testToken,
|
|
RoleSessionName: "test-session",
|
|
Policy: &sessionPolicy,
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithWebIdentity(ctx, request)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, response)
|
|
|
|
sessionInfo, err := service.ValidateSessionToken(ctx, response.Credentials.SessionToken)
|
|
require.NoError(t, err)
|
|
|
|
normalized, err := NormalizeSessionPolicy(sessionPolicy)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, normalized, sessionInfo.SessionPolicy)
|
|
|
|
t.Run("should_succeed_without_session_policy", func(t *testing.T) {
|
|
request := &AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
|
|
RoleSessionName: "test-session",
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithWebIdentity(ctx, request)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, response)
|
|
|
|
sessionInfo, err := service.ValidateSessionToken(ctx, response.Credentials.SessionToken)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, sessionInfo.SessionPolicy)
|
|
})
|
|
}
|
|
|
|
// Test edge case scenarios for the Policy field handling
|
|
func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) {
|
|
service := setupTestSTSService(t)
|
|
ctx := context.Background()
|
|
|
|
t.Run("malformed_json_policy_rejected", func(t *testing.T) {
|
|
malformedPolicy := `{"Version": "2012-10-17", "Statement": [` // Incomplete JSON
|
|
|
|
request := &AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
|
|
RoleSessionName: "test-session",
|
|
Policy: &malformedPolicy,
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithWebIdentity(ctx, request)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, response)
|
|
assert.Contains(t, err.Error(), "invalid session policy JSON")
|
|
})
|
|
|
|
t.Run("invalid_policy_document_rejected", func(t *testing.T) {
|
|
invalidPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow"}]}`
|
|
|
|
request := &AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
|
|
RoleSessionName: "test-session",
|
|
Policy: &invalidPolicy,
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithWebIdentity(ctx, request)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, response)
|
|
assert.Contains(t, err.Error(), "invalid session policy document")
|
|
})
|
|
|
|
t.Run("whitespace_policy_ignored", func(t *testing.T) {
|
|
whitespacePolicy := " \t\n "
|
|
|
|
request := &AssumeRoleWithWebIdentityRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
|
|
RoleSessionName: "test-session",
|
|
Policy: &whitespacePolicy,
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithWebIdentity(ctx, request)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, response)
|
|
|
|
sessionInfo, err := service.ValidateSessionToken(ctx, response.Credentials.SessionToken)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, sessionInfo.SessionPolicy)
|
|
})
|
|
}
|
|
|
|
// TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation verifies that the struct field exists and is optional.
|
|
func TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation(t *testing.T) {
|
|
request := &AssumeRoleWithWebIdentityRequest{}
|
|
|
|
assert.IsType(t, (*string)(nil), request.Policy,
|
|
"Policy field should be *string type for optional JSON policy")
|
|
assert.Nil(t, request.Policy,
|
|
"Policy field should default to nil (no session policy)")
|
|
|
|
policyValue := `{"Version": "2012-10-17"}`
|
|
request.Policy = &policyValue
|
|
assert.NotNil(t, request.Policy, "Should be able to assign policy value")
|
|
assert.Equal(t, policyValue, *request.Policy, "Policy value should be preserved")
|
|
}
|
|
|
|
// TestAssumeRoleWithCredentials_SessionPolicy verifies session policy support for credentials-based flow.
|
|
func TestAssumeRoleWithCredentials_SessionPolicy(t *testing.T) {
|
|
service := setupTestSTSService(t)
|
|
ctx := context.Background()
|
|
|
|
sessionPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"filer:CreateEntry","Resource":"arn:aws:filer::path/user-docs/*"}]}`
|
|
request := &AssumeRoleWithCredentialsRequest{
|
|
RoleArn: "arn:aws:iam::role/TestRole",
|
|
Username: "testuser",
|
|
Password: "testpass",
|
|
RoleSessionName: "test-session",
|
|
ProviderName: "test-ldap",
|
|
Policy: &sessionPolicy,
|
|
}
|
|
|
|
response, err := service.AssumeRoleWithCredentials(ctx, request)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, response)
|
|
|
|
sessionInfo, err := service.ValidateSessionToken(ctx, response.Credentials.SessionToken)
|
|
require.NoError(t, err)
|
|
|
|
normalized, err := NormalizeSessionPolicy(sessionPolicy)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, normalized, sessionInfo.SessionPolicy)
|
|
}
|