package policy import ( "testing" "github.com/stretchr/testify/assert" ) func TestAWSIAMMatch(t *testing.T) { evalCtx := &EvaluationContext{ RequestContext: map[string]interface{}{ "aws:username": "testuser", "saml:username": "john.doe", "oidc:sub": "user123", "aws:userid": "AIDACKCEVSQ6C2EXAMPLE", "aws:principaltype": "User", }, } tests := []struct { name string pattern string value string evalCtx *EvaluationContext expected bool }{ // Case insensitivity tests { name: "case insensitive exact match", pattern: "S3:GetObject", value: "s3:getobject", evalCtx: evalCtx, expected: true, }, { name: "case insensitive wildcard match", pattern: "S3:Get*", value: "s3:getobject", evalCtx: evalCtx, expected: true, }, // Policy variable expansion tests { name: "AWS username variable expansion", pattern: "arn:aws:s3:::mybucket/${aws:username}/*", value: "arn:aws:s3:::mybucket/testuser/document.pdf", evalCtx: evalCtx, expected: true, }, { name: "SAML username variable expansion", pattern: "home/${saml:username}/*", value: "home/john.doe/private.txt", evalCtx: evalCtx, expected: true, }, { name: "OIDC subject variable expansion", pattern: "users/${oidc:sub}/data", value: "users/user123/data", evalCtx: evalCtx, expected: true, }, // Mixed case and variable tests { name: "case insensitive with variable", pattern: "S3:GetObject/${aws:username}/*", value: "s3:getobject/testuser/file.txt", evalCtx: evalCtx, expected: true, }, // Universal wildcard { name: "universal wildcard", pattern: "*", value: "anything", evalCtx: evalCtx, expected: true, }, // Question mark wildcard { name: "question mark wildcard", pattern: "file?.txt", value: "file1.txt", evalCtx: evalCtx, expected: true, }, // No match cases { name: "no match different pattern", pattern: "s3:PutObject", value: "s3:GetObject", evalCtx: evalCtx, expected: false, }, { name: "variable not expanded due to missing context", pattern: "users/${aws:username}/data", value: "users/${aws:username}/data", evalCtx: nil, expected: true, // Should match literally when no context }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := awsIAMMatch(tt.pattern, tt.value, tt.evalCtx) assert.Equal(t, tt.expected, result, "AWS IAM match result should match expected") }) } } func TestMatchesActionsMultipartExpansion(t *testing.T) { engine := &PolicyEngine{initialized: true} evalCtx := &EvaluationContext{} tests := []struct { name string actions []string requestedAction string expected bool }{ { name: "PutObject directly matches PutObject", actions: []string{"s3:PutObject"}, requestedAction: "s3:PutObject", expected: true, }, { name: "PutObject implicitly allows CreateMultipartUpload", actions: []string{"s3:PutObject"}, requestedAction: "s3:CreateMultipartUpload", expected: true, }, { name: "PutObject implicitly allows UploadPart", actions: []string{"s3:PutObject"}, requestedAction: "s3:UploadPart", expected: true, }, { name: "PutObject implicitly allows CompleteMultipartUpload", actions: []string{"s3:PutObject"}, requestedAction: "s3:CompleteMultipartUpload", expected: true, }, { name: "PutObject implicitly allows AbortMultipartUpload", actions: []string{"s3:PutObject"}, requestedAction: "s3:AbortMultipartUpload", expected: true, }, { name: "PutObject implicitly allows ListMultipartUploadParts", actions: []string{"s3:PutObject"}, requestedAction: "s3:ListMultipartUploadParts", expected: true, }, { name: "PutObject implicitly allows ListBucketMultipartUploads", actions: []string{"s3:PutObject"}, requestedAction: "s3:ListBucketMultipartUploads", expected: true, }, { name: "PutObject does not allow GetObject", actions: []string{"s3:PutObject"}, requestedAction: "s3:GetObject", expected: false, }, { name: "GetObject does not allow CreateMultipartUpload", actions: []string{"s3:GetObject"}, requestedAction: "s3:CreateMultipartUpload", expected: false, }, { name: "wildcard s3:Put* implicitly allows multipart via PutObject match", actions: []string{"s3:Put*"}, requestedAction: "s3:CreateMultipartUpload", expected: true, }, { name: "case-insensitive multipart action lookup", actions: []string{"s3:PutObject"}, requestedAction: "S3:CREATEMULTIPARTUPLOAD", expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := engine.matchesActions(tt.actions, tt.requestedAction, evalCtx) assert.Equal(t, tt.expected, result) }) } } func TestExpandPolicyVariables(t *testing.T) { evalCtx := &EvaluationContext{ RequestContext: map[string]interface{}{ "aws:username": "alice", "saml:username": "alice.smith", "oidc:sub": "sub123", }, } tests := []struct { name string pattern string evalCtx *EvaluationContext expected string }{ { name: "expand aws username", pattern: "home/${aws:username}/documents/*", evalCtx: evalCtx, expected: "home/alice/documents/*", }, { name: "expand multiple variables", pattern: "${aws:username}/${oidc:sub}/data", evalCtx: evalCtx, expected: "alice/sub123/data", }, { name: "no variables to expand", pattern: "static/path/file.txt", evalCtx: evalCtx, expected: "static/path/file.txt", }, { name: "nil context", pattern: "home/${aws:username}/file", evalCtx: nil, expected: "home/${aws:username}/file", }, { name: "missing variable in context", pattern: "home/${aws:nonexistent}/file", evalCtx: evalCtx, expected: "home/${aws:nonexistent}/file", // Should remain unchanged }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := expandPolicyVariables(tt.pattern, tt.evalCtx) assert.Equal(t, tt.expected, result, "Policy variable expansion should match expected") }) } } func TestAWSWildcardMatch(t *testing.T) { tests := []struct { name string pattern string value string expected bool }{ { name: "case insensitive asterisk", pattern: "S3:Get*", value: "s3:getobject", expected: true, }, { name: "case insensitive question mark", pattern: "file?.TXT", value: "file1.txt", expected: true, }, { name: "mixed wildcards", pattern: "S3:*Object?", value: "s3:getobjects", expected: true, }, { name: "no match", pattern: "s3:Put*", value: "s3:GetObject", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := AwsWildcardMatch(tt.pattern, tt.value) assert.Equal(t, tt.expected, result, "AWS wildcard match should match expected") }) } }