diff --git a/README.md b/README.md index 236f6c4f..7e481e35 100644 --- a/README.md +++ b/README.md @@ -839,15 +839,24 @@ docker-compose logs -f app - **App**: Backend + Frontend in einem Container - **Caddy**: Reverse-Proxy mit automatischem SSL -**Umgebungsvariablen (`docker/.env`):** +> **Wann dieser Stack?** Wenn du **keinen** eigenen Reverse-Proxy betreibst – +> Caddy holt und erneuert das SSL-Zertifikat hier selbst. Hast du bereits einen +> Proxy davor (nginx, Nginx Proxy Manager, Traefik), nimm den Stack im +> Projektstamm; er bringt bewusst keinen eigenen Proxy mit. + +**Umgebungsvariablen (`docker/.env`):** – vollständige Vorlage mit Erklärungen +in `docker/.env.example` + ```env -MYSQL_ROOT_PASSWORD=sicheres-root-passwort -MYSQL_DATABASE=opencrm -MYSQL_USER=opencrm -MYSQL_PASSWORD=sicheres-passwort +DB_ROOT_PASSWORD=sicheres-root-passwort +DB_NAME=opencrm +DB_USER=opencrm +DB_PASSWORD=sicheres-passwort JWT_SECRET=sehr-langer-zufaelliger-string ENCRYPTION_KEY=64-zeichen-hex-string DOMAIN=crm.example.com +HTTPS_ENABLED=true # Caddy terminiert TLS → Secure-Cookies an +AUDIT_HMAC_KEY= # Siegel fürs Audit-Log, siehe oben RUN_SEED=true # Nur beim ersten Start ``` diff --git a/docker/.env.example b/docker/.env.example index 87ea08ca..77342287 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -7,6 +7,11 @@ DOMAIN=localhost # Database +# HINWEIS: In diesem Stack wird die Verbindungs-URL direkt aus diesen Werten +# zusammengesetzt. Sonderzeichen im Passwort ($ ! # @ : / ?) koennen die URL +# zerlegen und den Start verhindern. Nutze hier ein langes Passwort aus +# Buchstaben und Ziffern - oder den Stack im Projektstamm, dessen Entrypoint +# die Zeichen korrekt kodiert. DB_ROOT_PASSWORD=change-this-root-password DB_NAME=opencrm DB_USER=opencrm @@ -24,6 +29,22 @@ JWT_REFRESH_EXPIRES_IN=7d # Generate with: openssl rand -hex 32 ENCRYPTION_KEY=change-this-to-a-32-byte-hex-key +# TLS: In diesem Stack terminiert Caddy das HTTPS. Das Flag setzt das +# Secure-Attribut am Refresh-Cookie, trust proxy = 1 Hop und +# upgrade-insecure-requests in der CSP. +# Nur auf false setzen, wenn der Stack bewusst ohne TLS laeuft (reiner Test). +HTTPS_ENABLED=true + +# Nur noetig, wenn das Frontend unter einer ANDEREN Domain laeuft als das +# Backend. Beim Standard-Setup (alles hinter derselben Caddy-Domain) leer +# lassen. +CORS_ORIGINS= + +# Cloud-Deploy: blockt zusaetzlich private IP-Ranges, damit ein eingeloggter +# Admin ueber die Provider-/SMTP-Testverbindung keine internen Dienste +# anpingen kann. On-Prem meist false, weil Mailserver oft lokal laufen. +SSRF_BLOCK_PRIVATE_IPS=false + # First Install: Set to "true" to seed database on first startup # After first successful start, set back to "false" RUN_SEED=true diff --git a/docker/Caddyfile b/docker/Caddyfile index a6cf415c..22e31ce2 100644 --- a/docker/Caddyfile +++ b/docker/Caddyfile @@ -24,8 +24,13 @@ Referrer-Policy "strict-origin-when-cross-origin" } - # Gzip compression - encode gzip + # Gzip compression – NUR fuer statische Assets, NICHT fuer /api/*. + # Komprimierte API-Antworten ermoeglichen die BREACH-Attacke + # (CVE-2013-3587): aus der Antwortgroesse liessen sich unter bestimmten + # Bedingungen Geheimnisse aus JSON-Responses ableiten. Statische Dateien + # sind davon nicht betroffen und bleiben komprimiert. + @nicht_api not path /api/* + encode @nicht_api gzip # Handle file uploads (increase body limit) request_body { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 24bc6f34..7a2a2c30 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -38,8 +38,24 @@ services: PORT: 3001 DATABASE_URL: mysql://${DB_USER:-opencrm}:${DB_PASSWORD:-opencrm123}@db:3306/${DB_NAME:-opencrm} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET is required} - JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-7d} + # Access-Token kurzlebig (lebt nur im Browser-Speicher → XSS-Fenster + # klein halten), Refresh-Token lang im httpOnly-Cookie. + # Frueher stand hier 7d fuer den ACCESS-Token – das war ein Ueberbleibsel + # aus der Zeit vor dem Access-/Refresh-Pattern. + JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-15m} + JWT_REFRESH_EXPIRES_IN: ${JWT_REFRESH_EXPIRES_IN:-7d} ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required} + # Caddy terminiert TLS und steht als EIN Proxy davor. Das Flag setzt das + # Secure-Attribut am Refresh-Cookie, trust proxy = 1 Hop und + # upgrade-insecure-requests in der CSP. Nur auf false setzen, wenn der + # Stack bewusst ohne TLS laeuft (reiner Test ueber http://). + HTTPS_ENABLED: ${HTTPS_ENABLED:-true} + # Nur noetig, wenn das Frontend unter einer anderen Origin laeuft. + CORS_ORIGINS: ${CORS_ORIGINS:-} + # In der Container-Umgebung muss der Dienst fuer Caddy erreichbar sein. + LISTEN_ADDR: ${LISTEN_ADDR:-0.0.0.0} + # Cloud-Deploy: private IP-Ranges fuer Provider-/SMTP-Tests blocken. + SSRF_BLOCK_PRIVATE_IPS: ${SSRF_BLOCK_PRIVATE_IPS:-false} # Siegel für das Audit-Log (optional, fail-safe ohne Wert) AUDIT_HMAC_KEY: ${AUDIT_HMAC_KEY:-} AUDIT_HMAC_KEY_OLD: ${AUDIT_HMAC_KEY_OLD:-}