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
+19
View File
@@ -98,6 +98,24 @@ async function main() {
.filter((p) => p.resource === 'audit' || p.resource === 'gdpr')
.map((p) => p.id);
// Gegenbuch: NUR audit:read.
//
// Der externe Notar ruft genau zwei Endpunkte auf, /audit-logs/checkpoint
// und /audit-logs/verify, und beide verlangen audit:read. Bis hierher gab es
// dafuer keine passende Rolle: Wer dem Dienstkonto Leserechte aufs Protokoll
// geben wollte, musste den DSGVO-Haken setzen - und der vergibt `audit:*`
// KOMPLETT, also auch `audit:admin` mit seal-backlog, rehash und cleanup.
//
// Damit haette ein Einbruch auf der Gegenbuch-Maschine nicht nur den
// Waechter gehabt, sondern gleich die Mittel, das Bewachte umzuschreiben -
// genau die Waesche aus R185-01, und genau die Trennung, wegen der das
// Gegenbuch ueberhaupt auf einer eigenen Maschine laeuft. Das Kennwort des
// Dienstkontos liegt dort im Klartext in der .env; es muss deshalb so wenig
// wert sein wie moeglich.
const gegenbuchPermIds = allPermissions
.filter((p) => p.resource === 'audit' && p.action === 'read')
.map((p) => p.id);
// Mitarbeiter: customers + contracts + read auf Stammdaten
const employeePermIds = allPermissions
.filter(
@@ -137,6 +155,7 @@ async function main() {
{ name: 'DSGVO', description: 'DSGVO-Zugriff: Audit-Logs und Datenschutz-Verwaltung', permIds: gdprPermIds },
{ name: 'Mitarbeiter', description: 'Kann Kunden und Verträge verwalten', permIds: employeePermIds },
{ name: 'Mitarbeiter (Nur-Lesen)', description: 'Kann nur lesen, keine Änderungen', permIds: readOnlyPermIds },
{ name: 'Gegenbuch', description: 'Darf das Audit-Protokoll nur lesen und prüfen sonst nichts', permIds: gegenbuchPermIds },
{ name: 'Kunde', description: 'Kann nur eigene Daten lesen', permIds: readOnlyPermIds },
];