Derived from the running system: prerequisites (age key from the vault first), the three Borg repos with their archive layout, bootstrap order (host/K3s, the two manual secrets, Flux, kustomization dependencies), data restore and verification. Lives in git rather than Wiki.js on purpose — the wiki runs on the cluster being restored. Two previously undocumented findings: Flux pulls from Gitea rather than git.lab, so a simultaneous loss of rohana requires repointing gotk-sync first; and consumers must be scaled down before pg_restore or their startup schema collides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
8.7 KiB
Markdown
142 lines
8.7 KiB
Markdown
---
|
||
type: issue
|
||
id: "0030"
|
||
status: in-progress
|
||
created: 2026-08-06
|
||
milestone: M1
|
||
priority: medium
|
||
area: security
|
||
gitlab_iid: "30"
|
||
related: []
|
||
---
|
||
# Der Restore ist nie geprobt — Sicherungen sind bisher eine Vermutung
|
||
|
||
> Import aus [management#30](https://git.lab/axion1337.chat/management/-/issues/30) (2026-08-11). Kommentare und Verlauf bleiben dort; kanonisch ist ab jetzt diese Datei (ADR-0012).
|
||
|
||
Es wird gesichert: jeden Sonntag, alle Dienste, drei Versionen vorgehalten, GitLab auf Overmind mit Datenbank **und** Volumes nach MinIO auf dem DSM. Das ist mehr, als die meisten haben.
|
||
|
||
**Was fehlt, ist der Beweis, dass sich daraus etwas wiederherstellen lässt.** Es gibt kein dokumentiertes Verfahren und keinen je durchgespielten Versuch. Eine Suche über `docs/` und `verfahren/` findet nur Erwähnungen in `install.md` — keine Anleitung, keine Protokolle.
|
||
|
||
## Warum das der wichtigste der offenen Punkte ist
|
||
|
||
Eine Sicherung, die nie zurückgespielt wurde, ist eine **Vermutung**. Die typischen Fehler zeigen sich ausschließlich beim Zurückspielen und nie beim Sichern:
|
||
|
||
- die Datenbank ist gesichert, aber ohne das Volume mit den Uploads ist sie wertlos
|
||
- der Dump ist da, aber der Verschlüsselungsschlüssel lag nur auf dem Host, der weg ist
|
||
- es liegen drei Versionen, aber alle drei sind seit Wochen leer, weil ein Pfad umgezogen ist und keiner es gemerkt hat
|
||
- niemand weiß, in welcher Reihenfolge die Dienste hochkommen müssen
|
||
|
||
Der letzte Punkt ist hier besonders relevant: **Der SOPS-age-Schlüssel entschlüsselt alle Secrets im Cluster.** Wenn der nur an einer Stelle liegt, ist die Frage nicht, ob die Sicherung funktioniert, sondern ob sie überhaupt etwas nützt.
|
||
|
||
## Was zu tun ist
|
||
|
||
1. **Zuerst das Billigste:** stichprobenartig in die aktuellen Sicherungen hineinschauen. Sind sie plausibel groß? Enthalten sie, was sie sollen? Das findet stille Ausfälle sofort.
|
||
2. Ein echtes Wiederherstellungsverfahren schreiben — als Ablauf, nicht als Prosa: welcher Dienst zuerst, woher der SOPS-Schlüssel, woher der kubeconfig.
|
||
3. **Einmal wirklich durchspielen**, gegen eine Wegwerf-Umgebung, nicht gegen die Produktion. Was dabei fehlt, ist das Ergebnis.
|
||
4. Ergebnis als Verfahren in `verfahren/` ablegen und danach in bekanntem Abstand wiederholen.
|
||
|
||
⚠️ Bewusst **nicht** vorschlagen: die Sicherung erweitern, bevor die vorhandene geprüft ist. Mehr zu sichern, ohne zu wissen, ob das Vorhandene trägt, verschiebt das Problem nur.
|
||
|
||
## Grenzen dieses Issues
|
||
|
||
Ich kenne den Sicherungsaufbau nur aus deiner Beschreibung und einem Screenshot, nicht aus eigener Anschauung. Der erste Schritt ist deshalb Bestandsaufnahme, nicht Bewertung.
|
||
|
||
*Aufgenommen am 2026-08-06 bei einer Bestandsaufnahme der Sicherheitslage.*
|
||
|
||
## Schritt 1 erledigt 2026-08-14 — Bestandsaufnahme (Cluster-Seite)
|
||
|
||
Wie im Issue gefordert zuerst „das Billigste": in die vorhandenen Sicherungen hineingeschaut,
|
||
nichts erweitert. Geprüft wurde die **Matrix-Cluster-Seite** (Hetzner/K3s); die Homelab-Seite
|
||
(GitLab auf Overmind → MinIO/DSM) ist von hier nicht erreichbar und weiterhin ungeprüft.
|
||
|
||
**Befund: die Sicherungen selbst sind besser als vermutet.** Drei nächtliche CronJobs, alle
|
||
`Complete`, keiner suspendiert, alle **off-host** per Borg auf eine Hetzner Storage Box
|
||
(`u641795@…your-storagebox.de:23`) — also genau das Muster, das #0010 für Gitea erst plant:
|
||
|
||
| Job | Zeit | Ziel-Repo | Volumen (letzter Lauf) | Bewertung |
|
||
|---|---|---|---|---|
|
||
| `synapse-backup` (matrix) | 03:00 | `/./synapse-backup` | 199,5 MB, 247 Dateien, Dedup-Delta 3,7 MB | plausibel ✅ |
|
||
| `authentik-backup` (authentik) | 03:15 | `/./authentik-backup` | ~150 MB gesamt, Delta ~15 MB | plausibel ✅ |
|
||
| `wikijs-backup` (matrix) | 03:30 | `/./wikijs-backup` | 223 kB | plausibel ✅ (nur Postgres; die Wiki-**Inhalte** liegen per git-storage in Gitea/git.lab) |
|
||
|
||
Retention greift nachweislich (`borg prune`, 7 daily / 4 weekly / 6 monthly, „Deleted data"
|
||
in den Logs). Kein stiller Ausfall, keine leeren Archive — die Sicherung ist **keine bloße
|
||
Vermutung mehr**, zumindest was Existenz und Inhalt angeht.
|
||
|
||
### ⚠️ Kritischer Befund: Zirkelabhängigkeit beim age-Schlüssel
|
||
|
||
Genau das vom Issue vorhergesagte Muster („der Dump ist da, aber der Schlüssel lag nur auf dem
|
||
Host, der weg ist") liegt real vor:
|
||
|
||
1. Zum **Lesen** der Borg-Repos braucht man Borg-Passphrase **und** SSH-Key.
|
||
2. Beide liegen in `synapse-backup-secret.yaml` / `authentik-backup-secret.yaml` — **SOPS-verschlüsselt**.
|
||
3. Entschlüsselbar ist das nur mit **einem einzigen** age-Schlüssel
|
||
(Empfänger `age14l0hw…`, siehe `.sops.yaml`).
|
||
4. Dieser private Schlüssel existiert an **genau zwei Orten**, und beide sind „heiß":
|
||
- `sops-age`-Secret in `flux-system` — **im Cluster, den das Backup schützen soll**
|
||
- `~/.age/keys.txt` auf sorbs Mac — **ein einzelnes Gerät**
|
||
|
||
**Folge:** Gehen Cluster und Mac zusammen verloren (Totalschaden Hetzner + Laptop weg/defekt),
|
||
sind **alle drei Borg-Repos dauerhaft unlesbar**. Die Sicherungen wären technisch einwandfrei
|
||
und trotzdem wertlos. Eine Suche über `docs/` und `hosts/` findet **keine** dokumentierte
|
||
Auslagerung (Escrow, Offline-Kopie, Passwort-Manager) des Schlüssels.
|
||
|
||
**Billigste wirksame Gegenmaßnahme (vor jedem Restore-Test):** eine **kalte Kopie** des
|
||
age-Schlüssels außerhalb von Cluster und Mac anlegen — Passwort-Manager und/oder Ausdruck an
|
||
sicherem Ort — und die Fundstelle in `hosts/` dokumentieren (nur *wo*, nie der Wert). Erst danach
|
||
lohnt der eigentliche Restore-Durchlauf, sonst probt man einen Ablauf, dessen Voraussetzung
|
||
selbst ungesichert ist.
|
||
|
||
### Nächste Schritte (unverändert nach Issue-Plan)
|
||
|
||
2. Restore-Verfahren schreiben (Reihenfolge, Herkunft von age-Key und kubeconfig).
|
||
3. Einmal gegen eine Wegwerf-Umgebung durchspielen.
|
||
4. Ergebnis nach `verfahren/` und Wiederholungsrhythmus festlegen.
|
||
|
||
## Update 2026-08-14 — age-Schlüssel ist ausgelagert (Befund entschärft)
|
||
|
||
sorb bestätigt: der private age-Schlüssel liegt **zusätzlich im Passwort-Vault**, also außerhalb
|
||
von Cluster und Mac. Damit ist die oben beschriebene Zirkelabhängigkeit **aufgelöst** — ein
|
||
gleichzeitiger Verlust von Hetzner-Host und Laptop macht die Borg-Repos nicht mehr unlesbar.
|
||
Der Befund bleibt hier stehen, weil die Kette (Backup lesen → Borg-Passphrase → SOPS → **ein**
|
||
age-Schlüssel) beim Schreiben des Restore-Verfahrens explizit auftauchen muss: der Vault ist
|
||
Teil des Wiederherstellungswegs, nicht nur Nebensache.
|
||
|
||
**Zu ergänzen beim Verfahren (Schritt 2):** Fundort des Schlüssels benennen (nur *wo*, nie der
|
||
Wert) und im Restore-Ablauf als ersten Schritt führen — ohne ihn ist kein weiterer Schritt
|
||
möglich.
|
||
|
||
## Restaufwand nach heutiger Bestandsaufnahme
|
||
|
||
Schritt 1 (Bestandsaufnahme) ist erledigt und positiv ausgefallen; Schritt 2–4 stehen aus:
|
||
Restore-Verfahren schreiben, einmal gegen eine Wegwerf-Umgebung durchspielen, Ergebnis nach
|
||
`verfahren/` und Wiederholungsrhythmus. Die Homelab-Seite (GitLab auf Overmind → MinIO/DSM)
|
||
ist weiterhin ungeprüft — von der Hetzner-Seite aus nicht erreichbar.
|
||
|
||
## Schritt 2 erledigt 2026-08-14 — Restore-Verfahren geschrieben
|
||
|
||
[`docs/wiki/deployment/restore.md`](../wiki/deployment/restore.md) — als Ablauf, nicht als Prosa:
|
||
Voraussetzungen (age-Key aus dem Vault zuerst), Fundort und Layout der drei Borg-Repos,
|
||
Bootstrap-Reihenfolge (Host/K3s → die zwei manuellen Secrets `sops-age` + `flux-system` → Flux →
|
||
`infra-apps` → production/authentik/monitoring), Daten-Rückspielung und Verifikation.
|
||
|
||
**Abweichung vom Issue-Wortlaut:** abgelegt unter `docs/wiki/deployment/` statt `verfahren/` —
|
||
dort liegt bereits die Deploy-Übergabe, nur `docs/**` wird von `validate.py` geprüft, und die
|
||
Seite ist über den Wiki-Index auffindbar. `verfahren/` enthält bislang ausschließlich Skripte.
|
||
|
||
**Zwei Erkenntnisse beim Schreiben, die vorher nicht dokumentiert waren:**
|
||
|
||
1. **Flux zieht aus Gitea (rohana), nicht aus git.lab.** Ist rohana beim Ausfall ebenfalls weg,
|
||
hängt der Wiederanlauf an einem Host, der gar nicht Teil des Backup-Konzepts ist — dann muss
|
||
zuerst eine erreichbare Git-Quelle hergestellt und `gotk-sync.yaml` umgebogen werden. Im
|
||
Verfahren als Warnung vermerkt.
|
||
2. **Konsumenten müssen vor dem `pg_restore` heruntergefahren werden.** Synapse/MAS/Authentik/
|
||
Wiki.js legen beim Start ein leeres Schema an, gegen das ein Restore kollidiert.
|
||
|
||
Ebenfalls festgehalten: Borg nutzt `repokey-blake2`, die Passphrase allein genügt (keine separate
|
||
Schlüsseldatei), und Passphrase/SSH-Key sind **ohne Cluster** per `sops -d` aus einem lokalen
|
||
Clone lesbar — der age-Key aus dem Vault ist damit tatsächlich der einzige harte Startpunkt.
|
||
|
||
**Offen: Schritt 3** (einmal gegen eine Wegwerf-Umgebung durchspielen) und **Schritt 4**
|
||
(Wiederholungsrhythmus). Das Verfahren ist bis dahin abgeleitet, aber unerprobt.
|