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.
		
		
		
		
		
			
		
			
				
					
					
						
							357 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							357 lines
						
					
					
						
							11 KiB
						
					
					
				| package iam | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"os" | |
| 	"sync" | |
| 	"testing" | |
| 	"time" | |
| 
 | |
| 	"github.com/aws/aws-sdk-go/aws" | |
| 	"github.com/aws/aws-sdk-go/service/s3" | |
| 	"github.com/stretchr/testify/assert" | |
| 	"github.com/stretchr/testify/require" | |
| ) | |
| 
 | |
| // TestS3IAMDistributedTests tests IAM functionality across multiple S3 gateway instances | |
| func TestS3IAMDistributedTests(t *testing.T) { | |
| 	// Skip if not in distributed test mode | |
| 	if os.Getenv("ENABLE_DISTRIBUTED_TESTS") != "true" { | |
| 		t.Skip("Distributed tests not enabled. Set ENABLE_DISTRIBUTED_TESTS=true") | |
| 	} | |
| 
 | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	t.Run("distributed_session_consistency", func(t *testing.T) { | |
| 		// Test that sessions created on one instance are visible on others | |
| 		// This requires filer-based session storage | |
|  | |
| 		// Create S3 clients that would connect to different gateway instances | |
| 		// In a real distributed setup, these would point to different S3 gateway ports | |
| 		client1, err := framework.CreateS3ClientWithJWT("test-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		client2, err := framework.CreateS3ClientWithJWT("test-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Both clients should be able to perform operations | |
| 		bucketName := "test-distributed-session" | |
| 
 | |
| 		err = framework.CreateBucket(client1, bucketName) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Client2 should see the bucket created by client1 | |
| 		listResult, err := client2.ListBuckets(&s3.ListBucketsInput{}) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		found := false | |
| 		for _, bucket := range listResult.Buckets { | |
| 			if *bucket.Name == bucketName { | |
| 				found = true | |
| 				break | |
| 			} | |
| 		} | |
| 		assert.True(t, found, "Bucket should be visible across distributed instances") | |
| 
 | |
| 		// Cleanup | |
| 		_, err = client1.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(bucketName), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 	}) | |
| 
 | |
| 	t.Run("distributed_role_consistency", func(t *testing.T) { | |
| 		// Test that role definitions are consistent across instances | |
| 		// This requires filer-based role storage | |
|  | |
| 		// Create clients with different roles | |
| 		adminClient, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		readOnlyClient, err := framework.CreateS3ClientWithJWT("readonly-user", "TestReadOnlyRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		bucketName := "test-distributed-roles" | |
| 		objectKey := "test-object.txt" | |
| 
 | |
| 		// Admin should be able to create bucket | |
| 		err = framework.CreateBucket(adminClient, bucketName) | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Admin should be able to put object | |
| 		err = framework.PutTestObject(adminClient, bucketName, objectKey, "test content") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		// Read-only user should be able to get object | |
| 		content, err := framework.GetTestObject(readOnlyClient, bucketName, objectKey) | |
| 		require.NoError(t, err) | |
| 		assert.Equal(t, "test content", content) | |
| 
 | |
| 		// Read-only user should NOT be able to put object | |
| 		err = framework.PutTestObject(readOnlyClient, bucketName, "forbidden-object.txt", "forbidden content") | |
| 		require.Error(t, err, "Read-only user should not be able to put objects") | |
| 
 | |
| 		// Cleanup | |
| 		err = framework.DeleteTestObject(adminClient, bucketName, objectKey) | |
| 		require.NoError(t, err) | |
| 		_, err = adminClient.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(bucketName), | |
| 		}) | |
| 		require.NoError(t, err) | |
| 	}) | |
| 
 | |
| 	t.Run("distributed_concurrent_operations", func(t *testing.T) { | |
| 		// Test concurrent operations across distributed instances | |
| 		// Reduced concurrency (3x2=6 total ops) for CI stability | |
| 		const numGoroutines = 3 | |
| 		const numOperationsPerGoroutine = 2 | |
| 
 | |
| 		var wg sync.WaitGroup | |
| 		errors := make(chan error, numGoroutines*numOperationsPerGoroutine) | |
| 
 | |
| 		for i := 0; i < numGoroutines; i++ { | |
| 			wg.Add(1) | |
| 			go func(goroutineID int) { | |
| 				defer wg.Done() | |
| 
 | |
| 				client, err := framework.CreateS3ClientWithJWT(fmt.Sprintf("user-%d", goroutineID), "TestAdminRole") | |
| 				if err != nil { | |
| 					errors <- err | |
| 					return | |
| 				} | |
| 
 | |
| 				for j := 0; j < numOperationsPerGoroutine; j++ { | |
| 					bucketName := fmt.Sprintf("test-concurrent-%d-%d", goroutineID, j) | |
| 
 | |
| 					// Create bucket | |
| 					if err := framework.CreateBucket(client, bucketName); err != nil { | |
| 						errors <- err | |
| 						continue | |
| 					} | |
| 					// Small delay to reduce server load | |
| 					time.Sleep(100 * time.Millisecond) | |
| 
 | |
| 					// Put object | |
| 					objectKey := "test-object.txt" | |
| 					if err := framework.PutTestObject(client, bucketName, objectKey, fmt.Sprintf("content-%d-%d", goroutineID, j)); err != nil { | |
| 						errors <- err | |
| 						continue | |
| 					} | |
| 					// Small delay to reduce server load | |
| 					time.Sleep(100 * time.Millisecond) | |
| 
 | |
| 					// Get object | |
| 					if _, err := framework.GetTestObject(client, bucketName, objectKey); err != nil { | |
| 						errors <- err | |
| 						continue | |
| 					} | |
| 					// Small delay to reduce server load | |
| 					time.Sleep(100 * time.Millisecond) | |
| 
 | |
| 					// Delete object | |
| 					if err := framework.DeleteTestObject(client, bucketName, objectKey); err != nil { | |
| 						errors <- err | |
| 						continue | |
| 					} | |
| 					// Small delay to reduce server load | |
| 					time.Sleep(100 * time.Millisecond) | |
| 
 | |
| 					// Delete bucket | |
| 					if _, err := client.DeleteBucket(&s3.DeleteBucketInput{ | |
| 						Bucket: aws.String(bucketName), | |
| 					}); err != nil { | |
| 						errors <- err | |
| 						continue | |
| 					} | |
| 					// Small delay to reduce server load | |
| 					time.Sleep(100 * time.Millisecond) | |
| 				} | |
| 			}(i) | |
| 		} | |
| 
 | |
| 		wg.Wait() | |
| 		close(errors) | |
| 
 | |
| 		// Check for errors - allow some failures under concurrent load | |
| 		var errorList []error | |
| 		for err := range errors { | |
| 			errorList = append(errorList, err) | |
| 		} | |
| 
 | |
| 		totalOperations := numGoroutines * numOperationsPerGoroutine | |
| 		errorRate := float64(len(errorList)) / float64(totalOperations) | |
| 
 | |
| 		if len(errorList) > 0 { | |
| 			t.Logf("Concurrent operations: %d/%d operations failed (%.1f%% error rate). First error: %v", | |
| 				len(errorList), totalOperations, errorRate*100, errorList[0]) | |
| 		} | |
| 
 | |
| 		// Allow up to 50% error rate for concurrent stress testing | |
| 		// This tests that the system handles concurrent load gracefully | |
| 		if errorRate > 0.5 { | |
| 			t.Errorf("Concurrent operations error rate too high: %.1f%% (>50%%). System may be unstable under load.", errorRate*100) | |
| 		} else if len(errorList) > 0 { | |
| 			t.Logf("✅ Concurrent operations completed with acceptable error rate: %.1f%%", errorRate*100) | |
| 		} else { | |
| 			t.Logf("✅ All concurrent operations completed successfully") | |
| 		} | |
| 	}) | |
| } | |
| 
 | |
