HTTPS-Zertifikate + Custom-CA-Trust fuer Backend, Frontend und Keycloak #290

Open
opened 2026-07-10 11:17:30 +02:00 by niboer · 0 comments
Owner

Beschreibung

HTTPS-Zertifikate fuer alle drei Dienste (Backend, Frontend, Keycloak) bereitstellen — sowohl fuer lokale Entwicklung als auch Produktion. Eigene CA als Trust-Anchor hinzufuegbar (z. B. fuer self-signed Certs in der Entwicklung). Backend hat bereits SSL_KEY_PATH/SSL_CERT_PATH-Variablen und HTTP/2-Autodetektion (app.ts), aber dieser Code ist ungetestet, und es fehlt CA-Trust fuer ausgehende fetch()-Aufrufe (Keycloak-API, JWKS-Abruf, Health-Check).

Motivation / Anwendungsfall

Aktuell laeuft alles auf HTTP — auch serviceuebergreifend. Sobald das Projekt produktiv oder hinter Cloudflare betrieben wird, wird HTTPS benoetigt:

  1. Cloudflare Worker → Backend: Worker fetch() nutzt automatisch HTTP/2, wenn der Backend-Server HTTP/2 spricht. Ohne HTTPS am Backend faellt die Verbindung auf HTTP/1.1 zurueck — schlechtere Latenz, kein Multiplexing.
  2. Self-hosted Deployment: Ohne Cloudflare muessen Frontend + Backend + Keycloak selbst HTTPS sprechen, besonders fuer Cookies (secure: true) und moderne Browser-Features.
  3. Entwicklung: Lokale HTTPS-Entwicklung mit self-signed Certs und eigener CA, um die Produktionskonfiguration zu spiegeln.
  4. Sicherheit: Zertifikats-Pinning und CA-Trust verhindern Man-in-the-Middle auf interner Kommunikation.

Vorgeschlagene Umsetzung

Phase 1: Dev-CA + Zertifikats-Generierung

  • scripts/generate-dev-certs.mjs — Node.js-Script (plattformunabhaengig, nutzt node-forge).
  • Generiert: Root CA + Server-Certs fuer Backend, Frontend, Keycloak.
  • SAN-Eintraege: localhost, *.integration.local.
  • Output: dev/certs/{ca.crt,backend.{key,crt},frontend.{key,crt},keycloak.{key,crt}}.
  • dev/certs/ in .gitignore (bei Bedarf ca.crt ausnehmen fuer CI).

Phase 2: Backend — CA-Trust fuer ausgehende Verbindungen

  • backend/src/app.ts: SSL_CA_PATH auswerten.
    • Wenn gesetzt: CA-Zertifikat lesen, via undici.setGlobalDispatcher() als Trust-Anchor registrieren.
    • Dadurch vertrauen alle fetch()-Aufrufe (Keycloak-Admin-API, JWKS via jose, Health-Check) der eigenen CA.
    • Ohne SSL_CA_PATH / ohne Datei: keine Aenderung.
  • backend/src/__tests__/ssl.test.ts (NEU):
    • vi.mock('node:fs') — testet HTTPS-Aktivierung und CA-Trust ohne echte Dateien.
    • Test 1: Ohne Zertifikate → http2 und https sind nicht gesetzt.
    • Test 2: Mit Zertifikaten → http2: true und https.key/https.cert vorhanden.
    • Test 3: SSL_CA_PATH → prueft undici.setGlobalDispatcher-Aufruf mit korrektem CA-Buffer.

Phase 3: Frontend — HTTPS+HTTP/2-Einstiegspunkt

  • frontend/server.ts (NEU): Custom Entrypoint fuer adapter-node.
    • Gleiches Muster wie Backend: SSL_KEY_PATH/SSL_CERT_PATH pruefen.
    • Mit Certs: http2.createSecureServer({ key, cert }, handler).
    • Ohne Certs: http.createServer(handler) — Fallback (bisheriges build/index.js).
  • frontend/Dockerfile: CMD von node build/index.js auf node server.ts aendern.
  • frontend/src/hooks.server.ts: Cookie secure-Flag konfigurierbar via COOKIE_SECURE-Env-Var.

Phase 4: Keycloak — HTTPS-Production-Mode

  • docker-compose.yml:
    • Wenn Certs gemountet: command: ["start"] + KC_HTTPS_CERTIFICATE_FILE, KC_HTTPS_KEY_FILE, KC_HOSTNAME_STRICT_HTTPS: "true".
    • Ohne Certs: command: ["start-dev"] (keine Aenderung, kein Breaking Change).
    • Umschaltung via Docker-Entrypoint-Script, das Dateiexistenz prueft.

