Files
net-kingdom/sso-mfa/k8s/privacyidea/bootstrap-admin.sh
Bernd Worsch 1d94652ba1 feat(sso-mfa): T04 privacyIDEA manifests (NK-WP-0001-T04)
Deploy privacyIDEA (MFA core) in the mfa namespace:
- pvc.yaml: privacyidea-data (5Gi) and privacyidea-logs (2Gi)
- configmap.yaml: pi.cfg reading secrets from env vars
- deployment.yaml: Deployment + ClusterIP Service (port 8080)
- middleware.yaml: Traefik RateLimit + admin IP AllowList
- ingress.yaml: pink.coulomb.social (portal + admin), pink-account.coulomb.social (self-service)
- create-secrets.sh: creates privacyidea-config Secret
- enckey-bootstrap.sh: post-deploy key extraction + DR Secrets
- bootstrap-admin.sh: pi-admin, trigger-admin, privacyidea-trigger-admin Secret
- verify-t04.sh: 8-section done-criteria checker

Config points CP-NK-002 (pink.coulomb.social) and CP-NK-003
(pink-account.coulomb.social) registered in CONFIG.md.

pink = PrivacyIDEA Net Knights (project mnemonic).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 01:22:41 +00:00

163 lines
7.2 KiB
Bash
Executable File

#!/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]
#
# <secrets-dir> default: ../../bootstrap/secrets
# <pi-url> 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=<below>"
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"