Keycloak Service-Account (kora-platform-audit)¶
Der Audit-Poller liest Keycloak-Events über einen dedizierten Service-Account statt über den Master-Admin-Pfad (TODO-B2-03). Dieser Runbook beschreibt Bootstrap, Rotation, Re-Import und Troubleshooting.
Rolle im System¶
Der Client kora-platform-audit im kora-platform-Realm ist ein
Client-Credentials-Client ohne User-Login-Flow:
serviceAccountsEnabled: truepublicClient: false,standardFlowEnabled: false,directAccessGrantsEnabled: false,implicitFlowEnabled: false- Rechte (strikt minimal):
realm-management.view-events - Keine
view-users,view-realmoder anderen Management-Rollen. Der Poller braucht sie nicht (Event-Payload enthältuserId+details.username), und jede zusätzliche Rolle würde den Scope erweitern.
Der ehemalige Master-Admin-Pfad aus dem laufenden API-Container ist mit
TODO-B2-03 eliminiert. Der KORA_KEYCLOAK_ADMIN_USERNAME- und
KORA_KEYCLOAK_ADMIN_PASSWORD-Eintrag im API-Container existiert nicht
mehr; die Bootstrap-CLI nutzt Master-PW nur ad-hoc via
docker compose run --env-file .env.bootstrap.
Initiales Setup¶
Das Init-Script
infra/keycloak/init-scripts/create-audit-service-account.sh legt den
Client idempotent an und weist die Rolle zu. Einmalig beim ersten
Deployment ausführen:
# Master-PW aus .env.platform sourcen (KC_BOOTSTRAP_ADMIN_PASSWORD)
set -a && source .env.platform && set +a
./infra/keycloak/init-scripts/create-audit-service-account.sh
Das Script gibt den automatisch generierten Client-Secret aus. Diesen
Wert in .env.platform eintragen:
Danach: make redeploy-platform — der Poller nutzt die neuen ENVs.
Re-Run-Verhalten: Das Script erkennt einen existierenden Client und lässt den Secret unverändert. Re-Runs sind idempotent und sicher.
Bootstrap-CLI (einmalig pro Deployment)¶
Der initiale Operator-Admin wird nicht über den Service-Account
angelegt — dafür bräuchte der Account manage-users +
manage-realm-Rollen, was den Scope breit öffnen würde. Stattdessen
nutzt die Bootstrap-CLI weiterhin den Master-PW, aber nur ad-hoc via
Makefile-Wrapper (siehe
Compose-Invocations):
# Empfohlen: tmpfs-Pfad (weg nach Reboot)
mkdir -p /run/user/$UID
cp .env.bootstrap.example /run/user/$UID/.env.bootstrap
$EDITOR /run/user/$UID/.env.bootstrap # Master-PW eintragen
# ENVs in die aktuelle Shell laden:
set -a && source /run/user/$UID/.env.bootstrap && set +a
# Makefile-Wrapper: scheitert früh bei fehlenden Credentials,
# forwarded KC_BOOTSTRAP_ADMIN_* ephemer an einen docker-exec-Aufruf
# gegen den laufenden kora-platform-api-Container:
make platform-bootstrap cmd="bootstrap-operator-admin --email <admin-email>"
# Nach Nutzung: auf tmpfs-Pfad ist das nach Reboot eh weg. Explizit
# löschen für sofortige Bereinigung:
shred -u /run/user/$UID/.env.bootstrap 2>/dev/null || \
rm -f /run/user/$UID/.env.bootstrap
Der Wrapper nutzt intern docker exec -e VAR (ohne =value) —
forwardet die aktuelle Shell-Env nur an den einen exec-Prozess. Der
Master-PW erscheint nicht auf der Kommandozeile (nicht in
docker inspect oder ps aux sichtbar) und ist ausschließlich im
Kontext dieses exec-Aufrufs verfügbar, nicht container-weit —
docker exec kora-platform-api env zeigt den Wert nicht.
Alternative für frisches Deployment ohne laufenden API-Container: make
dev-platform starten, dann obigen Flow.
Alternativen zum tmpfs-Pfad:
./env.bootstrapim Projektverzeichnis —.gitignoreschützt vor versehentlichem Commit,shred -unach Nutzung- Secrets-Manager (Bitwarden, pass, 1Password) statt File
Bei Multi-User-Hosts oder strikten Ops-Umgebungen ist tmpfs zu bevorzugen — keine persistierten Master-Credentials auf der Platte.
Betroffene Bootstrap-Subcommands¶
Stand TODO-B2-03: genau ein Subcommand nutzt Master-PW:
kora-platform bootstrap-operator-admin— legt Operator-Admin-User imkora-platform-Realm an, weistoperator-admin-Rolle zu, versendetUPDATE_PASSWORD-Mail (48h).
Neue Bootstrap-Subcommands, die Master-PW brauchen würden, müssen
analog auf KC_BOOTSTRAP_ADMIN_* + .env.bootstrap-Pfad umgestellt
werden.
Client-Secret-Rotation¶
Empfohlene Frequenz: alle 90 Tage oder bei Personalwechsel im Ops-Team. Kalender- oder Cron-Reminder sinnvoll — ohne Erinnerung wird ein manueller Rotationsprozess in der Regel nicht eingehalten.
Rotation-Prozedur¶
- Keycloak-Admin-UI öffnen:
https://auth.kora.luki-net.org/admin/master/console/#/kora-platform/clients - Client
kora-platform-audit→ Tab "Credentials" → "Regenerate Secret" - Neuen Wert in
.env.platformunterKORA_KEYCLOAK_AUDIT_SECRETeintragen make redeploy-platform— API-Container startet neu, Poller holt Token mit neuem Secret- Erfolg verifizieren:
docker logs kora-platform-api --since 90s | grep -i audit— keineinvalid_client-Fehler
Alternativ: Init-Script mit Force-Regenerate (optional)¶
Das aktuelle Init-Script regeneriert bewusst nicht. Für automatisierte
Rotation könnte ein --rotate-Flag ergänzt werden — aktuell out-of-scope
(v1.x).
Realm-Re-Import-Verhalten¶
Das Keycloak-Image importiert Realm-JSONs nur beim ersten Realm-Create (nicht bei Service-Restart). Der Service-Account-Client wird nicht aus dem Realm-JSON importiert — das Init-Script legt ihn nachträglich via Admin-API an.
Bei vollständigem Realm-Re-Import (manuelles Löschen + Neuanlage des Realms):
- Realm wird mit Default-Clients aus
kora-platform-realm.jsonimportiert (ohnekora-platform-audit) - Init-Script erneut laufen lassen:
./infra/keycloak/init-scripts/create-audit-service-account.sh - Neuer Secret wird generiert und in
.env.platformeintragen make redeploy-platform
In Produktion sind Realm-Re-Imports zu vermeiden — alle User- und Consent-Daten gehen verloren. Bei tatsächlichem Bedarf: Backup + geplante Downtime + Smoke-Test-Runbook.
Rollback bei Problemen¶
Falls der Service-Account-Pfad nach einem Keycloak-Upgrade oder einer anderen Änderung ausfällt:
- Master-PW-Pfad vorübergehend wiederherstellen:
- Ursache analysieren (Keycloak-Version, Realm-Config, Secret-Drift)
- Fix implementieren, re-mergen
Der Master-Admin-User in Keycloak bleibt existent — er ist nur aus dem Dauer-ENV des API-Containers entfernt. Rollback re-aktiviert ihn in unter 5 Minuten.
Troubleshooting¶
Poller: HTTP 401 mit invalid_client oder unauthorized_client¶
Das Client-Secret in .env.platform passt nicht zum Wert in Keycloak —
oder der Client wurde gelöscht.
# Secret-Drift prüfen:
set -a && source .env.platform && set +a
docker exec kora-platform-api curl -sX POST \
http://kora-platform-keycloak:8080/realms/kora-platform/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=$KORA_KEYCLOAK_AUDIT_CLIENT_ID" \
-d "client_secret=$KORA_KEYCLOAK_AUDIT_SECRET" | python3 -m json.tool
Fix: Rotation-Prozedur (oben) durchlaufen oder Init-Script erneut ausführen (bei gelöschtem Client).
Poller: HTTP 403 auf /events¶
Rollen-Mapping fehlt. Via Admin-UI:
- Realm
kora-platform→ Clients →kora-platform-audit - Tab "Service accounts roles" → "Assign role"
- Filter:
realm-management→view-eventsauswählen → "Assign"
Alternativ: Init-Script erneut laufen lassen.
Prometheus-Counter-Spikes¶
Der Poller exportiert
audit_poller_auth_failures_total{reason="..."}. Häufige Werte:
reason="invalid_client"— Secret-Drift, siehe obenreason="invalid_grant"— Client-ID falsch, Client deaktiviertreason="forbidden"— Rolle fehlt, siehe obenreason="network"— DNS/Connect-Probleme zum Keycloak-Containerreason="missing_secret"—KORA_KEYCLOAK_AUDIT_SECRETleerreason="unknown"— alles andere; Logs prüfen
Grafana-Alert empfohlen: increase(audit_poller_auth_failures_total[5m]) > 3.
Referenzen¶
- Konzept: Fundament §2.2 (Vendor-Audit), §15.4 (Keycloak-Init)
- Code:
src/kora_platform/services/audit_poller.py - Init-Script:
infra/keycloak/init-scripts/create-audit-service-account.sh - Settings:
src/kora_platform/config.py→keycloak_audit_client_id,keycloak_audit_secret - Integration-Test:
tests/integration/test_audit_service_account.py