Phase 5: Docker-Compose-Verdrahtung

  • Volumes: ./dev/certs:/certs:ro an alle Dienste.
  • Env-Vars pro Service (Default-Pfade):
    • backend-api: SSL_KEY_PATH=./certs/backend.key, SSL_CERT_PATH=./certs/backend.crt, SSL_CA_PATH=./certs/ca.crt.
    • frontend: SSL_KEY_PATH=./certs/frontend.key, SSL_CERT_PATH=./certs/frontend.crt, COOKIE_SECURE=true.
    • keycloak: KC_HTTPS_CERTIFICATE_FILE=/certs/keycloak.crt, KC_HTTPS_KEY_FILE=/certs/keycloak.key.
  • Alle Env-Vars haben Defaults, die bei fehlenden Certs auf HTTP-Fallback fallen.

Phase 6: Tests + Dokumentation

  • frontend/src/__tests__/server.test.ts (NEU): HTTPS-Erkennung + Fallback-Tests.
  • .env.example: Neue SSL-Vars dokumentieren.
  • backend/AGENTS.md: SSL-Vars-Tabelle um SSL_CA_PATH ergaenzen.
  • frontend/AGENTS.md: COOKIE_SECURE dokumentieren.
  • docs/design/01-systemarchitektur.md: HTTPS-Architektur auf Ist-Zustand aktualisieren.

Checkliste

  • scripts/generate-dev-certs.mjs erstellt
  • backend/src/app.ts: SSL_CA_PATH + undici.setGlobalDispatcher implementiert
  • backend/src/__tests__/ssl.test.ts: 3 Testfaelle (ohne/mit Certs, CA-Trust)
  • frontend/server.ts: HTTP/2-Entrypoint fuer adapter-node
  • frontend/Dockerfile: CMD auf server.ts
  • frontend/src/hooks.server.ts: COOKIE_SECURE-Env-Var
  • frontend/src/__tests__/server.test.ts: HTTPS-Fallback-Tests
  • docker-compose.yml: Volumes + Env-Vars fuer alle Dienste
  • Keycloak-Entrypoint-Umschaltung (start vs start-dev)
  • .env.example: Neue SSL-Vars dokumentiert
  • backend/AGENTS.md, frontend/AGENTS.md, docs/design/01-systemarchitektur.md aktualisiert
