feat(PROJ-17): Admin Dashboard Systemauslastung immer anzeigen

- Systemauslastungs-Sektion wird immer gerendert (nicht nur bei Erfolg)
- Fehlermeldung wenn /api/admin/system/stats nicht erreichbar ist
- Feature-Status auf In Review gesetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-14 11:43:19 +01:00
parent a893084a88
commit d360c9a5ba
68 changed files with 11938 additions and 435 deletions
+145
View File
@@ -0,0 +1,145 @@
# PROJ-11: Audit-Log & Compliance-Berichte
## Status: In Progress
**Created:** 2026-03-12
**Last Updated:** 2026-03-13
## Dependencies
- Requires: PROJ-1 (Authentifizierung) Audit-Einträge sind an Nutzer geknüpft
## User Stories
- Als Admin möchte ich sicherheitsrelevante Ereignisse im Audit-Log einsehen, damit ich Zugriffe und Änderungen nachvollziehen kann.
- Als Admin möchte ich den Audit-Log nach Datum, Nutzer und Ereignistyp filtern.
- Als Admin möchte ich den Audit-Log als CSV exportieren, damit ich ihn für externe Prüfungen verwenden kann.
## Erfasste Ereignisse
**Ja wird geloggt:**
- Login (Erfolg und Fehlschlag) inkl. IP-Adresse
- Logout
- Suchanfragen (Suchbegriff, Anzahl Treffer, Nutzer)
- Import gestartet / abgeschlossen / fehlgeschlagen (Quelle, Anzahl E-Mails)
- Export gestartet / abgeschlossen (Format, Anzahl E-Mails)
**Nein wird nicht geloggt:**
- Lesezugriff auf einzelne E-Mails (kein per-Mail-Leselogging)
## Acceptance Criteria
- [ ] Jeder Log-Eintrag enthält: Zeitstempel (UTC), Nutzer-ID, Ereignistyp, Details (z.B. Suchbegriff, Import-Quelle), IP-Adresse
- [ ] Audit-Log-Ansicht **nur für Auditoren** unter `/audit/` kein Zugriff für `admin` oder `user`
- [ ] Filterung nach: Datum (vonbis), Nutzer, Ereignistyp
- [ ] Paginierung (50 Einträge pro Seite)
- [ ] Export als CSV (gefilterte oder vollständige Ansicht, Streaming-Download)
- [ ] Einträge sind unveränderlich: kein UPDATE/DELETE durch Admin oder Anwendung möglich
- [ ] Retention konfigurierbar in `config.yml` (`audit.retention_days`), kein Standardwert erzwungen
- [ ] Doppelte Speicherung: PostgreSQL (für GUI-Abfragen) + Append-only Logdatei auf Disk (als unveränderliches Backup)
- [ ] Logdatei-Format: JSON Lines (ein Eintrag pro Zeile)
## Edge Cases
- Audit-Log über Jahre sehr groß → paginierte DB-Abfragen mit Index auf `(timestamp, event_type, user_id)`, kein Full-Table-Scan
- Nutzer DSGVO-gelöscht → Audit-Einträge behalten, `user_id` durch `"anonymized"` ersetzen, IP-Adresse löschen
- Logdatei nicht beschreibbar beim Start → Warnung loggen, Dienst läuft weiter (DB-Log bleibt aktiv)
- Gleichzeitige Schreibzugriffe auf Logdatei → Append mit file lock
## Technical Requirements
- PostgreSQL: separate Tabelle `audit_log`, kein DELETE/UPDATE per DB-Constraint (Row-Level Security oder Trigger)
- Logdatei: `/var/log/archivmail/audit.log` (Pfad konfigurierbar), append-only, JSON Lines
- Log-Rotation über `logrotate` (extern konfiguriert), Datei wird nie vom Dienst selbst rotiert oder gelöscht
- Zeitstempel immer UTC (RFC 3339)
---
## Tech Design (Solution Architect)
### Komponentenstruktur
**Next.js Frontend (`/audit/*`):**
```
/audit ← nur für Auditoren (RoleGuard)
└── AuditTable
├── Spalten: Zeitstempel, Nutzer, Ereignis, Details, IP
├── Filter-Leiste
│ ├── Datepicker: von bis
│ ├── Dropdown: Nutzer auswählen
│ └── Dropdown: Ereignistyp
├── Paginierung (50 pro Seite)
└── [CSV exportieren] Button → Streaming-Download
```
**Go Backend:**
```
Audit-Service (intern, kein eigener HTTP-Handler)
├── WriteEvent(event AuditEvent)
│ ├── → INSERT INTO audit_log (kein UPDATE/DELETE je möglich)
│ └── → Append zu /var/log/archivmail/audit.log (JSON Line)
│ └── File-Lock beim Schreiben (sync.Mutex)
└── Audit-API (nur für Auditoren)
├── GET /api/audit/events ← Paginiert, gefiltert
└── GET /api/audit/export ← Streaming-CSV-Download
```
### Datenmodell
**Tabelle `audit_log`** (append-only via DB-Trigger):
| Feld | Beschreibung |
|------|-------------|
| `id` | Sequentielle ID |
| `timestamp` | UTC, RFC 3339 |
| `user_id` | Nutzer-ID (NULL nach DSGVO-Löschung) |
| `user_email` | E-Mail zum Zeitpunkt des Events (für Lesbarkeit nach Anonymisierung) |
| `event_type` | `login_ok`, `login_fail`, `logout`, `search`, `import_start`, `import_done`, `import_fail`, `export_start`, `export_done` |
| `details` | JSON: Suchbegriff / Import-Quelle / Anzahl / etc. |
| `ip_address` | IPv4/IPv6 (NULL nach DSGVO-Löschung) |
**DB-Constraint:** PostgreSQL-Trigger verhindert `UPDATE` und `DELETE` auf der gesamten Tabelle → physische Unveränderlichkeit.
**Logdatei-Format** (`/var/log/archivmail/audit.log`, JSON Lines):
```
{"ts":"2024-03-01T10:00:00Z","user":"alice@firma.de","event":"search","details":{"q":"Rechnung","hits":42},"ip":"192.168.1.1"}
```
### Schreibfluss
```
Beliebige Aktion (Login, Suche, Import...)
audit.WriteEvent() aufgerufen
├── PostgreSQL INSERT (non-blocking, Goroutine)
└── File-Append mit sync.Mutex
(Datei nicht beschreibbar? → Warnung auf stderr, Dienst läuft weiter)
```
### DSGVO-Löschfluss (Nutzer anonymisieren)
```
DELETE /api/admin/users/{id}
├── audit_log: user_id → NULL, ip_address → NULL
│ user_email → "anonymized"
└── Logdatei: bleibt unverändert (tamper-evident)
```
### Technische Entscheidungen
| Entscheidung | Begründung |
|---|---|
| **Audit-Ansicht nur für Auditoren** | Strict role separation — Admin hat keine Einsicht in Zugriffsprotokolle |
| **DB-Trigger für Unveränderlichkeit** | Applikationscode kann versehentlich löschen — Trigger ist eine härtere Garantie |
| **Doppelte Speicherung** | DB für GUI-Abfragen; Logdatei als tamper-evident Backup für externe Prüfungen |
| **logrotate extern** | Dienst rotiert nie selbst — Logdatei bleibt unter Systemadmin-Kontrolle |
| **DSGVO: IP-Adresse löschen, Event behalten** | Personenbezug entfernen, Compliance-Nachweis bleibt erhalten |
| **Composite Index `(timestamp, event_type, user_id)`** | Schnelle gefilterte Abfragen auch bei sehr großem Log über Jahre |
### Abhängigkeiten
**Go Backend:** Nur Stdlib + pgx (bereits vorhanden).
**Next.js:** shadcn/ui Table, Select, DatePicker (bereits installiert).
## QA Test Results
_To be added by /qa_
## Deployment
_To be added by /deploy_