| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- package policy
- import (
- "context"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TestPolicyEngineInitialization tests policy engine initialization
- func TestPolicyEngineInitialization(t *testing.T) {
- tests := []struct {
- name string
- config *PolicyEngineConfig
- wantErr bool
- }{
- {
- name: "valid config",
- config: &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- },
- wantErr: false,
- },
- {
- name: "invalid default effect",
- config: &PolicyEngineConfig{
- DefaultEffect: "Invalid",
- StoreType: "memory",
- },
- wantErr: true,
- },
- {
- name: "nil config",
- config: nil,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- engine := NewPolicyEngine()
- err := engine.Initialize(tt.config)
- if tt.wantErr {
- assert.Error(t, err)
- } else {
- assert.NoError(t, err)
- assert.True(t, engine.IsInitialized())
- }
- })
- }
- }
- // TestPolicyDocumentValidation tests policy document structure validation
- func TestPolicyDocumentValidation(t *testing.T) {
- tests := []struct {
- name string
- policy *PolicyDocument
- wantErr bool
- errorMsg string
- }{
- {
- name: "valid policy document",
- policy: &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "AllowS3Read",
- Effect: "Allow",
- Action: []string{"s3:GetObject", "s3:ListBucket"},
- Resource: []string{"arn:seaweed:s3:::mybucket/*"},
- },
- },
- },
- wantErr: false,
- },
- {
- name: "missing version",
- policy: &PolicyDocument{
- Statement: []Statement{
- {
- Effect: "Allow",
- Action: []string{"s3:GetObject"},
- Resource: []string{"arn:seaweed:s3:::mybucket/*"},
- },
- },
- },
- wantErr: true,
- errorMsg: "version is required",
- },
- {
- name: "empty statements",
- policy: &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{},
- },
- wantErr: true,
- errorMsg: "at least one statement is required",
- },
- {
- name: "invalid effect",
- policy: &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Effect: "Maybe",
- Action: []string{"s3:GetObject"},
- Resource: []string{"arn:seaweed:s3:::mybucket/*"},
- },
- },
- },
- wantErr: true,
- errorMsg: "invalid effect",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := ValidatePolicyDocument(tt.policy)
- if tt.wantErr {
- assert.Error(t, err)
- if tt.errorMsg != "" {
- assert.Contains(t, err.Error(), tt.errorMsg)
- }
- } else {
- assert.NoError(t, err)
- }
- })
- }
- }
- // TestPolicyEvaluation tests policy evaluation logic
- func TestPolicyEvaluation(t *testing.T) {
- engine := setupTestPolicyEngine(t)
- // Add test policies
- readPolicy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "AllowS3Read",
- Effect: "Allow",
- Action: []string{"s3:GetObject", "s3:ListBucket"},
- Resource: []string{
- "arn:seaweed:s3:::public-bucket/*", // For object operations
- "arn:seaweed:s3:::public-bucket", // For bucket operations
- },
- },
- },
- }
- err := engine.AddPolicy("", "read-policy", readPolicy)
- require.NoError(t, err)
- denyPolicy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "DenyS3Delete",
- Effect: "Deny",
- Action: []string{"s3:DeleteObject"},
- Resource: []string{"arn:seaweed:s3:::*"},
- },
- },
- }
- err = engine.AddPolicy("", "deny-policy", denyPolicy)
- require.NoError(t, err)
- tests := []struct {
- name string
- context *EvaluationContext
- policies []string
- want Effect
- }{
- {
- name: "allow read access",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::public-bucket/file.txt",
- RequestContext: map[string]interface{}{
- "sourceIP": "192.168.1.100",
- },
- },
- policies: []string{"read-policy"},
- want: EffectAllow,
- },
- {
- name: "deny delete access (explicit deny)",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:DeleteObject",
- Resource: "arn:seaweed:s3:::public-bucket/file.txt",
- },
- policies: []string{"read-policy", "deny-policy"},
- want: EffectDeny,
- },
- {
- name: "deny by default (no matching policy)",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:PutObject",
- Resource: "arn:seaweed:s3:::public-bucket/file.txt",
- },
- policies: []string{"read-policy"},
- want: EffectDeny,
- },
- {
- name: "allow with wildcard action",
- context: &EvaluationContext{
- Principal: "user:admin",
- Action: "s3:ListBucket",
- Resource: "arn:seaweed:s3:::public-bucket",
- },
- policies: []string{"read-policy"},
- want: EffectAllow,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result, err := engine.Evaluate(context.Background(), "", tt.context, tt.policies)
- assert.NoError(t, err)
- assert.Equal(t, tt.want, result.Effect)
- // Verify evaluation details
- assert.NotNil(t, result.EvaluationDetails)
- assert.Equal(t, tt.context.Action, result.EvaluationDetails.Action)
- assert.Equal(t, tt.context.Resource, result.EvaluationDetails.Resource)
- })
- }
- }
- // TestConditionEvaluation tests policy conditions
- func TestConditionEvaluation(t *testing.T) {
- engine := setupTestPolicyEngine(t)
- // Policy with IP address condition
- conditionalPolicy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "AllowFromOfficeIP",
- Effect: "Allow",
- Action: []string{"s3:*"},
- Resource: []string{"arn:seaweed:s3:::*"},
- Condition: map[string]map[string]interface{}{
- "IpAddress": {
- "seaweed:SourceIP": []string{"192.168.1.0/24", "10.0.0.0/8"},
- },
- },
- },
- },
- }
- err := engine.AddPolicy("", "ip-conditional", conditionalPolicy)
- require.NoError(t, err)
- tests := []struct {
- name string
- context *EvaluationContext
- want Effect
- }{
- {
- name: "allow from office IP",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::mybucket/file.txt",
- RequestContext: map[string]interface{}{
- "sourceIP": "192.168.1.100",
- },
- },
- want: EffectAllow,
- },
- {
- name: "deny from external IP",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::mybucket/file.txt",
- RequestContext: map[string]interface{}{
- "sourceIP": "8.8.8.8",
- },
- },
- want: EffectDeny,
- },
- {
- name: "allow from internal IP",
- context: &EvaluationContext{
- Principal: "user:alice",
- Action: "s3:PutObject",
- Resource: "arn:seaweed:s3:::mybucket/newfile.txt",
- RequestContext: map[string]interface{}{
- "sourceIP": "10.1.2.3",
- },
- },
- want: EffectAllow,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result, err := engine.Evaluate(context.Background(), "", tt.context, []string{"ip-conditional"})
- assert.NoError(t, err)
- assert.Equal(t, tt.want, result.Effect)
- })
- }
- }
- // TestResourceMatching tests resource ARN matching
- func TestResourceMatching(t *testing.T) {
- tests := []struct {
- name string
- policyResource string
- requestResource string
- want bool
- }{
- {
- name: "exact match",
- policyResource: "arn:seaweed:s3:::mybucket/file.txt",
- requestResource: "arn:seaweed:s3:::mybucket/file.txt",
- want: true,
- },
- {
- name: "wildcard match",
- policyResource: "arn:seaweed:s3:::mybucket/*",
- requestResource: "arn:seaweed:s3:::mybucket/folder/file.txt",
- want: true,
- },
- {
- name: "bucket wildcard",
- policyResource: "arn:seaweed:s3:::*",
- requestResource: "arn:seaweed:s3:::anybucket/file.txt",
- want: true,
- },
- {
- name: "no match different bucket",
- policyResource: "arn:seaweed:s3:::mybucket/*",
- requestResource: "arn:seaweed:s3:::otherbucket/file.txt",
- want: false,
- },
- {
- name: "prefix match",
- policyResource: "arn:seaweed:s3:::mybucket/documents/*",
- requestResource: "arn:seaweed:s3:::mybucket/documents/secret.txt",
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := matchResource(tt.policyResource, tt.requestResource)
- assert.Equal(t, tt.want, result)
- })
- }
- }
- // TestActionMatching tests action pattern matching
- func TestActionMatching(t *testing.T) {
- tests := []struct {
- name string
- policyAction string
- requestAction string
- want bool
- }{
- {
- name: "exact match",
- policyAction: "s3:GetObject",
- requestAction: "s3:GetObject",
- want: true,
- },
- {
- name: "wildcard service",
- policyAction: "s3:*",
- requestAction: "s3:PutObject",
- want: true,
- },
- {
- name: "wildcard all",
- policyAction: "*",
- requestAction: "filer:CreateEntry",
- want: true,
- },
- {
- name: "prefix match",
- policyAction: "s3:Get*",
- requestAction: "s3:GetObject",
- want: true,
- },
- {
- name: "no match different service",
- policyAction: "s3:GetObject",
- requestAction: "filer:GetEntry",
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := matchAction(tt.policyAction, tt.requestAction)
- assert.Equal(t, tt.want, result)
- })
- }
- }
- // Helper function to set up test policy engine
- func setupTestPolicyEngine(t *testing.T) *PolicyEngine {
- engine := NewPolicyEngine()
- config := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- }
- err := engine.Initialize(config)
- require.NoError(t, err)
- return engine
- }
|