M8: Backup/Restore-Prozess und Monitoring dokumentiert
docs/betrieb-backup-monitoring.md: taeglicher mysqldump-Cron-Job mit Aufbewahrung, Restore-Befehl inklusive Hinweis auf scripts/migrate.php, vierteljaehrlicher Restore-Test. Produktive PHP-Fehlerkonfiguration (display_errors aus, log_errors an) und die aktiv zu beobachtenden Signale ohne dediziertes APM-Tool: audit_log fuer ungewoehnliche Admin-Aktionen, rate_limit_attempts fuer Brute-Force-Versuche, outbound_emails.status=failed fuer Mailversand-Probleme. Damit ist M8 fuer den geplanten Scope abgeschlossen, mit einer bewusst offenen Ausnahme (Content-Security-Policy, braucht Template-Bereinigung der bestehenden Inline-Styles). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
|||||||
|
# Backup, Restore und Monitoring im Produktivbetrieb
|
||||||
|
|
||||||
|
Stand: 2026-07-15
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt die für den Produktivbetrieb auf dem Webspace
|
||||||
|
empfohlenen Backup-, Restore- und Monitoring-Maßnahmen. Es ergänzt
|
||||||
|
`docs/betrieb-mail.md` und `docs/m8-haertung.md`.
|
||||||
|
|
||||||
|
## Backups
|
||||||
|
|
||||||
|
Die App hat kein eigenes Backup-Tooling; das ist bei einer MySQL-Datenbank
|
||||||
|
auf Webspace-Hosting bewusst nicht nötig – `mysqldump` reicht.
|
||||||
|
|
||||||
|
Empfohlener täglicher Cron-Job (Zeitpunkt außerhalb der Hauptnutzungszeit):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
||||||
|
BACKUP_DIR="/pfad/ausserhalb/des/webroots/backups"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
mysqldump \
|
||||||
|
--single-transaction \
|
||||||
|
--routines \
|
||||||
|
--triggers \
|
||||||
|
-h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
|
||||||
|
| gzip > "$BACKUP_DIR/kaffeeliste_${TIMESTAMP}.sql.gz"
|
||||||
|
|
||||||
|
# Aufbewahrung: 14 Tage taegliche Backups behalten, aeltere loeschen
|
||||||
|
find "$BACKUP_DIR" -name 'kaffeeliste_*.sql.gz' -mtime +14 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
Wichtige Punkte:
|
||||||
|
|
||||||
|
- `--single-transaction` sorgt für einen konsistenten Snapshot ohne
|
||||||
|
Tabellen zu sperren (InnoDB, wie hier durchgängig verwendet).
|
||||||
|
- Der Backup-Ordner muss **außerhalb** des über HTTP erreichbaren Webroots
|
||||||
|
liegen, sonst wären Dumps öffentlich abrufbar.
|
||||||
|
- Zusätzlich zu lokalen Backups auf dem Webspace mindestens eine Kopie an
|
||||||
|
einen anderen Ort übertragen (z. B. verschlüsselt in Cloud-Speicher),
|
||||||
|
damit ein Ausfall des Webspace-Anbieters nicht auch die Backups
|
||||||
|
vernichtet.
|
||||||
|
- `var/uploads`, `var/mail` und `var/sessions` enthalten keine dauerhaft
|
||||||
|
relevanten Daten (Uploads werden nach Verarbeitung gelöscht,
|
||||||
|
`var/mail` ist nur der Dev-Log-Transport) und müssen nicht gesichert
|
||||||
|
werden.
|
||||||
|
|
||||||
|
## Restore
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gunzip -c kaffeeliste_20260715_030000.sql.gz | mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach `php scripts/migrate.php` laufen lassen, falls das Backup älter als
|
||||||
|
die zuletzt eingespielten Migrationen ist (das Skript ist idempotent und
|
||||||
|
wendet nur fehlende Migrationen an).
|
||||||
|
|
||||||
|
**Restore-Test:** Mindestens einmal pro Quartal einen echten Restore gegen
|
||||||
|
eine separate Test-Datenbank durchführen und die App dagegen starten
|
||||||
|
(`scripts/run-dev-server.sh` mit den Test-DB-Zugangsdaten). Ein Backup, das
|
||||||
|
nie zurückgespielt wurde, ist kein verlässliches Backup.
|
||||||
|
|
||||||
|
## Monitoring und Fehlerlogging
|
||||||
|
|
||||||
|
### PHP-Fehlerausgabe
|
||||||
|
|
||||||
|
Im Dev-Modus zeigt `display_errors=1` Fehler direkt im Browser (siehe
|
||||||
|
`.local/php-dev.ini`) – das ist für lokale Entwicklung richtig, aber
|
||||||
|
produktiv ein Sicherheitsrisiko (Stacktraces, Dateipfade). Produktiv muss
|
||||||
|
gelten:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
display_errors = Off
|
||||||
|
log_errors = On
|
||||||
|
error_log = /pfad/ausserhalb/des/webroots/logs/php-error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Die meisten Webspace-Anbieter setzen das bereits serverseitig; im Zweifel
|
||||||
|
per `.htaccess` oder `ini_set()` am Anfang von `config.php` erzwingen.
|
||||||
|
|
||||||
|
### Was aktiv beobachtet werden sollte
|
||||||
|
|
||||||
|
- **`php-error.log`**: Fatal Errors und Warnings. Ein plötzlicher Anstieg
|
||||||
|
deutet meist auf eine kaputte Migration oder einen fehlerhaften Deploy
|
||||||
|
hin.
|
||||||
|
- **`audit_log`**: Ungewöhnliche Häufung sicherheitsrelevanter Aktionen
|
||||||
|
(`participant.access_granted`, `tenant_settings.updated`,
|
||||||
|
`year_end_bonus.distributed`, `tenant_data.exported`) außerhalb der
|
||||||
|
üblichen Nutzungszeiten eines Mandanten.
|
||||||
|
- **`rate_limit_attempts`**: Viele Treffer in kurzer Zeit für einen
|
||||||
|
`login_ip:*`- oder `login_email:*`-Bucket zeigen einen laufenden
|
||||||
|
Brute-Force-Versuch, auch wenn er durch das Rate-Limit bereits
|
||||||
|
abgewehrt wird.
|
||||||
|
- **`outbound_emails` mit `status = 'failed'`**: Deutet meist auf ein
|
||||||
|
Problem mit dem lokalen Mailversand hin (siehe
|
||||||
|
`docs/betrieb-mail.md`), zum Beispiel einen falsch konfigurierten
|
||||||
|
`APP_MAIL_FROM` oder ein Zustellproblem beim Webspace-Anbieter.
|
||||||
|
|
||||||
|
### Bewusst nicht umgesetzt
|
||||||
|
|
||||||
|
Ein dediziertes APM-/Monitoring-Tool (z. B. Uptime-Checks, strukturiertes
|
||||||
|
Log-Shipping, Alerting) ist für den aktuellen Umfang nicht eingerichtet.
|
||||||
|
Das lohnt sich erst mit echtem Produktivbetrieb und mehreren zahlenden
|
||||||
|
Mandanten; bis dahin reichen die oben genannten manuellen Prüfpunkte.
|
||||||
@@ -308,7 +308,7 @@ Nicht tun:
|
|||||||
| M5 | App-Kern | Weit fortgeschritten: Kernseiten lesen und schreiben tenant-sicher gegen das Ledger, inklusive Hinweise und rollenbasiertem Zugang; offen ist ein eigener Zahlungs-Screen |
|
| M5 | App-Kern | Weit fortgeschritten: Kernseiten lesen und schreiben tenant-sicher gegen das Ledger, inklusive Hinweise und rollenbasiertem Zugang; offen ist ein eigener Zahlungs-Screen |
|
||||||
| M6 | Betriebsflows | Abgeschlossen: Import, Export, Mail und Jahresabschluss sind auditierbar |
|
| M6 | Betriebsflows | Abgeschlossen: Import, Export, Mail und Jahresabschluss sind auditierbar |
|
||||||
| M7 | Landingpage | Public-Seite und Auth-Seiten sind im gemeinsamen Stil nutzbar; spätere Ausbaustufen folgen |
|
| M7 | Landingpage | Public-Seite und Auth-Seiten sind im gemeinsamen Stil nutzbar; spätere Ausbaustufen folgen |
|
||||||
| M8 | Härtung | Weit fortgeschritten: Security-Headers, Rate-Limits, Audit-Log, Mandanten-Isolations- und Rollenmatrix-Tests stehen; Datenexport, Löschung und Betriebs-Doku offen |
|
| M8 | Härtung | Abgeschlossen: Security-Headers, Rate-Limits, Audit-Log, Mandanten-Isolation/Rollenmatrix getestet, Datenexport, Löschung/Anonymisierung, Backup/Monitoring dokumentiert (CSP als bewusst offener Punkt) |
|
||||||
| M9 | Cutover | Produktivumstellung ist vorbereitet und Legacy ist read-only |
|
| M9 | Cutover | Produktivumstellung ist vorbereitet und Legacy ist read-only |
|
||||||
|
|
||||||
### M0: Planungs- und Sicherheitsbaseline
|
### M0: Planungs- und Sicherheitsbaseline
|
||||||
@@ -641,8 +641,14 @@ Die SaaS-App für mehrere Kunden sicher betreiben.
|
|||||||
|
|
||||||
Schritte:
|
Schritte:
|
||||||
|
|
||||||
- Backups und Restore-Prozess definieren.
|
- Backups und Restore-Prozess definieren: erledigt (dokumentiert).
|
||||||
- Monitoring und Fehlerlogging einrichten.
|
`docs/betrieb-backup-monitoring.md` beschreibt den täglichen
|
||||||
|
`mysqldump`-Cron-Job, Aufbewahrung, Restore-Befehl und den
|
||||||
|
vierteljährlichen Restore-Test.
|
||||||
|
- Monitoring und Fehlerlogging einrichten: erledigt (dokumentiert).
|
||||||
|
Produktive PHP-Fehlerkonfiguration sowie die aktiv zu beobachtenden
|
||||||
|
Signale (`audit_log`, `rate_limit_attempts`, `outbound_emails`) sind in
|
||||||
|
`docs/betrieb-backup-monitoring.md` festgehalten.
|
||||||
- Audit-Log für Admin-Aktionen prüfen: erledigt. Neue Tabelle `audit_log`,
|
- Audit-Log für Admin-Aktionen prüfen: erledigt. Neue Tabelle `audit_log`,
|
||||||
protokolliert Mitgliederverwaltung, Storno, Mandant-Einstellungen,
|
protokolliert Mitgliederverwaltung, Storno, Mandant-Einstellungen,
|
||||||
Hinweise, CSV-Import, Jahresbonus und Live-Mailversand; sichtbar für
|
Hinweise, CSV-Import, Jahresbonus und Live-Mailversand; sichtbar für
|
||||||
@@ -672,7 +678,9 @@ Schritte:
|
|||||||
Ergebnis:
|
Ergebnis:
|
||||||
|
|
||||||
- SaaS ist betrieblich und datenschutzseitig belastbarer.
|
- SaaS ist betrieblich und datenschutzseitig belastbarer.
|
||||||
- Stand: gestartet. Dokumentation: `docs/m8-haertung.md`.
|
- Stand: abgeschlossen für den M8-Scope, mit einer bewusst offenen
|
||||||
|
Ausnahme (Content-Security-Policy, siehe `docs/m8-haertung.md`).
|
||||||
|
Dokumentation: `docs/m8-haertung.md`, `docs/betrieb-backup-monitoring.md`.
|
||||||
|
|
||||||
Abhängigkeiten:
|
Abhängigkeiten:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user