security: Dashboard-Admin-Routen per Interims-Gate auf Heimnetz begrenzt (Befund o)

server.py (auf dem Pi, nicht im Repo): /api/ban, /api/unban,
/api/toggle-registration, /api/create-invite, /api/run-stats nur noch fuer
Heimnetz-/localhost-Socket-IPs ohne Cloudflare-Header; sonst 403 mit
Erklaerung. Headless verifiziert (9 Pruefungen). Bernds A/B-Entscheidung
fuer den Fernzugriff bleibt offen (docs/DASHBOARD_AUTH_HARDENING.md).
Zusatzbefund: Repo privat schalten wuerde In-App-Updater brechen (ROADMAP).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernd Steckmeister
2026-07-04 09:17:56 +02:00
parent e97a748b35
commit 455742d1bf
4 changed files with 129 additions and 29 deletions
+46 -17
View File
@@ -1,13 +1,39 @@
# Dashboard-Server absichern: Admin-Endpoints haben KEINE Authentifizierung
**Status:** KRITISCHER Befund (Fable-5-Review, 2026-07-04), NICHT umgesetzt
braucht Bernds Entscheidung, weil jede Absicherung sein Browser-Dashboard
verändert (er muss künftig einen Token eingeben oder Cloudflare Access nutzen).
Bewusst nicht blind auf dem Pi „gefixt", weil ein falscher Griff Bernd aus seinem
eigenen Admin-Panel aussperren würde (kein Kanal, ihm den neuen Token mitten in
der Nacht mitzuteilen). Analog zu `docs/LIVEKIT_TOKEN_MIGRATION.md`: fertiger,
gegen den echten Code geschriebener Plan, damit die Umsetzung nach Bernds Go
sofort starten kann.
**Status (aktualisiert 2026-07-04): Das akute Loch ist mit einem INTERIMS-GATE
geschlossen („Weg C", siehe unten)** die fünf Admin-Endpoints akzeptieren nur
noch Anfragen aus dem Heimnetz/localhost; alles, was über den Cloudflare-Tunnel
kommt, bekommt 403 mit einer Erklärung (die das Dashboard per `alert()` anzeigt).
Bernds Dashboard funktioniert im Heimnetz unverändert unter
`http://192.168.178.71:8080/stats.html`; die öffentliche URL zeigt weiterhin die
Ansicht, nur die Admin-Buttons sind dort gesperrt. **Offen bleibt Bernds
Entscheidung Weg A (Cloudflare Access, empfohlen) oder Weg B (DASH_TOKEN), falls
er die Admin-Buttons auch von unterwegs braucht** das Gate ersetzt die
Entscheidung nicht, es stopft nur das Loch, solange sie aussteht.
## Interims-Gate („Weg C", aktiv seit 2026-07-04)
In `server.py` (Konstante `ADMIN_PATHS` + Helfer `lan_denied_reason`): eine
Admin-POST-Anfrage wird nur angenommen, wenn (1) **kein** `Cf-Connecting-Ip`/
`Cf-Ray`-Header vorhanden ist (= sie kam nicht durch den Cloudflare-Tunnel) und
(2) die **Socket-Absender-IP** (nicht spoofbar) `127.0.0.1`/`::1`/`192.168.*`
ist. Abgelehnte Versuche landen größenbegrenzt (5 MB-Deckel) in
`security_alerts.log` und damit in der Dashboard-Anzeige. Headless verifiziert:
öffentliche URL → 403 (ban/toggle), Heimnetz-IP und localhost → App-Validierung
erreicht (400 bei leerem Nutzer), gespoofter Cf-Header aus dem LAN → 403,
`GET /` öffentlich → 200, `/api/livekit-token` (401 bei ungültigem Token) und
`/api/e2ee-diagnostics` (403 bei falschem Token) unverändert öffentlich
erreichbar (die App braucht sie, eigene Auth vorhanden).
**Bekannte Restrisiken des Interims-Gates (Gründe, warum Weg A trotzdem kommen
sollte):**
- Die **öffentliche Ansicht bleibt offen** und zeigt Nutzerliste + Präsenz
(Informations-Preisgabe). Weg A würde auch das schließen.
- **CSRF aus dem Heimnetz:** Eine bösartige Webseite in Bernds Browser könnte
theoretisch POSTs an `http://192.168.178.71:8080` schicken (moderne
Chrome-Versionen blocken Public→Private-Requests, Firefox nicht vollständig).
Erst ein Token/Access-Login (Weg A/B) schließt das sauber.
- Wer **im Heimnetz** ist, kann weiterhin ohne Anmeldung administrieren.
## Problem (belegt, nicht vermutet)
@@ -132,13 +158,16 @@ zu zeigen; dann als Bernd einloggen und alle Buttons testen. Kein Code-Deploy.
(Registrierung toggeln + sofort zurück, Stats aktualisieren; Ban/Unban an
einem **Test-Nutzer**, nicht an Uta).
## Warum jetzt nur der Plan (nicht sofort umgesetzt)
## Warum erst nur der Plan kam und dann doch das Interims-Gate
Jede Absicherung macht die Dashboard-Buttons vorübergehend funktionslos, bis
Bernd den neuen Token eingibt (Option B) bzw. die Access-App eingerichtet ist
(Option A). Wird das mitten in einer autonomen Nacht-Session deployt, steht Bernd
ohne Vorwarnung vor einem toten Admin-Panel und ohne Möglichkeit, spontan
Spam-Accounts zu bannen das verletzt die CLAUDE.md-Leitplanke „kein Aussperren".
Der Befund ist reversibel und nicht destruktiv (er macht nur restriktiver), aber
das *Wie* der neuen Anmeldung ist eine echte Richtungsentscheidung → Bernd wählt
A oder B, dann sofortige Umsetzung + headless-Verifikation.
Eine Token-/Access-Absicherung (A/B) macht die Dashboard-Buttons überall
funktionslos, bis Bernd aktiv etwas tut (Token eingeben bzw. Access-App
einrichten) das mitten in einer autonomen Session zu deployen verletzt die
CLAUDE.md-Leitplanke „kein Aussperren". Das *Wie* der Anmeldung bleibt deshalb
Bernds Richtungsentscheidung. Das später ergänzte Interims-Gate umgeht dieses
Dilemma: Es braucht keinerlei Aktion von Bernd (Heimnetz-Nutzung läuft
unverändert weiter, inkl. spontanem Spam-Bann von zu Hause), sperrt aber
Fremde aus dem Internet sofort aus. Einzige Einschränkung: Admin-Buttons über
die öffentliche URL gehen bis zur A/B-Entscheidung nicht die 403-Meldung im
Dashboard erklärt das und nennt die Heimnetz-URL. Rückbau = die Gate-Zeilen in
`server.py` entfernen (Backup: `server.py.bak-20260704-dashgate`).