Files
einkaufsapp/docs/reverse-proxy.md
2026-08-08 20:31:58 +02:00

131 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Betrieb hinter dem Reverse Proxy (OPNsense)
Aufbau: Browser → HTTPS → nginx auf der OPNsense → HTTP → Port 46600 auf dem
Docker-Host → nginx im `web`-Container → `api`.
## Drei Werte in der `.env`
```
PUBLIC_BASE_URL=https://einkauf.example.de
COOKIE_SECURE=true
FORWARDED_ALLOW_IPS=<IP des Docker-Hosts oder *>
```
Nach jeder Änderung `docker compose up -d`, nicht `restart` ein Neustart
übernimmt keine geänderten Umgebungswerte.
**`PUBLIC_BASE_URL`** landet in jeder Verifikations-, Einladungs- und
Passwort-Reset-Mail. Steht dort noch `http://…:46600`, führen die Links ins
Leere, sobald jemand von außen klickt.
**`COOKIE_SECURE=true`** ist ab jetzt Pflicht, aber auch erst ab jetzt möglich:
Ein `Secure`-Cookie über reines HTTP wird vom Browser verworfen, und die
Anmeldung scheitert dann ohne verwertbare Meldung. Beides muss gleichzeitig
umgestellt werden.
## Was die OPNsense weiterreichen muss
Im nginx-Plugin unter *Http(s) → Location* für den Upstream:
```
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
```
`X-Forwarded-Proto` ist der wichtigste Wert: Ohne ihn hält die Anwendung die
Verbindung für unverschlüsselt.
Zusätzlich für Server-Sent Events (ab Phase 4):
```
proxy_buffering off;
proxy_read_timeout 3600s;
```
Ohne `proxy_buffering off` sammelt nginx die Ereignisse und gibt sie erst
gebündelt weiter die Liste aktualisiert sich dann mit Verzögerung oder gar
nicht.
## Client-IP fürs Rate Limiting
Mit zwei Proxys davor sieht die Anwendung ohne Weiterreichung nur die IP des
`web`-Containers. Dann würde eine einzige IP das Limit für alle auslösen.
`X-Forwarded-For` wird durch beide Stufen durchgereicht; uvicorn wertet es
wegen `--proxy-headers` aus.
`FORWARDED_ALLOW_IPS=*` ist vertretbar, solange der `api`-Container keinen
veröffentlichten Port hat dann kann den Header nur der `web`-Container
setzen. Sobald du den API-Port für Debugging freigibst, gehört dort die
konkrete IP hinein.
## PWA-Installation
Erst über HTTPS bietet der Browser „Zur Startseite hinzufügen" an und lässt den
Service Worker zu. Bei iOS ist die Installation zusätzlich Voraussetzung dafür,
dass Web Push in Phase 8 überhaupt funktioniert im normalen Safari-Tab gibt
es keine Benachrichtigungen.
## Prüfen
```bash
curl -sI https://einkauf.example.de/ | grep -i strict-transport
curl -s https://einkauf.example.de/readyz
```
Und nach der Anmeldung im Browser unter *Entwicklerwerkzeuge → Anwendung →
Cookies*: `ea_session` muss `Secure` und `HttpOnly` tragen, `ea_csrf` nur
`Secure`.
## Kopfzeilen: was aus dem Protokoll hervorgeht
Eine Auswertung der tatsächlich ausgelieferten Antworten hat drei Punkte
ergeben, die auf der OPNsense-Seite liegen.
### `X-XSS-Protection: 1` entfernen
Die OPNsense setzt diese Kopfzeile. Sie ist überholt: Chrome hat den
zugehörigen Filter entfernt, und in den Browsern, die ihn noch kennen, kann er
in Einzelfällen selbst Lücken aufreißen, indem er Teile der Seite unterdrückt.
Der Wert `1` ohne `mode=block` ist dabei die ungünstigste Variante.
Empfehlung: im nginx-Plugin abschalten oder auf `0` setzen. Der Schutz kommt
von der Content-Security-Policy, nicht von dieser Kopfzeile.
### HSTS: `preload` passt nicht zur Laufzeit
```
strict-transport-security: max-age=15768000; includeSubDomains; preload
```
Das sind gut sechs Monate. Für die Aufnahme in die Preload-Liste verlangen die
Browserhersteller mindestens ein Jahr (`31536000`); mit dem jetzigen Wert wird
das `preload` schlicht ignoriert.
Entweder auf `31536000` erhöhen dann aber im Bewusstsein, dass ein Eintrag in
der Preload-Liste die Domain für alle Unterdomains dauerhaft auf HTTPS
festlegt und sich nur mit Monaten Vorlauf rückgängig machen lässt oder das
`preload` weglassen.
### Doppelte Kopfzeilen
`X-Content-Type-Options`, `X-Frame-Options` und `Permissions-Policy` kommen
jeweils zweimal an: einmal vom `web`-Container, einmal von der OPNsense. Das
ist unschädlich, aber unnötig.
Wichtiger ist, was daraus folgt: **Eine doppelte
`Content-Security-Policy` wäre nicht unschädlich.** Browser wenden dann beide
an und lassen nur zu, was in *beiden* erlaubt ist. Eine zusätzliche CSP auf der
OPNsense könnte die Anwendung also lahmlegen, ohne dass eine Fehlermeldung
darauf hinweist sichtbar wäre nur, dass Module oder Stile nicht laden.
Derzeit setzt die OPNsense keine, das sollte so bleiben.
Die OPNsense überschreibt außerdem `Referrer-Policy` mit `same-origin` statt
des strengeren `no-referrer` aus dem Container. Unkritisch, solange die
Anwendung nicht nach außen verlinkt für die öffentlichen Listenlinks unter
`/s/<token>` bedeutet es, dass der Token nur an die eigene Herkunft übertragen
würde. Wer es strenger mag, stellt die OPNsense auf `no-referrer` um.