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:
@@ -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 (von–bis), 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_
|
||||
Reference in New Issue
Block a user