### Beschreibung HTTPS-Zertifikate fuer alle drei Dienste (Backend, Frontend, Keycloak) bereitstellen — sowohl fuer lokale Entwicklung als auch Produktion. Eigene CA als Trust-Anchor hinzufuegbar (z. B. fuer self-signed Certs in der Entwicklung). Backend hat bereits `SSL_KEY_PATH`/`SSL_CERT_PATH`-Variablen und HTTP/2-Autodetektion (`app.ts`), aber dieser Code ist ungetestet, und es fehlt CA-Trust fuer ausgehende `fetch()`-Aufrufe (Keycloak-API, JWKS-Abruf, Health-Check). ### Motivation / Anwendungsfall Aktuell laeuft alles auf HTTP — auch serviceuebergreifend. Sobald das Projekt produktiv oder hinter Cloudflare betrieben wird, wird HTTPS benoetigt: 1. **Cloudflare Worker → Backend**: Worker `fetch()` nutzt automatisch HTTP/2, **wenn der Backend-Server HTTP/2 spricht**. Ohne HTTPS am Backend faellt die Verbindung auf HTTP/1.1 zurueck — schlechtere Latenz, kein Multiplexing. 2. **Self-hosted Deployment**: Ohne Cloudflare muessen Frontend + Backend + Keycloak selbst HTTPS sprechen, besonders fuer Cookies (`secure: true`) und moderne Browser-Features. 3. **Entwicklung**: Lokale HTTPS-Entwicklung mit self-signed Certs und eigener CA, um die Produktionskonfiguration zu spiegeln. 4. **Sicherheit**: Zertifikats-Pinning und CA-Trust verhindern Man-in-the-Middle auf interner Kommunikation. ### Vorgeschlagene Umsetzung #### Phase 1: Dev-CA + Zertifikats-Generierung - **`scripts/generate-dev-certs.mjs`** — Node.js-Script (plattformunabhaengig, nutzt `node-forge`). - Generiert: Root CA + Server-Certs fuer Backend, Frontend, Keycloak. - SAN-Eintraege: `localhost`, `*.integration.local`. - Output: `dev/certs/{ca.crt,backend.{key,crt},frontend.{key,crt},keycloak.{key,crt}}`. - `dev/certs/` in `.gitignore` (bei Bedarf `ca.crt` ausnehmen fuer CI). #### Phase 2: Backend — CA-Trust fuer ausgehende Verbindungen - **`backend/src/app.ts`**: `SSL_CA_PATH` auswerten. - Wenn gesetzt: CA-Zertifikat lesen, via `undici.setGlobalDispatcher()` als Trust-Anchor registrieren. - Dadurch vertrauen alle `fetch()`-Aufrufe (Keycloak-Admin-API, JWKS via `jose`, Health-Check) der eigenen CA. - Ohne `SSL_CA_PATH` / ohne Datei: keine Aenderung. - **`backend/src/__tests__/ssl.test.ts`** (NEU): - `vi.mock('node:fs')` — testet HTTPS-Aktivierung und CA-Trust ohne echte Dateien. - Test 1: Ohne Zertifikate → `http2` und `https` sind nicht gesetzt. - Test 2: Mit Zertifikaten → `http2: true` und `https.key`/`https.cert` vorhanden. - Test 3: `SSL_CA_PATH` → prueft `undici.setGlobalDispatcher`-Aufruf mit korrektem CA-Buffer. #### Phase 3: Frontend — HTTPS+HTTP/2-Einstiegspunkt - **`frontend/server.ts`** (NEU): Custom Entrypoint fuer `adapter-node`. - Gleiches Muster wie Backend: `SSL_KEY_PATH`/`SSL_CERT_PATH` pruefen. - Mit Certs: `http2.createSecureServer({ key, cert }, handler)`. - Ohne Certs: `http.createServer(handler)` — Fallback (bisheriges `build/index.js`). - **`frontend/Dockerfile`**: `CMD` von `node build/index.js` auf `node server.ts` aendern. - **`frontend/src/hooks.server.ts`**: Cookie `secure`-Flag konfigurierbar via `COOKIE_SECURE`-Env-Var. #### Phase 4: Keycloak — HTTPS-Production-Mode - **`docker-compose.yml`**: - Wenn Certs gemountet: `command: ["start"]` + `KC_HTTPS_CERTIFICATE_FILE`, `KC_HTTPS_KEY_FILE`, `KC_HOSTNAME_STRICT_HTTPS: "true"`. - Ohne Certs: `command: ["start-dev"]` (keine Aenderung, kein Breaking Change). - Umschaltung via Docker-Entrypoint-Script, das Dateiexistenz prueft. #### Phase 5: Docker-Compose-Verdrahtung - Volumes: `./dev/certs:/certs:ro` an alle Dienste. - Env-Vars pro Service (Default-Pfade): - `backend-api`: `SSL_KEY_PATH=./certs/backend.key`, `SSL_CERT_PATH=./certs/backend.crt`, `SSL_CA_PATH=./certs/ca.crt`. - `frontend`: `SSL_KEY_PATH=./certs/frontend.key`, `SSL_CERT_PATH=./certs/frontend.crt`, `COOKIE_SECURE=true`. - `keycloak`: `KC_HTTPS_CERTIFICATE_FILE=/certs/keycloak.crt`, `KC_HTTPS_KEY_FILE=/certs/keycloak.key`. - Alle Env-Vars haben Defaults, die bei fehlenden Certs auf HTTP-Fallback fallen. #### Phase 6: Tests + Dokumentation - **`frontend/src/__tests__/server.test.ts`** (NEU): HTTPS-Erkennung + Fallback-Tests. - **`.env.example`**: Neue SSL-Vars dokumentieren. - **`backend/AGENTS.md`**: SSL-Vars-Tabelle um `SSL_CA_PATH` ergaenzen. - **`frontend/AGENTS.md`**: `COOKIE_SECURE` dokumentieren. - **`docs/design/01-systemarchitektur.md`**: HTTPS-Architektur auf Ist-Zustand aktualisieren. ### Checkliste - [ ] `scripts/generate-dev-certs.mjs` erstellt - [ ] `backend/src/app.ts`: `SSL_CA_PATH` + `undici.setGlobalDispatcher` implementiert - [ ] `backend/src/__tests__/ssl.test.ts`: 3 Testfaelle (ohne/mit Certs, CA-Trust) - [ ] `frontend/server.ts`: HTTP/2-Entrypoint fuer adapter-node - [ ] `frontend/Dockerfile`: CMD auf server.ts - [ ] `frontend/src/hooks.server.ts`: `COOKIE_SECURE`-Env-Var - [ ] `frontend/src/__tests__/server.test.ts`: HTTPS-Fallback-Tests - [ ] `docker-compose.yml`: Volumes + Env-Vars fuer alle Dienste - [ ] Keycloak-Entrypoint-Umschaltung (start vs start-dev) - [ ] `.env.example`: Neue SSL-Vars dokumentiert - [ ] `backend/AGENTS.md`, `frontend/AGENTS.md`, `docs/design/01-systemarchitektur.md` aktualisiert
Sign in to join this conversation.
No description provided.