Merge pull request 'Update README.md with enhanced configuration details and security measures' (#13) from dev into main

Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2026-07-07 14:46:14 +00:00

133
README.md
View File

@@ -21,13 +21,15 @@ Produktionsnahes internes Tool zur Erstellung von Thomas-Krenn.AG Newslettern au
- Plain Text (für Outlook)
- Raw HTML
- Optionaler SMTP-Versand:
- UI-konfigurierbar
- Verteilerlisten
- flexibler Zeitplan (täglich / wöchentlich an bestimmten Wochentagen / monatlich an einem Tag) über einen Hintergrund-Scheduler
- UI-konfigurierbar (nur für Admins)
- Verteilerlisten mit E-Mail-Validierung
- Versand per **BCC** (Empfänger nicht im sichtbaren `To:`-Header)
- flexibler Zeitplan (täglich / wöchentlich / monatlich) über einen Hintergrund-Scheduler
- eigene Inhalts-Vorgaben für den geplanten Versand (Zeitraum, Artikel-Auswahl, Kategorie)
- manueller Versand nur per Button „Generieren“ verschickt nie automatisch
- Versandprotokoll
- Docker/Compose Betrieb, inkl. automatischem Image-Build & Push in die Gitea Container Registry (per Versions-Tag)
- **„Generieren“ verschickt nie** manueller Versand nur per Button „Newsletter jetzt senden“
- automatischer Versand nur mit **zwei** aktiven Häkchen: Zeitplan + Bestätigung „ohne manuelle Freigabe“
- Versandprotokoll (nur für Editor/Admin sichtbar)
- Docker/Compose Betrieb mit Container-Hardening, inkl. automatischem Image-Build & Push in die Gitea Container Registry (per Versions-Tag)
## Start mit Docker
@@ -37,11 +39,17 @@ Produktionsnahes internes Tool zur Erstellung von Thomas-Krenn.AG Newslettern au
cp .env.example .env
```
2. Wichtige Werte in `.env` setzen:
- `HOST_PORT` (z. B. `8080`, falls `8000` bereits belegt ist)
- `SECRET_KEY`
- `ADMIN_BOOTSTRAP_EMAIL`
- `ADMIN_BOOTSTRAP_PASSWORD`
2. Wichtige Werte in `.env` setzen (siehe auch [Konfiguration](#konfiguration-env)):
| Variable | Lokal (Entwicklung) | Produktion |
|----------|---------------------|------------|
| `ENVIRONMENT` | `development` | `production` |
| `HOST_PORT` | z. B. `8080` | nach Bedarf |
| `SECRET_KEY` | beliebig lang | mind. 32 zufällige Zeichen |
| `ALLOWED_HOSTS` | `localhost,127.0.0.1` | konkreter Hostname |
| `COOKIE_SECURE` | `false` | `true` (mit TLS) |
| `ADMIN_BOOTSTRAP_EMAIL` | Admin-E-Mail | Admin-E-Mail |
| `ADMIN_BOOTSTRAP_PASSWORD` | Bootstrap-Passwort | starkes Passwort |
3. Start:
@@ -52,6 +60,30 @@ Produktionsnahes internes Tool zur Erstellung von Thomas-Krenn.AG Newslettern au
4. Zugriff:
- [http://localhost:8080/login](http://localhost:8080/login) (oder dein `HOST_PORT`)
> **Hinweis:** Mit `ENVIRONMENT=production` prüft die App beim Start strikt die Konfiguration. Fehlen starke Secrets oder sind unsichere Defaults gesetzt, startet der Container nicht.
## Konfiguration (`.env`)
Vollständige Vorlage: `.env.example`
| Variable | Beschreibung |
|----------|--------------|
| `ENVIRONMENT` | `development` = lokale Entwicklung ohne harte Checks; `production` = erzwingt sichere Einstellungen |
| `SECRET_KEY` | JWT-Signierung und Verschlüsselung von SMTP-Passwörtern in der DB |
| `ALGORITHM` | JWT-Algorithmus (Standard: `HS256`) |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | Gültigkeit der Login-Session in Minuten (Standard: `120`) |
| `JWT_ISSUER` / `JWT_AUDIENCE` | JWT-Claims zur Token-Validierung |
| `HOST_PORT` | Host-Port für Docker Compose (nur Compose, nicht App-intern) |
| `DATABASE_URL` | SQLite (Standard) oder PostgreSQL |
| `WIKI_API_URL` | MediaWiki-API-Endpunkt |
| `ALLOWED_HOSTS` | Kommagetrennte erlaubte Host-Header |
| `COOKIE_SECURE` | `Secure`-Flag für Session- und CSRF-Cookies (Pflicht `true` in Produktion) |
| `HSTS_MAX_AGE` | HSTS-Header-Dauer in Sekunden (nur bei HTTPS) |
| `ADMIN_BOOTSTRAP_EMAIL` / `ADMIN_BOOTSTRAP_PASSWORD` | Erster Admin-Account beim Start |
| `ADMIN_BOOTSTRAP_RESET` | Admin auf `.env`-Werte zurücksetzen (nur Entwicklung/Notfall, in Produktion verboten) |
| `EDITOR_TIP_MAX_LENGTH` | Max. Zeichen für Redaktionsnotiz (Standard: `10000`) |
| `HIGHLIGHTS_MAX_LENGTH` | Max. Zeichen für Top-Highlights (Standard: `5000`) |
## Container-Image (Gitea Packages)
Fertige Images werden automatisch in die Gitea Container Registry veröffentlicht:
@@ -71,6 +103,9 @@ docker run -d --name tk-newsletter-admin \
--env-file .env \
-p 8080:8000 \
-v newsletter_data:/app/data \
--read-only \
--tmpfs /tmp \
--security-opt no-new-privileges:true \
git.hexahost.dev/smueller/tk-wiki-newsletter:latest
```
@@ -83,11 +118,14 @@ services:
# build: . # <- nicht mehr nötig, wenn das fertige Image genutzt wird
```
Die mitgelieferte `docker-compose.yml` setzt bereits `read_only`, `no-new-privileges` und ein `tmpfs` für `/tmp`. Persistente Daten liegen im Volume `newsletter_data` unter `/app/data`.
### Automatischer Build (CI/CD)
Der Workflow `.gitea/workflows/docker-build.yml` baut und pusht das Image über Gitea Actions.
- **Auslöser**: nur beim Setzen eines Versions-Tags `v*` (kein Build bei normalen Pushes auf `dev`/`main`).
- **Vor dem Build**: `pip-audit` prüft die Python-Abhängigkeiten aus `requirements.txt`.
- **Release-Ablauf** (Build bei Merge `dev` → `main` mit Version):
```bash
@@ -106,25 +144,74 @@ Der Workflow `.gitea/workflows/docker-build.yml` baut und pusht das Image über
## Admin-Login & Passwort zurücksetzen
- Das Login erfolgt über **E-Mail + Passwort** (kein separater Benutzername). Beim ersten Start wird der Admin aus `ADMIN_BOOTSTRAP_EMAIL` / `ADMIN_BOOTSTRAP_PASSWORD` angelegt.
- **Passwortpolitik:** mindestens 10 Zeichen, mindestens ein Großbuchstabe, ein Kleinbuchstabe und eine Ziffer.
- **Rate-Limiting:** nach 5 fehlgeschlagenen Anmeldeversuchen pro IP/E-Mail innerhalb von 5 Minuten wird der Login temporär blockiert.
- **Wichtig:** Existiert der Admin bereits (persistente DB im Volume `newsletter_data`), wird eine spätere Passwort-Änderung in der `.env` normalerweise **nicht** übernommen.
- Um Passwort/Rolle/Status auf die `.env`-Werte zurückzusetzen: `ADMIN_BOOTSTRAP_RESET=true` setzen und Container neu starten:
- Um Passwort/Rolle/Status auf die `.env`-Werte zurückzusetzen (**nur Entwicklung**): `ADMIN_BOOTSTRAP_RESET=true` setzen und Container neu starten:
```bash
docker compose up -d --force-recreate
```
Nach erfolgreichem Login `ADMIN_BOOTSTRAP_RESET=false` setzen, damit UI-Passwortänderungen nicht bei jedem Neustart überschrieben werden.
Nach erfolgreichem Login `ADMIN_BOOTSTRAP_RESET=false` setzen. In `ENVIRONMENT=production` ist `ADMIN_BOOTSTRAP_RESET=true` nicht erlaubt die App startet dann nicht.
## Sicherheits-Hinweise für Produktion
- Reverse Proxy (z. B. Nginx/Traefik) mit TLS vor den Container setzen.
- `SECRET_KEY` lang und zufällig setzen.
- Bootstrap-Admin-Passwort nach erstem Login ändern.
- Netzwerkzugriff auf interne IPs/Netze beschränken.
- Regelmäßige Backups des `data` Volumes.
- **Passwort ändern** (Profil) oder **Abmelden** beendet alle aktiven Sessions des Benutzers.
## Rollenmodell
- `reader`: darf Ergebnisse sehen
- `editor`: darf Newsletter generieren und sofort versenden
- `admin`: zusätzlich Benutzerverwaltung und SMTP-Konfiguration
| Rolle | Rechte |
|-------|--------|
| `reader` | Newsletter-Vorschau und Artikellisten ansehen |
| `editor` | Newsletter generieren, exportieren und manuell versenden; Versandprotokoll und Empfängerliste einsehen |
| `admin` | Zusätzlich Benutzerverwaltung (anlegen, Rolle ändern, aktivieren/deaktivieren) und SMTP-Konfiguration |
Admins können in der Benutzerverwaltung (`/admin/users`):
- neue Benutzer mit Rolle anlegen
- die Rolle bestehender Benutzer ändern (beendet deren Sessions)
- Benutzer aktivieren oder deaktivieren (beendet deren Sessions)
Das eigene Konto kann weder deaktiviert noch die eigene Rolle geändert werden.
## Sicherheit
### Eingebaute Schutzmaßnahmen
- **bcrypt** für Passwort-Hashes
- **CSRF** Double-Submit-Cookie auf allen POST-Formularen
- **HttpOnly** + **SameSite=Strict** Session-Cookies; `Secure` bei HTTPS (`COOKIE_SECURE=true`)
- **JWT** mit `iss`, `aud`, Ablaufzeit und Session-Version (`session_version`)
- Session-Invalidierung bei Logout, Passwortwechsel, Rollen- oder Statusänderung
- **SMTP-Passwort** verschlüsselt in der Datenbank (Fernet), nicht im HTML-Formular
- **Security-Header:** CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, HSTS (bei HTTPS), `Cache-Control: no-store` auf geschützten Seiten
- **OpenAPI** (`/docs`, `/redoc`) in Produktion deaktiviert
- Abhängigkeiten in `requirements.txt` versioniert; CI führt `pip-audit` aus
### Produktions-Checkliste
Bei `ENVIRONMENT=production` erzwingt die App beim Start:
- `SECRET_KEY` mit mindestens 32 Zeichen (keine bekannten Defaults)
- starkes `ADMIN_BOOTSTRAP_PASSWORD`
- `ADMIN_BOOTSTRAP_RESET=false`
- `ALLOWED_HOSTS` mit konkreten Hostnamen (nicht `*`)
- `COOKIE_SECURE=true`
Zusätzlich empfohlen:
- Reverse Proxy (z. B. Nginx/Traefik) mit TLS vor den Container; Proxy muss `X-Forwarded-Proto: https` setzen
- Bootstrap-Admin-Passwort nach erstem Login im Profil ändern
- Netzwerkzugriff auf interne IPs/Netze beschränken
- Regelmäßige Backups des `newsletter_data` Volumes (SQLite-Datei unverschlüsselt)
### Beispiel `.env` für Produktion
```env
ENVIRONMENT=production
SECRET_KEY=<mind. 32 zufällige Zeichen>
ALLOWED_HOSTS=newsletter.intern.firma.de
COOKIE_SECURE=true
ADMIN_BOOTSTRAP_EMAIL=admin@firma.de
ADMIN_BOOTSTRAP_PASSWORD=<starkes Passwort>
ADMIN_BOOTSTRAP_RESET=false
```