diff --git a/docs/RECENT_FIXES_2026_03.md b/docs/RECENT_FIXES_2026_03.md new file mode 100644 index 0000000..d3ba35c --- /dev/null +++ b/docs/RECENT_FIXES_2026_03.md @@ -0,0 +1,250 @@ +# Kritische Fixes - März 2026 + +Diese Datei dokumentiert wichtige Bugfixes und Verbesserungen, die seit März 2026 implementiert wurden. + +## 1. Traefik Network Routing Fix ✅ + +**Problem:** User-Container wurden nicht im `web`-Netzwerk registriert, obwohl Labels vorhanden waren. + +**Ursache:** Docker SDK Parameter `network=` funktioniert nicht korrekt bei `containers.run()`. + +**Lösung:** Explizite Netzwerk-Verbindung nach Container-Start mit `network.connect()`. + +```python +# Vorher (funktioniert nicht): +container = client.containers.run(..., network=Config.TRAEFIK_NETWORK) + +# Nachher (funktioniert): +container = client.containers.run(...) # Ohne network-Parameter +network = client.networks.get(Config.TRAEFIK_NETWORK) +network.connect(container) +``` + +**Commits:** +- `65a2a6e`: Connect containers to Traefik network using network.connect() + +**Betroffen:** `container_manager.py` - Beide `spawn_container()` und `spawn_multi_container()` + +--- + +## 2. Traefik Router Service-Labels Fix ✅ + +**Problem:** Traefik erkannte neue Container-Routes nicht, obwohl Labels korrekt waren. + +**Ursache:** Router-Labels fehlte die Referenz zum Service. Traefik wusste nicht, zu welchem Service die Route führen soll. + +**Lösung:** Router-Labels mit `.service` Claim hinzufügen, die auf den Service zeigen. + +```python +# Vorher (unvollständig): +'traefik.http.routers.user1-template-dict.rule': '...', +'traefik.http.services.user1-template-dict.loadbalancer.server.port': '8080' + +# Nachher (komplett): +'traefik.http.routers.user1-template-dict.rule': '...', +'traefik.http.routers.user1-template-dict.service': 'user1-template-dict', # ← Wichtig! +'traefik.http.services.user1-template-dict.loadbalancer.server.port': '8080' +``` + +**Commits:** +- `45bd329`: Add missing router.service labels + +**Betroffen:** `container_manager.py` - Labels-Konfiguration + +--- + +## 3. Cookie-basierte JWT-Authentication ✅ + +**Problem:** User-Container waren öffentlich zugänglich - jeder konnte URL nutzen ohne Login. + +**Lösung:** JWT-Token als HttpOnly Cookie, validiert in jedem Container. + +**Implementierung:** + +### Backend (api.py) +- JWT-Token wird als HttpOnly Cookie nach Login gespeichert +- Cookie wird automatisch bei jedem Request mitgesendet +- Logout löscht den Cookie + +```python +def create_auth_response(access_token, user_data, expires_in): + response = make_response(jsonify(response_data)) + response.set_cookie( + 'spawner_token', + access_token, + max_age=expires_in, + httponly=True, # JavaScript-zugriff blockiert + secure=True, # Nur über HTTPS + samesite='Lax' # CSRF-Schutz + ) + return response +``` + +### Container (z.B. Dictionary-Template) +- `before_request` Hook validiert JWT im Cookie +- Ohne gültigen Token: `403 Forbidden` +- JWT_SECRET wird vom Spawner als Environment-Variable übergeben + +```python +@app.before_request +def validate_jwt_token(): + # GET / und /health sind öffentlich + if request.path == '/' or request.path == '/health': + return + + # Alle API-Calls brauchen gültigen Token + token = request.cookies.get('spawner_token') + if not token: + return jsonify({'error': 'Authentifizierung erforderlich'}), 401 + + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + g.user_id = payload.get('sub') + except jwt.InvalidTokenError: + return jsonify({'error': 'Token ungültig'}), 401 +``` + +**Commits:** +- `436b1c0`: Add cookie-based JWT authentication for user containers + +**Betroffen:** +- `api.py` - JWT-Cookie setzen/löschen +- `container_manager.py` - JWT_SECRET als Environment-Variable +- `user-template-dictionary/app.py` - JWT-Validierung +- `user-template-dictionary/requirements.txt` - PyJWT hinzugefügt + +--- + +## 4. install.sh Improvements ✅ + +### 4a. Git-Pull Auto-Fix für Synology + +**Problem:** Auf Synology schlugen `git pull` Befehle fehl wegen Dateiberechtigungen. + +**Lösung:** +```bash +git config core.filemode false # Ignoriere Berechtigungsbits +git reset --hard origin/main # Force-Sync mit Remote +``` + +**Commits:** +- `7111d7a`: Auto-fix git pull with core.filemode + +### 4b. Update-and-Re-Exec Mechanism + +**Problem:** Wenn `install.sh` selbst aktualisiert wird, lädt bash die alte Version weiter. + +**Lösung:** Nach `git pull` Checksumme vergleichen und Script neu starten mit `exec bash`. + +```bash +BEFORE_HASH=$(md5sum install.sh) +# ... git pull ... +AFTER_HASH=$(md5sum install.sh) + +if [ "$BEFORE_HASH" != "$AFTER_HASH" ]; then + export ALREADY_REEXECED="true" + exec bash install.sh +fi +``` + +**Commits:** +- `bb25750`: Add update-and-re-exec mechanism + +### 4c. Alte Container Auto-Cleanup + +**Problem:** Nach Code-Updates blieben alte Container und verursachten Traefik-Konflikte. + +**Lösung:** `install.sh` löscht alle alten User-Container vor Restart. + +```bash +# install.sh Sektion 8: +docker rm -f $(docker ps -a | grep "user-" | awk '{print $1}') +``` + +**Commits:** +- `7beb1d0`: Add detailed output for old container cleanup + +**Betroffen:** `install.sh` + +--- + +## 5. Dictionary Template Routing Fix ✅ + +**Problem:** `/api/words` Requests return 404 weil Traefik den Pfad-Prefix nicht entfernt. + +**Lösung:** API-Base-Pfad aus `window.location.pathname` berechnen. + +```javascript +// Vorher (falsch): +const response = await fetch('/api/words') + +// Nachher (richtig): +const apiBase = window.location.pathname.replace(/\/$/, ''); // z.B. "/e220dd278a12-template-dictionary" +const response = await fetch(`${apiBase}/api/words`) +``` + +**Commits:** +- `20a4d60`: Fix API paths in Dictionary template for Traefik routing + +**Betroffen:** `user-template-dictionary/templates/index.html` + +--- + +## Zusammengefasst + +| Fix | Status | Commits | Wichtigkeit | +|-----|--------|---------|-------------| +| Traefik Network (network.connect) | ✅ | 65a2a6e | 🔴 KRITISCH | +| Traefik Router Service-Labels | ✅ | 45bd329 | 🔴 KRITISCH | +| JWT-Cookie-Auth | ✅ | 436b1c0 | 🔴 KRITISCH (Security) | +| install.sh Git-Fix | ✅ | 7111d7a | 🟡 Wichtig | +| install.sh Re-Exec | ✅ | bb25750 | 🟡 Wichtig | +| install.sh Container-Cleanup | ✅ | 7beb1d0 | 🟡 Wichtig | +| Dictionary API-Paths | ✅ | 20a4d60 | 🟡 Wichtig | + +--- + +## Deployment-Schritte + +Nach diesen Fixes sollte auf der Synology folgende Procedure ausgeführt werden: + +```bash +bash install.sh +``` + +Das führt automatisch aus: +1. `git pull` mit Auto-Fix für Berechtigungen +2. Falls install.sh sich geändert hat: Neu starten +3. Alte User-Container löschen (Traefik-Konflikt-Prävention) +4. Alle Templates aus .env bauen +5. Docker-Compose mit neuen Containern starten + +--- + +## Testen nach Deployment + +1. **User-Authentication testen:** + ```bash + # Mit Browser: https://spawner.wieland.org/ + # Login mit Magic Link + ``` + +2. **Container-Routing testen:** + ```bash + # Container sollte im web-Netzwerk sein + docker network inspect web | grep user- + ``` + +3. **JWT-Protection testen:** + ```bash + # Versuche direkten Container-Zugriff OHNE Login + curl https://spawner.wieland.org/e220dd278a12-template-dictionary/api/words + # → Sollte 401 Unauthorized zurückgeben + ``` + +4. **JWT-Cookie testen:** + ```bash + # Nach erfolgreichem Login: + # Browser-DevTools → Application → Cookies + # → spawner_token sollte vorhanden und HttpOnly markiert sein + ``` diff --git a/docs/install/INSTALL_SCRIPT_ANALYSIS.md b/docs/install/INSTALL_SCRIPT_ANALYSIS.md index 1f069bf..3d4a0f9 100644 --- a/docs/install/INSTALL_SCRIPT_ANALYSIS.md +++ b/docs/install/INSTALL_SCRIPT_ANALYSIS.md @@ -1082,6 +1082,65 @@ df -h /volume1/docker/ --- +## Neue Features (März 2026) + +### 1. Git-Pull Auto-Fix für Synology + +**Problem:** `git pull` schlägt fehl wegen Dateiberechtigungen. + +**Lösung:** +```bash +git config core.filemode false # Ignoriere Berechtigungsbits +git reset --hard origin/main # Force-Sync mit Remote +``` + +**Automatisch aktiviert** wenn `git pull` fehlschlägt. + +### 2. Update-and-Re-Exec Mechanism + +**Problem:** Wenn `install.sh` selbst aktualisiert wird, lädt bash die alte Version weiter. + +**Lösung:** +```bash +# Vor git pull: Checksumme von install.sh speichern +BEFORE_HASH=$(md5sum install.sh) + +# Nach git pull: Checksumme vergleichen +AFTER_HASH=$(md5sum install.sh) + +# Wenn geändert: Script mit exec neu starten +if [ "$BEFORE_HASH" != "$AFTER_HASH" ]; then + export ALREADY_REEXECED="true" + exec bash install.sh # Neu starten mit neuem Code +fi +``` + +### 3. Alte User-Container Cleanup + +**Problem:** Nach Code-Updates bleiben alte Container und verursachen Traefik-Konflikte. + +**Lösung:** Automatisches Löschen aller alten User-Container vor Docker-Compose Restart. + +```bash +# Phase 8 in install.sh +docker ps -a | grep "user-" | awk '{print $NF}' # Zeige Container-Namen +docker rm -f $(docker ps -a | grep "user-" | awk '{print $1}') # Lösche alle +``` + +**Ausgabe:** +``` +Räume alte User-Container auf... + Gefunden: 3 alte User-Container: + • user-e220dd278a12-template-dictionary-1 + • user-abc123-template-01-2 + • user-xyz789-template-next-3 + + Lösche Container... + ✓ Alle alten Container gelöscht +``` + +--- + ## Häufige Fragen **F: Kann ich install.sh mehrmals hintereinander ausführen?** diff --git a/docs/templates/DICTIONARY_TEMPLATE.md b/docs/templates/DICTIONARY_TEMPLATE.md index 934c3ad..6663ad2 100644 --- a/docs/templates/DICTIONARY_TEMPLATE.md +++ b/docs/templates/DICTIONARY_TEMPLATE.md @@ -410,6 +410,78 @@ Falls die Anwendung wächst: --- +## 🔒 Sicherheit & Authentifizierung + +### JWT-Cookie Validierung + +Das Dictionary-Template ist **obligatorisch geschützt** mit JWT-Token-Validierung: + +1. **HttpOnly Cookie `spawner_token`** wird vom Spawner gesetzt +2. **Vor jedem API-Request** wird der Token validiert +3. **Ohne gültigen Token: 403 Forbidden** + +### How It Works + +``` +User Login + ↓ +Spawner setzt HttpOnly Cookie: spawner_token= + ↓ +Browser sendet Cookie automatisch bei jedem Request + ↓ +Dictionary-Template validiert JWT in: app.before_request() + ↓ +Gültig? → Erlauben API-Zugriff +Ungültig? → 403 Forbidden (Authentifizierung erforderlich) +``` + +### Implementation Details + +**Token-Validierung in `app.py`:** +```python +@app.before_request +def validate_jwt_token(): + # Öffentliche Endpoints (GET / und /health) + if request.path == '/' or request.path == '/health': + return + + # Alle API-Calls brauchen gültigen JWT + token = request.cookies.get('spawner_token') + if not token: + return jsonify({'error': 'Authentifizierung erforderlich'}), 401 + + # Dekodiere und validiere JWT + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + g.user_id = payload.get('sub') +``` + +### Sicherheits-Features + +- ✅ **HttpOnly Cookies** - JavaScript kann Token nicht auslesen +- ✅ **Secure Flag** - Nur über HTTPS übertragen +- ✅ **SameSite=Lax** - CSRF-Schutz +- ✅ **Token Expiration** - Standard: 1 Stunde (konfigurierbar) +- ✅ **JWT_SECRET** - Wird vom Spawner übergeben +- ✅ **Logout** - Cookie wird beim Logout gelöscht + +### Testing der Sicherheit + +```bash +# 1. Versuche direkten Zugriff OHNE Login +curl https://spawner.wieland.org/e220dd278a12-template-dictionary/api/words +# → Sollte 401 Unauthorized zurückgeben + +# 2. Nach erfolgreichem Login +curl -b "spawner_token=" https://spawner.wieland.org/e220dd278a12-template-dictionary/api/words +# → Sollte Wörter-Liste zurückgeben + +# 3. Überprüfe Cookie im Browser +# Browser DevTools → Application → Cookies +# → spawner_token sollte HttpOnly markiert sein +``` + +--- + ## Troubleshooting ### Problem: "Datenbankfehler beim Abrufen"