| // TestS3IAMPerformanceTests tests IAM performance characteristics | |
| func TestS3IAMPerformanceTests(t *testing.T) { | |
| 	// Skip if not in performance test mode | |
| 	if os.Getenv("ENABLE_PERFORMANCE_TESTS") != "true" { | |
| 		t.Skip("Performance tests not enabled. Set ENABLE_PERFORMANCE_TESTS=true") | |
| 	} | |
| 
 | |
| 	framework := NewS3IAMTestFramework(t) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	t.Run("authentication_performance", func(t *testing.T) { | |
| 		// Test authentication performance | |
| 		const numRequests = 100 | |
| 
 | |
| 		client, err := framework.CreateS3ClientWithJWT("perf-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		bucketName := "test-auth-performance" | |
| 		err = framework.CreateBucket(client, bucketName) | |
| 		require.NoError(t, err) | |
| 		defer func() { | |
| 			_, err := client.DeleteBucket(&s3.DeleteBucketInput{ | |
| 				Bucket: aws.String(bucketName), | |
| 			}) | |
| 			require.NoError(t, err) | |
| 		}() | |
| 
 | |
| 		start := time.Now() | |
| 
 | |
| 		for i := 0; i < numRequests; i++ { | |
| 			_, err := client.ListBuckets(&s3.ListBucketsInput{}) | |
| 			require.NoError(t, err) | |
| 		} | |
| 
 | |
| 		duration := time.Since(start) | |
| 		avgLatency := duration / numRequests | |
| 
 | |
| 		t.Logf("Authentication performance: %d requests in %v (avg: %v per request)", | |
| 			numRequests, duration, avgLatency) | |
| 
 | |
| 		// Performance assertion - should be under 100ms per request on average | |
| 		assert.Less(t, avgLatency, 100*time.Millisecond, | |
| 			"Average authentication latency should be under 100ms") | |
| 	}) | |
| 
 | |
| 	t.Run("authorization_performance", func(t *testing.T) { | |
| 		// Test authorization performance with different policy complexities | |
| 		const numRequests = 50 | |
| 
 | |
| 		client, err := framework.CreateS3ClientWithJWT("perf-user", "TestAdminRole") | |
| 		require.NoError(t, err) | |
| 
 | |
| 		bucketName := "test-authz-performance" | |
| 		err = framework.CreateBucket(client, bucketName) | |
| 		require.NoError(t, err) | |
| 		defer func() { | |
| 			_, err := client.DeleteBucket(&s3.DeleteBucketInput{ | |
| 				Bucket: aws.String(bucketName), | |
| 			}) | |
| 			require.NoError(t, err) | |
| 		}() | |
| 
 | |
| 		start := time.Now() | |
| 
 | |
| 		for i := 0; i < numRequests; i++ { | |
| 			objectKey := fmt.Sprintf("perf-object-%d.txt", i) | |
| 			err := framework.PutTestObject(client, bucketName, objectKey, "performance test content") | |
| 			require.NoError(t, err) | |
| 
 | |
| 			_, err = framework.GetTestObject(client, bucketName, objectKey) | |
| 			require.NoError(t, err) | |
| 
 | |
| 			err = framework.DeleteTestObject(client, bucketName, objectKey) | |
| 			require.NoError(t, err) | |
| 		} | |
| 
 | |
| 		duration := time.Since(start) | |
| 		avgLatency := duration / (numRequests * 3) // 3 operations per iteration | |
|  | |
| 		t.Logf("Authorization performance: %d operations in %v (avg: %v per operation)", | |
| 			numRequests*3, duration, avgLatency) | |
| 
 | |
| 		// Performance assertion - should be under 50ms per operation on average | |
| 		assert.Less(t, avgLatency, 50*time.Millisecond, | |
| 			"Average authorization latency should be under 50ms") | |
| 	}) | |
| } | |
| 
 | |
| // BenchmarkS3IAMAuthentication benchmarks JWT authentication | |
| func BenchmarkS3IAMAuthentication(b *testing.B) { | |
| 	if os.Getenv("ENABLE_PERFORMANCE_TESTS") != "true" { | |
| 		b.Skip("Performance tests not enabled. Set ENABLE_PERFORMANCE_TESTS=true") | |
| 	} | |
| 
 | |
| 	framework := NewS3IAMTestFramework(&testing.T{}) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	client, err := framework.CreateS3ClientWithJWT("bench-user", "TestAdminRole") | |
| 	require.NoError(b, err) | |
| 
 | |
| 	bucketName := "test-bench-auth" | |
| 	err = framework.CreateBucket(client, bucketName) | |
| 	require.NoError(b, err) | |
| 	defer func() { | |
| 		_, err := client.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(bucketName), | |
| 		}) | |
| 		require.NoError(b, err) | |
| 	}() | |
| 
 | |
