# Isolationslauf-Protokoll — Deployment in netzisolierter Umgebung (Issue #220) Nachweis für Maßnahmenplan P1-3 (ADR 0024): ein vollständiges Deployment läuft mit blockiertem Egress. Beantwortet zugleich die offene Frage der Ist-Aufnahme I-28 („Was bricht ohne Internetzugang?"). Belegstufe: **live verifiziert** — alle Ergebnisse stammen aus dem beschriebenen Lauf, Abweichungen sind ausgewiesen. - **Datum:** 2026-07-31 - **Host:** lokaler Docker-Host (aarch64); Compose-Projekt `dorfteich-isolated` aus dem Produktions-Compose (`deploy/compose/docker-compose.yml`) plus Override. - **Netzisolation:** beide Compose-Netze (`frontend`, `internal`) mit `internal: true` — Docker legt die Bridges ohne NAT/Default-Route an; es existiert kein Egress-Pfad. Port-Publishing ist dadurch wirkungslos, alle Prüfzugriffe erfolgen aus einem Treiber-Container (`node:22.15.1-alpine`, fetch-basierte Prüfskripte) im `frontend`-Netz gegen `http://api:3000`. - **Eigene Images:** `dorfteich-iso-{web,api,collab,backup}:isolated`, lokal gebaut aus `main@a758c9d`; das backup-Image nach dem Befund unten neu aus `4f6596e` (#288). Image-IDs: web `9d65c54f05fc`, api `1826dedd662a`, collab `8a1b2841b91c`, backup `117ff18c7e71`. - **Dritt-Images (digest-gepinnt, Compose-Pins):** `postgres:17.5-alpine@sha256:6567bca8…`, `pandoc/core:3.6@sha256:5b8a29d9…`, `gotenberg/gotenberg:8@sha256:67097317…`. - **Egress-Beweisführung:** `tcpdump -n -l not arp` auf beiden Bridge-Interfaces des Hosts über die gesamte Laufzeit (Setup bis nach dem Restore-Re-Run). ## Prüfschritte und Ergebnisse | Schritt | Ergebnis | | ------------------------------ | ------------------------------------------------------------------------------------------------ | | Installation / First-Run-Setup | ✅ migrate-on-start (41 Migrationen) + Pre-Seed-Setup über `SETUP_*`-Env (Registrierung closed) | | `/readyz` | ✅ alle Checks ok (backup-Check erst nach dem ersten Backup-Lauf, s. Abweichung 1) | | Login (Session-Cookie) | ✅ Status 200 | | Seite anlegen | ✅ Seite `3331f670…` im Personal-Teich | | Live-Kollaboration | ✅ echter Collab-Client (HocuspocusProvider) über den collab-Dienst, Inhalt synced + persistiert | | Markdown-Export | ✅ 78 Zeichen, Inhalt vorhanden | | Suche (Postgres-Volltext) | ✅ 1 Treffer auf den Seiteninhalt | | Datei-Upload + Download | ✅ 201 / 200 | | DOCX-Export (pandoc-Sidecar) | ✅ 9 926 Bytes | | PDF-Export (Gotenberg-Sidecar) | ✅ 20 903 Bytes | | Mail-Enqueue | ✅ 204 — Zustellverhalten s. eigener Abschnitt | | Backup | ✅ Set `20260731-132200` (Dump + Volume-Archiv), readyz-backup-Check danach ok | | Restore | ✅ nach Fix #288 — Exit 0, Inhalt exakt der Backup-Stand (s. Abweichung 2) | ## Egress-Bilanz Vollständige Capture-Auswertung (alle Pakete, beide Bridges): | Bridge | Pakete | Ziel-Verteilung | | ---------- | ------ | ---------------------------------------------------------------------------- | | `internal` | 37 897 | ausnahmslos `192.168.156.0/24` → `192.168.156.0/24` (api/collab/backup ↔ db) | | `frontend` | 448 | ausnahmslos `192.168.158.0/24` → `192.168.158.0/24` (Treiber ↔ api/Sidecars) | Einzige weitere Frames: 2 × IPv6 Link-Local → `ff02::16` (MLDv2 Multicast Listener Report, Kernel-Housekeeping der Bridge — kein Egress). **Kein einziges Paket an ein Ziel außerhalb der isolierten Subnetze, keine DNS-Anfragen auf den Bridges.** Die Anwendung unternimmt genau **einen** nach außen gerichteten Verbindungsversuch: SMTP (s. u.). Damit ist I-28 beantwortet: ohne Internetzugang bricht nichts außer der Mail-**Zustellung**; Telemetrie-, Update-, CDN- oder Font-Abrufe existieren nicht (bestätigt die Ist-Aufnahme §0.2). ## SMTP-Verhalten ohne Egress Die einzige nach außen gerichtete Verbindung; genau der Punkt, den eine Behörde zulassen oder untersagen wird: - `POST`-Auslöser antwortet **204** — die Outbox entkoppelt Annahme und Zustellung; kein Nutzer-Request hängt oder scheitert an fehlendem Egress. - Der Zustell-Worker scheitert bei jedem Versuch an der DNS-Auflösung: `getaddrinfo EAI_AGAIN smtp.egress-nachweis.example` (Dockers embedded DNS löst in `internal`-Netzen nicht extern auf; auf den Bridges ist dabei kein Paket sichtbar). - Retry-Leiter: 5 Versuche, danach Log `mail delivery failed permanently` (Level warn), Outbox-Status `FAILED` mit `last_error`. Die Instanz bleibt voll funktionsfähig. - **Betriebsempfehlung:** entweder ein von der Behörde zugelassenes internes SMTP-Relay konfigurieren oder bewusst ohne Mail betreiben (Einschränkung: keine Verifikations-/Benachrichtigungs-Mails; Site-Admin legt Konten dann manuell verifiziert an). ## Abweichungen und Befunde 1. **readyz-backup-Check `warn` vor dem ersten Backup-Lauf** („no backup status recorded yet") — erwartetes Verhalten des Status-File-Checks, nach dem ersten Lauf `ok`. Keine Maßnahme. 2. **Restore meldete FAILED trotz inhaltlich korrekter Wiederherstellung** → Befund **#288**: `pg_restore --clean` scheiterte an den geerbten Partitions-PKs von `read_events` (partitioniert seit #224), Exit ≠ 0 bei 4 ignorierten Fehlern. Fix (Schema-Reset vor `pg_restore`, macht den Restore zugleich exakt) in derselben PR-Kette; Re-Run im selben isolierten Stack: Exit 0, bewusst angelegte Post-Backup-Tabelle entfernt, Seiteninhalt und alle `read_events`-Partitionen intakt, `/readyz` danach vollständig grün. ## Rückfluss (Akzeptanzkriterium) - **#288** — aus dem Lauf entstandener Fix, in der M28-PR-Kette. - **#221** — der Offline-Update-Pfad wird in derselben isolierten Umgebung mit denselben Images geprobt. - **#229** — Betriebshandbuch §1 (Airgap-Variante) auf diesen Nachweis aktualisiert.