| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package policy
- import (
- "context"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TestPolicyVariableMatchingInActionsAndResources tests that Actions and Resources
- // now support policy variables like ${aws:username} just like string conditions do
- func TestPolicyVariableMatchingInActionsAndResources(t *testing.T) {
- engine := NewPolicyEngine()
- config := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- }
- err := engine.Initialize(config)
- require.NoError(t, err)
- ctx := context.Background()
- filerAddress := ""
- // Create a policy that uses policy variables in Action and Resource fields
- policyDoc := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "AllowUserSpecificActions",
- Effect: "Allow",
- Action: []string{
- "s3:Get*", // Regular wildcard
- "s3:${aws:principaltype}*", // Policy variable in action
- },
- Resource: []string{
- "arn:aws:s3:::user-${aws:username}/*", // Policy variable in resource
- "arn:aws:s3:::shared/${saml:username}/*", // Different policy variable
- },
- },
- },
- }
- err = engine.AddPolicy(filerAddress, "user-specific-policy", policyDoc)
- require.NoError(t, err)
- tests := []struct {
- name string
- principal string
- action string
- resource string
- requestContext map[string]interface{}
- expectedEffect Effect
- description string
- }{
- {
- name: "policy_variable_in_action_matches",
- principal: "test-user",
- action: "s3:AssumedRole", // Should match s3:${aws:principaltype}* when principaltype=AssumedRole
- resource: "arn:aws:s3:::user-testuser/file.txt",
- requestContext: map[string]interface{}{
- "aws:username": "testuser",
- "aws:principaltype": "AssumedRole",
- },
- expectedEffect: EffectAllow,
- description: "Action with policy variable should match when variable is expanded",
- },
- {
- name: "policy_variable_in_resource_matches",
- principal: "alice",
- action: "s3:GetObject",
- resource: "arn:aws:s3:::user-alice/document.pdf", // Should match user-${aws:username}/*
- requestContext: map[string]interface{}{
- "aws:username": "alice",
- },
- expectedEffect: EffectAllow,
- description: "Resource with policy variable should match when variable is expanded",
- },
- {
- name: "saml_username_variable_in_resource",
- principal: "bob",
- action: "s3:GetObject",
- resource: "arn:aws:s3:::shared/bob/data.json", // Should match shared/${saml:username}/*
- requestContext: map[string]interface{}{
- "saml:username": "bob",
- },
- expectedEffect: EffectAllow,
- description: "SAML username variable should be expanded in resource patterns",
- },
- {
- name: "policy_variable_no_match_wrong_user",
- principal: "charlie",
- action: "s3:GetObject",
- resource: "arn:aws:s3:::user-alice/file.txt", // charlie trying to access alice's files
- requestContext: map[string]interface{}{
- "aws:username": "charlie",
- },
- expectedEffect: EffectDeny,
- description: "Policy variable should prevent access when username doesn't match",
- },
- {
- name: "missing_policy_variable_context",
- principal: "dave",
- action: "s3:GetObject",
- resource: "arn:aws:s3:::user-dave/file.txt",
- requestContext: map[string]interface{}{
- // Missing aws:username context
- },
- expectedEffect: EffectDeny,
- description: "Missing policy variable context should result in no match",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- evalCtx := &EvaluationContext{
- Principal: tt.principal,
- Action: tt.action,
- Resource: tt.resource,
- RequestContext: tt.requestContext,
- }
- result, err := engine.Evaluate(ctx, filerAddress, evalCtx, []string{"user-specific-policy"})
- require.NoError(t, err, "Policy evaluation should not error")
- assert.Equal(t, tt.expectedEffect, result.Effect,
- "Test %s: %s. Expected %s but got %s",
- tt.name, tt.description, tt.expectedEffect, result.Effect)
- })
- }
- }
- // TestActionResourceConsistencyWithStringConditions verifies that Actions, Resources,
- // and string conditions all use the same AWS IAM-compliant matching logic
- func TestActionResourceConsistencyWithStringConditions(t *testing.T) {
- engine := NewPolicyEngine()
- config := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- }
- err := engine.Initialize(config)
- require.NoError(t, err)
- ctx := context.Background()
- filerAddress := ""
- // Policy that uses case-insensitive matching in all three areas
- policyDoc := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "CaseInsensitiveMatching",
- Effect: "Allow",
- Action: []string{"S3:GET*"}, // Uppercase action pattern
- Resource: []string{"arn:aws:s3:::TEST-BUCKET/*"}, // Uppercase resource pattern
- Condition: map[string]map[string]interface{}{
- "StringLike": {
- "s3:RequestedRegion": "US-*", // Uppercase condition pattern
- },
- },
- },
- },
- }
- err = engine.AddPolicy(filerAddress, "case-insensitive-policy", policyDoc)
- require.NoError(t, err)
- evalCtx := &EvaluationContext{
- Principal: "test-user",
- Action: "s3:getobject", // lowercase action
- Resource: "arn:aws:s3:::test-bucket/file.txt", // lowercase resource
- RequestContext: map[string]interface{}{
- "s3:RequestedRegion": "us-east-1", // lowercase condition value
- },
- }
- result, err := engine.Evaluate(ctx, filerAddress, evalCtx, []string{"case-insensitive-policy"})
- require.NoError(t, err)
- // All should match due to case-insensitive AWS IAM-compliant matching
- assert.Equal(t, EffectAllow, result.Effect,
- "Actions, Resources, and Conditions should all use case-insensitive AWS IAM matching")
- // Verify that matching statements were found
- assert.Len(t, result.MatchingStatements, 1,
- "Should have exactly one matching statement")
- assert.Equal(t, "Allow", string(result.MatchingStatements[0].Effect),
- "Matching statement should have Allow effect")
- }
|