Versiegeln dokumentiert: Abschnitt aktualisiert und im Haupt-README verlinkt

Der Abschnitt in tools/audit-notary/README.md war an drei Stellen
ueberholt. Er sagte "nimm dein Administratorkonto" - seit der Aufteilung
von DSGVO und Audit-Betrieb hat die Admin-Rolle gar keine Audit-Rechte
mehr, das Recht kommt jetzt ueber den Haken "Audit-Betrieb". Er kannte
weder das RESEAL-Gate noch die Rehash-Anzeige noch den Zustand "leer".

Neu geschrieben, mit dem, was die Praxis gezeigt hat:
- Wer darf es, und warum nicht ueber DSGVO oder Admin
- Vier Felder, die vor dem Siegeln stimmen muessen - allen voran
  rehashes:[], denn nach einer Neuberechnung sagt chainGaps nichts mehr
  ueber die Zeit davor
- Den HTTP-Code beim Pruefen IMMER mit ausgeben: Token laufen nach 15
  Minuten ab, und ohne den Code liest sich eine 401 wie ein fehlender
  Eintrag. Genau das ist uns bei der Vorbereitung des Prod-Siegels
  passiert und haette fast zu einem falschen Stopp gefuehrt.
- Die drei Punkte zur Gegenpruefung danach, inklusive "intakt" statt
  "leer"
- Der Hinweis, dass ein unversiegeltes CRM nicht "bewacht mit Warnung"
  ist, sondern unbewacht: Das Gegenbuch bricht vor dem Anhaengen ab.

