keycloak_bootstrap: parametrisierbar für baremetal-Prod

Das Bootstrap-Skript funktioniert jetzt gegen jede Keycloak-Instanz (Dev-Docker
wie baremetal-Prod) und nimmt Realm + Server-Adresse als Parameter:
- --url <basis-inkl-port>, --realm <name> (überschreibt den Namen in der
  JSON-Payload), --admin/--admin-password.
- --provisioner-secret <s>: ersetzt das Dev-Secret in der Payload (Prod).
- --no-test-user: entfernt "testfahrer" aus der Payload (Prod).
- Docker wird per Default NICHT mehr angefasst (Prod=baremetal); nur via --up.
- Alle Optionen auch via Env; CLI hat Vorrang.

README: parametrisierte Nutzung (Dev + Prod-Beispiel) + Abschnitt "Wie die
Authentifizierung funktioniert" (Skript→Keycloak Admin-Grant; Laufzeit
App↔Backend↔Keycloak: PKCE-Login, JWKS-JWT-Validierung mit aud/iss, Provisioner
client-credentials, X-Admin-Api-Key für /admin).

Verifiziert gegen Dev-Keycloak: custom Realm + überschriebenes Secret +
--no-test-user erzeugt korrekten Realm; Test-Realm danach wieder entfernt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-06-01 18:24:55 +02:00
parent f3e4c008ce
commit c65e13485d
2 changed files with 188 additions and 95 deletions

View File

@ -47,20 +47,61 @@ Admin-UI Änderungen macht, sollte sie **in die JSON zurückspielen**.
**Wichtig:** Das compose-`--import-realm` greift nur beim **ersten** Start
(solange das `keycloak-data`-Volume leer ist) — spätere JSON-Änderungen landen
NICHT automatisch in Keycloak. Zum Anwenden/Bootstrappen:
NICHT automatisch in Keycloak. Realm anwenden/bootstrappen geht daher über
`tool/keycloak_bootstrap.sh` (spricht nur die Admin-REST-API an, funktioniert
gegen **Dev-Docker wie baremetal-Prod**):
```bash
# Importiert den Realm über die Admin-REST-API (idempotent, startet Keycloak
# bei Bedarf via docker compose). Tut nichts, falls der Realm schon existiert.
./tool/keycloak_bootstrap.sh
# Dev (lokales Docker mit hochfahren, Default-Realm "holzleitner")
./tool/keycloak_bootstrap.sh --up
# Production (baremetal Keycloak, eigener Realm, frisches Secret, kein Test-User)
./tool/keycloak_bootstrap.sh \
--url https://auth.holzleitner.de \
--realm holzleitner-prod \
--admin kc-admin --admin-password "$KC_PW" \
--provisioner-secret "$(openssl rand -hex 24)" \
--no-test-user
# Sauberer Neu-Import (LÖSCHT den Realm inkl. provisionierter Fahrer-Konten):
./tool/keycloak_bootstrap.sh --reset
./tool/keycloak_bootstrap.sh --url ... --realm ... --reset
```
Das Skript gibt am Ende die `[keycloak]`-Werte aus, die in die `config.toml`
gehören (issuer_url, audience, provisioner-secret …). Overrides via
`KC_URL` / `KC_ADMIN` / `KC_ADMIN_PASSWORD` / `REALM_FILE`.
Wichtigste Optionen: `--url` (Keycloak-Basis inkl. Port), `--realm` (Realm-Name),
`--admin`/`--admin-password`, `--provisioner-secret`, `--no-test-user`, `--reset`,
`--up`. Alles auch via Env (`KC_URL`, `REALM`, `KC_ADMIN`, `KC_ADMIN_PASSWORD`,
`PROVISIONER_SECRET`, `REALM_FILE`). Default ohne `--up` wird **kein** Docker
angefasst. Das Skript gibt am Ende die passenden `[keycloak]`-Werte für die
`config.toml` aus.
### Wie die Authentifizierung funktioniert
Es gibt zwei getrennte Auth-Kontexte:
**a) Bootstrap-Skript → Keycloak (einmalig/selten).** Das Skript meldet sich am
`master`-Realm über den eingebauten Client `admin-cli` per Passwort-Grant an
(`--admin`/`--admin-password`) und nutzt das Admin-Token nur, um den Realm
anzulegen. In Prod ein echtes Admin-Konto + HTTPS verwenden; das Passwort besser
über die Env-Variable `KC_ADMIN_PASSWORD` setzen (nicht in der History).
**b) Laufzeit — App ↔ Backend ↔ Keycloak.**
1. Die Flutter-App loggt sich per **OIDC Authorization Code + PKCE** gegen
`{KC_URL}/realms/{realm}` ein und erhält ein **Access-Token (JWT, RS256)** mit
`aud=holzleitner-api`, Claim `personalnummer` und Realm-Rolle `driver`.
2. Das Backend **validiert das JWT public-key-basiert**: es holt die Realm-JWKS
von `{issuer}/protocol/openid-connect/certs`, prüft Signatur, `iss` (==
`config.toml [keycloak] issuer_url`), `aud` (enthält `holzleitner-api`) und
Ablauf. Kein gemeinsames Secret nötig — nur der öffentliche Schlüssel.
3. Die `/admin`-Maschinen-Endpunkte des Backends laufen NICHT über Keycloak,
sondern über den statischen `X-Admin-Api-Key` (siehe `[admin] api_key`).
4. Der **Provisioner** (`holzleitner-provisioner`, confidential, Client-
Credentials-Grant mit `manage-users`) wird vom ERP-Sync genutzt, um neue
Fahrer-Konten im Realm anzulegen — dafür gilt das `provisioner_client_secret`.
Damit b) klappt, müssen `issuer_url`/`realm`/`admin_url` in der `config.toml`
**exakt** auf denselben Server + Realm zeigen, den das Skript angelegt hat — und
`issuer_url` muss dem von Keycloak ausgestellten `iss`-Claim entsprechen (in
Prod also Keycloaks Frontend-/Hostname-URL korrekt setzen, sonst `invalid issuer`).
### Token für Dev-Tests holen