| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- #!/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"
|