internal/audit: eigenes, strukturiertes Audit-Datenmodell (Akteur, Aktion, Zielobjekt, Zeitpunkt, Tenant) in der Registry-DB, getrennt von jedem allgemeinen Anwendungs-Log (eigenes Paket, eigene Tabelle audit_events, kein Logging-Framework). Log.Record ist der EINE zentrale Schreibpfad — es gibt keine zweite Schreibmoeglichkeit, ueber die ein Handler die Validierung umgehen koennte. Fehlender Tenant-Bezug wird zweifach verhindert (Akzeptanzkriterium 2): Log.Record weist leeren TenantSlug direkt ab (ErrMissingTenant), zusaetzlich erzwingt eine CHECK-Constraint in der Migration dasselbe auf Datenbankebene, selbst wenn Log.Record umgangen wuerde. Mandantenuebergreifende Ereignisse (z.B. Superadmin-Aktionen) nutzen den reservierten Wert audit.SystemTenant statt NULL oder leerem String — es gibt keinen Weg, ganz ohne Tenant-Bezug zu schreiben. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Automatisierter Test belegt genau einen Audit-Eintrag pro sicherheitsrelevantem Vorgang — TestRecord_PersistsExactlyOneEventPerSecurityIncident (simulierter fehlgeschlagener Login), Feldinhalte verifiziert. PASS. 2. Fehlender Tenant-Bezug durch Constraint/Test verhindert — TestRecord_RejectsMissingTenant (App-Ebene) UND TestConstraint_RejectsMissingTenantAtDatabaseLevel (direkter INSERT unter Umgehung von Log.Record, durch CHECK-Constraint abgewiesen). PASS. 3. Datenmodell von zweiter Person gegen Dokumentation geprueft — NICHT durchgefuehrt (keine zweite Person in dieser Session verfuegbar). Offen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 lines
898 B
SQL
18 lines
898 B
SQL
-- Zentrales Audit-Log-Modell (AUD-01, siehe core-kanban/tickets/AUD-01.md).
|
|
-- Getrennt vom allgemeinen Anwendungs-Log (Akzeptanzkriterium 2): eigene
|
|
-- Tabelle, eigenes Paket (internal/audit), kein Log-Framework.
|
|
-- tenant_slug ist NOT NULL + darf nicht leer sein (Akzeptanzkriterium 2 /
|
|
-- Pruefung 2) — mandantenuebergreifende Ereignisse nutzen den reservierten
|
|
-- Wert 'system', niemals NULL oder leeren String.
|
|
CREATE TABLE audit_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
tenant_slug TEXT NOT NULL CHECK (tenant_slug <> ''),
|
|
actor TEXT NOT NULL CHECK (actor <> ''),
|
|
action TEXT NOT NULL CHECK (action <> ''),
|
|
target TEXT NOT NULL,
|
|
metadata JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX audit_events_tenant_slug_idx ON audit_events (tenant_slug, occurred_at);
|