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++ } }) }