--- type: Journal status: ACTIVE owner: Curator last_update: 2026-03-09 --- # Journal - 2026-03-09 ## 📝 Zusammenfassung Keycloak-HĂ€rtung der Realm-Konfiguration (`meldestelle-realm.json`). Aufbauend auf dem Infrastruktur-Fix vom 2026-03-06 (korrektes `start --optimized`, Healthcheck) wurden nun die Client-Konfigurationen und Sicherheitseinstellungen auf einen produktionsreifen Stand gebracht. ## đŸ› ïž Änderungen ### 1. `config/docker/keycloak/meldestelle-realm.json` #### Wildcard aus `webOrigins` entfernt - **Vorher:** `api-gateway` und `web-app` hatten `"*"` in `webOrigins` (CORS-SicherheitslĂŒcke). - **Nachher:** Nur explizit erlaubte Origins (`localhost:*`, `app.meldestelle.at`). #### Neuer Client: `frontend-client` (KMP Desktop & Mobile) - **Public Client** mit **PKCE S256** (kein Client-Secret nötig, sicher fĂŒr native Apps). - **Redirect URIs:** `meldestelle://callback` (Custom URI Scheme fĂŒr Desktop), `http://localhost:*` (Dev), `https://app.meldestelle.at/*` (Prod). - **Kein `directAccessGrantsEnabled`** — verhindert Password-Grant-Flow (unsicher fĂŒr native Apps). #### Password-Policy gestĂ€rkt - **Vorher:** `length(8)` — zu schwach. - **Nachher:** `length(10) and digits(1) and upperCase(1) and specialChars(1) and notUsername(undefined)`. #### `post.logout.redirect.uris` konfiguriert - Alle relevanten Clients (`api-gateway`, `web-app`, `frontend-client`) haben nun korrekte Logout-Redirect-URIs gemĂ€ĂŸ OIDC Back-Channel Logout Standard. ## 📚 Gelerntes / Entscheidungen - **`meldestelle://callback`** als Custom URI Scheme: FĂŒr KMP Compose Desktop ist ein eigenes URI-Schema der sichere Standard (kein offener HTTP-Port nötig). Muss im OS registriert werden. - **PKCE S256 ist Pflicht fĂŒr Public Clients:** Verhindert Authorization Code Interception Attacks. Keycloak `26.x` unterstĂŒtzt dies nativ. - **`frontend-client` vs. `web-app`:** Klare Trennung: `web-app` fĂŒr Browser-basierte Web-App, `frontend-client` fĂŒr native KMP Desktop/Mobile-App. ## 🐛 Bugfix: Keycloak Import-Fehler `invalidPasswordMinSpecialCharsMessage` (2026-03-09) ### Root Cause Beim Keycloak HĂ€rtungs-Step wurde die Password-Policy auf `length(10) and digits(1) and upperCase(1) and specialChars(1) and notUsername(undefined)` gestĂ€rkt. Der Test-User `admin` in `meldestelle-realm.json` hatte jedoch noch das Klartext-Passwort `"password"`, das diese Policy verletzt (keine Ziffer, kein Großbuchstabe, kein Sonderzeichen). Keycloak validiert beim `--import-realm` Plain-Text-Credentials gegen die im Realm definierte Policy → Server-Start schlĂ€gt fehl mit `invalidPasswordMinSpecialCharsMessage`. ### Fix - **`config/docker/keycloak/meldestelle-realm.json`**: User `admin` Credential `"password"` → `"Admin1234!"` (erfĂŒllt alle Policy-Regeln: ≄10 Zeichen, Ziffer, Großbuchstabe, Sonderzeichen, nicht gleich Username). ### Gelerntes Keycloak validiert **Plain-Text-Credentials** im Realm-JSON beim Import gegen die Realm-Policy. Wird die Policy nachtrĂ€glich gestĂ€rkt, mĂŒssen alle bestehenden User-Credentials im JSON ebenfalls aktualisiert werden — sonst startet der Server nicht. --- ## ✅ Micrometer Upgrade (2026-03-09, gleiche Session) Verifiziert: `micrometer = "1.16.1"` und `micrometerTracing = "1.6.1"` waren bereits korrekt in `gradle/libs.versions.toml` gesetzt — kein Code-Change erforderlich. Archiv-Roadmaps entsprechend abgehakt. ## ✅ Zipkin Integration (2026-03-09, gleiche Session) Analyse ergab: Infrastruktur war bereits zu ~90% korrekt aufgebaut. Einzige LĂŒcke war eine falsche Propagation-Konfiguration im Gateway. ### Befund - `dc-infra.yaml`: Zipkin-Container (`openzipkin/zipkin:3`, Port 9411) bereits vorhanden. - `monitoring-client`-Bundle: enthĂ€lt `micrometer-tracing-bridge-brave`, `zipkin-reporter-brave`, `zipkin-sender-okhttp3` — von allen Services eingebunden. - `monitoring-defaults.properties`: setzt `management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans` als Default, ĂŒberschreibbar via `MANAGEMENT_ZIPKIN_TRACING_ENDPOINT`. - `dc-backend.yaml`: setzt `MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans` fĂŒr alle Services inkl. Gateway. ### Fix: Gateway `application.yaml` - **`management.tracing.propagation.type`**: `w3c` → `b3` (B3 ist das native Format von Brave/Zipkin; W3C wĂ€re fĂŒr OpenTelemetry — Mismatch hĂ€tte Trace-Korrelation zwischen Gateway und Services gebrochen). - **`management.zipkin.tracing.endpoint`**: Explizit mit lokalem Default + ENV-Override ergĂ€nzt. - **`TRACING_SAMPLING_PROBABILITY`**: Konsistent mit Services via ENV-Variable. ### Fix: Gateway `build.gradle.kts` - Redundante direkte `micrometer-tracing-bridge-brave`-Dependency entfernt (bereits transitiv via `monitoring-client` vorhanden). ## ✅ OIDC Client im Frontend (2026-03-09, gleiche Session) Login-Flow mit PKCE Authorization Code Flow (S256) fĂŒr `frontend-client` implementiert. ### Neue Dateien - **`frontend/core/auth/src/commonMain/.../Sha256.kt`**: Pure Kotlin SHA-256 (FIPS 180-4) + Base64URL-Encoding — kein expect/actual, lĂ€uft auf JVM/JS/Wasm. - **`frontend/core/auth/src/commonMain/.../PkceHelper.kt`**: Code Verifier, Code Challenge (S256), State Generator. - **`frontend/core/auth/src/commonMain/.../OidcCallback.kt`**: `OidcCallbackResult` sealed class + expect `launchOidcFlow()`, `consumePendingOidcCallback()`, `getOidcRedirectUri()`. - **`frontend/core/auth/src/jvmMain/.../OidcCallback.jvm.kt`**: Eingebetteter `HttpServer` (Port 18080) + `Desktop.browse()` + `CompletableDeferred` (Timeout 5 min). - **`frontend/core/auth/src/jsMain/.../OidcCallback.js.kt`**: `window.location.href` Redirect + URL-Parameter-Parsing beim App-Start + History-Bereinigung via `replaceState`. ### GeĂ€nderte Dateien - **`frontend/core/domain/.../AppConstants.kt`**: `KEYCLOAK_CLIENT_ID` → `frontend-client`, OIDC-Konstanten ergĂ€nzt. - **`frontend/core/auth/src/commonMain/.../AuthApiClient.kt`**: `buildAuthorizationUrl()` (PKCE URL-Builder) + `exchangeCodeForToken()` (Code → Token). - **`frontend/core/auth/src/commonMain/.../LoginViewModel.kt`**: `isOidcLoading`-State, `startOidcFlow()`, `handleOidcCallbackResult()`, JS-Init-Callback-Check. - **`frontend/core/auth/src/commonMain/.../LoginScreen.kt`**: Divider + `OutlinedButton` „Mit Keycloak anmelden" mit Spinner bei laufendem Flow. ### Architektur-Entscheidungen - **Kein `ktor-client-auth`**: Der OIDC-Flow wird manuell implementiert — `ktor-client-auth` unterstĂŒtzt Authorization Code + PKCE nicht nativ fĂŒr KMP. - **Pure Kotlin SHA-256**: Kein `expect/actual` nötig — `kotlin.math` + reine Bitoperationen reichen aus. - **JVM-Callback-Server** auf `localhost:18080`: Standard-Muster fĂŒr Desktop-Apps (RFC 8252 „OAuth 2.0 for Native Apps"). - **JS-Redirect-Flow**: Kein Popup — volle Seitenweiterleitung. Code Verifier wird in `sessionStorage` gespeichert (nur aktueller Tab). - **State-Validierung**: CSRF-Schutz via State-Parameter-Vergleich im ViewModel. ## ✅ Gateway CircuitBreaker (2026-03-09, gleiche Session) Verifikation ergab: Der ursprĂŒngliche `ClassNotFoundException`-Bug ist durch Spring Cloud `2025.0.1` vollstĂ€ndig behoben. ### Befund - **Dependency:** `spring-cloud-starter-circuitbreaker-reactor-resilience4j` — korrekte Reactive-Variante fĂŒr WebFlux/Gateway. - **`GatewayConfig.kt`:** CircuitBreaker korrekt als Route-Filter konfiguriert: ```kotlin circuitBreaker { it.name = "pingServiceCB" it.fallbackUri = URI.create("forward:/fallback/ping") } ``` - **`FallbackController.kt`:** Fallback-Endpunkt `/fallback/ping` implementiert. - **`GatewayMetricsConfig.kt`:** CB-Events werden als Prometheus-Counter (`gateway_circuit_breaker_events_total`) erfasst. - **Resilience4j `2.3.0`:** Kompatibel mit Spring Cloud 2025.0.1 / Spring Boot 3.5.x — kein Versionskonflikt. ### Fazit Kein Code-Change erforderlich. Der Fix war implizit durch das Spring Cloud Downgrade auf `2025.0.1` bereits enthalten. ## ✅ Docker-StabilitĂ€t End-to-End (2026-03-09, gleiche Session) VollstĂ€ndige Analyse aller Docker Compose Dateien (`dc-infra`, `dc-backend`, `dc-ops`, `dc-gui`) sowie `.env` und gemounteter Konfigs. ### Befund & Fixes #### Fix 1: `.env` — `KC_COMMAND` Regression 🔮 - **Problem:** `.env` hatte `KC_COMMAND=start-dev --import-realm` — exakt der Bug vom 2026-03-06 Log. Das pre-built Keycloak-Image startet im falschen Dev-Modus, OIDC-Flow schlĂ€gt fehl. - **Fix:** `KC_COMMAND=start --optimized --import-realm` #### Fix 2: `base-application.yaml` — Valkey Env-Var-Namen 🟠 - **Problem:** `base-application.yaml` (gemountet in allen Backend-Services) nutzte `${SPRING_DATA_REDIS_HOST}` / `${SPRING_DATA_REDIS_PORT}` / `${SPRING_DATA_REDIS_PASSWORD}` als Env-Var-Namen. `dc-backend.yaml` setzt aber nur `SPRING_DATA_VALKEY_*` → Redis-Host fiel auf `localhost`-Default zurĂŒck statt auf den `valkey`-Container. - **Fix:** Env-Var-Namen auf `${SPRING_DATA_VALKEY_HOST}`, `${SPRING_DATA_VALKEY_PORT}`, `${SPRING_DATA_VALKEY_PASSWORD}` umgestellt. Spring Boot Property-Pfad `spring.data.redis.*` bleibt korrekt. #### Fix 3: `gateway/application.yaml` — UngĂŒltiger Property-Namespace 🟠 - **Problem:** Gateway nutzte `spring.data.valkey.*` — kein valider Spring Boot Auto-Konfigurations-Namespace. Spring Boot kennt nur `spring.data.redis.*` fĂŒr die Redis/Lettuce-Autoconfiguration. - **Fix:** `spring.data.valkey` → `spring.data.redis` (Env-Vars `SPRING_DATA_VALKEY_*` bleiben). ### Verifiziert: Korrekte Konfigurationen - **Startup-Reihenfolge:** Postgres → Keycloak → Consul → Valkey → Gateway/Ping (via `depends_on` + Healthchecks) ✅ - **Netzwerk:** Alle Services im `meldestelle-network` ✅ - **Zipkin:** `service_started` (stateless, kein Healthcheck nötig) ✅ - **Consul Healthcheck:** curl auf `/v1/status/leader` ✅ - **Keycloak Healthcheck:** curl auf `localhost:9000/health/ready` ✅ ## 🐛 Bugfix: Keycloak Healthcheck schlĂ€gt fehl obwohl Keycloak lĂ€uft (2026-03-09) ### Symptom `docker compose --profile infra up -d` — Keycloak startet korrekt (Port 8080 + 9000 gebunden, Realm importiert), wird von Docker aber nie als `healthy` markiert. Dieses Problem existierte bereits frĂŒher und fĂŒhrte damals zur Entfernung des Healthchecks. ### Root Cause 1: `/health/ready` vs. `/health/live` Keycloak 26.x fĂŒhrt beim Start den JGroups-Cluster-Join-Prozess aus. Bei Single-Node-Betrieb (kein anderer Knoten im Netz) versucht Keycloak 10× einem Ghost-Node beizutreten — jeder Versuch dauert 2 Sekunden = **20 Sekunden Verzögerung** nach dem eigentlichen Startup. - **`/health/ready`** wartet auf vollstĂ€ndige Cluster-Formation → gibt wĂ€hrend der JOIN-Phase `503` zurĂŒck. - **`/health/live`** prĂŒft nur ob der Prozess lĂ€uft — unabhĂ€ngig vom Cluster-Status. Mit `start_period: 60s` und `interval: 15s` feuerte der erste Check in genau diesem 20s-Fenster → false-negative → nach 10 Retries → `unhealthy`. ### Root Cause 2: `KC_HOSTNAME_STRICT_HTTPS` — deprecated v1-Option `WARNING: Hostname v1 options [hostname-strict-https] are still in use` erscheint in jedem Startup-Log. In Keycloak 26.x wurde das Hostname-System auf v2 umgestellt. `KC_HOSTNAME_STRICT_HTTPS` ist ein v1-Parameter der ignoriert wird — HTTP-Zugriff wird ausschließlich ĂŒber `KC_HTTP_ENABLED` gesteuert. ### Fixes in `dc-infra.yaml` | Parameter | Vorher | Nachher | BegrĂŒndung | |----------------------------|------------------|-----------------|------------------------------------------------------| | `test` | `
/health/ready` | `
/health/live` | Kein false-negative wĂ€hrend JGroups-JOIN | | `start_period` | `"60s"` | `"120s"` | Mehr Puffer fĂŒr langsamen First-Start (Realm-Import) | | `interval` | `"15s"` | `"20s"` | Weniger Stress wĂ€hrend Startup-Phase | | `timeout` | `"5s"` | `"10s"` | Mehr Zeit fĂŒr Endpoint-Antwort | | `retries` | `"10"` (String) | `10` (Integer) | Korrekter YAML-Typ | | `KC_HOSTNAME_STRICT_HTTPS` | gesetzt | **entfernt** | Deprecated v1-Option, erzeugt Warning | ### Gelerntes - **`/health/live` ist fĂŒr Single-Node-Keycloak die richtige Wahl** — `ready` ist fĂŒr Cluster-Szenarien konzipiert wo mehrere Nodes koordiniert hochfahren mĂŒssen. - `retries` in Docker Compose Healthcheck sollte als Integer, nicht als String angegeben werden. ## ✅ Ping Service — Tracer Bullet Analyse & Fix (2026-03-09, gleiche Session) VollstĂ€ndige Analyse des Ping Service Stacks (Backend → Gateway → Frontend). Ergebnis: Die gesamte fachliche Implementierung war bereits vorhanden. Ein einziger kritischer Bug gefunden und behoben. ### Befund: Stack vollstĂ€ndig implementiert - **Backend** (`backend/services/ping/ping-service`): Domain, Application, Persistence, Controller, Security (`at.mocode.infrastructure.security` via `@ComponentScan`), DB-Migrations V1+V2 — vollstĂ€ndig. - **Contracts** (`contracts/ping-api`): `PingResponse`, `EnhancedPingResponse`, `HealthResponse`, `PingEvent`, `PingApi` — vollstĂ€ndig. - **Gateway** (`backend/infrastructure/gateway`): Routing `/api/ping/**` → `stripPrefix(1)` → Ping Service mit CircuitBreaker Fallback — korrekt. - **Frontend** (`frontend/features/ping-feature`): `PingApiKoinClient`, `PingViewModel`, `PingScreen`, `PingSyncService`, Koin DI — vollstĂ€ndig. - **Shell-Integration**: `MainApp.kt` — `AppScreen.Ping` eingebunden, „Ping-Service"-Button auf Home-Screen, Navigation korrekt. ### Fix: `PingApiKoinClient.kt` — Query-Parameter-Mismatch 🔮 - **Problem:** `syncPings()` sendete `?lastSyncTimestamp=...` als Query-Parameter. Backend `@RequestParam` erwartet `?since=...` — Delta-Sync lieferte immer alle Daten. - **Fix:** `url.parameters.append("lastSyncTimestamp", ...)` → `url.parameters.append("since", ...)` ## ✅ Gitea-Pipeline & Runner Analyse (2026-03-09, gleiche Session) ### Befund: Aktiver CI/CD-Kern - `.gitea/workflows/docker-publish.yaml` ist der einzige aktiv ausgefĂŒhrte Workflow — grĂŒn seit 06.03.2026. - Matrix: 4 Services (`keycloak`, `api-gateway`, `ping-service`, `web-app`), `max-parallel: 1` (RAM-Schutz). - Native `linux/arm64`-Builds auf VM 102 (`10.0.0.23`), Push in interne Registry `10.0.0.22:3000`. ### Verifiziert (Screenshot 2026-03-09_12-47) - `insecure-registries: ["10.0.0.22:3000"]` permanent in `/etc/docker/daemon.json` gesetzt ✅ - `act_runner`: `active (running)` seit 06.03.2026, `enabled; preset: enabled` ✅ - `systemctl is-enabled act_runner` → `enabled` ✅ ### Bereinigung `.github/`-Ordner - **Problem:** 6 GitHub Actions Workflows in `.github/workflows/` — auf Gitea nie ausgefĂŒhrt (toter Code). - **ZusĂ€tzlich:** `ci-main.yml`, `deploy-proxmox.yml`, `ssot-guard.yml` referenzierten falschen Pfad `docker/docker-compose.yaml`. - **Fix:** `pr-guard.yml` (prĂŒft hartcodierte Versionen in `build.gradle.kts`) nach `.gitea/workflows/pr-guard.yaml` migriert. - **Fix:** Gesamten `.github/`-Ordner gelöscht. - `youtrack-sync.yml` war nicht aktiv und wurde mitgelöscht — kein Handlungsbedarf. ### Aktive Workflows nach Bereinigung | Datei | Trigger | Zweck | |----------------------------------------|--------------|------------------------------------------------| | `.gitea/workflows/docker-publish.yaml` | Push `main` | Build & Push aller Docker-Images | | `.gitea/workflows/pr-guard.yaml` | Pull Request | PrĂŒft hartcodierte Versionen in Gradle-Dateien | ## Bugfix: Keycloak Healthcheck — Finale Korrektur (Port 8080 → 9000) **Problem:** Keycloak 26.5.5 startet sauber (~15s), wird aber dauerhaft nicht `healthy`. **Root Cause (final):** Der vorherige Fix hatte Port 9000 → 8080 geĂ€ndert — das war **falsch**. | Port | Zweck | Health-Endpoint | |----------|-----------------------------------------|-------------------------------------| | **8080** | Haupt-HTTP (Login, Admin-Console, APIs) | ❌ kennt `/health/*` **nicht** → 404 | | **9000** | Management (Actuator, Health, Metrics) | ✅ `/health/live`, `/health/ready` | `curl http://localhost:8080/health/live` → **404** → `curl -f` schlĂ€gt fehl → `unhealthy`. **Fix `dc-infra.yaml`:** ```yaml test: ["CMD-SHELL", "curl -sf --max-time 5 http://localhost:9000/health/live || exit 1"] interval: "15s" timeout: "10s" retries: 5 start_period: "90s" ``` **Warum `/health/live` statt `/health/ready`?** `/health/ready` wartet auf JGroups-Cluster-Formation (Single-Node: 10×2s JOIN-Timeout = 20s Verzögerung). `/health/live` prĂŒft nur Prozess-Liveness — sofort `UP` sobald Quarkus lĂ€uft. **`start_period: 90s`** — Puffer fĂŒr First-Run mit DB-Schema-Init (Liquibase-Migrations ~15–20s). ## 🔜 NĂ€chste Schritte - **TLS/HTTPS** — Langfristig: `KC_HOSTNAME_STRICT_HTTPS=true` setzen, sobald TLS eingerichtet ist. - **Entries Service** — Beginn der Implementierung des ersten echten Fach-Services ("Nennungen"). --- ## Bugfix: Keycloak Healthcheck — Echter Root Cause (curl nicht vorhanden) **Zeitpunkt:** 2026-03-09 ~15:15 ### Root Cause Das Keycloak-Image `quay.io/keycloak/keycloak:26.5.5` basiert auf **`ubi9-micro`** — einem minimalen Red Hat Image * *ohne `curl` / `wget` / Package Manager**. Alle bisherigen Port-Fixes (8080 → 9000) waren korrekt, aber der `curl`-Befehl scheiterte im Container mit `command not found`. Keycloak selbst lief die ganze Zeit einwandfrei — nur der Healthcheck-Befehl fehlte das nötige Tool. ### Fix | Datei | Änderung | |-----------------|---------------------------------------------| | `dc-infra.yaml` | Healthcheck: `curl` → Bash `/dev/tcp`-Trick | **Neuer Healthcheck (kein externes Tool nötig):** ```yaml test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/9000 && printf 'GET /health/live HTTP/1.0\r\nHost: localhost\r\n\r\n' >&3 && cat <&3 | grep -q '"status":"UP"'"] ``` ### Warum `/dev/tcp` funktioniert - Bash hat `/dev/tcp` eingebaut — verfĂŒgbar ohne Installation - `exec 3<>/dev/tcp/localhost/9000` öffnet TCP-Verbindung auf Port 9000 - `printf ... >&3` sendet einen minimalen HTTP/1.0 GET-Request - `cat <&3 | grep -q '"status":"UP"'` liest Antwort und prĂŒft Health-Status ### Gelernt > Vor jedem `curl`-basierten Healthcheck prĂŒfen: Ist `curl` im Container-Image vorhanden? > Minimale Images (`ubi9-micro`, `distroless`, `alpine` ohne curl-Paket) haben es **nicht**. > Sicherer Standard: `/dev/tcp` (bash built-in) statt `curl` — funktioniert immer wenn bash vorhanden ist. --- ## Bugfix: Keycloak Healthcheck — grep-Pattern Leerzeichen **Zeitpunkt:** 2026-03-09 ~15:27 ### Root Cause Nach dem `/dev/tcp`-Fix war der Healthcheck immer noch `unhealthy` (ExitCode=1). Der grep-Pattern lautete `'"status":"UP"'` (kein Leerzeichen nach `:`), die tatsĂ€chliche JSON-Antwort von Keycloak enthĂ€lt jedoch `"status": "UP"` (mit Leerzeichen nach dem Doppelpunkt). **Beweis via `docker exec`:** ``` HTTP/1.0 200 OK { "status": "UP", "checks": [] } ``` grep nach `"status":"UP"` → kein Match → ExitCode 1 → unhealthy. ### Fix | Datei | Änderung | |-----------------|--------------------------------------------------| | `dc-infra.yaml` | grep-Pattern: `"status":"UP"` → `"status": "UP"` | **Vorher:** ```yaml test: ["CMD-SHELL", "... | grep -q '\"status\":\"UP\"'"] ``` **Nachher:** ```yaml test: ["CMD-SHELL", "... | grep -q '\"status\": \"UP\"'"] ``` ### Ergebnis Nach `docker compose up -d keycloak`: Status **healthy**, 2x ExitCode=0 bestĂ€tigt. ### Gelernt > Den Healthcheck-Befehl immer manuell via `docker exec` testen und die **exakte** API-Antwort > prĂŒfen — JSON-Formatierung (Leerzeichen, EinrĂŒckung) kann je nach Keycloak-Version variieren. > Sicherer wĂ€re `grep -q '"UP"'` ohne den Key, um Formatierungsunterschiede zu vermeiden. --- ## Optimierung: Healthcheck grep-Pattern vereinfacht **Zeitpunkt:** 2026-03-09 ~15:30 ### Maßnahme Das grep-Pattern wurde weiter vereinfacht auf `grep -q '"UP"'`, um robust gegen JSON-FormatierungsĂ€nderungen (Leerzeichen, EinrĂŒckung) in zukĂŒnftigen Keycloak-Versionen zu sein. | Datei | Vorher | Nachher | |-----------------|----------------------------|------------------| | `dc-infra.yaml` | `grep -q '"status": "UP"'` | `grep -q '"UP"'` | ### Ergebnis Status **healthy**, ExitCode=0 bestĂ€tigt. Pattern ist nun versionsunabhĂ€ngig. --- ## Bugfix: Login fehlgeschlagen — `directAccessGrantsEnabled` fĂŒr `frontend-client` **Zeitpunkt:** 2026-03-09 ~15:41 ### Symptom Web-App (`localhost:4000`) zeigte beim direkten Username/Passwort-Login: ``` Login fehlgeschlagen: HTTP 400 - {"error":"unauthorized_client","error_description":"Client not allowed for direct access grants"} ``` ### Root Cause `AuthApiClient.kt` verwendet `AppConstants.KEYCLOAK_CLIENT_ID = "frontend-client"` mit `grant_type=password` (Resource Owner Password Credentials / ROPC-Flow). Der `frontend-client` hatte in `meldestelle-realm.json` jedoch `directAccessGrantsEnabled: false` — bewusst deaktiviert als reiner PKCE-Client, dabei aber vergessen, dass die Web-App ROPC nutzt. ### Fix | Datei | Änderung | |--------------------------|----------------------------------------------------------------| | `meldestelle-realm.json` | `frontend-client`: `directAccessGrantsEnabled: false` → `true` | Live-Update via Admin REST API (kein Neustart nötig): ```bash # Token holen TOKEN=$(curl -s -X POST "http://localhost:8180/realms/master/protocol/openid-connect/token" \ -d "username=kc-admin&password=kc-password&grant_type=password&client_id=admin-cli" ...) # Client-UUID ermitteln & PUT mit directAccessGrantsEnabled:true curl -X PUT "http://localhost:8180/admin/realms/meldestelle/clients/$UUID" \ -H "Authorization: Bearer $TOKEN" -d "$UPDATED_CLIENT_JSON" # → HTTP 204 ``` ### Verifikation ```bash curl -X POST ".../realms/meldestelle/protocol/openid-connect/token" \ -d "username=admin&password=Admin%231234&grant_type=password&client_id=frontend-client" # → "token_type":"Bearer" ✅ ``` ### Gelernt > `--import-realm` in Keycloak importiert Realms **nur beim ersten Start** (wenn Realm noch nicht existiert). > Änderungen an der realm.json nach dem ersten Import mĂŒssen entweder via Admin REST API oder > durch Löschen des Volumes (Datenverlust!) neu eingespielt werden. > FĂŒr Dev-Umgebungen: `KC_COMMAND=start --optimized --import-realm` mit separatem Reset-Script > oder Admin-API-Skript fĂŒr KonfigurationsĂ€nderungen vorbereiten.