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 }