Bei Hash-Version 1 sind nur 7 von 24 Spalten gehasht. Ein DB-Schreibzugriff konnte eine LOGIN_FAILED-Zeile auf success=1 setzen, das Label umschreiben und errorMessage leeren - alles Nicht-Hash-Felder, Hash unveraendert - und /verify meldete weiterhin valid=true. Ein Einbruchsversuch war unsichtbar in einen Erfolg umschreibbar. Rueckwirkend signieren geht nicht, ein Rehash waere die falsche Medizin. Stattdessen ein einmaliges, nicht destruktives Bestandssiegel: je Altzeile ein Blattwert, die Wurzel darueber in einem HMAC-signierten Marker. Umgesetzt nach den vier Bedingungen aus dem Pentest: 1. Blaetter ueber den vollen Zeileninhalt inkl. id und hashVersion, nicht ueber den 7-Feld-V1-Hash - sonst lebte die Luecke im Siegel weiter. 2. Wurzel signiert (steht im Marker, der selbst V3/HMAC ist). Ohne AUDIT_HMAC_KEY wird das Siegeln abgelehnt. 3. Bereich fix auf [1 ... v3FromId-1] statt Live-Abfrage hashVersion < 3. Sonst haette ein Up-Flip der Grenzzeile sie aus der geprueften Menge gedraengt. 4. Pruefung je id: vorhanden, weiterhin Altbestand, Inhalt == Blatt, dazu Wurzelabgleich. Neuer Endpunkt POST /audit-logs/seal-backlog (audit:admin, confirm SEAL). /verify meldet den Siegelzustand im Klartext, auch wenn kein Siegel existiert. Verifiziert in separater Wegwerf-DB mit Mischbestand (6xV1, 5xV2, 5xV3): ohne Siegel ist der Angriff unsichtbar, mit Siegel wird er erkannt und die Zeile benannt; der Up-Flip der Grenzzeile wird ebenfalls erkannt. Wegwerf-DB danach geloescht, Dev-Daten unberuehrt. tsc + vite build gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import { Router } from 'express';
|
||
import { authenticate, requirePermission } from '../middleware/auth.js';
|
||
import * as auditLogController from '../controllers/auditLog.controller.js';
|
||
|
||
const router = Router();
|
||
|
||
// Alle Routen erfordern Authentifizierung
|
||
router.use(authenticate);
|
||
|
||
// ACHTUNG Reihenfolge: Alle konkreten Pfade MÜSSEN vor der Parameter-Route
|
||
// '/:id' stehen, sonst schluckt diese sie und antwortet mit
|
||
// "Ungültige Audit-Log-ID". Genau so war GET /retention-policies unerreichbar
|
||
// (Pentest R166-04).
|
||
|
||
// Audit-Logs abrufen
|
||
router.get('/', requirePermission('audit:read'), auditLogController.getAuditLogs);
|
||
|
||
// Audit-Logs exportieren
|
||
router.get('/export', requirePermission('audit:read'), auditLogController.exportAuditLogs);
|
||
|
||
// Retention-Policies
|
||
router.get('/retention-policies', requirePermission('audit:admin'), auditLogController.getRetentionPolicies);
|
||
router.put('/retention-policies/:id', requirePermission('audit:admin'), auditLogController.updateRetentionPolicy);
|
||
|
||
// Audit-Logs für einen Kunden (DSGVO)
|
||
router.get('/customer/:customerId', requirePermission('audit:read'), auditLogController.getAuditLogsByCustomer);
|
||
|
||
// Hash-Ketten-Integrität prüfen
|
||
router.post('/verify', requirePermission('audit:read'), auditLogController.verifyIntegrity);
|
||
|
||
// Hash-Kette reparieren
|
||
router.post('/rehash', requirePermission('audit:admin'), auditLogController.rehashAll);
|
||
|
||
// Altbestand versiegeln (nicht destruktiv)
|
||
router.post('/seal-backlog', requirePermission('audit:admin'), auditLogController.sealBacklog);
|
||
|
||
// Retention-Cleanup manuell ausführen
|
||
router.post('/cleanup', requirePermission('audit:admin'), auditLogController.runRetentionCleanup);
|
||
|
||
// Einzelnes Audit-Log abrufen – als LETZTE GET-Route, siehe Hinweis oben
|
||
router.get('/:id', requirePermission('audit:read'), auditLogController.getAuditLogById);
|
||
|
||
export default router;
|