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

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
}