docs: Update all documentation with latest fixes - Traefik routing, JWT-cookie auth, install.sh improvements
This commit is contained in:
parent
436b1c0b0e
commit
02280cf0b4
250
docs/RECENT_FIXES_2026_03.md
Normal file
250
docs/RECENT_FIXES_2026_03.md
Normal file
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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?**
|
||||
|
|
|
|||
72
docs/templates/DICTIONARY_TEMPLATE.md
vendored
72
docs/templates/DICTIONARY_TEMPLATE.md
vendored
|
|
@ -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=<JWT>
|
||||
↓
|
||||
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=<JWT>" 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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user