d360c9a5ba
- 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>
6.0 KiB
6.0 KiB
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üradminoderuser - 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_iddurch"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