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.
		
		
		
		
		
			
		
			
				
					
					
						
							307 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							307 lines
						
					
					
						
							10 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] | |
| 			// 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 | |
| }
 |