Files
craftvia/docs/KONZEPT-backup-restore.md
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
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>
2026-09-14 11:05:39 +02:00

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

  1. Disaster-Recovery der gesamten DB (Totalausfall, Ransomware, „Zeitpunkt-T-Wiederherstellung").
  2. Gezielter Einzelkunden-Restore (Kunde A zurück, Kunde B unangetastet) — über das Betreiber-Portal.
  3. DSGVO-Auskunft/-Datenportabilität (Art. 15/20) — je Mandant und je betroffener Person.
  4. 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 Operation WHERE tenant_id = A ist 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ünftig Identity) 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':
    1. Mandant sperren (status = SUSPENDED) → keine parallelen Schreibzugriffe.
    2. Pre-Restore-Sicherheitsschnappschuss des aktuellen Standes (Restore ist damit reversibel).
    3. Replace: DELETE … WHERE tenant_id='A' (child→parent) + Reinsert aus dem Artefakt (parent→child). Constraints ggf. DEFERRED.
    4. MinIO-Prefix des Mandanten wiederherstellen.
    5. Mandant reaktivieren, Audit schreiben.
  • 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 durch requirePlatformFullAdmin + 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:

  1. Per-Mandant — der gesamte Kundendatensatz (= das Backup-Artefakt aus §3, maschinenlesbar/JSON). Nutzen: Portabilität beim Anbieterwechsel, Offboarding-Kopie.
  2. Per-Betroffener (eine natürliche Person) — alle personenbezogenen Zeilen dieser Person: User (Mitgliedschaft), Auth-Daten aus Identity (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:

  1. Mandanten-Löschung / Offboarding — alle tenant_id='A'-Zeilen (child→parent, eine Transaktion) + MinIO-Prefix. Danach globale Aufräumung: eine Identity ohne verbleibende Mitgliedschaft wird gelöscht/anonymisiert.
  2. 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 referenzierte Identity → sauber behandeln (neu verknüpfen/Einladung).
  • Löschung ist controller-scoped (pro Mandant); globale Identity erst 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_KEY stehen nicht im per-Mandant-Artefakt. Ein Restore in eine Umgebung mit anderem Pepper/MFA_ENC_KEY bricht 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:

  1. Schicht A (pgBackRest/wal-g + PITR + verschlüsselt) — unabhängig, sofort möglich.
  2. Traversierungs-Engine über TENANT_MODELS (Export/Restore-Kern) — nach WS0.
  3. Betreiber-Portal-Restore (Worker-Job + Kontrollen).
  4. DSGVO-Export (per-Mandant + per-Person).
  5. 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.