Rolle "Gegenbuch": Leserecht aufs Audit-Protokoll ohne audit:admin

Beim Selbst-Nachpruefen eines Deploys auf Staging aufgefallen: Das
Gegenbuch-Dienstkonto meldete beim Login audit:read, audit:export,
audit:admin, gdpr:export, gdpr:delete und gdpr:admin. Es braucht genau
eines davon - audit:read -, denn es ruft nur /audit-logs/checkpoint und
/audit-logs/verify auf.

Das war kein Bedienfehler, sondern ein Konstruktionsfehler: Es gab keine
Rolle, die nur Leserecht aufs Protokoll gibt. Wer das wollte, musste den
Haken "DSGVO-Zugriff" setzen - und der vergibt audit:* komplett, also
auch audit:admin mit seal-backlog, rehash und cleanup. Das Label
("Audit-Logs, Datenschutz") legt Lesen nahe und liefert Vollzugriff.

Warum das ernst ist: Das Passwort des Dienstkontos liegt im Klartext in
tools/audit-notary/.env auf der Gegenbuch-Maschine. Mit audit:admin
haette ein Einbruch dort nicht nur den Waechter gehabt, sondern gleich
die Mittel zur Waesche aus R185-01 - und damit genau die Trennung
aufgehoben, wegen der das Gegenbuch auf einer eigenen Maschine laeuft.

Neue Rolle "Gegenbuch" in sync-roles.ts mit ausschliesslich audit:read.
sync-roles laeuft beim Containerstart mit, die Rolle erscheint danach in
der Benutzerverwaltung. README des Gegenbuchs umgeschrieben: Rolle statt
"selbst anlegen", ausdrueckliche Warnung vor dem DSGVO-Haken, dazu eine
Gegenprobe (checkpoint -> 200, seal-backlog -> 403).

Bewusst NICHT angefasst: dass die DSGVO-Rolle selbst audit:admin traegt,
ist ein Gewaltenteilungs-Problem - wer das Protokoll beaufsichtigt, kann
seine Beweisgrundlage ersetzen. Das zu aendern entzieht bestehenden
DSGVO-Konten Rechte und gehoert entschieden, nicht nebenbei gemacht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 12:41:34 +02:00
co-authored by Claude Opus 5
parent ef2411ebe4
commit d2460fa7c0
3 changed files with 73 additions and 7 deletions
+29
View File
@@ -97,6 +97,35 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt
- [x] **🔑 Rolle „Gegenbuch": Leserecht aufs Audit-Protokoll ohne `audit:admin`** (2026-08-26)
- **Beim Selbst-Nachprüfen eines Deploys aufgefallen:** Das Gegenbuch-
Dienstkonto auf Staging meldete beim Login
`["audit:read","audit:export","audit:admin","gdpr:export","gdpr:delete","gdpr:admin"]`.
Es braucht **genau eines** davon `audit:read` denn es ruft nur
`/audit-logs/checkpoint` und `/audit-logs/verify` auf.
- **Kein Bedienfehler, ein Konstruktionsfehler:** Es gab keine Rolle, die
nur Leserecht aufs Protokoll gibt. Wer das wollte, musste den Haken
„DSGVO-Zugriff" setzen und der vergibt `audit:*` komplett, also auch
`audit:admin` mit `seal-backlog`, `rehash`, `cleanup`. Das Label
(„Audit-Logs, Datenschutz") legt Lesen nahe und liefert Vollzugriff.
- **Warum das ernst ist:** Das Passwort des Dienstkontos liegt im Klartext
in `tools/audit-notary/.env` auf der Gegenbuch-Maschine. Mit `audit:admin`
hätte ein Einbruch dort nicht nur den Wächter gehabt, sondern gleich die
Mittel zur Wäsche aus R185-01 und damit genau die Trennung aufgehoben,
wegen der das Gegenbuch überhaupt auf einer eigenen Maschine läuft.
- Neue Rolle **`Gegenbuch`** in `sync-roles.ts` mit ausschließlich
`audit:read`. Läuft beim Containerstart mit, erscheint danach in der
Benutzerverwaltung. README des Gegenbuchs umgeschrieben: Rolle statt
„selbst anlegen", ausdrückliche Warnung vor dem DSGVO-Haken, plus eine
Gegenprobe (`checkpoint` → 200, `seal-backlog` → 403).
- **Offen, bewusst nicht angefasst:** Dass die DSGVO-Rolle selbst
`audit:admin` trägt, ist ein Gewaltenteilungs-Problem wer das Protokoll
beaufsichtigt, kann seine Beweisgrundlage ersetzen. Ändern hieße
bestehenden DSGVO-Konten Rechte entziehen; gehört entschieden, nicht
nebenbei gemacht. An den Pentester gemeldet, dessen nächstes Thema das
Rechtemodell ist.
- Dateien: `backend/prisma/sync-roles.ts`, `tools/audit-notary/README.md`
- [x] **📤 Eingegrenzter Export lieferte alles (Pentest R186-01, MEDIUM)** (2026-08-26)
- Der Tester fand: `GET /audit-logs/export?userId=…` filterte **nicht**
`userId=999999` gab alle 2761 Datensätze zurück, byte-identisch zum