#!/usr/bin/env bash # bootstrap-admin.sh — create pi-admin and trigger-admin in privacyIDEA # # Run AFTER enckey-bootstrap.sh (key material must exist before admin setup). # # What it does: # 1. Creates pi-admin — full admin (single-credential bootstrap moment). # 2. Creates trigger-admin — limited admin for Keycloak's triggerchallenge calls. # 3. Creates the trigger-admin policy (triggerchallenge right only, via REST API). # 4. Prints instructions to immediately enroll MFA for pi-admin. # # Usage: # ./bootstrap-admin.sh [secrets-dir] [pi-url] # # default: ../../bootstrap/secrets # default: https://pink.coulomb.social # # The script uses the in-cluster kubectl exec path for creating admin users # (avoids the need for a network route to pink.coulomb.social during bootstrap). # The trigger-admin policy is created via REST API, so the URL must be reachable. set -euo pipefail NAMESPACE="mfa" SECRETS_DIR="${1:-../../bootstrap/secrets}" PI_URL="${2:-https://pink.coulomb.social}" PI_ENV="$SECRETS_DIR/privacyidea/secrets.env" if [[ ! -f "$PI_ENV" ]]; then echo "ERROR: $PI_ENV not found — run gen-secrets.sh first." >&2 exit 1 fi PI_ADMIN_PASS=$(bash -c "source '$PI_ENV' 2>/dev/null; echo \$PI_ADMIN_PASSWORD") if [[ -z "$PI_ADMIN_PASS" ]]; then echo "ERROR: PI_ADMIN_PASSWORD not found in $PI_ENV" >&2 exit 1 fi # trigger-admin gets its own random password (generated here, stored in KeePassXC). TRIGGER_PASS=$(openssl rand -base64 32 | tr -d '\n/+=' | head -c 40) # ── 1. Find running pod ─────────────────────────────────────────────────────── PI_POD=$(kubectl get pod -n "$NAMESPACE" \ -l app.kubernetes.io/name=privacyidea \ --field-selector=status.phase=Running \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") if [[ -z "$PI_POD" ]]; then echo "ERROR: no Running privacyIDEA pod found in namespace '$NAMESPACE'." >&2 exit 1 fi echo "Using pod: $PI_POD" # ── 2. Create pi-admin (full admin) ────────────────────────────────────────── echo "" echo "Creating admin: pi-admin" if kubectl exec -n "$NAMESPACE" "$PI_POD" -- \ pi-manage admin add pi-admin --password "$PI_ADMIN_PASS" 2>&1 | grep -q "already exist"; then echo " pi-admin already exists — skipping." else echo " pi-admin created." fi # ── 3. Create trigger-admin (limited admin) ─────────────────────────────────── echo "" echo "Creating admin: trigger-admin" if kubectl exec -n "$NAMESPACE" "$PI_POD" -- \ pi-manage admin add trigger-admin --password "$TRIGGER_PASS" 2>&1 | grep -q "already exist"; then echo " trigger-admin already exists — skipping password update." echo " WARNING: if you need to rotate trigger-admin's password, do so manually." else echo " trigger-admin created." fi # ── 4. Create K8s Secret for trigger-admin credentials ─────────────────────── # Keycloak's privacyIDEA Provider reads the trigger-admin credentials from # a K8s Secret that is referenced in the Keycloak realm configuration (T05/T06). echo "" echo "Creating K8s Secret: privacyidea-trigger-admin (namespace: mfa)" kubectl create secret generic privacyidea-trigger-admin \ --namespace=mfa \ --from-literal=username=trigger-admin \ --from-literal=password="$TRIGGER_PASS" \ --dry-run=client -o yaml | kubectl apply -f - echo " Done." # ── 5. Create trigger-admin policy via REST API ─────────────────────────────── # This restricts trigger-admin to the triggerchallenge action only. # Requires pink.coulomb.social to be reachable. echo "" echo "Creating trigger-admin policy via REST API ($PI_URL)..." echo " (Skip this step if $PI_URL is not yet reachable — do it via the WebUI instead.)" echo "" # Authenticate as pi-admin to get a bearer token. AUTH_RESPONSE=$(curl -sf -X POST "$PI_URL/auth" \ -H "Content-Type: application/json" \ -d "{\"username\":\"pi-admin\",\"password\":\"$PI_ADMIN_PASS\"}" || echo "CURL_FAILED") if [[ "$AUTH_RESPONSE" == "CURL_FAILED" ]]; then echo " Could not reach $PI_URL — skipping REST policy creation." echo " Create the trigger-admin policy manually (see README.md — Post-deploy steps)." else PI_TOKEN=$(echo "$AUTH_RESPONSE" | python3 -c \ "import sys,json; print(json.load(sys.stdin)['result']['value']['token'])" 2>/dev/null || echo "") if [[ -z "$PI_TOKEN" ]]; then echo " ERROR: could not extract auth token from response." >&2 echo " Create the trigger-admin policy manually (see README.md)." >&2 else # Create policy: trigger-admin can only call triggerchallenge. POLICY_RESP=$(curl -sf -X POST "$PI_URL/policy/trigger-admin-rights" \ -H "Authorization: $PI_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "scope": "admin", "action": "triggerchallenge", "adminrealm": "*", "adminuser": "trigger-admin", "realm": "*", "priority": 1 }' || echo "CURL_FAILED") if [[ "$POLICY_RESP" == "CURL_FAILED" ]]; then echo " WARNING: policy creation request failed." >&2 echo " Create the trigger-admin policy manually (see README.md)." >&2 else echo " Policy 'trigger-admin-rights' created." fi fi fi # ── 6. Summary ──────────────────────────────────────────────────────────────── echo "" echo "════════════════════════════════════════════════════════════" echo " Admin bootstrap complete." echo "════════════════════════════════════════════════════════════" echo "" echo "CRITICAL — Do these steps NOW:" echo "" echo " 1. Store trigger-admin password in KeePassXC:" echo " KeePassXC group: net-kingdom/privacyIDEA" echo " Entry: trigger-admin → username=trigger-admin password=" echo " trigger-admin password: $TRIGGER_PASS" echo " Then shred this terminal history." echo "" echo " 2. Log in to the privacyIDEA WebUI as pi-admin:" echo " $PI_URL" echo " Enroll MFA for pi-admin IMMEDIATELY (TOTP or hardware token)." echo " Until MFA is enrolled, pi-admin has only password authentication." echo "" echo " 3. Verify the trigger-admin policy:" echo " WebUI → Config → Policies → trigger-admin-rights" echo " Scope: admin Action: triggerchallenge AdminUser: trigger-admin" echo " If it was not created automatically, create it here." echo "" echo " 4. Test admin MFA:" echo " Log out, log back in as pi-admin — MFA challenge must appear." echo "" echo "Next step: run ../verify-t04.sh"