Sicherheits-Härtung und Bugfixes
This commit is contained in:
+56
-8
@@ -2,6 +2,8 @@
|
||||
|
||||
Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http://<host>:8484`
|
||||
|
||||
Die interaktive API-Dokumentation (Swagger-UI unter `/docs`) ist standardmäßig deaktiviert und kann mit der Umgebungsvariable `ENABLE_API_DOCS=1` eingeschaltet werden.
|
||||
|
||||
## Endpunkte
|
||||
|
||||
### System
|
||||
@@ -9,8 +11,8 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
| Methode | Pfad | Beschreibung |
|
||||
|---------|------|-------------|
|
||||
| `GET` | `/api/health` | Health Check (kein Auth nötig) |
|
||||
| `GET` | `/api/config` | Konfiguration lesen |
|
||||
| `PUT` | `/api/config` | Konfiguration speichern |
|
||||
| `GET` | `/api/config` | Konfiguration lesen (ohne Secrets) |
|
||||
| `PUT` | `/api/config` | Konfiguration speichern (siehe [Konfiguration](#konfiguration)) |
|
||||
|
||||
### Jobs
|
||||
|
||||
@@ -20,8 +22,8 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
| `POST` | `/api/jobs` | Neuen Job erstellen |
|
||||
| `GET` | `/api/jobs/:id` | Job-Details abrufen |
|
||||
| `PUT` | `/api/jobs/:id` | Job aktualisieren |
|
||||
| `DELETE` | `/api/jobs/:id` | Job löschen |
|
||||
| `POST` | `/api/jobs/:id/record` | Job-Aufnahme starten |
|
||||
| `DELETE` | `/api/jobs/:id` | Job löschen (nicht während einer laufenden Aufnahme → 400) |
|
||||
| `POST` | `/api/jobs/:id/record` | Job-Aufnahme starten (läuft bereits → 409) |
|
||||
|
||||
### Senderbibliothek
|
||||
|
||||
@@ -31,7 +33,7 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
| `POST` | `/api/stations` | Neuen Sender erstellen |
|
||||
| `GET` | `/api/stations/:id` | Sender-Details abrufen |
|
||||
| `PUT` | `/api/stations/:id` | Sender aktualisieren |
|
||||
| `DELETE` | `/api/stations/:id` | Sender löschen |
|
||||
| `DELETE` | `/api/stations/:id` | Sender löschen (verknüpfte Jobs werden gelöst) |
|
||||
|
||||
### Schnellaufnahme
|
||||
|
||||
@@ -48,6 +50,8 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
}
|
||||
```
|
||||
|
||||
Erlaubte URL-Schemata: `http`, `https`, `rtmp`, `rtmps`, `rtsp`, `rtsps`. Für Job-Felder gelten dieselben Prüfungen wie im Web-UI (siehe [Jobs](jobs.md#validierung)).
|
||||
|
||||
### Aufnahmen
|
||||
|
||||
| Methode | Pfad | Beschreibung |
|
||||
@@ -55,7 +59,7 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
| `GET` | `/api/recordings` | Alle Aufnahmen (Parameter: `active_only`, `limit`) |
|
||||
| `GET` | `/api/recordings/:id` | Aufnahme-Status |
|
||||
| `POST` | `/api/recordings/:id/stop` | Aufnahme stoppen |
|
||||
| `POST` | `/api/recordings/:id/extend?minutes=30` | Aufnahme verlängern |
|
||||
| `POST` | `/api/recordings/:id/extend?minutes=30` | Aufnahme verlängern (`minutes` 1–1440; bei unbegrenzter Aufnahme → 400) |
|
||||
| `POST` | `/api/recordings/:id/unlimited` | Zeitlimit entfernen |
|
||||
|
||||
### Logs
|
||||
@@ -64,12 +68,56 @@ Alle Funktionen des Web-UI sind auch per REST-API verfügbar. Basis-URL: `http:/
|
||||
|---------|------|-------------|
|
||||
| `GET` | `/api/logs` | Protokoll (Parameter: `limit`, `level`) |
|
||||
|
||||
Zeitstempel in API-Antworten sind UTC mit Offset, z.B. `2026-10-11T14:30:00+00:00`.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
`GET /api/config` liefert keine Secrets. Stattdessen zeigen boolesche Felder an, ob ein Wert gesetzt ist:
|
||||
|
||||
| Feld | Bedeutung |
|
||||
|------|-----------|
|
||||
| `auth.password_set` | Passwort ist gesetzt |
|
||||
| `ntfy.token_set` | NTFY-Token ist gesetzt |
|
||||
| `plik.api_key_set` | Plik-API-Key ist gesetzt |
|
||||
|
||||
Regeln für `PUT /api/config`:
|
||||
|
||||
- Secret-Felder (`auth.password`, `ntfy.token`, `plik.api_key`) weglassen = unverändert
|
||||
- Leerer String `""` = Secret entfernen (nicht beim Passwort)
|
||||
- `auth.enabled: true` erfordert ein Passwort (mind. 8 Zeichen)
|
||||
- `download_path` muss innerhalb von `RECORDING_ROOTS` liegen
|
||||
- Unbekannte Schlüssel → 422
|
||||
- Der Abschnitt `server` kann nicht über die API geändert werden
|
||||
|
||||
```bash
|
||||
curl -u admin:passwort -X PUT http://localhost:8484/api/config -H "Content-Type: application/json" -d '{"ntfy": {"url": "https://ntfy.sh/mein-topic", "events": "start,error"}}'
|
||||
```
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
Bei aktiviertem Auth wird Basic Authentication verwendet:
|
||||
Bei aktiviertem Auth (Standard bei Neuinstallationen) wird Basic Authentication verwendet:
|
||||
|
||||
```bash
|
||||
curl -u admin:passwort http://localhost:8484/api/jobs
|
||||
```
|
||||
|
||||
Der Health-Endpunkt (`/api/health`) ist immer ohne Auth erreichbar.
|
||||
- Der Health-Endpunkt (`/api/health`) ist immer ohne Auth erreichbar.
|
||||
- Nach 10 fehlgeschlagenen Logins innerhalb von 5 Minuten antwortet der Server für diese IP bis zum Ende des Zeitfensters mit 429.
|
||||
|
||||
## Browser-Zugriff
|
||||
|
||||
CORS ist nicht aktiviert. Schreibende Requests (`POST`, `PUT`, `DELETE`) mit fremdem `Origin` werden mit 403 abgewiesen. Die API ist damit aus Skripten/curl (ohne `Origin`-Header) und aus dem eigenen Web-UI nutzbar, nicht aber von fremden Webseiten.
|
||||
|
||||
## Fehlercodes
|
||||
|
||||
Fehler werden als JSON mit Feld `detail` zurückgegeben.
|
||||
|
||||
| Code | Bedeutung |
|
||||
|------|-----------|
|
||||
| `400` | Ungültige Anfrage, Meldung auf Deutsch (z.B. nicht erlaubte ffmpeg-Argumente, unzulässiger Pfad) |
|
||||
| `401` | Anmeldung erforderlich oder fehlgeschlagen |
|
||||
| `403` | Cross-Site-Request abgewiesen |
|
||||
| `404` | Objekt nicht gefunden |
|
||||
| `409` | Konflikt, z.B. Job nimmt bereits auf |
|
||||
| `422` | Validierungsfehler (ungültige Felder, unbekannte Schlüssel) |
|
||||
| `429` | Zu viele fehlgeschlagene Logins |
|
||||
|
||||
+8
-2
@@ -27,9 +27,11 @@ Unterhalb der aktiven Aufnahmen zeigt das Dashboard die letzten abgeschlossenen
|
||||
|
||||
Laufende Aufnahmen können verlängert werden:
|
||||
- **+15 / +30 / +60 / +120 Minuten** — Schnellauswahl
|
||||
- **Eigene Dauer** — Beliebige Minutenzahl eingeben
|
||||
- **Eigene Dauer** — Minutenzahl zwischen 1 und 1440 eingeben
|
||||
- **Unbegrenzt** — Zeitlimit komplett entfernen
|
||||
|
||||
Eine bereits unbegrenzte Aufnahme kann nicht weiter verlängert werden.
|
||||
|
||||
## Aufnahme stoppen
|
||||
|
||||
Über den **Stoppen**-Button im Dashboard. Die Aufnahme wird sauber beendet (SIGINT → ffmpeg finalisiert die Datei).
|
||||
@@ -42,6 +44,10 @@ Bei Verbindungsabbrüchen versucht der Recorder automatisch, die Verbindung wied
|
||||
|
||||
Bei jedem Reconnect wird eine neue Datei angelegt, um Datenverlust zu vermeiden.
|
||||
|
||||
## Neustart
|
||||
|
||||
Aufnahmen, die beim Neustart des Containers liefen, werden beim nächsten Start mit dem Fehler „Abgebrochen durch Neustart" markiert. Geplante Jobs starten wieder regulär nach Zeitplan.
|
||||
|
||||
## Segment-Splitting
|
||||
|
||||
Lange Aufnahmen können automatisch in Teile zerlegt werden. Die Segment-Dauer wird pro Job in Minuten konfiguriert.
|
||||
@@ -69,4 +75,4 @@ Kann pro Job manuell überschrieben werden (z.B. `mkv`, `ts`).
|
||||
|
||||
## Speicherort
|
||||
|
||||
Aufnahmen werden unter `data/recordings/<job-name>/` gespeichert (im Container: `/app/data/recordings/`). Der Pfad ist über `recording.download_path` konfigurierbar.
|
||||
Aufnahmen werden unter `data/recordings/<job-name>/` gespeichert (im Container: `/app/data/recordings/`). Der Pfad ist über `recording.download_path` konfigurierbar und muss innerhalb eines Verzeichnisses aus `RECORDING_ROOTS` liegen (Standard: `/app/data/recordings:/recordings`), siehe [Konfiguration](konfiguration.md#umgebungsvariablen).
|
||||
|
||||
+35
-3
@@ -13,7 +13,7 @@ Im Web-UI unter **Jobs → + Neuer Job**.
|
||||
| Feld | Beschreibung |
|
||||
|------|-------------|
|
||||
| **Name** | Eindeutiger Name (wird als Ordnername verwendet) |
|
||||
| **Stream-URL** | URL des Streams (RTMP, HLS, HTTP, etc.) |
|
||||
| **Stream-URL** | URL des Streams. Erlaubt: `http`, `https`, `rtmp`, `rtmps`, `rtsp`, `rtsps` |
|
||||
|
||||
### Optionale Felder
|
||||
|
||||
@@ -25,7 +25,39 @@ Im Web-UI unter **Jobs → + Neuer Job**.
|
||||
| **Ausgabeformat** | Dateiendung. Leer = automatisch (mp4 für Video, mp3 für Audio) |
|
||||
| **Max. Dauer** | Zeitlimit in Minuten. Entfällt bei Zeitplan mit Endzeit |
|
||||
| **Segment-Dauer** | Aufnahme in Teile zerlegen (Minuten pro Segment) |
|
||||
| **Extra ffmpeg Argumente** | Zusätzliche ffmpeg-Parameter (z.B. `-b:a 192k`) |
|
||||
| **Extra ffmpeg Argumente** | Zusätzliche ffmpeg-Parameter (z.B. `-b:a 192k`), siehe [Extra ffmpeg-Argumente](#extra-ffmpeg-argumente) |
|
||||
|
||||
## Extra ffmpeg-Argumente
|
||||
|
||||
Aus Sicherheitsgründen sind nur bestimmte ffmpeg-Optionen erlaubt. Alles andere wird mit einer Fehlermeldung abgelehnt.
|
||||
|
||||
| Art | Erlaubte Optionen |
|
||||
|-----|-------------------|
|
||||
| Ohne Wert | `-vn` `-an` `-sn` `-dn` `-shortest` `-copyts` |
|
||||
| Mit einem Wert | `-c` `-c:a` `-c:v` `-codec` `-codec:a` `-codec:v` `-acodec` `-vcodec` `-b:a` `-b:v` `-ar` `-ac` `-map` `-metadata` `-metadata:s:a` `-t` `-to` `-q:a` `-aq` `-af` `-filter:a` `-bsf:a` `-bsf:v` `-f` `-movflags` `-segment_format` `-fflags` `-avoid_negative_ts` `-max_muxing_queue_size` |
|
||||
|
||||
Audiofilter (`-af`, `-filter:a`) dürfen keine Quellen wie `movie`, `amovie`, `sendcmd`, `zmq` oder Dateien verwenden. ffmpeg läuft zudem mit einer Protokoll-Whitelist, lokale Dateien können nicht als Eingabe gelesen werden.
|
||||
|
||||
Beispiele:
|
||||
|
||||
```
|
||||
-b:a 192k -ar 44100
|
||||
-c:a libmp3lame -q:a 2
|
||||
-af loudnorm -metadata title="Meine Sendung"
|
||||
```
|
||||
|
||||
## Validierung
|
||||
|
||||
| Feld | Regel |
|
||||
|------|-------|
|
||||
| **Startzeit / Endzeit** | Format `HH:MM` |
|
||||
| **Startdatum / Enddatum** | Format `YYYY-MM-DD` |
|
||||
| **Tage** | `*` oder Liste/Bereiche wie `Mo-Fr,So` (siehe [Tage-Syntax](#tage-syntax)) |
|
||||
| **Vor-/Nachlaufzeit** | 0–120 Minuten |
|
||||
| **Segment-Dauer** | 10 Sekunden bis 24 Stunden |
|
||||
| **Max. Dauer** | bis 30 Tage |
|
||||
|
||||
Ungültige Eingaben werden mit einer Fehlermeldung abgelehnt (API: 400/422).
|
||||
|
||||
## Zeitplan
|
||||
|
||||
@@ -85,7 +117,7 @@ Die globale NTFY- und Plik-Konfiguration (URL, Token, etc.) erfolgt in den [Eins
|
||||
|
||||
## Manuelle Aufnahme
|
||||
|
||||
Jobs können auch manuell über den **Aufnehmen**-Button gestartet werden, unabhängig vom Zeitplan.
|
||||
Jobs können auch manuell über den **Aufnehmen**-Button gestartet werden, unabhängig vom Zeitplan. Pro Job läuft höchstens eine Aufnahme gleichzeitig. Ein Job mit laufender Aufnahme kann nicht gelöscht werden.
|
||||
|
||||
## Schnellaufnahme
|
||||
|
||||
|
||||
+46
-6
@@ -8,13 +8,15 @@ Die Konfiguration liegt in `data/config.yml` und kann über das Web-UI unter **E
|
||||
|
||||
Beim ersten Start wird die Datei automatisch mit Standardwerten erstellt. Eine Vorlage mit Kommentaren findet sich in `bin/config.yml.dist`.
|
||||
|
||||
Die Datei enthält Secrets und wird beim Containerstart auf die Rechte `600` gesetzt.
|
||||
|
||||
## Abschnitte
|
||||
|
||||
### Aufnahme
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|-------------|
|
||||
| `recording.download_path` | `/app/data/recordings` | Speicherort für Aufnahmen |
|
||||
| `recording.download_path` | `/app/data/recordings` | Speicherort für Aufnahmen. Muss innerhalb eines Verzeichnisses aus `RECORDING_ROOTS` liegen |
|
||||
| `recording.max_retries` | `5` | Max. Reconnect-Versuche bei Verbindungsabbruch (0 = unbegrenzt) |
|
||||
| `recording.retry_delay` | `5` | Wartezeit (Sekunden) zwischen Reconnect-Versuchen |
|
||||
|
||||
@@ -25,13 +27,26 @@ Beim ersten Start wird die Datei automatisch mit Standardwerten erstellt. Eine V
|
||||
| `server.host` | `0.0.0.0` | Bind-Adresse des Webservers |
|
||||
| `server.port` | `8484` | Port des Webservers |
|
||||
|
||||
Der Abschnitt `server` wird nur aus der Datei gelesen und kann nicht über Web-UI/API geändert werden. Da die Anwendung nicht als root läuft, sind Ports unter 1024 nicht möglich.
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|-------------|
|
||||
| `auth.enabled` | `false` | Basic Auth aktivieren |
|
||||
| `auth.enabled` | `true` | Basic Auth aktivieren (erfordert ein Passwort mit mind. 8 Zeichen) |
|
||||
| `auth.username` | `admin` | Benutzername |
|
||||
| `auth.password` | `stream-recorder` | Passwort |
|
||||
| `auth.password_hash` | _(generiert)_ | PBKDF2-Hash des Passworts |
|
||||
| `auth.password` | — | Optional: Klartext-Passwort, wird beim Start automatisch in `password_hash` umgewandelt |
|
||||
|
||||
Beim ersten Start (keine `config.yml` vorhanden) ist die Anmeldung aktiv. Benutzer ist `admin`, das zufällig erzeugte Passwort wird einmalig im Container-Log ausgegeben:
|
||||
|
||||
```bash
|
||||
docker logs stream-recorder
|
||||
```
|
||||
|
||||
Bestehende Installationen behalten ihre bisherige Auth-Einstellung. Nach 10 fehlgeschlagenen Logins innerhalb von 5 Minuten wird die IP für den Rest des Zeitfensters mit HTTP 429 abgewiesen.
|
||||
|
||||
**Passwort vergessen:** In `data/config.yml` den Eintrag `password_hash` durch `password: neues-passwort` ersetzen und den Container neu starten.
|
||||
|
||||
### Zeitplan-Puffer
|
||||
|
||||
@@ -47,7 +62,7 @@ Standardwerte für die Vor- und Nachlaufzeit bei neuen Jobs mit Zeitplan.
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|-------------|
|
||||
| `ntfy.url` | _(leer)_ | NTFY Topic-URL (z.B. `https://ntfy.sh/mein-topic`) |
|
||||
| `ntfy.token` | _(leer)_ | Bearer-Token für authentifizierte Topics |
|
||||
| `ntfy.token` | _(leer)_ | Bearer-Token für authentifizierte Topics (wird von der API nie zurückgegeben) |
|
||||
| `ntfy.events` | `error` | Kommaseparierte Events: `start`, `stop`, `error`, `upload` |
|
||||
|
||||
### Plik
|
||||
@@ -55,7 +70,7 @@ Standardwerte für die Vor- und Nachlaufzeit bei neuen Jobs mit Zeitplan.
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|-------------|
|
||||
| `plik.url` | _(leer)_ | Plik-Server URL (z.B. `https://plik.example.com`) |
|
||||
| `plik.api_key` | _(leer)_ | API Key für authentifizierte Uploads |
|
||||
| `plik.api_key` | _(leer)_ | API Key für authentifizierte Uploads (wird von der API nie zurückgegeben) |
|
||||
| `plik.ttl` | `30d` | Aufbewahrungsdauer der Uploads (`30d`, `12h`, etc.) |
|
||||
|
||||
### Logging
|
||||
@@ -64,6 +79,31 @@ Standardwerte für die Vor- und Nachlaufzeit bei neuen Jobs mit Zeitplan.
|
||||
|-----------|----------|-------------|
|
||||
| `logging.level` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||||
|
||||
Anwendungs-Logs erscheinen im Web-UI und zusätzlich auf stdout (`docker logs stream-recorder`). Einträge älter als `LOG_RETENTION_DAYS` Tage werden automatisch gelöscht.
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
| Variable | Standard | Beschreibung |
|
||||
|----------|----------|-------------|
|
||||
| `TZ` | — | Zeitzone (z.B. `Europe/Berlin`) |
|
||||
| `PUID` | `1000` | UID, unter der die Anwendung läuft |
|
||||
| `PGID` | `1000` | GID, unter der die Anwendung läuft |
|
||||
| `RECORDING_ROOTS` | `/app/data/recordings:/recordings` | Erlaubte Basisverzeichnisse für `download_path` (durch `:` getrennt) |
|
||||
| `LOG_RETENTION_DAYS` | `90` | Aufbewahrung der Log-Einträge in Tagen |
|
||||
| `ENABLE_API_DOCS` | — | `1` aktiviert die Swagger-UI unter `/docs` |
|
||||
|
||||
Der Container startet als root, passt UID/GID des internen Users `app` an `PUID`/`PGID` an, setzt den Besitzer von `data/` und den vorhandenen Verzeichnissen aus `RECORDING_ROOTS` (nur abweichende Dateien) und startet die Anwendung anschließend ohne Root-Rechte.
|
||||
|
||||
Eigener Aufnahmepfad, z.B. auf einem separaten Laufwerk:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- /mnt/storage/recordings:/recordings
|
||||
```
|
||||
|
||||
Danach in den Einstellungen `download_path` auf `/recordings` setzen. Andere Pfade müssen zusätzlich in `RECORDING_ROOTS` eingetragen werden.
|
||||
|
||||
## Beispiel
|
||||
|
||||
```yaml
|
||||
@@ -79,7 +119,7 @@ server:
|
||||
auth:
|
||||
enabled: true
|
||||
username: admin
|
||||
password: mein-passwort
|
||||
password: mein-passwort # wird beim Start in password_hash umgewandelt
|
||||
|
||||
schedule:
|
||||
default_pre_buffer: 5
|
||||
|
||||
+4
-2
@@ -23,7 +23,7 @@ Push-Nachrichten per [ntfy.sh](https://ntfy.sh) bei verschiedenen Events.
|
||||
|
||||
### Authentifizierung
|
||||
|
||||
Für private Topics kann ein Bearer-Token in den Einstellungen hinterlegt werden.
|
||||
Für private Topics kann ein Bearer-Token in den Einstellungen hinterlegt werden. Der Token wird nach dem Speichern nicht mehr von der API ausgegeben.
|
||||
|
||||
---
|
||||
|
||||
@@ -54,4 +54,6 @@ Die Plik-URL wird in der Datenbank gespeichert und im Dashboard bei den **Letzte
|
||||
|
||||
### Segment-Upload
|
||||
|
||||
Bei Segment-Splitting werden alle Segmente einzeln hochgeladen. Die NTFY-Nachricht enthält die Anzahl der hochgeladenen Segmente.
|
||||
Bei Segment-Splitting werden nur die Segmente der jeweiligen Aufnahme einzeln hochgeladen. Wurde die Aufnahme nach Verbindungsabbrüchen fortgesetzt, werden alle Teile hochgeladen. Die NTFY-Nachricht enthält die Anzahl der hochgeladenen Segmente.
|
||||
|
||||
Der Plik-API-Key wird wie der NTFY-Token nach dem Speichern nicht mehr von der API ausgegeben.
|
||||
|
||||
Reference in New Issue
Block a user