| 	b.ResetTimer() | |
| 	b.RunParallel(func(pb *testing.PB) { | |
| 		for pb.Next() { | |
| 			_, err := client.ListBuckets(&s3.ListBucketsInput{}) | |
| 			if err != nil { | |
| 				b.Error(err) | |
| 			} | |
| 		} | |
| 	}) | |
| } | |
| 
 | |
| // BenchmarkS3IAMAuthorization benchmarks policy evaluation | |
| func BenchmarkS3IAMAuthorization(b *testing.B) { | |
| 	if os.Getenv("ENABLE_PERFORMANCE_TESTS") != "true" { | |
| 		b.Skip("Performance tests not enabled. Set ENABLE_PERFORMANCE_TESTS=true") | |
| 	} | |
| 
 | |
| 	framework := NewS3IAMTestFramework(&testing.T{}) | |
| 	defer framework.Cleanup() | |
| 
 | |
| 	client, err := framework.CreateS3ClientWithJWT("bench-user", "TestAdminRole") | |
| 	require.NoError(b, err) | |
| 
 | |
| 	bucketName := "test-bench-authz" | |
| 	err = framework.CreateBucket(client, bucketName) | |
| 	require.NoError(b, err) | |
| 	defer func() { | |
| 		_, err := client.DeleteBucket(&s3.DeleteBucketInput{ | |
| 			Bucket: aws.String(bucketName), | |
| 		}) | |
| 		require.NoError(b, err) | |
| 	}() | |
| 
 | |
| 	b.ResetTimer() | |
| 	b.RunParallel(func(pb *testing.PB) { | |
| 		i := 0 | |
| 		for pb.Next() { | |
| 			objectKey := fmt.Sprintf("bench-object-%d.txt", i) | |
| 			err := framework.PutTestObject(client, bucketName, objectKey, "benchmark content") | |
| 			if err != nil { | |
| 				b.Error(err) | |
| 			} | |
| 			i++ | |
| 		} | |
| 	}) | |
| }
 |