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.
		
		
		
		
		
			
		
			
				
					
					
						
							309 lines
						
					
					
						
							9.9 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							309 lines
						
					
					
						
							9.9 KiB
						
					
					
				
								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]
							 | 
						|
											// Add padding if needed
							 | 
						|
											for len(payload)%4 != 0 {
							 | 
						|
												payload += "="
							 | 
						|
											}
							 | 
						|
											decoded, err := base64.StdEncoding.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)
							 | 
						|
									tokenResp, err := framework.keycloakClient.AuthenticateUser("admin-user", "admin123")
							 | 
						|
									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
							 | 
						|
								}
							 |