diff options
Diffstat (limited to 'test/s3/iam/setup_keycloak_docker.sh')
| -rwxr-xr-x | test/s3/iam/setup_keycloak_docker.sh | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/test/s3/iam/setup_keycloak_docker.sh b/test/s3/iam/setup_keycloak_docker.sh new file mode 100755 index 000000000..e648bb7b6 --- /dev/null +++ b/test/s3/iam/setup_keycloak_docker.sh @@ -0,0 +1,419 @@ +#!/bin/bash +set -e + +# Keycloak configuration for Docker environment +KEYCLOAK_URL="http://keycloak:8080" +KEYCLOAK_ADMIN_USER="admin" +KEYCLOAK_ADMIN_PASSWORD="admin" +REALM_NAME="seaweedfs-test" +CLIENT_ID="seaweedfs-s3" +CLIENT_SECRET="seaweedfs-s3-secret" + +echo "🔧 Setting up Keycloak realm and users for SeaweedFS S3 IAM testing..." +echo "Keycloak URL: $KEYCLOAK_URL" + +# Wait for Keycloak to be ready +echo "⏳ Waiting for Keycloak to be ready..." +timeout 120 bash -c ' + until curl -f "$0/health/ready" > /dev/null 2>&1; do + echo "Waiting for Keycloak..." + sleep 5 + done + echo "✅ Keycloak health check passed" +' "$KEYCLOAK_URL" + +# Download kcadm.sh if not available +if ! command -v kcadm.sh &> /dev/null; then + echo "📥 Downloading Keycloak admin CLI..." + wget -q https://github.com/keycloak/keycloak/releases/download/26.0.7/keycloak-26.0.7.tar.gz + tar -xzf keycloak-26.0.7.tar.gz + export PATH="$PWD/keycloak-26.0.7/bin:$PATH" +fi + +# Wait a bit more for admin user initialization +echo "⏳ Waiting for admin user to be fully initialized..." +sleep 10 + +# Function to execute kcadm commands with retry and multiple password attempts +kcadm() { + local max_retries=3 + local retry_count=0 + local passwords=("admin" "admin123" "password") + + while [ $retry_count -lt $max_retries ]; do + for password in "${passwords[@]}"; do + if kcadm.sh "$@" --server "$KEYCLOAK_URL" --realm master --user "$KEYCLOAK_ADMIN_USER" --password "$password" 2>/dev/null; then + return 0 + fi + done + retry_count=$((retry_count + 1)) + echo "🔄 Retry $retry_count of $max_retries..." + sleep 5 + done + + echo "❌ Failed to execute kcadm command after $max_retries retries" + return 1 +} + +# Create realm +echo "📝 Creating realm '$REALM_NAME'..." +kcadm create realms -s realm="$REALM_NAME" -s enabled=true || echo "Realm may already exist" +echo "✅ Realm created" + +# Create OIDC client +echo "📝 Creating client '$CLIENT_ID'..." +CLIENT_UUID=$(kcadm create clients -r "$REALM_NAME" \ + -s clientId="$CLIENT_ID" \ + -s secret="$CLIENT_SECRET" \ + -s enabled=true \ + -s serviceAccountsEnabled=true \ + -s standardFlowEnabled=true \ + -s directAccessGrantsEnabled=true \ + -s 'redirectUris=["*"]' \ + -s 'webOrigins=["*"]' \ + -i 2>/dev/null || echo "existing-client") + +if [ "$CLIENT_UUID" != "existing-client" ]; then + echo "✅ Client created with ID: $CLIENT_UUID" +else + echo "✅ Using existing client" + CLIENT_UUID=$(kcadm get clients -r "$REALM_NAME" -q clientId="$CLIENT_ID" --fields id --format csv --noquotes | tail -n +2) +fi + +# Configure protocol mapper for roles +echo "🔧 Configuring role mapper for client '$CLIENT_ID'..." +MAPPER_CONFIG='{ + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "name": "realm-roles", + "config": { + "claim.name": "roles", + "jsonType.label": "String", + "multivalued": "true", + "usermodel.realmRoleMapping.rolePrefix": "" + } +}' + +kcadm create clients/"$CLIENT_UUID"/protocol-mappers/models -r "$REALM_NAME" -b "$MAPPER_CONFIG" 2>/dev/null || echo "✅ Role mapper already exists" +echo "✅ Realm roles mapper configured" + +# Configure audience mapper to ensure JWT tokens have correct audience claim +echo "🔧 Configuring audience mapper for client '$CLIENT_ID'..." +AUDIENCE_MAPPER_CONFIG='{ + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "name": "audience-mapper", + "config": { + "included.client.audience": "'$CLIENT_ID'", + "id.token.claim": "false", + "access.token.claim": "true" + } +}' + +kcadm create clients/"$CLIENT_UUID"/protocol-mappers/models -r "$REALM_NAME" -b "$AUDIENCE_MAPPER_CONFIG" 2>/dev/null || echo "✅ Audience mapper already exists" +echo "✅ Audience mapper configured" + +# Create realm roles +echo "📝 Creating realm roles..." +for role in "s3-admin" "s3-read-only" "s3-write-only" "s3-read-write"; do + kcadm create roles -r "$REALM_NAME" -s name="$role" 2>/dev/null || echo "Role $role may already exist" +done + +# Create users with roles +declare -A USERS=( + ["admin-user"]="s3-admin" + ["read-user"]="s3-read-only" + ["write-user"]="s3-read-write" + ["write-only-user"]="s3-write-only" +) + +for username in "${!USERS[@]}"; do + role="${USERS[$username]}" + password="${username//[^a-zA-Z]/}123" # e.g., "admin-user" -> "adminuser123" + + echo "📝 Creating user '$username'..." + kcadm create users -r "$REALM_NAME" \ + -s username="$username" \ + -s enabled=true \ + -s firstName="Test" \ + -s lastName="User" \ + -s email="$username@test.com" 2>/dev/null || echo "User $username may already exist" + + echo "🔑 Setting password for '$username'..." + kcadm set-password -r "$REALM_NAME" --username "$username" --new-password "$password" + + echo "➕ Assigning role '$role' to '$username'..." + kcadm add-roles -r "$REALM_NAME" --uusername "$username" --rolename "$role" +done + +# Create IAM configuration for Docker environment +echo "🔧 Setting up IAM configuration for Docker environment..." +cat > iam_config.json << 'EOF' +{ + "sts": { + "tokenDuration": "1h", + "maxSessionLength": "12h", + "issuer": "seaweedfs-sts", + "signingKey": "dGVzdC1zaWduaW5nLWtleS0zMi1jaGFyYWN0ZXJzLWxvbmc=" + }, + "providers": [ + { + "name": "keycloak", + "type": "oidc", + "enabled": true, + "config": { + "issuer": "http://keycloak:8080/realms/seaweedfs-test", + "clientId": "seaweedfs-s3", + "clientSecret": "seaweedfs-s3-secret", + "jwksUri": "http://keycloak:8080/realms/seaweedfs-test/protocol/openid-connect/certs", + "userInfoUri": "http://keycloak:8080/realms/seaweedfs-test/protocol/openid-connect/userinfo", + "scopes": ["openid", "profile", "email"], + "claimsMapping": { + "username": "preferred_username", + "email": "email", + "name": "name" + }, + "roleMapping": { + "rules": [ + { + "claim": "roles", + "value": "s3-admin", + "role": "arn:seaweed:iam::role/KeycloakAdminRole" + }, + { + "claim": "roles", + "value": "s3-read-only", + "role": "arn:seaweed:iam::role/KeycloakReadOnlyRole" + }, + { + "claim": "roles", + "value": "s3-write-only", + "role": "arn:seaweed:iam::role/KeycloakWriteOnlyRole" + }, + { + "claim": "roles", + "value": "s3-read-write", + "role": "arn:seaweed:iam::role/KeycloakReadWriteRole" + } + ], + "defaultRole": "arn:seaweed:iam::role/KeycloakReadOnlyRole" + } + } + } + ], + "policy": { + "defaultEffect": "Deny" + }, + "roles": [ + { + "roleName": "KeycloakAdminRole", + "roleArn": "arn:seaweed:iam::role/KeycloakAdminRole", + "trustPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "keycloak" + }, + "Action": ["sts:AssumeRoleWithWebIdentity"] + } + ] + }, + "attachedPolicies": ["S3AdminPolicy"], + "description": "Admin role for Keycloak users" + }, + { + "roleName": "KeycloakReadOnlyRole", + "roleArn": "arn:seaweed:iam::role/KeycloakReadOnlyRole", + "trustPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "keycloak" + }, + "Action": ["sts:AssumeRoleWithWebIdentity"] + } + ] + }, + "attachedPolicies": ["S3ReadOnlyPolicy"], + "description": "Read-only role for Keycloak users" + }, + { + "roleName": "KeycloakWriteOnlyRole", + "roleArn": "arn:seaweed:iam::role/KeycloakWriteOnlyRole", + "trustPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "keycloak" + }, + "Action": ["sts:AssumeRoleWithWebIdentity"] + } + ] + }, + "attachedPolicies": ["S3WriteOnlyPolicy"], + "description": "Write-only role for Keycloak users" + }, + { + "roleName": "KeycloakReadWriteRole", + "roleArn": "arn:seaweed:iam::role/KeycloakReadWriteRole", + "trustPolicy": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "keycloak" + }, + "Action": ["sts:AssumeRoleWithWebIdentity"] + } + ] + }, + "attachedPolicies": ["S3ReadWritePolicy"], + "description": "Read-write role for Keycloak users" + } + ], + "policies": [ + { + "name": "S3AdminPolicy", + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": ["*"] + }, + { + "Effect": "Allow", + "Action": ["sts:ValidateSession"], + "Resource": ["*"] + } + ] + } + }, + { + "name": "S3ReadOnlyPolicy", + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:seaweed:s3:::*", + "arn:seaweed:s3:::*/*" + ] + }, + { + "Effect": "Allow", + "Action": ["sts:ValidateSession"], + "Resource": ["*"] + } + ] + } + }, + { + "name": "S3WriteOnlyPolicy", + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": [ + "arn:seaweed:s3:::*", + "arn:seaweed:s3:::*/*" + ] + }, + { + "Effect": "Deny", + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Resource": [ + "arn:seaweed:s3:::*", + "arn:seaweed:s3:::*/*" + ] + }, + { + "Effect": "Allow", + "Action": ["sts:ValidateSession"], + "Resource": ["*"] + } + ] + } + }, + { + "name": "S3ReadWritePolicy", + "document": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": [ + "arn:seaweed:s3:::*", + "arn:seaweed:s3:::*/*" + ] + }, + { + "Effect": "Allow", + "Action": ["sts:ValidateSession"], + "Resource": ["*"] + } + ] + } + } + ] +} +EOF + +# Validate setup by testing authentication +echo "🔍 Validating setup by testing admin-user authentication and role mapping..." +KEYCLOAK_TOKEN_URL="http://keycloak:8080/realms/$REALM_NAME/protocol/openid-connect/token" + +# Get access token for admin-user +ACCESS_TOKEN=$(curl -s -X POST "$KEYCLOAK_TOKEN_URL" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password" \ + -d "client_id=$CLIENT_ID" \ + -d "client_secret=$CLIENT_SECRET" \ + -d "username=admin-user" \ + -d "password=adminuser123" \ + -d "scope=openid profile email" | jq -r '.access_token') + +if [ "$ACCESS_TOKEN" = "null" ] || [ -z "$ACCESS_TOKEN" ]; then + echo "❌ Failed to obtain access token" + exit 1 +fi + +echo "✅ Authentication validation successful" + +# Decode and check JWT claims +PAYLOAD=$(echo "$ACCESS_TOKEN" | cut -d'.' -f2) +# Add padding for base64 decode +while [ $((${#PAYLOAD} % 4)) -ne 0 ]; do + PAYLOAD="${PAYLOAD}=" +done + +CLAIMS=$(echo "$PAYLOAD" | base64 -d 2>/dev/null | jq .) +ROLES=$(echo "$CLAIMS" | jq -r '.roles[]?') + +if [ -n "$ROLES" ]; then + echo "✅ JWT token includes roles: [$(echo "$ROLES" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')]" +else + echo "⚠️ No roles found in JWT token" +fi + +echo "✅ Keycloak test realm '$REALM_NAME' configured for Docker environment" +echo "🐳 Setup complete! You can now run: docker-compose up -d" |
