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:
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user