You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							191 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							191 lines
						
					
					
						
							6.0 KiB
						
					
					
				| 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") | |
| }
 |