131 lines
4.9 KiB
Markdown
131 lines
4.9 KiB
Markdown
# Betrieb hinter dem Reverse Proxy (OPNsense)
|
||
|
||
Aufbau: Browser → HTTPS → nginx auf der OPNsense → HTTP → Port 8080 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://…:8080`, 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.
|