diff options
Diffstat (limited to 'test/s3/iam/s3_keycloak_integration_test.go')
| -rw-r--r-- | test/s3/iam/s3_keycloak_integration_test.go | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/test/s3/iam/s3_keycloak_integration_test.go b/test/s3/iam/s3_keycloak_integration_test.go new file mode 100644 index 000000000..0bb87161d --- /dev/null +++ b/test/s3/iam/s3_keycloak_integration_test.go @@ -0,0 +1,307 @@ +package iam + +import ( + "encoding/base64" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/service/s3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testKeycloakBucket = "test-keycloak-bucket" +) + +// TestKeycloakIntegrationAvailable checks if Keycloak is available for testing +func TestKeycloakIntegrationAvailable(t *testing.T) { + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + if !framework.useKeycloak { + t.Skip("Keycloak not available, skipping integration tests") + } + + // Test Keycloak health + assert.True(t, framework.useKeycloak, "Keycloak should be available") + assert.NotNil(t, framework.keycloakClient, "Keycloak client should be initialized") +} + +// TestKeycloakAuthentication tests authentication flow with real Keycloak +func TestKeycloakAuthentication(t *testing.T) { + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + if !framework.useKeycloak { + t.Skip("Keycloak not available, skipping integration tests") + } + + t.Run("admin_user_authentication", func(t *testing.T) { + // Test admin user authentication + token, err := framework.getKeycloakToken("admin-user") + require.NoError(t, err) + assert.NotEmpty(t, token, "JWT token should not be empty") + + // Verify token can be used to create S3 client + s3Client, err := framework.CreateS3ClientWithKeycloakToken(token) + require.NoError(t, err) + assert.NotNil(t, s3Client, "S3 client should be created successfully") + + // Test bucket operations with admin privileges + err = framework.CreateBucket(s3Client, testKeycloakBucket) + assert.NoError(t, err, "Admin user should be able to create buckets") + + // Verify bucket exists + buckets, err := s3Client.ListBuckets(&s3.ListBucketsInput{}) + require.NoError(t, err) + + found := false + for _, bucket := range buckets.Buckets { + if *bucket.Name == testKeycloakBucket { + found = true + break + } + } + assert.True(t, found, "Created bucket should be listed") + }) + + t.Run("read_only_user_authentication", func(t *testing.T) { + // Test read-only user authentication + token, err := framework.getKeycloakToken("read-user") + require.NoError(t, err) + assert.NotEmpty(t, token, "JWT token should not be empty") + + // Debug: decode token to verify it's for read-user + parts := strings.Split(token, ".") + if len(parts) >= 2 { + payload := parts[1] + // JWTs use URL-safe base64 encoding without padding (RFC 4648 ยง5) + decoded, err := base64.RawURLEncoding.DecodeString(payload) + if err == nil { + var claims map[string]interface{} + if json.Unmarshal(decoded, &claims) == nil { + t.Logf("Token username: %v", claims["preferred_username"]) + t.Logf("Token roles: %v", claims["roles"]) + } + } + } + + // First test with direct HTTP request to verify OIDC authentication works + t.Logf("Testing with direct HTTP request...") + err = framework.TestKeycloakTokenDirectly(token) + require.NoError(t, err, "Direct HTTP test should succeed") + + // Create S3 client with Keycloak token + s3Client, err := framework.CreateS3ClientWithKeycloakToken(token) + require.NoError(t, err) + + // Test that read-only user can list buckets + t.Logf("Testing ListBuckets with AWS SDK...") + _, err = s3Client.ListBuckets(&s3.ListBucketsInput{}) + assert.NoError(t, err, "Read-only user should be able to list buckets") + + // Test that read-only user cannot create buckets + t.Logf("Testing CreateBucket with AWS SDK...") + err = framework.CreateBucket(s3Client, testKeycloakBucket+"-readonly") + assert.Error(t, err, "Read-only user should not be able to create buckets") + }) + + t.Run("invalid_user_authentication", func(t *testing.T) { + // Test authentication with invalid credentials + _, err := framework.keycloakClient.AuthenticateUser("invalid-user", "invalid-password") + assert.Error(t, err, "Authentication with invalid credentials should fail") + }) +} + +// TestKeycloakTokenExpiration tests JWT token expiration handling +func TestKeycloakTokenExpiration(t *testing.T) { + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + if !framework.useKeycloak { + t.Skip("Keycloak not available, skipping integration tests") + } + + // Get a short-lived token (if Keycloak is configured for it) + // Use consistent password that matches Docker setup script logic: "adminuser123" + tokenResp, err := framework.keycloakClient.AuthenticateUser("admin-user", "adminuser123") + require.NoError(t, err) + + // Verify token properties + assert.NotEmpty(t, tokenResp.AccessToken, "Access token should not be empty") + assert.Equal(t, "Bearer", tokenResp.TokenType, "Token type should be Bearer") + assert.Greater(t, tokenResp.ExpiresIn, 0, "Token should have expiration time") + + // Test that token works initially + token, err := framework.getKeycloakToken("admin-user") + require.NoError(t, err) + + s3Client, err := framework.CreateS3ClientWithKeycloakToken(token) + require.NoError(t, err) + + _, err = s3Client.ListBuckets(&s3.ListBucketsInput{}) + assert.NoError(t, err, "Fresh token should work for S3 operations") +} + +// TestKeycloakRoleMapping tests role mapping from Keycloak to S3 policies +func TestKeycloakRoleMapping(t *testing.T) { + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + if !framework.useKeycloak { + t.Skip("Keycloak not available, skipping integration tests") + } + + testCases := []struct { + username string + expectedRole string + canCreateBucket bool + canListBuckets bool + description string + }{ + { + username: "admin-user", + expectedRole: "S3AdminRole", + canCreateBucket: true, + canListBuckets: true, + description: "Admin user should have full access", + }, + { + username: "read-user", + expectedRole: "S3ReadOnlyRole", + canCreateBucket: false, + canListBuckets: true, + description: "Read-only user should have read-only access", + }, + { + username: "write-user", + expectedRole: "S3ReadWriteRole", + canCreateBucket: true, + canListBuckets: true, + description: "Read-write user should have read-write access", + }, + } + + for _, tc := range testCases { + t.Run(tc.username, func(t *testing.T) { + // Get Keycloak token for the user + token, err := framework.getKeycloakToken(tc.username) + require.NoError(t, err) + + // Create S3 client with Keycloak token + s3Client, err := framework.CreateS3ClientWithKeycloakToken(token) + require.NoError(t, err, tc.description) + + // Test list buckets permission + _, err = s3Client.ListBuckets(&s3.ListBucketsInput{}) + if tc.canListBuckets { + assert.NoError(t, err, "%s should be able to list buckets", tc.username) + } else { + assert.Error(t, err, "%s should not be able to list buckets", tc.username) + } + + // Test create bucket permission + testBucketName := testKeycloakBucket + "-" + tc.username + err = framework.CreateBucket(s3Client, testBucketName) + if tc.canCreateBucket { + assert.NoError(t, err, "%s should be able to create buckets", tc.username) + } else { + assert.Error(t, err, "%s should not be able to create buckets", tc.username) + } + }) + } +} + +// TestKeycloakS3Operations tests comprehensive S3 operations with Keycloak authentication +func TestKeycloakS3Operations(t *testing.T) { + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + if !framework.useKeycloak { + t.Skip("Keycloak not available, skipping integration tests") + } + + // Use admin user for comprehensive testing + token, err := framework.getKeycloakToken("admin-user") + require.NoError(t, err) + + s3Client, err := framework.CreateS3ClientWithKeycloakToken(token) + require.NoError(t, err) + + bucketName := testKeycloakBucket + "-operations" + + t.Run("bucket_lifecycle", func(t *testing.T) { + // Create bucket + err = framework.CreateBucket(s3Client, bucketName) + require.NoError(t, err, "Should be able to create bucket") + + // Verify bucket exists + buckets, err := s3Client.ListBuckets(&s3.ListBucketsInput{}) + require.NoError(t, err) + + found := false + for _, bucket := range buckets.Buckets { + if *bucket.Name == bucketName { + found = true + break + } + } + assert.True(t, found, "Created bucket should be listed") + }) + + t.Run("object_operations", func(t *testing.T) { + objectKey := "test-object.txt" + objectContent := "Hello from Keycloak-authenticated SeaweedFS!" + + // Put object + err = framework.PutTestObject(s3Client, bucketName, objectKey, objectContent) + require.NoError(t, err, "Should be able to put object") + + // Get object + content, err := framework.GetTestObject(s3Client, bucketName, objectKey) + require.NoError(t, err, "Should be able to get object") + assert.Equal(t, objectContent, content, "Object content should match") + + // List objects + objects, err := framework.ListTestObjects(s3Client, bucketName) + require.NoError(t, err, "Should be able to list objects") + assert.Contains(t, objects, objectKey, "Object should be listed") + + // Delete object + err = framework.DeleteTestObject(s3Client, bucketName, objectKey) + assert.NoError(t, err, "Should be able to delete object") + }) +} + +// TestKeycloakFailover tests fallback to mock OIDC when Keycloak is unavailable +func TestKeycloakFailover(t *testing.T) { + // Temporarily override Keycloak URL to simulate unavailability + originalURL := os.Getenv("KEYCLOAK_URL") + os.Setenv("KEYCLOAK_URL", "http://localhost:9999") // Non-existent service + defer func() { + if originalURL != "" { + os.Setenv("KEYCLOAK_URL", originalURL) + } else { + os.Unsetenv("KEYCLOAK_URL") + } + }() + + framework := NewS3IAMTestFramework(t) + defer framework.Cleanup() + + // Should fall back to mock OIDC + assert.False(t, framework.useKeycloak, "Should fall back to mock OIDC when Keycloak is unavailable") + assert.Nil(t, framework.keycloakClient, "Keycloak client should not be initialized") + assert.NotNil(t, framework.mockOIDC, "Mock OIDC server should be initialized") + + // Test that mock authentication still works + s3Client, err := framework.CreateS3ClientWithJWT("admin-user", "TestAdminRole") + require.NoError(t, err, "Should be able to create S3 client with mock authentication") + + // Basic operation should work + _, err = s3Client.ListBuckets(&s3.ListBucketsInput{}) + // Note: This may still fail due to session store issues, but the client creation should work +} |
