diff options
Diffstat (limited to 'weed/iam/policy/policy_variable_matching_test.go')
| -rw-r--r-- | weed/iam/policy/policy_variable_matching_test.go | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/weed/iam/policy/policy_variable_matching_test.go b/weed/iam/policy/policy_variable_matching_test.go new file mode 100644 index 000000000..6b9827dff --- /dev/null +++ b/weed/iam/policy/policy_variable_matching_test.go @@ -0,0 +1,191 @@ +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") +} |
