Drei Betriebsskripte fuer Linux, im Auslieferungspaket neben den Programmen. setup.sh richtet eine Anlage vollstaendig ein: PostgreSQL auf Wunsch mit (apt/dnf/yum/zypper/pacman), Dienstkonto, Verschluesselungsschluessel, Schema, erster Administrator, gehaertetes Repository, gehaertete systemd-Einheit. Jeder Schritt vermerkt, was er angelegt hat; bricht der Lauf ab, wird genau das zurueckgebaut und nichts sonst. Eine bestehende Installation wird nicht ueberschrieben — dafuer gibt es update.sh, und der Unterschied ist, dass ein Update vorher sichert. update.sh haelt die Reihenfolge ein, um die es geht: sichern, anhalten, tauschen, migrieren, starten, pruefen. Kommt der Dienst danach nicht hoch, holt es die vorige Fassung zurueck. Ohne pg_dump wird gar nicht erst begonnen — ohne Sicherung gibt es nach einer misslungenen Migration keinen Weg zurueck. Repository und Verschluesselungsschluessel bleiben unberuehrt. uninstall.sh entfernt standardmaessig NUR Dienst und Programme. Datenbank, Repository und Konfiguration bleiben liegen; jede dieser Loeschungen verlangt ein woertlich getipptes Bestaetigungswort an einem Terminal. Ein Deinstallationsskript, das nebenbei die Backups mitnimmt, vernichtet genau das, wofuer jemand jahrelang Speicher bezahlt hat. Gegen Debian 12 im Container gefahren — Installation, Anmeldung, Repository eingetragen, Loeschschutz gemessen (advisory auf overlayfs, richtig), echter Sicherungslauf, Update mit unveraendertem Bestand, beide Abbauarten. Drei Fehler dabei gefunden und behoben, alle derselben Art: - "tr </dev/urandom | head -c 32" und "psql | grep -q": Der frueh geschlossene Pipe schickt dem Schreiber SIGPIPE, und mit "set -o pipefail" bricht das Skript mitten in der Einrichtung ab, ohne erkennbaren Grund. - update.sh las die laufende Fassung mit "grep -o" aus der Antwort von /health/ready. Die enthaelt gar kein Versionsfeld; grep endet mit 1, und das Skript nahm ein GELUNGENES Update wieder zurueck — wegen einer Zeile, die nur der Ausgabe dient. Dazu: SYNCOVA_ADMIN_PASSWORD stand in der Doku und existiert nicht — das Passwort kommt ueber die Standardeingabe. Und "syncova-repo break-lock" fehlte zwar nicht mehr, aber der Test auf die Uebereinstimmung der drei Skripte ist neu: Drei Skripte, die sich ueber den Installationsort uneinig sind, ergeben eine Anlage, die sich nicht mehr entfernen laesst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
471 lines
34 KiB
Markdown
471 lines
34 KiB
Markdown
# Syncova Backups
|
||
|
||
Backup-, Recovery-, Verification-, Security- und Monitoring-Plattform für Proxmox VE, Windows, Linux, physische Systeme sowie Dateien und Ordner.
|
||
|
||
> **Leitsatz des Produkts:** Ein Backup gilt erst als vertrauenswürdig, wenn seine Integrität geprüft und seine Wiederherstellbarkeit nachgewiesen wurde.
|
||
|
||
## Stand der Entwicklung
|
||
|
||
**Phase 0** (Produktfundament), **Phase 1** (Identität und Sicherheit), **Phase 2** (Repository Engine), **Phase 3** (Backup-Format), **Phase 4** (Backup Engine), **Phase 5** und **Phase 6** (Agenten — Dienstanbindungen geschrieben, auf echten Systemen ungeprüft), **Phase 8** (Scheduler), **Phase 9** (Recovery Engine), **Phase 10** (Verification und Recovery Assurance), **Phase 11** (Immutability), **Phase 12** (Weboberfläche), **Phase 13** (Kennzahlen), **Phase 14** (Meldungen), **Phase 15** (Security Center), **Phase 16** (Ransomware-Heuristik), **Phase 17** (Berichte) **Phase 18** (Disaster Recovery) **Phase 19** (Härtung) **Phase 20** (Leistungsmessung) und **Phase 21** (Chaos Testing) des [Implementierungsplans](SYNCOVA_IMPLEMENTATION_PLAN.md) sind abgeschlossen. Vorhanden sind:
|
||
|
||
- Control-Plane-API in Go mit Health-Engine, strukturiertem Logging und Correlation IDs
|
||
- PostgreSQL-Anbindung samt versioniertem Migrationsframework
|
||
- Anmeldung mit Argon2id-Passwörtern, TOTP-Zweitfaktor und Wiederherstellungscodes
|
||
- Rollenbasierte Zugriffssteuerung mit sieben Rollen und 34 Einzelberechtigungen
|
||
- Append-only-Auditprotokoll, per Datenbank-Trigger gegen Änderung geschützt
|
||
- Brute-Force-Schutz durch Kontosperre und Rate-Limiting
|
||
- Weboberfläche mit Übersicht, Wiederherstellungspunkten, Sicherungsaufträgen, Repositories, Agenten, Wiederherstellungen, Ereignissen, Benutzern und Rollen — unfertige Bereiche erscheinen benannt statt versteckt oder leer
|
||
- Repository Engine: inhaltsadressierte Chunk-Ablage mit Deduplizierung, versionierte Manifeste, atomares Commit-Protokoll, Integritätsscan, Katalog-Wiederaufbau ohne Datenbank, gehärteter Modus mit Aufbewahrungsschutz
|
||
- Versioniertes Backup-Format: portabler Container mit Versionsverhandlung, streamendem Schreiben, durchgehender Integritätsprüfung und Export/Import zwischen Repositories
|
||
- Backup Engine: inhaltsabhängiges Chunking, Deduplizierung trotz Verschlüsselung, zstd-Kompression in vier Stufen, AES-256-GCM, Streaming-Pipeline mit Worker-Pool und Backpressure, bitgenaue Wiederherstellung
|
||
- Security Center: acht geprüfte Bereiche, vollständige Befunde mit Empfehlung, Verlauf der Bewertung
|
||
- Meldungswesen: zehn geprüfte Regeln, eine Ursache je Meldung, automatische Auflösung, Zustellung per E-Mail und Webhook
|
||
- Kennzahlen: zwölf Diagramme über sieben Zeiträume, Lücken bleiben Lücken statt zu Nullen zu werden
|
||
- Unveränderlichkeit: gemessene statt behauptete Durchsetzungsstufe, Löschschutz über das Dateisystem, Legal Hold, Aufbewahrungsregeln mit Vorschau und Schutz des letzten Backups
|
||
- Prüfung und Recovery Assurance: fünf Prüfarten vom Manifest bis zum vollständigen Wiederherstellungstest, objektive Einstufung jedes Backups, Bewertung aus zehn Größen, die Ungemessenes niemals als gut zählt
|
||
- Agent: Aufnahme über einmalige Tokens, eigenes Betriebstoken ohne Benutzerrechte, Lebendmeldung mit Wiederaufnahme nach Netzunterbrechung, Dateierfassung mit Ein- und Ausschlussregeln
|
||
|
||
**Phase 5 ist für Windows nicht abgeschlossen:** die Dienstanbindung ist geschrieben, aber auf dieser Entwicklungsplattform nicht prüfbar. Einzelheiten in [docs/agent.md](docs/agent.md). Der Proxmox-Provider ist gebaut, der Nachweis auf echter Hardware steht aus — siehe unten. Funktionen, die noch nicht implementiert sind, werden ausdrücklich als solche gekennzeichnet und niemals vorgetäuscht.
|
||
|
||
## Ein System sichern und wiederherstellen
|
||
|
||
Die vertikale Scheibe aus [§27 des Implementierungsplans](SYNCOVA_IMPLEMENTATION_PLAN.md) ist geschlossen — Sichern und Wiederherstellen funktionieren end-to-end:
|
||
|
||
```bash
|
||
export SYNCOVA_ENCRYPTION_KEYS="v1:$(openssl rand -base64 32)" # sicher verwahren!
|
||
|
||
syncova-repo create --path /backup/repo-01 --name "Produktiv"
|
||
syncova-agent backup --repository /backup/repo-01 --path /daten --id tagessicherung
|
||
syncova-repo scan --path /backup/repo-01 --deep
|
||
syncova-agent restore --repository /backup/repo-01 --id tagessicherung --target /wiederhergestellt
|
||
|
||
# Folgeläufe lesen nur, was sich geändert hat:
|
||
syncova-agent backup --repository /backup/repo-01 --path /daten --id nacht-2 --incremental
|
||
```
|
||
|
||
Real nachgewiesen: 41 Dateien mit Verzeichnissen, Rechten und symbolischem Verweis gesichert, Quelle gelöscht, wiederhergestellt — **Struktur, Inhalt, Rechte und Verweise bitgenau identisch**. Ein zweiter Lauf ohne Änderungen legt 0 Byte ab (100 % dedupliziert, trotz Verschlüsselung).
|
||
|
||
Die Zusatzsicherung spart **Zeit, nicht Speicher** — den Speicher spart die Deduplizierung ohnehin. Bei 190,7 MiB, von denen sich eine von 41 Dateien änderte: 1 815 ms voll gegen 133 ms inkrementell. Das entstehende Manifest bleibt trotzdem vollständig, eine Wiederherstellung braucht also nie die Kette.
|
||
|
||
## Sicherungsaufträge
|
||
|
||
Aufträge werden über `/api/v1/jobs` angelegt und laufen nach Zeitplan — die Ausführungsschleife läuft im API-Dienst mit und sichert über die Backup Engine.
|
||
|
||
Angelegt werden sie über den **Backup-Wizard** in der Oberfläche (zehn Schritte von Name bis Anlegen) oder direkt über die API.
|
||
|
||
Real nachgewiesen: 38 MiB beauftragt, vom Scheduler gesichert, Integritätsprüfung sauber, Quelle gelöscht, aus der Zusatzsicherung **bitgenau** wiederhergestellt. Der zweite Lauf las 0,0 MiB statt 38,1 MiB. Einzelheiten und Grenzen in [docs/scheduler.md](docs/scheduler.md) — gesichert werden derzeit nur Dateisystemquellen; Aufbewahrung, Prüfung und Benachrichtigung sind im Wizard als „noch nicht verfügbar" gekennzeichnet.
|
||
|
||
## Wiederherstellung
|
||
|
||
Vor jeder Wiederherstellung steht die **Vorabprüfung**: Sie schreibt nichts und stellt fest, ob jeder benötigte Block noch im Repository liegt — der eigentliche Nachweis der Wiederherstellbarkeit, lange bevor jemand ihn braucht.
|
||
|
||
```bash
|
||
curl -X POST /api/v1/restores/validate -d '{"backup_id":"…","target_path":"/wiederhergestellt"}'
|
||
curl -X POST /api/v1/restores -d '{"backup_id":"…","target_path":"/wiederhergestellt"}'
|
||
```
|
||
|
||
Real nachgewiesen: 11,4 MiB gesichert, Quelle gelöscht, über die API wiederhergestellt — **bitgenau identisch**. Nach dem Entfernen eines einzigen Blocks meldet die Prüfung „NICHT WIEDERHERSTELLBAR" und benennt die betroffene Datei; der Auftrag wird abgelehnt. Einzelheiten in [docs/recovery.md](docs/recovery.md).
|
||
|
||
## Ist dieses Backup vertrauenswürdig?
|
||
|
||
Die Frage, die das Produkt trägt — und die einzige Antwort, die zählt, kommt vom **Wiederherstellungstest**: Das Backup wird an ein Wegwerfziel zurückgeschrieben und Datei für Datei gegen seine Prüfsummen verglichen. Manifest-, Block- und Kettenprüfung sind gute Indizien, aber Indizien.
|
||
|
||
```bash
|
||
curl -X POST /api/v1/verification -d '{"backup_id":"…","verification_type":"restore_test"}'
|
||
curl /api/v1/backups/<id>/assurance
|
||
```
|
||
|
||
Jedes Backup trägt daraufhin eine Einstufung — `successful` → `verified` → `recoverable`, oder `corrupted` — und eine Bewertung aus zehn Größen. **Was nie gemessen wurde, zählt nie als gut:** Eine ungemessene Größe bekommt null Punkte und wird als ungemessen ausgewiesen; `missing_measurements` sagt, was als Nächstes zu prüfen ist.
|
||
|
||
Real nachgewiesen, vollständiger Lebenszyklus gegen den laufenden Dienst:
|
||
|
||
| Zustand | Einstufung | Bewertung |
|
||
| --- | --- | --- |
|
||
| gesichert, ungeprüft | `successful` | 35 % |
|
||
| Integritätsprüfung bestanden | `verified` | 55 % |
|
||
| Wiederherstellungstest bestanden | `recoverable` | 90 % |
|
||
| ein Byte in einem Block gekippt | `corrupted` | **0 %** |
|
||
| Block zurückgespielt, erneut geprüft | `verified` | 55 % |
|
||
|
||
Der Befund nannte die betroffene Datei, nicht nur eine Zahl. Dass ein beschädigtes Backup 0 % bekommt und nicht 70 %, ist eine Korrektur aus genau diesem Nachweis: Ein Backup, aus dem sich ein Block nicht mehr lesen lässt, ist nicht zu 70 % wiederherstellbar. Einzelheiten in [docs/verification.md](docs/verification.md).
|
||
|
||
## Die Oberfläche sagt, was sie nicht weiß
|
||
|
||
Der Implementierungsplan nennt fünfzehn Seiten und zehn Kennzahlen. Sieben Kennzahlen haben heute eine Datengrundlage. Die übrigen drei — kritische Meldungen, Kapazitätsprognose, Security Score — erscheinen trotzdem, mit der Angabe, was fehlt:
|
||
|
||
> **Kritische Meldungen** · noch nicht verfügbar
|
||
> Es gibt noch kein Meldungswesen (Phase 14). Eine Null an dieser Stelle hieße „keine Probleme" und würde bedeuten „es wird nicht geprüft".
|
||
|
||
Dieselbe Regel überall: Ohne hinterlegte Speicherkapazität gibt es keinen Prozentsatz, sondern „nicht bezifferbar". Ohne Lauf in sieben Tagen gibt es keine Erfolgsquote von 100 %, sondern eine Warnung. Ein Wiederherstellungspunkt ohne Bewertung zeigt „nicht berechnet", nicht „0 %".
|
||
|
||
```bash
|
||
make web-install # einmalig
|
||
make web-dev # Oberfläche auf http://127.0.0.1:5173
|
||
```
|
||
|
||
Einzelheiten in [docs/web-ui.md](docs/web-ui.md).
|
||
|
||
## Kann jemand die Backups überhaupt vernichten?
|
||
|
||
Das Security Center prüft acht Bereiche — vom Löschschutz über den zweiten Faktor bis zur Verteilung destruktiver Rechte — und beantwortet die Frage, die vor der Wiederherstellbarkeit kommt.
|
||
|
||
Jeder Befund trägt vier Angaben, und die vierte ist die wichtigste:
|
||
|
||
> **Kein Repository mit nachgewiesenem Löschschutz** · kritisch
|
||
> *Warum:* Bei keinem Repository verhindert das Dateisystem die Löschung. Ein kompromittiertes Konto kann alle Sicherungen entfernen — Verschlüsselung und zweiter Faktor halten dann niemanden auf.
|
||
> *Betroffen:* alle 3 erreichbaren Repositories
|
||
> *Was tun:* Legen Sie ein gehärtetes Repository an und messen Sie die Durchsetzungsstufe.
|
||
|
||
Ein Befund ohne Empfehlung ist eine Beunruhigung — deshalb wird ein unvollständiger Befund abgelehnt statt ausgeliefert.
|
||
|
||
**Ein kritischer Befund deckelt die Einstufung** auf „unzureichend", unabhängig von der Prozentzahl: Eine Anlage, bei der ein gestohlenes Konto alles vernichten kann, ist nicht „gut abgesichert mit kleinem Mangel". Und zwei der zehn Bereiche werden **nicht** geprüft — sekundäre Kopien und Agentenzertifikate gibt es nicht. Sie zählen weder als bestanden noch als durchgefallen.
|
||
|
||
Real nachgewiesen: 35 % („unzureichend", 2 kritische Befunde) → Repository gehärtet → 40 % → zweiter Faktor eingerichtet → 57 % („verbesserungsbedürftig", 0 kritisch). Einzelheiten in [docs/security-center.md](docs/security-center.md).
|
||
|
||
## Der Alarm, den niemand mehr liest
|
||
|
||
Zehn Regeln prüfen fortlaufend, ob jemand hinsehen muss — vom gescheiterten Lauf über das volle Repository bis zum Verdacht auf massenhafte Verschlüsselung. Der Feind des Meldungswesens ist dabei nicht der fehlende Alarm:
|
||
|
||
> Es ist der Alarm, den niemand mehr liest.
|
||
|
||
Deshalb zwei Eigenschaften, die alles tragen: **Eine Ursache erzeugt genau eine Meldung** (der wiederholte Befund erhöht nur einen Zähler), und **Meldungen lösen sich selbst auf**, sobald ihre Ursache verschwindet. Real nachgewiesen: Zwei von vier nicht erreichbaren Repositories wieder verfügbar gemacht — genau deren zwei Meldungen schlossen sich automatisch, die anderen blieben offen.
|
||
|
||
„Bestätigen" heißt dabei „ich weiß davon", nicht „weg damit": Die Meldung bleibt in der Liste, bis der Zustand tatsächlich vorbei ist.
|
||
|
||
Die elfte Regel — ablaufende Agentenzertifikate — wird **nicht** ausgewertet und sagt das: Die Agenten weisen sich über Betriebstokens aus, die Tabelle bleibt leer. Eine Regel, die dauerhaft schweigt, ist gefährlicher als keine.
|
||
|
||
Zustellung per E-Mail und Webhook, jeweils mit eigener Schwelle. Einzelheiten in [docs/alerting.md](docs/alerting.md).
|
||
|
||
## Melden, niemals handeln
|
||
|
||
Sechs Signale beschreiben einen Sicherungslauf: neu abgelegte Datenmenge, geänderte Objekte, verschwundene Objekte, Anteil nicht verkleinerbarer Blöcke, neue Dateiendungen, Größe des Laufs. Gemessen wird nicht gegen eine feste Schwelle, sondern gegen den **Normalverlauf derselben Kette** — ein Dateiserver mit zwei geänderten Dateien am Tag und ein Buildserver mit vierzigtausend sind beide normal.
|
||
|
||
Der Anteil nicht verkleinerbarer Blöcke ist dabei das aussagekräftigste Signal, und er kostet nichts: Die Engine entscheidet ohnehin bei jedem Block, ob er sich komprimieren ließ. Verschlüsselte Daten lassen sich nicht komprimieren — ein Sprung von 5 % auf 100 % ist der Fingerabdruck massenhafter Verschlüsselung.
|
||
|
||
Was Syncova daraufhin tut, ist der eigentliche Punkt:
|
||
|
||
> Nichts.
|
||
|
||
Es wird nichts gelöscht, nichts gesperrt, nichts angehalten. Eine Heuristik, die selbsttätig handelt, macht aus jedem Fehlalarm einen Schaden — und von außen sieht ein Betriebssystem-Update aus wie ein Angriff. Die Empfehlung lautet stattdessen: *Prüfen Sie die Quelle, bevor Sie ältere Wiederherstellungspunkte löschen.* Sie ist eine Unterlassung, denn wer nach einem Angriff die Aufbewahrung laufen lässt, vernichtet die letzten sauberen Wiederherstellungspunkte.
|
||
|
||
Nachgewiesen in beide Richtungen — ein Detektor, der auch jeden großen Arbeitstag meldet, wird nach einer Woche ignoriert:
|
||
|
||
| Lauf | Einstufung | Auffällig |
|
||
| --- | --- | --- |
|
||
| 200 neue, komprimierbare Dokumente | erhöht | 1 von 6 Signalen |
|
||
| 150 Dateien durch Zufallsdaten mit Endung `.locked` ersetzt, Originale gelöscht | **hoch** | 4 von 6 Signalen |
|
||
|
||
Einzelheiten in [docs/ransomware.md](docs/ransomware.md).
|
||
|
||
## Was ein Bericht nicht sagen darf
|
||
|
||
Neun Berichte — vom Tagesbericht über die Repository-Kapazität bis zum Bericht für Prüfungen — in CSV, JSON und PDF. Das PDF ist selbst geschrieben: Ein Prüfbericht wird als PDF verlangt, nicht als Tabelle.
|
||
|
||
Der Unterschied zu einer gewöhnlichen Berichtsmaske steckt in den leeren Zellen:
|
||
|
||
> Ein Zeitraum ohne Sicherungslauf hat **keine** Erfolgsquote — nicht hundert Prozent und nicht null.
|
||
|
||
Ein Bericht verlässt die Anlage. Er landet in einer Tabellenkalkulation, wo eine Null summiert, gemittelt und in ein Diagramm gezeichnet wird, und in einem Ordner, aus dem ihn Monate später jemand zieht, ohne nachfragen zu können. Syncova lässt die Zelle deshalb leer und schreibt den Grund daneben.
|
||
|
||
Am deutlichsten wird das beim RTO: Die Zeit, die eine Wiederherstellung *bräuchte*, kennt niemand, bevor sie stattgefunden hat. Dort steht eine tatsächlich gemessene Dauer — oder „nicht gemessen". Eine Hochrechnung aus Datenmenge und Durchsatz wäre die bequemste Zahl des Berichts und die einzige, auf die sich im Ernstfall niemand verlassen könnte.
|
||
|
||
Und der Bericht für Prüfungen liefert Messwerte statt Urteile: kein „erfüllt", kein Haken, keine Norm. Er ist keine Zertifizierung und sagt das auch.
|
||
|
||
Einzelheiten in [docs/reports.md](docs/reports.md).
|
||
|
||
## Der Tag, an dem nichts mehr da ist
|
||
|
||
Vier Notfallszenarien — Control-Server verloren, Datenbank verloren, Katalog verloren, Abbruch mitten im Lauf. Alle vier wurden nicht beschrieben, sondern **durchgespielt**.
|
||
|
||
Die Entscheidung, an der alles hängt, steht in einem Satz:
|
||
|
||
> Eine Konfigurationssicherung, die nur auf dem Control-Server liegt, ist beim Verlust des Control-Servers wertlos.
|
||
|
||
Server und Datenbank gehen meist gemeinsam verloren — sie stehen auf derselben Maschine. Der einzige Ort, der das überlebt, ist das Repository. Dort liegt deshalb auch die Konfiguration, neben den Backups: Aufträge, Quellen, Aufbewahrungsregeln, Konten mit ihren Rollen.
|
||
|
||
Was dort **nicht** liegt, ist ebenso wichtig: keine Passwörter, keine zweiten Faktoren, keine Zugangsdaten der Benachrichtigungswege — auch nicht deren Konfiguration, denn in einer Webhook-Adresse steckt oft das Token. Ein Repository liegt naturgemäß außerhalb der Anlage, womöglich bei einem Dienstleister.
|
||
|
||
Nach dem Wiederaufbau läuft nichts von selbst wieder an: Aufträge kommen angehalten zurück, Repositories als „nicht erreichbar", Konten deaktiviert. Ein Zeitplan, der um zwei Uhr nachts von allein startet, könnte auf ein halb wiederhergestelltes System schreiben.
|
||
|
||
Der Nachweis, in Kurzform:
|
||
|
||
```text
|
||
DROP DATABASE syncova → 0 Tabellen
|
||
syncova-dr restore --catalog → Konfiguration + Wiederherstellungspunkte zurück
|
||
Quellverzeichnis gelöscht → Wiederherstellung: alle Prüfsummen stimmen
|
||
|
||
rm -rf indexes/ → Katalog baut sich aus den Manifesten neu auf
|
||
|
||
SIGKILL bei 496 von 1500 → Freigabe, Fortsetzung, 1500 Dateien, 0 übersprungen
|
||
```
|
||
|
||
Dabei fiel ein Fehler auf, den fünfzehn Phasen lang niemand bemerkt hatte: Jedes Manifest trug die Größe **null**, weil die Backup Engine an der Zählung der Schreibsession vorbeischrieb. Aufgefallen ist das erst, als das Repository zum ersten Mal die alleinige Quelle war — genau der Fall, für den es gebaut ist.
|
||
|
||
Einzelheiten in [docs/disaster-recovery.md](docs/disaster-recovery.md).
|
||
|
||
## Angegriffen statt behauptet
|
||
|
||
Elf Angriffsarten, gefahren gegen die laufende Anlage. Sieben wurden abgewehrt — SQL Injection, RBAC-Umgehung, Rechteausweitung, gefälschte Tokens, XSS, Pfadausbruch, und ein gesperrtes Konto verlor seinen Zugang sofort statt erst beim Ablauf des Tokens.
|
||
|
||
Vier kamen durch:
|
||
|
||
| Angriff | Was möglich war |
|
||
| --- | --- |
|
||
| Wiederherstellungsziel | Ein Backup nach `/etc/cron.d` zurückschreiben — aus einem Anwendungsrecht wird ein Systemzugang |
|
||
| SSRF | Ein Webhook auf `169.254.169.254` — den Metadatendienst der Cloud, der mit Zugangsdaten antwortet |
|
||
| Rate Limit | 100 von 100 Anfragen liefen durch; nur der Login war begrenzt |
|
||
| TLS | Gab es nicht |
|
||
|
||
Alle vier sind behoben, jeder mit einem Regressionstest. Beim SSRF-Schutz sitzt die Prüfung an **zwei** Stellen — beim Anlegen und erneut vor dem Verbindungsaufbau. Der Grund ist kein Übereifer: Ein DNS-Eintrag, der beim Prüfen öffentlich und beim Zustellen intern auflöst, ist der übliche Weg um eine einmalige Prüfung herum.
|
||
|
||
Dazu kam ein Fund aus dem Abhängigkeitsscan: neun Schwachstellen in der Go-Standardbibliothek. Nach dem Anheben der Toolchain: null.
|
||
|
||
Einzelheiten in [docs/hardening.md](docs/hardening.md).
|
||
|
||
## Zahlen, die sagen, woher sie kommen
|
||
|
||
Sieben Messszenarien auf einem Apple M1 mit acht Kernen, lokaler SSD und **inkompressiblen** Daten:
|
||
|
||
| Szenario | Ergebnis |
|
||
| --- | --- |
|
||
| Eine große Quelle (512 MiB) | 151,9 MiB/s |
|
||
| Zweiter Lauf über unveränderte Daten | 662,8 MiB/s |
|
||
| Vier gleichzeitige Aufträge | 148,4 MiB/s |
|
||
| Viele kleine Dateien (4000 × 16 KiB) | 107 Dateien/s |
|
||
|
||
Der zweite Lauf ist viermal schneller als der erste — gelesen und gehasht wird weiterhin alles, gespart wird das Ablegen. Das bestätigt unabhängig, was Phase 6 behauptet: Der Gewinn einer Zusatzsicherung ist Zeit, nicht Speicher.
|
||
|
||
Warum jede Zahl ihre Bedingungen mitträgt, hat einen Grund in der eigenen Geschichte: In Phase 4 zeigte ein Messlauf eine 1021-fache Kompression — die Testdaten waren periodisch, die Zahl wertlos. Heute lehnt das Messmodell einen Durchsatz aus komprimierbaren Daten ab.
|
||
|
||
Die Messung fand zwei Dinge. Der Chunker legte für **jede** Datei einen 4-MiB-Lesepuffer an, auch für eine von 16 KiB: 4000 Dateien forderten exakt 16 GiB Speicher an. Und bei kleinen Dateien ist nicht die Anlage der Engpass, sondern `fsync` — direkt gemessen 113 Dateien pro Sekunde mit, 4722 ohne. Die Anlage erreicht 107; ihr Eigenanteil liegt bei fünf Prozent. Das ist der Preis der Haltbarkeitszusage, kein Fehler.
|
||
|
||
Einzelheiten in [docs/performance.md](docs/performance.md).
|
||
|
||
## Jeder Fehler muss vorhersagbar enden
|
||
|
||
Platten laufen voll, Datenbanken fallen aus, Prozesse werden abgeschossen. Die Frage ist nicht, ob das passiert, sondern wie es endet. „Kontrolliert" heißt hier vier Dinge — und drei von vier genügen ausdrücklich nicht:
|
||
|
||
1. Der Fehler wird gemeldet, nicht verschluckt.
|
||
2. Er ist klassifiziert — sonst weiß niemand, ob eine Wiederholung sinnvoll ist.
|
||
3. Es bleibt **kein unvollständiges Backup zurück, das gültig aussieht**.
|
||
4. Der Zustand danach ist konsistent.
|
||
|
||
Die dritte wiegt am schwersten. Ein abgebrochener Lauf, der nichts hinterlässt, ist ein Ärgernis. Einer, der ein halbes Backup als gültig hinterlässt, ist ein Datenverlust mit Zeitzünder — er fällt erst auf, wenn jemand wiederherstellen will.
|
||
|
||
Geprüft mit einem **echten** 40-MiB-Dateisystem, einer Sicherung von 120 MiB und einem angehaltenen Datenbankcontainer. Zwei Funde:
|
||
|
||
**Eine volle Platte wurde als Quellfehler eingestuft — und deshalb wiederholt.** Die Quelle war in Ordnung; jeder neue Versuch legte weitere Blöcke ab und verschärfte die Lage. Eine falsche Fehlerklasse führt hier nicht zu einem sinnlosen, sondern zu einem schädlichen Versuch.
|
||
|
||
**Ein Datenbankausfall meldete sich als „Sitzung abgelaufen".** Die Tokenprüfung braucht die Datenbank; fällt sie aus, scheitert jede Anmeldung. Der Betreiber meldet sich neu an, was ebenfalls scheitert, und sucht den Fehler an der falschen Stelle.
|
||
|
||
Einzelheiten in [docs/chaos.md](docs/chaos.md).
|
||
|
||
## Eine Lücke ist keine Null
|
||
|
||
Zwölf Diagramme über sieben Zeiträume, von der Erfolgsquote bis zum Alter des jüngsten Wiederherstellungspunkts. Die Regel, die darüber entscheidet, ob eine Kurve etwas wert ist:
|
||
|
||
> Ein Zeitfenster ohne Sicherungslauf hat **keinen** Durchsatz — nicht null Byte je Sekunde.
|
||
|
||
Wer Lücken als Nullen zeichnet, erzeugt eine Kurve, die nachts auf den Boden fällt: Die Anlage sieht aus, als wäre ihre Leistung eingebrochen, obwohl sie nur nichts zu tun hatte. Syncova unterbricht die Linie stattdessen. Das Diagramm ist deshalb selbst geschrieben — die gängigen Bibliotheken machen es standardmäßig falsch.
|
||
|
||
Zwei der zwölf Diagramme haben keine Datengrundlage und sagen das: Die Agenten melden keine Ressourcendaten, und die Größe nach der Kompression wird nirgends festgehalten. Eine Kurve aus geschätzten Werten wäre eine erfundene Statistik.
|
||
|
||
```bash
|
||
curl "/api/v1/metrics" # Katalog aller Diagramme
|
||
curl "/api/v1/metrics/backup_throughput?range=24h" # eine Reihe
|
||
```
|
||
|
||
Einzelheiten in [docs/metrics.md](docs/metrics.md).
|
||
|
||
## Kann ein Angreifer die Backups löschen?
|
||
|
||
Syncova beantwortet die Frage nicht mit einer Datenbankspalte, sondern mit einer **Messung**: Eine Probedatei wird angelegt und zu löschen versucht. Gemeldet wird nur, was das Betriebssystem nachweislich verhindert.
|
||
|
||
```bash
|
||
curl -X POST /api/v1/repositories/<id>/enforcement/measure
|
||
```
|
||
|
||
| Stufe | Bedeutung |
|
||
| --- | --- |
|
||
| `advisory` | Nur diese Software hält sich daran. Wer Dateizugriff hat, löscht trotzdem. |
|
||
| `filesystem` | Das Dateisystem verweigert die Löschung. Ein Systemverwalter kann den Schutz weiterhin aufheben. |
|
||
| `storage` | S3 Object Lock / WORM — **nicht umgesetzt**, wird deshalb nie gemeldet. |
|
||
|
||
In einem gehärteten Repository tragen Manifeste, Datenblöcke, Schutzvermerke, der Descriptor **und der Datenschlüssel** das Unveränderlich-Kennzeichen des Dateisystems. Real nachgewiesen: `rm -rf` auf ein solches Repository verweigert 15 Löschungen, alle geschützten Dateien überleben, und der anschließende Tiefenscan meldet das Backup als vollständig.
|
||
|
||
Zwei Angriffsversuche waren zunächst erfolgreich und haben den Umfang bestimmt: Beim ersten überlebte das Manifest, aber nicht seine Blöcke — ein Backup, das sich für vollständig ausgibt und leer ist. Beim zweiten fehlten Descriptor und Datenschlüssel; die Daten waren da und trotzdem verloren.
|
||
|
||
Dazu **Legal Hold** (unbefristet, mit Begründungspflicht), Fristverlängerung (verkürzen ist ausgeschlossen — auch für Administratoren) und Aufbewahrungsregeln mit Vorschau. Jede Regel schützt das letzte vorhandene Backup: Ohne diese Sicherung löschte „7 Tage" bei einem drei Wochen nicht gesicherten System *jedes* Backup. Einzelheiten in [docs/immutability.md](docs/immutability.md).
|
||
|
||
## Proxmox
|
||
|
||
Ein Gast lässt sich entdecken, sichern, prüfen und bitgenau zurückschreiben — die Kette läuft von der Auftragsverwaltung über vzdump und die Backup Engine ins Repository und wieder zurück auf den Knoten. Ein Test belegt den vollständigen Rundlauf.
|
||
|
||
**Ungeprüft bleibt der entscheidende Schritt: ob die wiederhergestellte Maschine startet.** Dafür stand keine echte Umgebung zur Verfügung. Bis zu diesem Nachweis ist der Provider nicht freigegeben; der Ablauf dafür steht in [docs/proxmox.md](docs/proxmox.md).
|
||
|
||
Die Lücke, die alles erklärt: **Proxmox kann eine Sicherung anstoßen, gibt die entstandene Datei aber nicht über die REST-API heraus.** Deshalb braucht Syncova einen zweiten Zugriffsweg auf den Knoten — als Dateizugriff auf eine gemeinsame Freigabe (`local`) oder über SSH. Beim SSH-Weg wird ein Knoten ohne hinterlegten Fingerabdruck seines Wirtsschlüssels abgelehnt; einen Schalter „Wirtsschlüssel egal" gibt es nicht.
|
||
|
||
Rein lesende Erfassung gegen einen echten Verbund — der erste Schritt, er verändert nichts:
|
||
|
||
```bash
|
||
export SYNCOVA_PROXMOX_TOKEN_SECRET="<geheimnis>"
|
||
syncova-proxmox discover --url https://pve.example:8006 \
|
||
--token 'syncova@pve!backup' --fingerprint <sha256> --disks
|
||
```
|
||
|
||
## Was ist eingefroren?
|
||
|
||
Vor einer Auslieferung wird festgeschrieben, worauf sich andere verlassen: der API-Vertrag, die Migrationen, das Backup-Format und das Repository-Protokoll. Nicht als Absichtserklärung, sondern als Prüfung, die anschlägt.
|
||
|
||
| Vertrag | Was auffällt |
|
||
| --- | --- |
|
||
| 102 API-Endpunkte | ein neuer, ein entfallener oder ein **anders berechtigter** Endpunkt |
|
||
| 26 Migrationsdateien | eine nachträglich geänderte Migration, eine fehlende Gegenrichtung |
|
||
| Backup-Container | ein Container von gestern, der heute nicht mehr lesbar ist |
|
||
| Repository | ein umbenanntes Verzeichnis, ein beschädigter Block |
|
||
|
||
Die letzten beiden liegen als **echte Dateien** im Quellbestand. Ein gewöhnlicher Rundlauftest schreibt und liest mit demselben Code — er bliebe grün, wenn sich beide Seiten gemeinsam ändern, also genau im gefürchteten Fall.
|
||
|
||
Der wichtigste neue Test ist der, den es nie gab: **eine alte Datenbank mit Daten, über die eine neue Version läuft.** Bisher wurde jedes Schema von Grund auf angelegt; damit prüft man ausschließlich die Neuinstallation. Eine Spalte mit `NOT NULL` ohne Vorgabewert läuft auf einer leeren Tabelle einwandfrei durch und scheitert auf einer gefüllten.
|
||
|
||
Der vollständige Durchlauf brachte dabei einen Fund, den keine der 21 Phasen davor gefunden hatte: **Ein Repository ließ sich über die API gar nicht anlegen** — jeder frühere Nachweis hatte die Datenbankzeile selbst geschrieben. Einzelheiten in [docs/release-candidate.md](docs/release-candidate.md).
|
||
|
||
## Auslieferung
|
||
|
||
```bash
|
||
make release
|
||
```
|
||
|
||
Erzeugt je Zielplattform einen Verzeichnisbaum und ein Archiv — Programme, Oberfläche, Migrationen, Dokumentation, Prüfsummen. Die Programme sind statisch gebunden und laufen in einem leeren `debian:12-slim`.
|
||
|
||
| Paket | Inhalt |
|
||
| --- | --- |
|
||
| `linux-amd64`, `linux-arm64` | vollständiger Server, Oberfläche, Agent, alle Werkzeuge, `setup.sh`/`update.sh`/`uninstall.sh` |
|
||
| `windows-amd64` | Agent und `syncova-repo` |
|
||
|
||
macOS und ein vollständiger Windows-Server werden bewusst nicht ausgeliefert: Eine Plattform ohne Betriebskonzept weckt Erwartungen, die niemand einlöst.
|
||
|
||
Installieren: `sudo ./setup.sh` aus dem entpackten Paket. Aktualisieren: `sudo ./update.sh` — es sichert vorher und nimmt sich zurück, wenn der Dienst danach nicht hochkommt. Entfernen: `sudo ./uninstall.sh` — Datenbank, Repository und Konfiguration bleiben liegen, sofern man nicht ausdrücklich etwas anderes verlangt.
|
||
|
||
**Release veröffentlichen und das System durchtesten:** [docs/release-howto.md](docs/release-howto.md) — vier Stufen vom Rauchtest bis zum offenen Proxmox-Meilenstein, jeweils mit Gegenprobe.
|
||
|
||
Für den Einstieg: [Installation](docs/installation.md) · [Wiederherstellung im Ernstfall](docs/recovery-runbook.md) · [Sicherheitsleitfaden](docs/security-guide.md) · [API](docs/api.md) · [Störungen](docs/troubleshooting.md)
|
||
|
||
## Schnellstart
|
||
|
||
Voraussetzungen: Go 1.26+, Node.js 22+, Docker.
|
||
|
||
```bash
|
||
make dev-env # erzeugt .env mit Zufallspasswort und Schlüssel
|
||
make dev-up # startet PostgreSQL
|
||
make migrate-up # wendet die Datenbankmigrationen an
|
||
make create-admin USERNAME=admin # legt den ersten Administrator an
|
||
make run-api # startet die API auf 127.0.0.1:8080
|
||
|
||
# in einer zweiten Shell:
|
||
make web-install
|
||
make web-dev # startet die Oberfläche auf 127.0.0.1:5173
|
||
```
|
||
|
||
Es gibt bewusst **kein** vorkonfiguriertes Standardkonto: das wäre eine bekannte Schwachstelle jeder Installation. Der erste Administrator wird auf dem Server angelegt, das Passwort dabei verdeckt eingegeben.
|
||
|
||
Prüfung, ob alles läuft:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8080/api/v1/health
|
||
```
|
||
|
||
`make help` listet alle verfügbaren Ziele.
|
||
|
||
## Projektstruktur
|
||
|
||
```text
|
||
apps/
|
||
api/ Control-Plane-API und Migrationskommando
|
||
web/ Weboberfläche (React + TypeScript)
|
||
agent/ Windows- und Linux-Agent (ab Phase 5)
|
||
worker/ Hintergrundverarbeitung (ab Phase 4)
|
||
packages/
|
||
platform/ Querschnitt: Konfiguration, Logging, Datenbank, Health
|
||
migrations/ Versionierte SQL-Migrationen
|
||
deployment/ Lokale Entwicklungsumgebung
|
||
docs/ Betriebs- und Architekturdokumentation
|
||
```
|
||
|
||
## Kommandos
|
||
|
||
| Kommando | Zweck |
|
||
| --- | --- |
|
||
| `syncova-api` | Startet den Control-Plane-API-Dienst |
|
||
| `syncova-migrate up` | Wendet alle ausstehenden Migrationen an |
|
||
| `syncova-migrate status` | Zeigt den Migrationsstand |
|
||
| `syncova-migrate down` | Nimmt genau eine Migration zurück |
|
||
| `syncova-admin generate-key` | Erzeugt einen Verschlüsselungsschlüssel |
|
||
| `syncova-admin create-admin` | Legt den ersten Administrator an |
|
||
| `syncova-admin reset-password` | Setzt ein Passwort zurück (Aussperrung) |
|
||
| `syncova-repo create` | Legt ein Repository an (`--hardened` für Aufbewahrungsschutz) |
|
||
| `syncova-dr export` / `inspect` / `restore` | Konfiguration ins Repository sichern, ansehen, einspielen |
|
||
| `syncova-bench run` | Fährt die Leistungsmessung mit benannten Messbedingungen |
|
||
| `syncova-repo scan --deep` | Prüft jeden Chunk gegen seine Prüfsumme |
|
||
| `syncova-repo rebuild` | Baut den Katalog allein aus den Manifesten auf |
|
||
| `syncova-repo list` / `info` / `health` | Backups, Angaben und Zustand anzeigen |
|
||
| `syncova-repo prune` | Entfernt verwaiste Chunks (`--apply` zum Ausführen) |
|
||
| `syncova-repo export` | Schreibt ein Backup als portablen Container |
|
||
| `syncova-repo import` | Liest einen Container in ein Repository ein |
|
||
| `syncova-repo inspect` | Prüft einen Container vollständig, ohne ihn einzulesen |
|
||
| `syncova-agent register` | Nimmt den Agent mit einem Aufnahme-Token auf |
|
||
| `syncova-agent run` | Startet den Agent als Dienst |
|
||
| `syncova-agent discover` | Zeigt, welche Dateien erfasst würden |
|
||
| `syncova-agent backup` | Sichert ein Verzeichnis in ein Repository |
|
||
| `syncova-agent restore` | Stellt ein Backup wieder her |
|
||
| `syncova-proxmox discover` | Erfasst Verbund, Knoten, Gäste und Platten (rein lesend) |
|
||
| `syncova-proxmox clusters` | Zeigt die eingerichteten Virtualisierungsverbünde |
|
||
| `syncova-proxmox restore-guest` | Stellt einen gesicherten Gast wieder her |
|
||
|
||
Migrationen laufen bewusst als eigenes Kommando: der Start des API-Dienstes verändert das Schema niemals selbst. Passt das Schema nicht zur Programmversion, verweigert der Dienst den Start mit einer erklärenden Meldung.
|
||
|
||
## Konfiguration
|
||
|
||
Die Konfiguration erfolgt ausschließlich über Umgebungsvariablen mit dem Präfix `SYNCOVA_`; siehe [.env.example](.env.example). Es gibt bewusst keine eingebauten Zugangsdaten: fehlen `SYNCOVA_DB_PASSWORD` oder `SYNCOVA_ENCRYPTION_KEYS`, startet kein Dienst.
|
||
|
||
**Der Verschlüsselungsschlüssel ist kritisch.** Ohne ihn sind MFA-Secrets und später Repository-Zugangsdaten dauerhaft unlesbar. Er gehört sicher verwahrt und getrennt von der Datenbank gesichert.
|
||
|
||
## Dokumentation
|
||
|
||
| Dokument | Inhalt |
|
||
| --- | --- |
|
||
| [PROMPT.md](PROMPT.md) | Produktdefinition und verbindliche Entwicklungsregeln |
|
||
| [SYNCOVA_ARCHITECTURE.md](SYNCOVA_ARCHITECTURE.md) | Systemarchitektur, Backup-Format, Repository-Protokoll |
|
||
| [SYNCOVA_DATABASE.md](SYNCOVA_DATABASE.md) | Datenbankschema der Control Plane |
|
||
| [SYNCOVA_API.md](SYNCOVA_API.md) | REST-API-Vertrag |
|
||
| [SYNCOVA_IMPLEMENTATION_PLAN.md](SYNCOVA_IMPLEMENTATION_PLAN.md) | Phasen, Exit-Kriterien, Akzeptanztests |
|
||
| [docs/development.md](docs/development.md) | Entwicklungsumgebung und Arbeitsweise |
|
||
| [docs/architecture.md](docs/architecture.md) | Aufbau des aktuellen Codes |
|
||
| [docs/security.md](docs/security.md) | Sicherheitsarchitektur: Passwörter, MFA, Sitzungen, RBAC, Audit |
|
||
| [docs/ransomware.md](docs/ransomware.md) | Ransomware-Heuristik: sechs Signale, robuster Basiswert, „Alert first" |
|
||
| [docs/reports.md](docs/reports.md) | Berichte: neun Arten, drei Formate, selbst geschriebenes PDF |
|
||
| [docs/disaster-recovery.md](docs/disaster-recovery.md) | Notfallhandbuch: vier Szenarien, real durchgespielt |
|
||
| [docs/hardening.md](docs/hardening.md) | Härtung: elf Angriffsarten, vier Funde, vier Behebungen |
|
||
| [docs/performance.md](docs/performance.md) | Leistungsmessung: sieben Szenarien, Zahlen mit Bedingungen |
|
||
| [docs/chaos.md](docs/chaos.md) | Chaos Testing: neun Störungen, zwei Funde |
|
||
| [docs/repository.md](docs/repository.md) | Repository-Format, Commit-Protokoll, Integrität, Wiederaufbau |
|
||
| [docs/backup-format.md](docs/backup-format.md) | Container-Format, Versionsverhandlung, Export und Import |
|
||
| [docs/backup-engine.md](docs/backup-engine.md) | Pipeline, Chunking, Deduplizierung trotz Verschlüsselung, Messwerte |
|
||
| [docs/linux-agent.md](docs/linux-agent.md) | Linux-Agent: Sonderdateien, systemd-Härtung, bekannte Grenzen |
|
||
| [docs/agent-installation.md](docs/agent-installation.md) | Agent einrichten: Windows-Dienst, systemd, Repositoryzugriff |
|
||
| [docs/agent-tasks.md](docs/agent-tasks.md) | Auftragsübermittlung: der Agent holt ab, führt aus, meldet zurück |
|
||
| [docs/agent.md](docs/agent.md) | Agent: Tokenarten, Registrierung, Erfassung, offene Punkte |
|
||
|
||
## Entwicklungsregeln
|
||
|
||
Diese Regeln gelten ohne Ausnahme:
|
||
|
||
1. Ein Teilfehler heißt `PARTIAL FAILURE`, niemals `SUCCESS`.
|
||
2. Integritätsfehler werden nie verborgen; kein Fehler wird still verschluckt.
|
||
3. Backup-Nutzdaten gehören niemals in PostgreSQL.
|
||
4. Secrets erscheinen nie in Logs, Fehlermeldungen, API-Antworten oder der Oberfläche.
|
||
5. Berechtigungen werden immer serverseitig geprüft.
|
||
6. Destruktive Aktionen werden bestätigt und auditiert.
|
||
7. Unfertige Funktionen werden als „nicht implementiert“ gekennzeichnet, nicht simuliert.
|
||
8. Kein Backup-Feature ohne zugehörigen Recovery-Test.
|