| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- 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
- }
|