| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- package policy
- import (
- "context"
- "fmt"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TestDistributedPolicyEngine verifies that multiple PolicyEngine instances with identical configurations
- // behave consistently across distributed environments
- func TestDistributedPolicyEngine(t *testing.T) {
- ctx := context.Background()
- // Common configuration for all instances
- commonConfig := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory", // For testing - would be "filer" in production
- StoreConfig: map[string]interface{}{},
- }
- // Create multiple PolicyEngine instances simulating distributed deployment
- instance1 := NewPolicyEngine()
- instance2 := NewPolicyEngine()
- instance3 := NewPolicyEngine()
- // Initialize all instances with identical configuration
- err := instance1.Initialize(commonConfig)
- require.NoError(t, err, "Instance 1 should initialize successfully")
- err = instance2.Initialize(commonConfig)
- require.NoError(t, err, "Instance 2 should initialize successfully")
- err = instance3.Initialize(commonConfig)
- require.NoError(t, err, "Instance 3 should initialize successfully")
- // Test policy consistency across instances
- t.Run("policy_storage_consistency", func(t *testing.T) {
- // Define a test policy
- testPolicy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: "AllowS3Read",
- Effect: "Allow",
- Action: []string{"s3:GetObject", "s3:ListBucket"},
- Resource: []string{"arn:seaweed:s3:::test-bucket/*", "arn:seaweed:s3:::test-bucket"},
- },
- {
- Sid: "DenyS3Write",
- Effect: "Deny",
- Action: []string{"s3:PutObject", "s3:DeleteObject"},
- Resource: []string{"arn:seaweed:s3:::test-bucket/*"},
- },
- },
- }
- // Store policy on instance 1
- err := instance1.AddPolicy("", "TestPolicy", testPolicy)
- require.NoError(t, err, "Should be able to store policy on instance 1")
- // For memory storage, each instance has separate storage
- // In production with filer storage, all instances would share the same policies
- // Verify policy exists on instance 1
- storedPolicy1, err := instance1.store.GetPolicy(ctx, "", "TestPolicy")
- require.NoError(t, err, "Policy should exist on instance 1")
- assert.Equal(t, "2012-10-17", storedPolicy1.Version)
- assert.Len(t, storedPolicy1.Statement, 2)
- // For demonstration: store same policy on other instances
- err = instance2.AddPolicy("", "TestPolicy", testPolicy)
- require.NoError(t, err, "Should be able to store policy on instance 2")
- err = instance3.AddPolicy("", "TestPolicy", testPolicy)
- require.NoError(t, err, "Should be able to store policy on instance 3")
- })
- // Test policy evaluation consistency
- t.Run("evaluation_consistency", func(t *testing.T) {
- // Create evaluation context
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::test-bucket/file.txt",
- RequestContext: map[string]interface{}{
- "sourceIp": "192.168.1.100",
- },
- }
- // Evaluate policy on all instances
- result1, err1 := instance1.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result2, err2 := instance2.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result3, err3 := instance3.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- require.NoError(t, err1, "Evaluation should succeed on instance 1")
- require.NoError(t, err2, "Evaluation should succeed on instance 2")
- require.NoError(t, err3, "Evaluation should succeed on instance 3")
- // All instances should return identical results
- assert.Equal(t, result1.Effect, result2.Effect, "Instance 1 and 2 should have same effect")
- assert.Equal(t, result2.Effect, result3.Effect, "Instance 2 and 3 should have same effect")
- assert.Equal(t, EffectAllow, result1.Effect, "Should allow s3:GetObject")
- // Matching statements should be identical
- assert.Len(t, result1.MatchingStatements, 1, "Should have one matching statement")
- assert.Len(t, result2.MatchingStatements, 1, "Should have one matching statement")
- assert.Len(t, result3.MatchingStatements, 1, "Should have one matching statement")
- assert.Equal(t, "AllowS3Read", result1.MatchingStatements[0].StatementSid)
- assert.Equal(t, "AllowS3Read", result2.MatchingStatements[0].StatementSid)
- assert.Equal(t, "AllowS3Read", result3.MatchingStatements[0].StatementSid)
- })
- // Test explicit deny precedence
- t.Run("deny_precedence_consistency", func(t *testing.T) {
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "s3:PutObject",
- Resource: "arn:seaweed:s3:::test-bucket/newfile.txt",
- }
- // All instances should consistently apply deny precedence
- result1, err1 := instance1.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result2, err2 := instance2.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result3, err3 := instance3.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- require.NoError(t, err1)
- require.NoError(t, err2)
- require.NoError(t, err3)
- // All should deny due to explicit deny statement
- assert.Equal(t, EffectDeny, result1.Effect, "Instance 1 should deny write operation")
- assert.Equal(t, EffectDeny, result2.Effect, "Instance 2 should deny write operation")
- assert.Equal(t, EffectDeny, result3.Effect, "Instance 3 should deny write operation")
- // Should have matching deny statement
- assert.Len(t, result1.MatchingStatements, 1)
- assert.Equal(t, "DenyS3Write", result1.MatchingStatements[0].StatementSid)
- assert.Equal(t, EffectDeny, result1.MatchingStatements[0].Effect)
- })
- // Test default effect consistency
- t.Run("default_effect_consistency", func(t *testing.T) {
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "filer:CreateEntry", // Action not covered by any policy
- Resource: "arn:seaweed:filer::path/test",
- }
- result1, err1 := instance1.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result2, err2 := instance2.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- result3, err3 := instance3.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
- require.NoError(t, err1)
- require.NoError(t, err2)
- require.NoError(t, err3)
- // All should use default effect (Deny)
- assert.Equal(t, EffectDeny, result1.Effect, "Should use default effect")
- assert.Equal(t, EffectDeny, result2.Effect, "Should use default effect")
- assert.Equal(t, EffectDeny, result3.Effect, "Should use default effect")
- // No matching statements
- assert.Empty(t, result1.MatchingStatements, "Should have no matching statements")
- assert.Empty(t, result2.MatchingStatements, "Should have no matching statements")
- assert.Empty(t, result3.MatchingStatements, "Should have no matching statements")
- })
- }
- // TestPolicyEngineConfigurationConsistency tests configuration validation for distributed deployments
- func TestPolicyEngineConfigurationConsistency(t *testing.T) {
- t.Run("consistent_default_effects_required", func(t *testing.T) {
- // Different default effects could lead to inconsistent authorization
- config1 := &PolicyEngineConfig{
- DefaultEffect: "Allow",
- StoreType: "memory",
- }
- config2 := &PolicyEngineConfig{
- DefaultEffect: "Deny", // Different default!
- StoreType: "memory",
- }
- instance1 := NewPolicyEngine()
- instance2 := NewPolicyEngine()
- err1 := instance1.Initialize(config1)
- err2 := instance2.Initialize(config2)
- require.NoError(t, err1)
- require.NoError(t, err2)
- // Test with an action not covered by any policy
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "uncovered:action",
- Resource: "arn:seaweed:test:::resource",
- }
- result1, _ := instance1.Evaluate(context.Background(), "", evalCtx, []string{})
- result2, _ := instance2.Evaluate(context.Background(), "", evalCtx, []string{})
- // Results should be different due to different default effects
- assert.NotEqual(t, result1.Effect, result2.Effect, "Different default effects should produce different results")
- assert.Equal(t, EffectAllow, result1.Effect, "Instance 1 should allow by default")
- assert.Equal(t, EffectDeny, result2.Effect, "Instance 2 should deny by default")
- })
- t.Run("invalid_configuration_handling", func(t *testing.T) {
- invalidConfigs := []*PolicyEngineConfig{
- {
- DefaultEffect: "Maybe", // Invalid effect
- StoreType: "memory",
- },
- {
- DefaultEffect: "Allow",
- StoreType: "nonexistent", // Invalid store type
- },
- }
- for i, config := range invalidConfigs {
- t.Run(fmt.Sprintf("invalid_config_%d", i), func(t *testing.T) {
- instance := NewPolicyEngine()
- err := instance.Initialize(config)
- assert.Error(t, err, "Should reject invalid configuration")
- })
- }
- })
- }
- // TestPolicyStoreDistributed tests policy store behavior in distributed scenarios
- func TestPolicyStoreDistributed(t *testing.T) {
- ctx := context.Background()
- t.Run("memory_store_isolation", func(t *testing.T) {
- // Memory stores are isolated per instance (not suitable for distributed)
- store1 := NewMemoryPolicyStore()
- store2 := NewMemoryPolicyStore()
- policy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Effect: "Allow",
- Action: []string{"s3:GetObject"},
- Resource: []string{"*"},
- },
- },
- }
- // Store policy in store1
- err := store1.StorePolicy(ctx, "", "TestPolicy", policy)
- require.NoError(t, err)
- // Policy should exist in store1
- _, err = store1.GetPolicy(ctx, "", "TestPolicy")
- assert.NoError(t, err, "Policy should exist in store1")
- // Policy should NOT exist in store2 (different instance)
- _, err = store2.GetPolicy(ctx, "", "TestPolicy")
- assert.Error(t, err, "Policy should not exist in store2")
- assert.Contains(t, err.Error(), "not found", "Should be a not found error")
- })
- t.Run("policy_loading_error_handling", func(t *testing.T) {
- engine := NewPolicyEngine()
- config := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- }
- err := engine.Initialize(config)
- require.NoError(t, err)
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::bucket/key",
- }
- // Evaluate with non-existent policies
- result, err := engine.Evaluate(ctx, "", evalCtx, []string{"NonExistentPolicy1", "NonExistentPolicy2"})
- require.NoError(t, err, "Should not error on missing policies")
- // Should use default effect when no policies can be loaded
- assert.Equal(t, EffectDeny, result.Effect, "Should use default effect")
- assert.Empty(t, result.MatchingStatements, "Should have no matching statements")
- })
- }
- // TestFilerPolicyStoreConfiguration tests filer policy store configuration for distributed deployments
- func TestFilerPolicyStoreConfiguration(t *testing.T) {
- t.Run("filer_store_creation", func(t *testing.T) {
- // Test with minimal configuration
- config := map[string]interface{}{
- "filerAddress": "localhost:8888",
- }
- store, err := NewFilerPolicyStore(config, nil)
- require.NoError(t, err, "Should create filer policy store with minimal config")
- assert.NotNil(t, store)
- })
- t.Run("filer_store_custom_path", func(t *testing.T) {
- config := map[string]interface{}{
- "filerAddress": "prod-filer:8888",
- "basePath": "/custom/iam/policies",
- }
- store, err := NewFilerPolicyStore(config, nil)
- require.NoError(t, err, "Should create filer policy store with custom path")
- assert.NotNil(t, store)
- })
- t.Run("filer_store_missing_address", func(t *testing.T) {
- config := map[string]interface{}{
- "basePath": "/seaweedfs/iam/policies",
- }
- store, err := NewFilerPolicyStore(config, nil)
- assert.NoError(t, err, "Should create filer store without filerAddress in config")
- assert.NotNil(t, store, "Store should be created successfully")
- })
- }
- // TestPolicyEvaluationPerformance tests performance considerations for distributed policy evaluation
- func TestPolicyEvaluationPerformance(t *testing.T) {
- ctx := context.Background()
- // Create engine with memory store (for performance baseline)
- engine := NewPolicyEngine()
- config := &PolicyEngineConfig{
- DefaultEffect: "Deny",
- StoreType: "memory",
- }
- err := engine.Initialize(config)
- require.NoError(t, err)
- // Add multiple policies
- for i := 0; i < 10; i++ {
- policy := &PolicyDocument{
- Version: "2012-10-17",
- Statement: []Statement{
- {
- Sid: fmt.Sprintf("Statement%d", i),
- Effect: "Allow",
- Action: []string{"s3:GetObject", "s3:ListBucket"},
- Resource: []string{fmt.Sprintf("arn:seaweed:s3:::bucket%d/*", i)},
- },
- },
- }
- err := engine.AddPolicy("", fmt.Sprintf("Policy%d", i), policy)
- require.NoError(t, err)
- }
- // Test evaluation performance
- evalCtx := &EvaluationContext{
- Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
- Action: "s3:GetObject",
- Resource: "arn:seaweed:s3:::bucket5/file.txt",
- }
- policyNames := make([]string, 10)
- for i := 0; i < 10; i++ {
- policyNames[i] = fmt.Sprintf("Policy%d", i)
- }
- // Measure evaluation time
- start := time.Now()
- for i := 0; i < 100; i++ {
- _, err := engine.Evaluate(ctx, "", evalCtx, policyNames)
- require.NoError(t, err)
- }
- duration := time.Since(start)
- // Should be reasonably fast (less than 10ms per evaluation on average)
- avgDuration := duration / 100
- t.Logf("Average policy evaluation time: %v", avgDuration)
- assert.Less(t, avgDuration, 10*time.Millisecond, "Policy evaluation should be fast")
- }
|