From 93cb5ffd270718546048a0b8d71c9fc01b741356 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Wed, 19 Aug 2026 19:06:35 +0200 Subject: [PATCH] Doku: Audit-Siegel (AUDIT_HMAC_KEY) verstaendlich erklaert README und .env.example erklaeren jetzt zuerst in Alltagssprache, was das Audit-Log ist, wogegen der Schluessel schuetzt und was passiert, wenn man ihn weglaesst oder verliert - inklusive Bild vom Notarstempel. Die technische Ebene bleibt vollstaendig erhalten, in der README als aufklappbarer Block (Hash-Versionen 1/2/3, Version-Floor, Loeschungs-Manifest, Rueckgabefelder von /verify, bekannte Grenze) und in .env.example als eigener Abschnitt. Ausserdem dokumentiert: Schluesselwechsel ueber AUDIT_HMAC_KEY_OLD ohne Rehash, pro Umgebung ein eigener Schluessel, und die Warnung zu den beiden Endpunkten mit Nebenwirkung (rehash/cleanup). AUDIT_HMAC_KEY zusaetzlich in die Production-Checkliste aufgenommen. Korrigiert: Die Integritaetspruefung ist NICHT ueber die Oberflaeche erreichbar (verifyIntegrity wird von keiner Komponente genutzt) - die Doku zeigt jetzt den API-Aufruf. Rechte, Endpunkte und Rueckgabefelder gegen den Code geprueft. Co-Authored-By: Claude Opus 5 --- README.md | 146 +++++++++++++++++++++++++++++++++++++++++++ backend/.env.example | 70 +++++++++++++++++---- 2 files changed, 203 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 907ac08f..af111c7f 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,10 @@ LISTEN_ADDR=127.0.0.1 # Bei separatem Frontend-Host: erlaubte Origins CORS_ORIGINS=https://crm.deine-domain.de + +# Siegel fuer das Audit-Log – schuetzt die Beweisspur vor nachtraeglicher +# Faelschung. Siehe Abschnitt "Audit-Siegel" weiter unten. +AUDIT_HMAC_KEY=$(openssl rand -hex 32) ``` ### Deployment-Modus: On-Prem vs. Cloud @@ -298,6 +302,148 @@ SSRF-Schalter: SSRF_BLOCK_PRIVATE_IPS=true ``` +### Audit-Siegel (`AUDIT_HMAC_KEY`) + +**Worum geht es?** +OpenCRM führt ein Audit-Log: eine lückenlose Aufzeichnung, wer wann welche +Daten gesehen oder geändert hat. Das ist die Beweisspur, wenn es Streit gibt, +etwas verschwindet oder der Datenschutz nachfragt. + +Damit diese Aufzeichnung etwas wert ist, muss man ihr ansehen können, ob +jemand nachträglich daran herumgeschrieben hat. Dafür trägt jeder Eintrag +einen Fingerabdruck, der auch den Fingerabdruck des vorherigen Eintrags +enthält – wie eine Kette. Ändert jemand einen alten Eintrag, passen die +Fingerabdrücke nicht mehr und es fällt auf. + +**Und wozu dann noch ein Schlüssel?** +Weil jemand mit Zugriff auf die Datenbank die ganze Kette neu berechnen +könnte. Er ändert also einen Eintrag – zum Beispiel einen fehlgeschlagenen +Login-Versuch in einen erfolgreichen – und zieht anschließend alle +Fingerabdrücke glatt. Danach sieht die Fälschung echt aus. + +Mit `AUDIT_HMAC_KEY` bekommt jeder Eintrag zusätzlich ein **Siegel**, das sich +nur mit diesem Schlüssel erzeugen lässt. Der Schlüssel liegt in der +`.env`-Datei, also **außerhalb der Datenbank**. Wer nur die Datenbank in die +Hände bekommt, kann damit nichts fälschen, ohne dass es auffliegt. + +> Bildlich: Die Fingerabdruck-Kette ist die fortlaufende Nummerierung der +> Aktenseiten. Der Schlüssel ist der Stempel des Notars. Die Akte steht zwar +> im Schrank – aber den Stempel hat nur der Notar. + +**Einrichten** + +```bash +# Einmalig pro Umgebung einen Schlüssel erzeugen +openssl rand -hex 32 +``` + +Den Wert in die `.env` der jeweiligen Umgebung eintragen. Wichtig: **pro +Umgebung ein eigener Schlüssel** (Entwicklung, Test, Produktion) – und +niemals ins Git-Repository. + +**Häufige Fragen** + +| Frage | Antwort | +|---|---| +| Was, wenn ich den Schlüssel gar nicht setze? | Nichts fällt aus. Das Audit-Log läuft normal weiter, nur ohne dieses zusätzliche Siegel. | +| Was, wenn ich ihn verliere? | Die damit gesiegelten Einträge lassen sich nicht mehr prüfen. Sie gelten dann als **„nicht prüfbar"** – ausdrücklich nicht als gefälscht. Kein Fehlalarm, aber der Nachweis für diesen Zeitraum ist weg. Deshalb: sichern wie ein Passwort. | +| Muss ich ihn irgendwo eintragen außer in der `.env`? | Nein. Einmal setzen, Backup anlegen, fertig. | +| Verlangsamt das etwas? | Nein, spürbar nicht. | + +**Schlüssel wechseln (`AUDIT_HMAC_KEY_OLD`)** + +Möchtest du den Schlüssel austauschen – etwa weil du vermutest, dass er in +falsche Hände geraten ist – geht das ohne Datenverlust: + +```env +AUDIT_HMAC_KEY= +AUDIT_HMAC_KEY_OLD= +``` + +Neue Einträge werden ab sofort mit dem neuen Schlüssel gesiegelt, die +bisherigen bleiben über den alten Schlüssel weiterhin prüfbar. Nach einer +Übergangszeit kann `AUDIT_HMAC_KEY_OLD` geleert werden – danach sind die alten +Einträge allerdings nicht mehr prüfbar. + +**Prüfen, ob alles in Ordnung ist** + +Die Prüfung läuft derzeit nur über die API (eine Schaltfläche in der Oberfläche +gibt es dafür noch nicht) – als angemeldeter Benutzer mit dem Recht +`audit:read`: + +```bash +curl -X POST https://crm.deine-domain.de/api/audit-logs/verify \ + -H "Authorization: Bearer " +``` + +Die Antwort enthält einen Klartext-Satz im Feld `message` und unterscheidet +zwei Dinge: + +- **„Manipulierte Einträge"** – jemand hat einen bestehenden Eintrag + nachträglich verändert. Das ist ernst. +- **„Strukturelle Lücken"** – die Kette hat eine Unterbrechung, die Inhalte + sind aber unverändert. Meist harmlos (z. B. gelöschte alte Einträge). + Notiere dir die Zahl nach dem ersten Deploy: Bleibt sie konstant, ist alles + in Ordnung. Steigt sie, lohnt ein Blick. + +
+Technische Details (für Entwickler/Admins) + +**Verfahren.** Jeder Audit-Eintrag trägt einen Hash über seine Inhaltsspalten +plus den Hash des Vorgängers (`previousHash`) – daraus entsteht die Kette. Es +gibt drei Hash-Versionen, die Spalte `hashVersion` hält fest, welche verwendet +wurde: + +| Version | Verfahren | Abdeckung | +|---|---|---| +| 1 | SHA-256 | 7 Felder – Altbestand vor der Härtung | +| 2 | SHA-256 | alle 24 Inhaltsspalten, unsigniert | +| 3 | **HMAC-SHA256** mit `AUDIT_HMAC_KEY` | alle 24 Inhaltsspalten, signiert | + +Bestandsdaten werden **nicht** nachträglich neu berechnet – ein Rehash würde die +Beweiskraft der Vergangenheit überschreiben. Alte Einträge bleiben mit ihrer +Version gültig. + +**Version-Floor.** Die zu erwartende Version wird aus der Kette abgeleitet +(erste je mit Version *n* geschriebene Zeile), **nicht** aus der Selbstauskunft +der Zeile. Sonst ließe sich per Downgrade (`hashVersion` 3 → 1) die schwächere +Prüfung erzwingen und anschließend ein gültiger Hash über die wenigen +abgedeckten Felder nachziehen. Zusätzlich gilt: Eine unerklärte Lücke direkt vor +einer signierten Zeile ist ein Befund, kein struktureller Zufall – deren +`previousHash` lässt sich ohne Schlüssel nicht fälschen. + +**Löschungen.** `POST /api/audit-logs/cleanup` schreibt ein Manifest +(ID-Bereich, Anzahl, Policy, Cutoff) als eigenen verketteten Eintrag. Die +Prüfung liest es aus und meldet nur Lücken **ohne** dokumentierte Löschung als +erklärungsbedürftig (`unexplainedGaps`) – sonst könnte sich eine böswillige +Löschung als harmlose Lücke tarnen. + +**Rückgabe von `POST /api/audit-logs/verify`:** + +| Feld | Bedeutung | +|---|---| +| `tamperedEntries` | Inhalt nachträglich verändert, Versions-Downgrade oder gebrochener Anker – **ernst** | +| `chainGaps` | Verkettung unterbrochen (alle) | +| `unexplainedGaps` | Teilmenge davon ohne dokumentierte Löschung | +| `unverifiableEntries` | signiert, aber kein Schlüssel konfiguriert – **kein** Manipulationsverdacht | + +**Fail-safe.** Ohne `AUDIT_HMAC_KEY` schreibt der Dienst weiter Version 2; das +Logging fällt nie wegen fehlender Konfiguration aus. + +**Bekannte Grenze.** Der Anker schützt gegen reinen Datenbank-Schreibzugriff. +Wer Schlüssel *und* Datenbank kontrolliert, kann die Kette konsistent neu +rechnen. Eine Off-Site-Notarisierung (regelmäßiger Export/Versiegelung des +Kettenkopfes außerhalb des Systems) wäre die nächste Stufe. + +**Achtung – zwei Endpunkte mit Nebenwirkung:** +`POST /api/audit-logs/rehash` berechnet alle Hashes neu; danach meldet die +Prüfung überall „gültig", aber eine bestehende Fälschung würde mitbesiegelt. +`POST /api/audit-logs/cleanup` löscht nach Aufbewahrungsregeln, jede gelöschte +Zeile erzeugt eine Lücke. Beide laufen nur manuell und schreiben einen Marker +ins Log. + +
+ Cloud-Metadata-Endpoints (`169.254.169.254`, `metadata.google.internal` etc.) sind UNABHÄNGIG vom Flag **immer** geblockt – das ist Mindestschutz gegen AWS/GCP/Azure-IMDS-Diebstahl. diff --git a/backend/.env.example b/backend/.env.example index 8ae33db2..4bc520f6 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -20,20 +20,64 @@ ENCRYPTION_KEY="32-byte-hex-key-for-aes-256-gcm" PORT=3001 NODE_ENV=development -# ==================== AUDIT-ANKER ==================== -# Signiert Audit-Log-Eintraege per HMAC-SHA256 mit einem Schluessel, der NICHT -# in der Datenbank liegt. Damit reicht ein reiner DB-Schreibzugriff nicht mehr -# aus, um die Hash-Kette glaubwuerdig umzuschreiben (Pentest R166/R167). +# ==================== AUDIT-SIEGEL ==================== +# Was ist das Audit-Log? +# OpenCRM protokolliert luekenlos, wer wann welche Daten gesehen oder +# geaendert hat. Das ist die Beweisspur, wenn es Streit gibt oder der +# Datenschutz nachfragt. # -# Erzeugen: openssl rand -hex 32 -# Fail-safe: Ohne Schluessel schreibt das Audit-Log weiter die unsignierte -# Version 2 - es faellt nichts aus, der Anker fehlt nur. -# ACHTUNG: Schluessel sichern. Geht er verloren, sind alle damit signierten -# Eintraege nicht mehr pruefbar (sie gelten dann als "nicht pruefbar", -# NICHT als manipuliert). +# Wogegen schuetzt dieser Schluessel? +# Ohne ihn koennte jemand mit Zugriff auf die Datenbank einen Eintrag +# nachtraeglich umschreiben - zum Beispiel einen fehlgeschlagenen +# Login-Versuch in einen erfolgreichen verwandeln - und die Faelschung so +# glattziehen, dass die Pruefung sie fuer echt haelt. +# Mit dem Schluessel bekommt jeder Eintrag ein Siegel, das sich nur mit +# diesem Schluessel erzeugen laesst. Wer ihn nicht hat, kann nichts +# faelschen, ohne dass es auffliegt. Bildlich: Der Schluessel ist der +# Stempel des Notars - die Akte liegt zwar im Schrank, aber den Stempel +# hat nur der Notar. +# +# Wie einrichten? +# Einmalig einen Schluessel erzeugen und hier eintragen: +# openssl rand -hex 32 +# Pro Umgebung ein EIGENER Schluessel (Entwicklung, Test, Produktion). +# +# Was passiert, wenn ich ihn weglasse? +# Nichts faellt aus. Das Audit-Log laeuft normal weiter, nur eben ohne +# dieses zusaetzliche Siegel. +# +# Was passiert, wenn ich ihn verliere? +# Die damit gesiegelten Eintraege lassen sich nicht mehr pruefen. Sie +# gelten dann als "nicht pruefbar" - NICHT als gefaelscht. Es gibt also +# keinen Fehlalarm, aber der Nachweis fuer diesen Zeitraum ist weg. +# Deshalb: Schluessel sichern, so wie ein Passwort. AUDIT_HMAC_KEY= -# Nur waehrend eines Schluesselwechsels setzen: der vorherige Schluessel wird -# bei der Pruefung zusaetzlich akzeptiert, damit Altbestand ohne Rehash gueltig -# bleibt. Nach dem Wechsel wieder leeren. +# Nur voruebergehend beim Schluesselwechsel setzen. +# Moechtest du den Schluessel oben austauschen (z. B. weil du vermutest, +# dass er in falsche Haende geraten ist), trage den ALTEN Schluessel hier +# ein und den NEUEN oben. Dann bleiben die bisherigen Eintraege pruefbar, +# waehrend neue schon mit dem neuen Schluessel gesiegelt werden. +# Nach ein paar Wochen kann dieses Feld wieder geleert werden - danach +# sind die alten Eintraege allerdings nicht mehr pruefbar. AUDIT_HMAC_KEY_OLD= + +# --- Technisch (fuer Entwickler/Admins) --------------------------------- +# Verfahren : HMAC-SHA256 ueber alle Inhaltsspalten eines Audit-Eintrags, +# inkl. previousHash (Verkettung). Entspricht Hash-Version 3. +# Abdeckung : Version 1 = 7 Felder (Altbestand), Version 2 = alle 24 +# Inhaltsspalten (SHA-256, unsigniert), Version 3 = wie 2, aber +# HMAC-signiert. Die erwartete Version wird aus der Kette +# abgeleitet (Version-Floor), nicht aus der Selbstauskunft der +# Zeile - sonst liesse sich per Downgrade die schwaechere +# Pruefung erzwingen. +# Fail-safe : Ohne Schluessel schreibt der Dienst weiter Version 2. Bereits +# signierte Zeilen landen dann in `unverifiableEntries`, +# ausdruecklich NICHT in `tamperedEntries`. +# Rotation : AUDIT_HMAC_KEY_OLD wird bei der Pruefung zusaetzlich +# akzeptiert - Wechsel ohne Rehash. Ein Rehash waere ohnehin zu +# vermeiden, er wuerde die Beweiskraft der Vergangenheit +# ueberschreiben. +# Grenze : Schuetzt gegen DB-Schreibzugriff ohne Schluessel. Wer Schluessel +# UND Datenbank hat, kann die Kette konsistent neu rechnen. +# Erzeugung : openssl rand -hex 32 (256 Bit)