- Clarified SMTP sending options, including BCC support and email validation for distribution lists. - Updated instructions for setting environment variables in `.env`, emphasizing security requirements for production. - Added notes on password policies, rate limiting, and user role management in the admin section. - Enhanced Docker/Compose setup with security hardening features and clarified the automatic build process.
218 lines
9.5 KiB
Markdown
218 lines
9.5 KiB
Markdown
# TK Wiki Newsletter Admin
|
||
|
||
Produktionsnahes internes Tool zur Erstellung von Thomas-Krenn.AG Newslettern auf Basis der letzten Wiki-Änderungen.
|
||
|
||
## Funktionen
|
||
|
||
- Login und Benutzerverwaltung mit RBAC (`admin`, `editor`, `reader`)
|
||
- Verpflichtender CSRF-Schutz auf allen POST-Formularen
|
||
- Filter in der Web-UI:
|
||
- Zeitraum in Tagen oder „Letzter Monat“
|
||
- Artikel-Auswahl: alle, nur neue oder nur bearbeitete Artikel
|
||
- Kategorie
|
||
- Redaktioneller Feinschliff pro Ausgabe:
|
||
- **Redaktionsnotiz** – optionaler Hinweistext, der als eigene Box im Newsletter erscheint
|
||
- **Top-Highlights** – bis zu 3 Artikel per Titel hervorheben (erscheinen oben als Highlight-Box)
|
||
- Datenquelle: MediaWiki API (`recentchanges` über `wikiDE/api.php`)
|
||
- E-Mail-Layout im Thomas-Krenn Corporate Design:
|
||
- Hero-Header mit Logo, Artikel-Karten mit „Zum Beitrag“-Button, Kontakt- und Adress-Footer
|
||
- Neu-/Bearbeitet-Trennung, Zusammenfassungs-Kacheln, Outlook-taugliche Kopiervorschau
|
||
- Export:
|
||
- Plain Text (für Outlook)
|
||
- Raw HTML
|
||
- Optionaler SMTP-Versand:
|
||
- 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)
|
||
- **„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
|
||
|
||
1. Konfiguration anlegen:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
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:
|
||
|
||
```bash
|
||
docker compose up --build -d
|
||
```
|
||
|
||
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:
|
||
|
||
```
|
||
git.hexahost.dev/smueller/tk-wiki-newsletter
|
||
```
|
||
|
||
Verfügbare Tags: `latest`, die Version (`X.Y.Z`, `X.Y`, `X`) sowie der kurze Commit-SHA.
|
||
|
||
Image ziehen und mit vorhandener `.env` starten:
|
||
|
||
```bash
|
||
docker login git.hexahost.dev
|
||
docker pull git.hexahost.dev/smueller/tk-wiki-newsletter:latest
|
||
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
|
||
```
|
||
|
||
Alternativ in `docker-compose.yml` statt `build: .` das Image referenzieren:
|
||
|
||
```yaml
|
||
services:
|
||
newsletter-admin:
|
||
image: git.hexahost.dev/smueller/tk-wiki-newsletter:latest
|
||
# 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
|
||
git checkout main
|
||
git merge --no-ff dev
|
||
git push origin main
|
||
git tag v1.0.0
|
||
git push origin v1.0.0 # <- startet Build & Push
|
||
```
|
||
|
||
- **Benötigte Repo-Secrets** (Settings → Actions → Secrets):
|
||
- `REGISTRY_TOKEN`: Gitea Access-Token mit Scope **Package: Read and write**
|
||
- `REGISTRY_USER` (optional): Registry-Benutzername, falls abweichend vom Repo-Owner
|
||
- **Optionale Repo-Variable**: `REGISTRY`, um den Registry-Host zu überschreiben (Default `git.hexahost.dev`).
|
||
|
||
## 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 (**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. In `ENVIRONMENT=production` ist `ADMIN_BOOTSTRAP_RESET=true` nicht erlaubt – die App startet dann nicht.
|
||
|
||
- **Passwort ändern** (Profil) oder **Abmelden** beendet alle aktiven Sessions des Benutzers.
|
||
|
||
## Rollenmodell
|
||
|
||
| 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
|
||
```
|