Ausserdem im Haupt-README verlinkt. Wer AUDIT_HMAC_KEY auf einer
bestehenden Installation setzt, hat zwangslaeufig einen Altbestand -
erfuhr davon aber nur, wenn er ein Gegenbuch betreibt. Die Anleitung gilt
auch ohne.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 19:07:12 +02:00
co-authored by Claude Opus 5
parent eb0580ac54
commit ed676ca4eb
2 changed files with 125 additions and 33 deletions
+21
View File
@@ -398,6 +398,27 @@ lässt das Siegel aus.
| Muss ich ihn irgendwo eintragen außer in der `.env`? | Nein. Einmal setzen, Backup anlegen, fertig. | | Muss ich ihn irgendwo eintragen außer in der `.env`? | Nein. Einmal setzen, Backup anlegen, fertig. |
| Verlangsamt das etwas? | Nein, spürbar nicht. | | Verlangsamt das etwas? | Nein, spürbar nicht. |
**Wichtig bei einer bestehenden Installation: den Altbestand versiegeln**
Das Siegel gilt nur für Einträge, die **ab** dem Setzen des Schlüssels
geschrieben werden. Alles, was vorher im Protokoll steht, bleibt ungeschützt
Änderungen daran wären nicht erkennbar. Die Integritätsprüfung sagt das auch:
Hinweis: Der Altbestand ist nicht versiegelt Änderungen daran wären
nicht erkennbar. Behebbar mit POST /api/audit-logs/seal-backlog
{"confirm":"SEAL"}.
Das ist ein **einmaliger** Schritt: Er zieht ein Siegel über den vorhandenen
Bestand, sodass spätere Änderungen daran auffallen. Der Ablauf wer es darf
(Haken **„Audit-Betrieb"** in der Benutzerverwaltung), was vorher zu prüfen ist
und wie man das Ergebnis gegenprüft steht ausführlich in
**[tools/audit-notary/README.md](tools/audit-notary/README.md)**, Abschnitt
*„Wenn der erste Lauf `exit=2` meldet: den Altbestand versiegeln"*. Die
Anleitung gilt auch ohne Gegenbuch.
Den Zustand siehst du jederzeit unter **Einstellungen → Audit-Protokoll**, ganz
oben.
**Schlüssel wechseln (`AUDIT_HMAC_KEY_OLD`)** **Schlüssel wechseln (`AUDIT_HMAC_KEY_OLD`)**
Möchtest du den Schlüssel austauschen etwa weil du vermutest, dass er in Möchtest du den Schlüssel austauschen etwa weil du vermutest, dass er in
+104 -33
View File
@@ -90,53 +90,124 @@ Welche laufen, steuert `COMPOSE_PROFILES` in der `.env`.
Ein CRM, das schon länger läuft, hat fast immer einen **Altbestand** Einträge Ein CRM, das schon länger läuft, hat fast immer einen **Altbestand** Einträge
aus der Zeit, bevor das Protokoll signiert wurde. Solange der nicht versiegelt aus der Zeit, bevor das Protokoll signiert wurde. Solange der nicht versiegelt
ist, meldet die Prüfung `valid: false`, und das Gegenbuch schlägt zu Recht ist, meldet die Prüfung `valid: false`, das Gegenbuch schlägt zu Recht Alarm
Alarm. Typischerweise steht dann im Log: und, wichtig: **es beglaubigt so lange gar nichts.** Es bricht vor dem Anhängen
ab, weil ein Checkpoint über einen ungeklärten Zustand diesen mitbeglaubigen
würde. Ein unversiegeltes CRM ist also nicht „bewacht mit Warnung", sondern
unbewacht.
Hinweis: Der Altbestand ist nicht versiegelt Änderungen daran wären Das Siegeln ist **einmalig** und passiert im CRM, nicht hier.
nicht erkennbar.
Das ist **einmalig** zu erledigen, im CRM, nicht hier: #### Wer darf das
Das Recht `audit:admin`. Das bekommt man über den Haken **„Audit-Betrieb"** in
der Benutzerverwaltung **nicht** über die Admin-Rolle (die hat bewusst keine
Audit-Rechte) und **nicht** über den DSGVO-Haken (der darf lesen, nicht
eingreifen). Das Gegenbuch-Dienstkonto hat es erst recht nicht.
```bash ```bash
TOKEN=$(curl -s -X POST https://<crm>/api/auth/login \ CRM=https://<crm>
-H 'Content-Type: application/json' \ read -s -p "Passwort: " PASS; echo
-d '{"email":"…","password":"…"}' | jq -r '.data.token') AT=$(curl -s -X POST $CRM/api/auth/login -H 'Content-Type: application/json' \
-d "{\"email\":\"…\",\"password\":\"$PASS\"}" | jq -r '.data.token')
curl -s -X POST https://<crm>/api/audit-logs/seal-backlog \ curl -s -X POST $CRM/api/auth/login -H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d "{\"email\":\"…\",\"password\":\"$PASS\"}" | jq -r '.data.user.permissions[]' | grep audit
-d '{"confirm":"SEAL"}' | jq
``` ```
Dafür braucht es das Recht `audit:admin` das Gegenbuch-Dienstkonto hat es Muss `audit:read` **und** `audit:admin` zeigen.
absichtlich **nicht**. Nimm dein Administratorkonto.
**Zuerst nachsehen, was versiegelt wird.** Das Siegel schreibt den aktuellen #### Erst nachsehen, was du festschreibst
Zustand fest, samt aller vorhandenen Lücken. Wer blind siegelt, beglaubigt
gegebenenfalls auch eine Lücke, die von einer Löschung stammt. Deshalb vorher: Das Siegel hält den **aktuellen** Zustand fest, samt aller vorhandenen Lücken.
Wer blind siegelt, beglaubigt womöglich eine Lücke, die von einer Löschung
stammt. Der Schritt ist praktisch einwegs: Danach ist die Beglaubigung tragend,
und ein Siegel wieder zu entfernen erzeugt den Zustand `entfernt` also selbst
einen Befund.
```bash ```bash
curl -s -X POST https://<crm>/api/audit-logs/verify \ B="Authorization: Bearer $AT"
-H "Authorization: Bearer $TOKEN" \ curl -s -X POST $CRM/api/audit-logs/verify -H "$B" \
| jq '.data | {valid, chainGaps, unexplainedGaps, tamperedEntries}' | jq '.data | {valid, checkedCount, rehashes, chainGaps, unexplainedGaps,
tamperedEntries, backlogSealStatus}'
``` ```
Stehen dort Lücken, sieh dir die betroffenen IDs und ihre Nachbarn an Vier Dinge müssen stimmen:
(`GET /api/audit-logs/<id>`). Liegen sie jeweils **innerhalb einer Sekunde**
zusammen mit ihren Nachbarn und fehlt kein Eintrag (kein `404`), sind es
Schreibkollisionen aus paralleln Anfragen harmlos. Fehlt dagegen ein Eintrag
oder steht etwas in `tamperedEntries`, **erst klären, dann siegeln**.
Nach dem Siegeln meldet die Prüfung wieder `valid: true`, und die bekannten | Feld | Erwartung | Warum |
Alt-Lücken erscheinen als **beglaubigt**: |---|---|---|
| `rehashes` | `[]` | Eine Neuberechnung verknüpft alles neu danach ist die Kette *zwangsläufig* stimmig, auch über Löschungen hinweg. Steht hier etwas, sagt `chainGaps` nichts über die Zeit davor aus. |
| `tamperedEntries` | `[]` | Veränderter Inhalt gehört geklärt, nicht beglaubigt. |
| `chainGaps` | erklärbar | siehe unten |
| `backlogSealStatus` | `kein_siegel` | sonst ist es kein Erst-Siegeln (siehe nächster Abschnitt) |
Alle Einträge sind unverändert. Seit dem Bestandssiegel ist keine neue **Lücken einordnen.** Sieh dir die betroffenen IDs und ihre Nachbarn an:
Lücke entstanden. 6 Lücken stammen aus der Zeit vor dem Bestandssiegel
und sind darin als Vorbefund beglaubigt (IDs …).
Sie verschwinden also nicht aus dem Bericht sie zählen nur nicht mehr als ```bash
offener Befund. Jede **neue** Lücke, jede veränderte oder entfernte Altzeile for id in <lücke-1> <nachbar> …; do
und jedes gebrochene Siegel lösen weiterhin sofort Alarm aus. printf '%-6s ' "$id"
curl -s -o /tmp/r.json -w 'HTTP %{http_code} ' "$CRM/api/audit-logs/$id" -H "$B"
jq -r 'if .success then (.data|"\(.createdAt[0:19]) v\(.hashVersion) \(.action)/\(.resourceType)") else .error end' /tmp/r.json
done
```
> **Den HTTP-Code mit ausgeben, immer.** Zugangstoken laufen nach 15 Minuten ab.
> Ohne den Code liest sich eine abgelehnte Anfrage (401) wie ein fehlender
> Eintrag ein Prüfwerkzeug, das ein verweigertes Lesen als Löschung meldet.
> Uns ist genau das passiert, mitten in der Vorbereitung eines Prod-Siegels.
Liegen die Lücken jeweils **innerhalb einer Sekunde** zusammen mit ihren
Nachbarn, betreffen nur `Authentication` und fehlt kein Eintrag, sind es
Schreibkollisionen aus parallelen Anfragen harmlos, historisch, nicht mehr
reproduzierbar. Fehlt dagegen ein Eintrag oder passt eine Lücke nicht in dieses
Bild: **erst klären, dann siegeln.**
*(Ein abgeschnittener **Anfang** des Protokolls wird seit dem Anfangs-Detektor
mitgeprüft: Die erste Zeile eines Protokolls trägt einen leeren `previousHash`;
trägt die erste vorhandene Zeile einen Wert, fehlt eine Vorgängerin. Das
erscheint als ganz niedrige Lücke und kippt `valid` du musst es nicht selbst
suchen.)*
#### Siegeln
```bash
curl -s -X POST $CRM/api/audit-logs/seal-backlog -H "$B" \
-H 'Content-Type: application/json' -d '{"confirm":"SEAL"}' | jq
```
`SEAL` ist das Erst-Siegeln. Besteht bereits ein Siegel, verlangt der Endpunkt
stattdessen `RESEAL` siehe nächster Abschnitt. Notiere `root` aus der Antwort.
#### Danach: drei Punkte gegenprüfen
```bash
curl -s -X POST $CRM/api/audit-logs/verify -H "$B" \
| jq '.data | {valid, chainGaps, attestedGaps, backlogSealStatus, backlogSealCount}'
SID=$(curl -s "$CRM/api/audit-logs?resourceType=AuditBacklogSeal&limit=1" -H "$B" | jq -r '.data[0].id')
curl -s "$CRM/api/audit-logs/$SID" -H "$B" \
| jq '{vorbefund:.data.changesBefore.befund.ketten_luecken, siegel:.data.changesAfter}'
```
1. `valid: true`, und `chainGaps` = `attestedGaps` = die bekannten Lücken
2. `backlogSealStatus: "intakt"` **nicht** `leer`. `leer` heißt: Das Siegel
umschließt nichts, es gab keinen Altbestand. Kein Fehler, aber auch keine
Zusage.
3. Die Lücken stehen im `changesBefore.befund.ketten_luecken` des Markers
genau daran hängt die Beglaubigung.
Die Lücken verschwinden also **nicht** aus dem Bericht. Sie zählen nur nicht
mehr als offener Befund. Jede **neue** Lücke, jede veränderte oder entfernte
Altzeile und jedes gebrochene Siegel lösen weiterhin sofort Alarm aus.
#### Zuletzt: das Gegenbuch quittieren
Der nächste Lauf meldet **einmal** `exit=2` („Erstmals ein Bestandssiegel
gesetzt") erwartet, kein Befund. Wurzel aus `data/<instanz>/status.txt` in
`PROD_SEAL_ACK` bzw. `STAGING_SEAL_ACK` eintragen, `docker compose up -d`, nach
dem Lauf mit `exit=0` wieder leeren. Details im nächsten Abschnitt.
Ab da schreibt das Gegenbuch wieder Checkpoints fort und setzt im selben Lauf
die Grundlage für die Rehash-Überwachung.
### Wenn sich die Siegelwurzel ändert: Alarm, und warum ### Wenn sich die Siegelwurzel ändert: Alarm, und warum