Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 KiB
Konzept — Datensicherung, Wiederherstellung, DSGVO-Export & Löschung (certvia)
Status: Konzept/Entscheidungsvorlage (kein Code). Produkt: certvia (ISMS-Tool, Single-DB + Postgres-RLS, alle Mandanten in denselben Tabellen mit
tenant_id). Kernidee: eine mandanten-scoped Traversierungs-Engine bedient vier Zwecke — Per-Tenant-Backup, gezielten Restore, DSGVO-Auskunft/-Export und DSGVO-Löschung/Offboarding.
0. Zielbild
- Disaster-Recovery der gesamten DB (Totalausfall, Ransomware, „Zeitpunkt-T-Wiederherstellung").
- Gezielter Einzelkunden-Restore (Kunde A zurück, Kunde B unangetastet) — über das Betreiber-Portal.
- DSGVO-Auskunft/-Datenportabilität (Art. 15/20) — je Mandant und je betroffener Person.
- DSGVO-Löschung (Art. 17) — ganzer Mandant (Offboarding) und einzelne Person, mit ISMS-konformer Aufbewahrung.
Alle vier teilen sich einen Baustein: das Durchlaufen aller mandantengescopten Tabellen in FK-Reihenfolge, gefiltert auf tenant_id (bzw. auf eine Person).
1. Architektur-Grundlage (bestehend, wird genutzt)
TENANT_MODELS(src/server/db.ts) = autoritative Menge aller mandantengescopten Tabellen. Plus deren FK-Topologie ⇒ die verbindliche Reihenfolge für Export (parent→child) und Löschung (child→parent).tenant_id-Spalte + RLS ⇒ jede OperationWHERE tenant_id = Aist beweisbar auf einen Mandanten begrenzt.- cuid-Primärschlüssel (kein Serial) ⇒ Reinsert kollisionsfrei, IDs bleiben erhalten, keine Sequenz-Konflikte.
- Owner-
prisma-Client (BYPASSRLS) für Backup/Restore/Löschung; nie der RLS-Client. - Globale Tabellen (Kataloge,
Permission,Tenant-Stammsatz, künftigIdentity) sind nicht tenant-scoped → über Schicht A gesichert, nicht Teil des Tenant-Artefakts. - MinIO/S3 hält Dateien/Logos je Mandant (Prefix) — DB-Backup allein reicht nicht.
2. Schicht A — Cluster-Disaster-Recovery (ganze DB)
- pgBackRest oder wal-g: periodisches Vollbackup + kontinuierliches WAL-Archiving nach S3/offsite ⇒ PITR (Point-in-Time-Recovery).
- Zweck: Totalausfall/Ransomware/menschlicher Massenfehler → „DB auf Zeitpunkt T".
- Verschlüsselt (SSE-KMS oder client-seitig) — dockt an die (vorerst geparkte) DB-Härtung an.
- Deckt auch die globalen Tabellen inkl.
Identity/Credentials ab, die Schicht B bewusst nicht anfasst.
3. Schicht B — Mandanten-scoped Logical Export/Restore
Export (Backup-Artefakt je Mandant)
- Aus einem konsistenten Snapshot (
REPEATABLE READ-Transaktion), damit die FK-Integrität innerhalb des Mandanten stimmt. - Je Tabelle aus
TENANT_MODELS:COPY (SELECT * FROM t WHERE tenant_id = 'A') TO …→ ein Artefakt (z. B. NDJSON/COPY) pro Mandant → gzip → verschlüsselt nach S3. - Enthält zusätzlich den MinIO-Prefix des Mandanten (Datei-Snapshot) und ein Manifest (Schema-/Migrationsversion, Zeitstempel, Zeilenzahlen je Tabelle, Prüfsummen).
- Zeitplan: nächtlich + on-demand vor riskanten Operationen (Restore, Massen-Import).
Restore (gezielt Kunde A)
- Owner-Client, eine Transaktion, FK-Reihenfolge, alles
WHERE tenant_id = 'A':- Mandant sperren (
status = SUSPENDED) → keine parallelen Schreibzugriffe. - Pre-Restore-Sicherheitsschnappschuss des aktuellen Standes (Restore ist damit reversibel).
- Replace:
DELETE … WHERE tenant_id='A'(child→parent) + Reinsert aus dem Artefakt (parent→child). Constraints ggf.DEFERRED. - MinIO-Prefix des Mandanten wiederherstellen.
- Mandant reaktivieren, Audit schreiben.
- Mandant sperren (
- Beweisbar sicher für andere Mandanten: keine Operation verlässt
tenant_id='A'. - Schema-Version im Manifest gegen aktuelle Migration prüfen; bei Differenz Artefakt vor Reinsert migrieren/abweisen.
4. Betreiber-Portal — Restore-Flow
Restore ist eine Betreiber-Fähigkeit (kein Mandanten-Admin) → Plattform-Portal (/admin, platformAuth).
- Auslöser: Aktion „Wiederherstellen" auf
/admin/[id], gegated durchrequirePlatformFullAdmin+ frischer MFA-Step-up (assertPlatformStepUp). - Auswahl: Mandant + Sicherungspunkt (Liste der Per-Tenant-Snapshots / PITR-Zeitpunkt) + Vorschau/Dry-run (Zeilenzahlen, Snapshot-Zeit) + getippte Bestätigung („RESTORE kunde-a").
- Ausführung als Hintergrund-Job im Worker (BullMQ, vorhanden) — nicht inline in der Server-Action (Timeouts/Progress/Audit). Die Action enqueued nur.
- Kontrollen: Sperre während Restore · Pre-Restore-Schnappschuss ·
tenant_id-Scope · Plattform-Audit (wer/Mandant/Snapshot/wann) · MinIO mit.
5. DSGVO-Export (Art. 15 Auskunft / Art. 20 Portabilität)
Rollen (wichtig): certvia ist Auftragsverarbeiter, der Mandant ist Verantwortlicher. Anfragen richten sich an den Mandanten; certvia liefert das Werkzeug. Deshalb existiert der Export an zwei Stellen:
- Mandanten-Self-Service (Mandanten-Admin, für die eigenen Betroffenen),
- Betreiber-Portal (AVV-Unterstützung / Ausfallhilfe).
Zwei Granularitäten:
- Per-Mandant — der gesamte Kundendatensatz (= das Backup-Artefakt aus §3, maschinenlesbar/JSON). Nutzen: Portabilität beim Anbieterwechsel, Offboarding-Kopie.
- Per-Betroffener (eine natürliche Person) — alle personenbezogenen Zeilen dieser Person:
User(Mitgliedschaft), Auth-Daten ausIdentity(Existenz/E-Mail/MFA-Status — keine Secrets), sowie alle Referenzen (owner_id,assigneeId,createdBy,AuditLog.actorId,TaskParticipant,Audit.*UserId, …) und Freitext mit Personenbezug.
Format/Zustellung: ZIP (JSON + zugehörige Dateien aus MinIO), erzeugt vom Worker-Job, Zustellung über zeitlich begrenzten signierten Link (nicht per Mail). Export wird auditiert.
6. DSGVO-Löschung (Art. 17 „Recht auf Vergessenwerden")
Zwei Scopes:
- Mandanten-Löschung / Offboarding — alle
tenant_id='A'-Zeilen (child→parent, eine Transaktion) + MinIO-Prefix. Danach globale Aufräumung: eineIdentityohne verbleibende Mitgliedschaft wird gelöscht/anonymisiert. - Einzelne Person (Betroffener) — Kernspannung im ISMS: Löschung vs. Nachweis-/Aufbewahrungspflicht.
Löschen vs. Anonymisieren (die zentrale Design-Regel):
- Datensätze, die aus ISMS-/Nachweisgründen oder wegen rechtlicher Pflicht (Art. 17 Abs. 3) erhalten bleiben müssen (v. a. Audit-Trail, Freigaben, Nachweise), werden anonymisiert/pseudonymisiert (Name/E-Mail → Tombstone, referenzielle Struktur bleibt), nicht hart gelöscht — sonst bricht die Nachvollziehbarkeit.
- Wo keine Aufbewahrungspflicht greift: Hard-Delete.
- Ergebnis: Löschnachweis/„Deletion Certificate" (wer/wann/Scope/was gelöscht vs. anonymisiert), auditiert.
Personen über mehrere Mandanten (Auth-Umbau!): Jeder Mandant ist ein eigener Verantwortlicher. Löschung erfolgt pro Mandant-Scope (Mitgliedschaft + PII in dessen Datensätzen) — nicht global über alle Mandanten. Erst wenn keine Mitgliedschaft der Person mehr existiert, wird die globale Identity entfernt/anonymisiert.
Backups vs. Löschung (bekannte Spannung): Unveränderliche Backups lassen sich nicht punktuell „aufbohren". Standard: Löschung wirkt auf Live-Daten + wird über eine Tombstone-/Löschliste beim Restore erneut angewandt (ein alter Snapshot bringt gelöschte PII nicht zurück); Backups laufen über Retention aus. Diese Regel muss dokumentiert und im Restore-Job erzwungen werden.
7. Gemeinsame Engine (der Architektur-Gewinn)
Backup-Export, DSGVO-Export, Offboarding und Löschung teilen eine tenant-scoped Traversierung über TENANT_MODELS + FK-Topologie:
- Export = SELECT je Tabelle,
tenant_id- oder personen-gefiltert. - Restore = DELETE+INSERT,
tenant_id-gescopt. - Löschung = DELETE (child→parent) bzw. UPDATE-Anonymisierung,
tenant_id- oder personen-gescopt. Ein Baustein, vier Anwendungsfälle → geringe Redundanz, konsistentes Verhalten, ein Testfokus (Isolation).
8. Schnittstelle zum Auth-Umbau (Identity)
- Export einer Person vereint globale
Identity(Existenz/E-Mail/MFA-Status, keine Secrets) + alle Mitgliedschaften + zugewiesene/erstellte Objekte. - Restore eines Mandanten holt Mitgliedschaften (
User) zurück, nicht den globalen Credential-Store (der liegt in Schicht A). Fehlt beim Reinsert die referenzierteIdentity→ sauber behandeln (neu verknüpfen/Einladung). - Löschung ist controller-scoped (pro Mandant); globale
Identityerst bei 0 Mitgliedschaften. ⇒ Diese Punkte jetzt mitdesignen, umsetzen nach WS0 (Identity-Fundament).
9. Verschlüsselung & Schlüssel (Entscheidung)
Entscheidung: Client-seitige AES-256-Verschlüsselung mit einem Schlüssel pro Umgebung — nicht allein auf Storage-SSE verlassen. Grund: das Artefakt ist verschlüsselt, bevor es S3/MinIO erreicht → der Speicher-Betreiber sieht nie Klartext (at-rest und in-transit und zero-knowledge vom Speicher). Beide Backup-Tools können das nativ (keine Zusatzkomponente). Dies ist das gemeinsame Primitiv, auf dem die Backup-Lane und die Härtungs-Lane bauen.
Stack (durchgängig AES-256, konsistent zur bestehenden TOTP-Verschlüsselung AES-256-GCM):
| Schutzobjekt | Mechanismus |
|---|---|
Host at-rest (pgdata, MinIO) |
LUKS / provider-verschlüsseltes Volume (deckt physischen Plattendiebstahl/Decommission; transparent im Betrieb) |
| Cluster-Backup (Schicht A) | pgBackRest mit nativer AES-256-Repo-Verschlüsselung (repo-cipher-type=aes-256-cbc + repo-cipher-pass) → S3/MinIO |
| Per-Tenant-Export + DSGVO-Pakete (Schicht B) | age je Artefakt (X25519, encrypt-then-upload) |
| MinIO-Dateien | restic (eingebaute Verschlüsselung) oder MinIO-SSE als Defense-in-Depth |
Warum nicht SSE-only: SSE (AWS SSE-KMS / MinIO KES+KMS) ist transparent, aber der Speicher-Betreiber hält die Schlüssel, und self-hosted MinIO-SSE bräuchte KES+KMS als Extra-Komponente. SSE gern zusätzlich, nicht als alleinige Zusicherung.
Schlüsselverwaltung:
- Jetzt (self-hosted VPS): ein AES-256-Schlüssel (pgBackRest) + ein
age-Keypair pro Umgebung (test/dev/prod getrennt), als Coolify-Env-Secret + Offline-Kopie im Org-Passwortmanager (versiegelt). - Register „restore-kritische Secrets": führt zusammen — Backup-Keys, Pepper,
MFA_ENC_KEY,AUTH_SECRET. Regeln: niemals im selben Bucket wie die verschlüsselten Artefakte; Verlust des Keys = Verlust der Wiederherstellbarkeit. - ⚠ Umgebungs-Secret-Kohärenz beim Restore: Pepper und
MFA_ENC_KEYstehen nicht im per-Mandant-Artefakt. Ein Restore in eine Umgebung mit anderem Pepper/MFA_ENC_KEYbricht alle Passwort-/MFA-Prüfungen. Restore daher nur in eine Umgebung mit passenden Secrets (oder Passwort-/MFA-Reset einplanen). - Später (Phase 2): Upgrade-Pfad auf HashiCorp Vault oder Provider-KMS (Rotation/Audit/Trennung) — bewusst offen, nicht jetzt bauen.
Aufgabenteilung der Lanes: die Backup-Lane ruft die Verschlüsselung auf (pgBackRest-Repo-Key bzw. age-Recipient); die Härtungs-Lane stellt Host-Encryption (LUKS) bereit und verwaltet/rotiert die Keys + das Secrets-Register.
Aufbewahrung & Test:
- Retention je Schicht dokumentieren (DR-WAL/Base, Per-Tenant-Snapshots, DSGVO-Exporte) — dem DSB vorzulegen.
- Restore-Test regelmäßig + dokumentiert (der Prod-Runbook fordert das bereits für
pgdata).
10. Governance-/Sicherheitskontrollen (Zusammenfassung)
| Operation | Wer | Zusatzschutz |
|---|---|---|
| Cluster-PITR | Betreiber/Ops | Offsite-Zugriff, 4-Augen empfohlen |
| Tenant-Restore | Plattform-Full-Admin | MFA-Step-up, getippte Bestätigung, Mandant-Sperre, Pre-Restore-Snapshot, Audit |
| DSGVO-Export | Mandanten-Admin oder Betreiber | signierter Link, Audit |
| Löschung Person | Mandanten-Admin (Verantwortlicher) | Anonymisierungs-Regeln, Löschnachweis, Audit |
| Mandanten-Löschung | Plattform-Full-Admin | MFA-Step-up, getippte Bestätigung, Löschnachweis, MinIO + globale Identity-Aufräumung |
11. Phasen & offene Entscheidungen
Phasen:
- Schicht A (pgBackRest/wal-g + PITR + verschlüsselt) — unabhängig, sofort möglich.
- Traversierungs-Engine über
TENANT_MODELS(Export/Restore-Kern) — nach WS0. - Betreiber-Portal-Restore (Worker-Job + Kontrollen).
- DSGVO-Export (per-Mandant + per-Person).
- DSGVO-Löschung (Anonymisierung vs. Hard-Delete + Löschnachweis + Tombstone-on-Restore).
Offene Entscheidungen (für PM/DSB):
- Retention-Fristen je Schicht (DSB-Vorgabe).
- Welche Tabellen/Felder bei Personen-Löschung anonymisiert (Nachweispflicht) vs. hart gelöscht werden — eine explizite Feld-Klassifikation ist nötig.
- Aufbewahrung/Weg der DSGVO-Export-Pakete (Ablauf des signierten Links).
- Ob per-Mandant-Snapshots als DSGVO-Portabilitätsformat genügen oder ein zusätzliches „menschenlesbares" Format nötig ist.