Files
fail2ban-ui/development/oidc/init-keycloak.sh

167 lines
6.2 KiB
Bash
Executable File

#!/bin/bash
# Automatic Keycloak OIDC client configuration script
# This script creates the fail2ban-ui OIDC client in Keycloak automatically
set -e
KEYCLOAK_URL="${KEYCLOAK_URL:-http://localhost:3000}"
KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN:-admin}"
KEYCLOAK_PASSWORD="${KEYCLOAK_PASSWORD:-admin}"
REALM="${REALM:-master}"
CLIENT_ID="${CLIENT_ID:-fail2ban-ui}"
CLIENT_SECRET="${CLIENT_SECRET:-}"
# Use PUBLIC_FRONTEND_URL if provided, otherwise default to localhost
PUBLIC_FRONTEND_URL="${PUBLIC_FRONTEND_URL:-http://localhost:3080}"
REDIRECT_URI="${REDIRECT_URI:-${PUBLIC_FRONTEND_URL}/auth/callback}"
WEB_ORIGIN="${WEB_ORIGIN:-${PUBLIC_FRONTEND_URL}}"
# Extract host and port from KEYCLOAK_URL for health check
# KEYCLOAK_URL is the internal URL (e.g., http://keycloak:8080)
# Health endpoint is on management port 9000
KEYCLOAK_HOST=$(echo "${KEYCLOAK_URL}" | sed -E 's|https?://([^:/]+).*|\1|')
KEYCLOAK_HEALTH_URL="http://${KEYCLOAK_HOST}:9000/health/ready"
echo "Waiting for Keycloak to be ready..."
echo "Checking health endpoint: ${KEYCLOAK_HEALTH_URL}"
max_attempts=120 # Increased timeout since Keycloak can take a while
attempt=0
while [ $attempt -lt $max_attempts ]; do
# Check health endpoint on management port 9000
if curl -s -f "${KEYCLOAK_HEALTH_URL}" > /dev/null 2>&1; then
echo "Keycloak is ready!"
break
fi
# Also try the main port as fallback
if curl -s -f "${KEYCLOAK_URL}/health/ready" > /dev/null 2>&1; then
echo "Keycloak is ready (via main port)!"
break
fi
attempt=$((attempt + 1))
if [ $((attempt % 10)) -eq 0 ]; then
echo "Attempt $attempt/$max_attempts: Keycloak not ready yet, waiting..."
fi
sleep 2
done
if [ $attempt -eq $max_attempts ]; then
echo "ERROR: Keycloak did not become ready in time"
exit 1
fi
echo "Waiting for Keycloak admin API to be available..."
sleep 5
echo "Getting admin access token..."
ADMIN_TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${KEYCLOAK_ADMIN}" \
-d "password=${KEYCLOAK_PASSWORD}" \
-d "grant_type=password" \
-d "client_id=admin-cli" | jq -r '.access_token')
if [ -z "$ADMIN_TOKEN" ] || [ "$ADMIN_TOKEN" = "null" ]; then
echo "ERROR: Failed to get admin token"
exit 1
fi
echo "Checking if client already exists..."
EXISTING_CLIENT=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id // empty')
if [ -n "$EXISTING_CLIENT" ]; then
echo "Client '${CLIENT_ID}' already exists, updating..."
CLIENT_UUID="$EXISTING_CLIENT"
# Update client configuration
curl -s -X PUT "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"${CLIENT_ID}\",
\"enabled\": true,
\"clientAuthenticatorType\": \"client-secret\",
\"redirectUris\": [\"${REDIRECT_URI}\"],
\"webOrigins\": [\"${WEB_ORIGIN}\"],
\"protocol\": \"openid-connect\",
\"publicClient\": false,
\"standardFlowEnabled\": true,
\"directAccessGrantsEnabled\": true
}" > /dev/null
echo "Client updated successfully"
else
echo "Creating new client '${CLIENT_ID}'..."
# Create client
CLIENT_RESPONSE=$(curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/clients" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"clientId\": \"${CLIENT_ID}\",
\"enabled\": true,
\"clientAuthenticatorType\": \"client-secret\",
\"redirectUris\": [\"${REDIRECT_URI}\"],
\"webOrigins\": [\"${WEB_ORIGIN}\"],
\"protocol\": \"openid-connect\",
\"publicClient\": false,
\"standardFlowEnabled\": true,
\"directAccessGrantsEnabled\": true
}")
if [ $? -ne 0 ]; then
echo "ERROR: Failed to create client"
exit 1
fi
# Get the client UUID
CLIENT_UUID=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.[0].id')
echo "Client created successfully with UUID: ${CLIENT_UUID}"
fi
# Get or regenerate client secret
echo "Getting client secret..."
CLIENT_SECRET=$(curl -s -X GET "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.value')
if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then
echo "Regenerating client secret..."
CLIENT_SECRET=$(curl -s -X POST "${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_UUID}/client-secret" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" | jq -r '.value')
fi
if [ -z "$CLIENT_SECRET" ] || [ "$CLIENT_SECRET" = "null" ]; then
echo "ERROR: Failed to get client secret"
exit 1
fi
echo ""
echo "=========================================="
echo "OIDC Client Configuration Complete!"
echo "=========================================="
echo "Client ID: ${CLIENT_ID}"
echo "Client Secret: ${CLIENT_SECRET}"
echo "Realm: ${REALM}"
echo "Redirect URI: ${REDIRECT_URI}"
echo "=========================================="
# Save secret to shared volume for fail2ban-ui to read
SECRET_FILE="${SECRET_FILE:-/config/keycloak-client-secret}"
# Create directory if it doesn't exist
mkdir -p "$(dirname "${SECRET_FILE}")" 2>/dev/null || true
# Write secret file (running as root, so should have permissions)
if echo "${CLIENT_SECRET}" > "${SECRET_FILE}" 2>/dev/null; then
chmod 644 "${SECRET_FILE}" 2>/dev/null || true
echo "Client secret saved to ${SECRET_FILE} for fail2ban-ui"
else
echo "ERROR: Failed to write client secret to ${SECRET_FILE}"
echo "Client secret: ${CLIENT_SECRET}"
echo "Please save this secret manually to ${SECRET_FILE}"
exit 1
fi