Full deployment exercised in a compose stack whose networks are all internal: true — setup, login, live collaboration, search, upload, all export formats, backup and restore. tcpdump full capture on both bridges: zero packets leave the isolated subnets; the only outbound attempt the application makes is SMTP, which fails contained in the outbox (5 retries, then FAILED) while the instance stays fully functional. The restore finding became #288, fixed earlier in this chain and re-verified in the same stack. Plan checkbox P1-3 and the I-28 open question ticked; operations handbook airgap section updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
6.6 KiB
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-isolatedaus dem Produktions-Compose (deploy/compose/docker-compose.yml) plus Override. - Netzisolation: beide Compose-Netze (
frontend,internal) mitinternal: 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) imfrontend-Netz gegenhttp://api:3000. - Eigene Images:
dorfteich-iso-{web,api,collab,backup}:isolated, lokal gebaut ausmain@a758c9d; das backup-Image nach dem Befund unten neu aus4f6596e(#288). Image-IDs: web9d65c54f05fc, api1826dedd662a, collab8a1b2841b91c, backup117ff18c7e71. - 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 arpauf 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 ininternal-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-StatusFAILEDmitlast_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
- readyz-backup-Check
warnvor dem ersten Backup-Lauf („no backup status recorded yet") — erwartetes Verhalten des Status-File-Checks, nach dem ersten Laufok. Keine Maßnahme. - Restore meldete FAILED trotz inhaltlich korrekter
Wiederherstellung → Befund #288:
pg_restore --cleanscheiterte an den geerbten Partitions-PKs vonread_events(partitioniert seit #224), Exit ≠ 0 bei 4 ignorierten Fehlern. Fix (Schema-Reset vorpg_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 alleread_events-Partitionen intakt,/readyzdanach 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.