From 1cf0458593a89cf6eb4be68854535f4622a06336 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Fri, 31 Jul 2026 09:54:01 +0200 Subject: [PATCH 1/3] #228: security documentation (architecture, data flows, network plan) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/vs-nfd/60-sicherheitsdokumentation.md: component diagram with per- service purpose and privileges, network plan digit-exact against the deploy compose (127.0.0.1-only app bindings, internal-only data zone), data-flow diagrams (auth, realtime editing incl. LISTEN/NOTIFY and the 60s collab token, export via the pinned sidecars, backup incl. the ADR-0026 allowlist, and every read channel), named trust boundaries (reverse proxy, plugin sandbox, outbound SMTP/mirror), and the complete list of content copies — in-database, on-volume and outside the instance — that the deletion concept in #229 builds on. Mermaid only, German (assessor audience), with the maintained-in-same-PR rule stated. Co-Authored-By: Claude Fable 5 (1M context) --- docs/vs-nfd/20-massnahmenplan.md | 4 +- docs/vs-nfd/60-sicherheitsdokumentation.md | 277 +++++++++++++++++++++ 2 files changed, 279 insertions(+), 2 deletions(-) create mode 100644 docs/vs-nfd/60-sicherheitsdokumentation.md diff --git a/docs/vs-nfd/20-massnahmenplan.md b/docs/vs-nfd/20-massnahmenplan.md index 9b36183..0d62aa9 100644 --- a/docs/vs-nfd/20-massnahmenplan.md +++ b/docs/vs-nfd/20-massnahmenplan.md @@ -187,8 +187,8 @@ nach sich zieht. - [ ] **Härtungsleitfaden** mit Referenzkonfiguration „VS-NfD-Betrieb": lokale Auth aus, Public-API aus, MCP aus, Feeds aus, Plugins aus, Backup nur lokal · 3 AT · #227 -- [ ] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan, - Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 +- [x] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan, + Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 → `60-sicherheitsdokumentation.md` - [ ] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore, Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 - [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung diff --git a/docs/vs-nfd/60-sicherheitsdokumentation.md b/docs/vs-nfd/60-sicherheitsdokumentation.md new file mode 100644 index 0000000..0cc7231 --- /dev/null +++ b/docs/vs-nfd/60-sicherheitsdokumentation.md @@ -0,0 +1,277 @@ +# Sicherheitsdokumentation (VS-NfD, Issue #228) + +Zweck: das Dokument, das ein Prüfer zuerst liest — Architektur, +Datenflüsse, Netzplan, Vertrauensgrenzen und die vollständige Liste +aller Inhaltskopien. Es konsolidiert bestehende Referenzdokumente, +erfindet nichts neu und ist **auf die Ziffer genau** an +`deploy/compose/docker-compose.yml` ausgerichtet (die Compose-Datei ist +die maßgebliche Dienst- und Portliste; weicht dieses Dokument ab, ist +das ein Fehler in diesem Dokument). + +Einordnung: Dorfteich erbringt **keine Sicherheitsgrundfunktion** +(§52 VSA, ADR 0019, Abgrenzungserklärung +`40-abgrenzungserklaerung.md`). Diese Dokumentation beschreibt, was die +Anwendung tut — Verschlüsselung, Netztrennung, Datenträgerschutz und +Beweissicherung auf Plattformebene fallen dem Betreiber zu. + +Quell-Dokumente (englisch, maßgeblich für Details): +`docs/architecture/security.md`, `deployment.md`, `data-model.md`, +`permissions.md`, `realtime-collaboration.md`, +`plugin-architecture.md`, `operations.md`, `audit-events.md`; +Deploy-Sicht: `deploy/stages.md`, `deploy/compose/docker-compose.yml`. + +Pflegeregel: Änderungen an Diensten, Ports, Datenflüssen oder +Speicherorten werden **im selben PR** hier nachgezogen (dieselbe Regel +wie beim Härtungsleitfaden `50-haertungsleitfaden.md`). + +--- + +## 1 Komponenten + +```mermaid +flowchart LR + subgraph client [Endgerät] + B[Browser SPA] + end + subgraph host [Betreiber-Host / Reverse-Proxy-Zone] + RP[Reverse Proxy TLS] + end + subgraph frontend [Docker-Netz frontend] + W[web nginx statisch] + A[api NestJS] + C[collab Hocuspocus] + end + subgraph internal [Docker-Netz internal] + DB[(PostgreSQL 17.5)] + P[pandoc 3.6] + G[gotenberg 8] + BK[backup Sidecar] + end + SMTP[(SMTP-Relay extern)] + MIR[(Backup-Spiegel rsync/WebDAV, Allowlist)] + + B -->|HTTPS 443| RP + RP -->|/| W + RP -->|/api| A + RP -->|/collab WebSocket| C + A --> DB + C --> DB + A -->|HTTP pandoc:3030| P + A -->|HTTP gotenberg:3000| G + A -->|Mail| SMTP + BK --> DB + BK -.->|nur bei Konfiguration + Allowlist| MIR + BK -->|Fehler-Mail| SMTP +``` + +| Dienst | Image (gepinnt, #203/#236) | Zweck | Privilegien | +| ---------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `web` | eigenes Image (nginx-unprivileged 1.27, uid 101) | statische SPA-Auslieferung | kein Volume, nur Netz `frontend`, kein DB-Zugang | +| `api` | eigenes Image (Node 22.15.1-alpine, User `node`) | REST-API, Permissions, Scheduler-Jobs, Import/Export-Orchestrierung, Audit | Volumes `uploads`, `plugins`, `secrets`, `backups` (ro); Netze `frontend`+`internal`; DB-Vollzugriff | +| `collab` | eigenes Image (Node, User `node`) | Echtzeit-Editing (Yjs/Hocuspocus), Persistenz der Dokumente | Netze `frontend`+`internal`; DB-Vollzugriff; keine Datei-Volumes | +| `db` | `postgres:17.5-alpine@sha256:…` | einziger Datenbestand (außer Uploads) | nur Netz `internal`; Volume `db-data` | +| `pandoc` | `pandoc/core:3.6@sha256:…` | Dokumentkonvertierung (Import/Export DOCX/ODT) | nur `internal`; zustandslos, kein Volume, kein DB-Zugang | +| `gotenberg` | `gotenberg/gotenberg:8@sha256:…` | HTML→PDF-Rendering (Chromium) | nur `internal`; zustandslos, kein Volume, kein DB-Zugang | +| `backup` | eigenes Image | nächtlicher `pg_dump -Fc` + Volume-Tar, Retention, optionaler Spiegel | nur `internal`; Volumes `uploads`, `plugins`, `secrets` (ro), `backups` (rw); DB-Zugang | +| `caddy` (Compose-Profil, optional) | `caddy:2.10-alpine@sha256:…` | TLS-Terminierung, wenn kein Host-Proxy existiert | Ports 80/443 nach außen; Netz `frontend` | + +Alle eigenen Images laufen als non-root (`USER node`); die Stages auf +ONE nutzen einen Host-Reverse-Proxy (Caddy des Hosts), das +`caddy`-Profil bleibt dort inaktiv. + +## 2 Netzplan (Ports und Protokolle) + +Maßgeblich: `deploy/compose/docker-compose.yml`. Alle +Anwendungs-Portbindungen sind **auf 127.0.0.1 beschränkt** — von außen +erreichbar ist ausschließlich der Reverse Proxy. + +| Von | Nach | Port/Protokoll | Exposition | +| ----------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| Client | Reverse Proxy | 443/TCP HTTPS (80 nur Redirect) | **extern** | +| Reverse Proxy | `web` | `127.0.0.1:${WEB_PORT:-8100}` → Container 8080, HTTP | Host-lokal | +| Reverse Proxy | `api` | `127.0.0.1:${API_PORT:-8101}` → Container 3000, HTTP | Host-lokal | +| Reverse Proxy | `collab` | `127.0.0.1:${COLLAB_PORT:-8102}` → Container 3000, HTTP + WebSocket-Upgrade | Host-lokal | +| `api`/`collab`/`backup` | `db` | 5432/TCP (nur Docker-Netz `internal`) | intern | +| `api` | `pandoc` | `http://pandoc:3030` (nur `internal`) | intern | +| `api` | `gotenberg` | `http://gotenberg:3000` (nur `internal`) | intern | +| `api`, `backup` | SMTP-Relay | vom Betreiber konfiguriert (`SMTP_*`) | **ausgehend extern** | +| `backup` | Spiegel-Ziel | rsync über SSH (Port konfigurierbar) bzw. WebDAV/HTTPS — **nur** an Hosts der Deploy-Allowlist `BACKUP_ALLOWED_TARGETS` (ADR 0026, #192); leere Allowlist = alle Fernziele hart aus | **ausgehend extern, allowlist-beschränkt** | +| optional `caddy` | Client | 80/443 | extern (nur wenn Profil aktiv) | + +Es gibt **keine eingehenden** Verbindungen außer über den Reverse +Proxy. `db`, `pandoc`, `gotenberg`, `backup` haben keinerlei +Portbindung an den Host. + +## 3 Datenflüsse + +### 3.1 Authentifizierung + +```mermaid +sequenceDiagram + participant B as Browser + participant A as api + participant DB as PostgreSQL + B->>A: POST /api/v1/auth/login (Origin-Header Pflicht, fail-closed CSRF #189) + A->>DB: user_identities (Argon2id-Hash prüfen), rate_limits (10/min/IP) + A-->>B: Set-Cookie dt_session (HttpOnly, Secure, SameSite=Lax) + Note over A,DB: Session-Row: gehashte Id, expiresAt (SESSION_ABSOLUTE_HOURS),
Idle-Grenze (SESSION_IDLE_HOURS, #190); Audit auth.login_* +``` + +Externe Authentisierung (OIDC/Proxy-Header) ist geplant (M27, +#214–#217) und ändert diesen Fluss; bis dahin ist lokale +Passwort-Authentifizierung der einzige Weg. + +### 3.2 Editieren (Echtzeit) + +```mermaid +sequenceDiagram + participant B as Browser + participant A as api + participant C as collab + participant DB as PostgreSQL + B->>A: GET /pages/:id/collab-token (Guard: Read-Recht) + A-->>B: Kurzlebiges Token (60 s TTL, HKDF-Subkey aus COLLAB_TOKEN_SECRET, #188), mode rw/ro + B->>C: WebSocket /collab + Token + C->>C: Token-Verifikation (shared token-crypto), rw nur bei Write-Grant + B-->>C: Yjs-Updates (CRDT) + C->>DB: debounced Persist: pages.ydoc_state + page_updates-Log,
abgeleiteter page_content_cache (plain/markdown/html/outline/tsvector) + A-->>C: Rechteänderungen via Postgres LISTEN/NOTIFY (Kanal-Konstanten shared) → close(4205) +``` + +### 3.3 Export (PDF/DOCX/ODT/ZIP) + +```mermaid +sequenceDiagram + participant B as Browser + participant A as api + participant P as pandoc + participant G as gotenberg + B->>A: POST /pages/:id/export {format} + A->>A: conversion_jobs-Row (input = aufbereitetes Markdown/HTML,
bei eingestufter Seite Option {marking}, #208/#209) + A->>G: html → pdf (Kennzeichnung im Header/Footer-Template je Seite) + A->>P: gfm → docx/odt (reference-doc mit Kennzeichnung, in-Request-Datei) + A-->>B: Poll GET /jobs/:id → Download /jobs/:id/result + Note over A: Payloads werden nach conversion.payloadRetentionDays genullt (#233) +``` + +Der Pond-ZIP-Export streamt direkt aus `page_content_cache` + +`uploads`-Volume (permissionsgefiltert) und enthält seit M26 +Frontmatter/Aufdruck, `manifest.json` und Begleitdateien für +eingestufte Inhalte (#210/#212). + +### 3.4 Backup + +```mermaid +sequenceDiagram + participant BK as backup-Sidecar + participant DB as PostgreSQL + participant V as Volumes uploads/plugins + participant M as Spiegel (optional) + BK->>DB: nächtlich pg_dump -Fc + BK->>V: tar der Volumes (lesend) + BK->>BK: Restore-Set aufs backups-Volume, Retention (BACKUP_RETENTION_DAYS) + BK-->>M: rsync/WebDAV NUR an BACKUP_ALLOWED_TARGETS (ADR 0026); leer = aus + BK-->>BK: status.json; Fehler-Mail direkt via SMTP +``` + +Backups sind **unverschlüsselt by design** (ADR 0015/0019) — +Datenträger- und Transportschutz ist Plattformsache +(Abgrenzungserklärung §2). + +### 3.5 Lesekanäle + +Jeder Kanal, über den Seiteninhalt die Anwendung verlässt (zugleich die +Instrumentierungsliste für den Lesetrail, #222): + +| Kanal | Pfad | Rechteprüfung | Kennzeichnung (M26) | +| -------------------------- | -------------------------------- | -------------------------------------- | ----------------------------------- | +| SPA-Seitenansicht | `GET /api/v1/pages/:id` u. a. | Guard (shared Resolver) | Banner oben+unten (#206) | +| Public-/No-JS-Ansicht | `GET /api/v1/public/:pond/:page` | `public`-Grant, sonst 404 | Banner oben+unten (#211) | +| Public REST API | `/api/public/v1/**` | `api.enabled` + Pond-Opt-in + PAT | `classification`-Feld (#211) | +| MCP-Endpoint | `/api/mcp` | `mcp.enabled` + Pond-Opt-in + PAT | wie Public API (gleiches Modell) | +| Atom-Feeds | `…/feed.xml` | `feeds.enabled`; Grant bzw. Feed-Token | ``-Element (#211) | +| Suche | `GET /api/v1/search` | per-Treffer-Resolution | Level je Treffer (#211) | +| Attachment-Download | `GET /api/v1/media/:fileId` | Guard über Seite/Pond | Dateinamens-Präfix `VS-NfD_` (#212) | +| Export (PDF/Office/ZIP/MD) | s. 3.3 | Guard; ZIP permissionsgefiltert | je Kanal (#207–#210) | +| Collab-WebSocket | `/collab` | Token aus 3.2 | Inhalt = Editor-Ansicht (#206) | + +## 4 Vertrauensgrenzen + +1. **Client ↔ Instanz** (Internet/Behördennetz): TLS am Reverse Proxy + terminiert (Betreiber-Zone). Die Anwendung setzt die + Security-Header selbst (eigene Middleware, #197: CSP `script-src +'self'`, `X-Frame-Options: SAMEORIGIN`, restriktives CORS); + Cookie-Mutationen sind Origin-pflichtig (fail-closed, #189). +2. **Reverse Proxy ↔ Anwendungscontainer**: nur 127.0.0.1-Bindungen; + der Proxy ist die einzige Eintrittsstelle. Der geplante + Proxy-Header-/mTLS-Authentisierungspfad (#215) verschiebt die + Authentisierungs-Vertrauensgrenze an genau diese Stelle — bis dahin + trägt der Proxy nur Transport. +3. **Anwendungs- ↔ Datenzone**: `db`, `pandoc`, `gotenberg`, `backup` + sind nur im Docker-Netz `internal` erreichbar; `web` hat keinerlei + Zugang dorthin. +4. **Plugin-Sandbox** (ADR 0008): Plugin-Code läuft ausschließlich im + sandboxed iframe (eigene Origin-lose Umgebung, Message-Protokoll, + deklarierte Capabilities); serverseitig wird beim Install ein + Static-Gate erzwungen. Instanzweiter Kill-Switch `plugins.enabled` + (#200); Hash-Pinning ist bewusst verschoben (#232, + Restrisikoliste). Der Sandbox-Escape-Regressionstest (bösartiges + Fixture-Plugin) läuft in CI. +5. **Ausgehende Kanäle**: SMTP (Betreiber-Relay) und Backup-Spiegel + (Allowlist, ADR 0026) sind die einzigen initiierten + Außenverbindungen. Es gibt keine Telemetrie, keine Update-Pings, + keine externen Font-/CDN-Ladungen (ADR 0016: Fonts self-hosted). +6. **Betreiber-Plattform**: Host, Docker-Daemon, Volumes, Netzwerk und + Backups liegen außerhalb der Anwendungsverantwortung + (Abgrenzungserklärung). + +## 5 Vollständige Liste der Inhaltskopien + +Grundlage des Löschkonzepts im Betriebshandbuch +(`70-betriebshandbuch.md` §5) — Löschen ist nur vollständig, wenn es +jede dieser Kopien erreicht oder ihren Verbleib begründet. + +**In der Datenbank (Volume `db-data`):** + +| Ort | Inhalt | Lebenszyklus | +| ------------------------------ | ------------------------------------------------- | ----------------------------------------------------- | +| `pages.ydoc_state` | aktuelles Dokument (Yjs-Binärzustand) | bis Purge der Seite | +| `page_updates` | inkrementelles Update-Log | Compaction; Purge löscht | +| `page_content_cache` | Klartext, Markdown, HTML, Outline, **Suchvektor** | bei jedem Persist ersetzt; Purge löscht | +| `page_versions` | eigenständige Snapshots (Historie) | bis Purge der Seite | +| `comments` | Kommentartexte | Purge der Seite kaskadiert | +| `conversion_jobs.input/result` | Export-/Import-Payloads (ganze Dokumente) | genullt nach `conversion.payloadRetentionDays` (#233) | +| `mail_outbox` | Mail-Bodies (Digest-Titel!) | gelöscht nach `mail.outboxRetentionDays` (#234) | +| `page_links.target_slug` | Slug-Text auch nach Ziel-Purge | bewusst behalten (#235, Restrisiko I-24) | +| `audit_log` | Metadaten (nie Inhalt) | `audit.retentionDays` (#196) | +| `attachments` (Row) | Metadaten + SHA-256 | Purge der Seite/des Ponds | + +**Auf Volumes:** + +| Ort | Inhalt | Lebenszyklus | +| --------- | -------------------------------------------- | --------------------------------------------------------------- | +| `uploads` | Attachment-Bytes | Purge löscht Datei + Quota; Orphan-Sweep (#194) räumt Verwaiste | +| `backups` | nächtliche Restore-Sets (Dump + Volume-Tars) | `BACKUP_RETENTION_DAYS` | +| `plugins` | Plugin-Pakete (kein Seiteninhalt) | Uninstall | +| `secrets` | Secret-Store (SMTP etc., kein Seiteninhalt) | Betreiber | + +**Außerhalb der Instanz (nicht von Anwendungs-Löschung erreichbar — +Löschkonzept muss sie benennen):** + +| Ort | Inhalt | Kontrolle | +| ----------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------ | +| Backup-Spiegel (BASEL/WebDAV) | vollständige Restore-Sets | Betreiber; Retention auf Zielsystem | +| Export-Artefakte | PDF/DOCX/ODT/ZIP beim Nutzer | organisatorisch (VS-Handhabung, Kennzeichnung M26) | +| Browser-IndexedDB | Offline-Kopie zuletzt geöffneter Seiten | Endgeräteschutz (Restrisiko I-25) | +| Feed-Reader / API-Konsumenten | abonnierte Inhalte | organisatorisch; Referenzkonfiguration schaltet Feeds/API ab | +| Mails beim Empfänger | Digest-/Benachrichtigungstexte (Titel) | Restrisiko I-23; Referenzkonfiguration ohne SMTP | +| Container-stdout-Logs | Metadaten inkl. Audit-Zeilen (nie Seiteninhalt, security.md §Logging) | Docker json-file mit Rotation; Collector des Betreibers | + +## 6 Kryptografie-Inventar (Bestand, kein Anspruch) + +Zur Einordnung (vollständige Liste: Abgrenzungserklärung §3): Argon2id +für Passwort-Hashes, HMAC-signierte Kurzzeit-Tokens über `jose` mit +HKDF-Zweckableitung (#188), SHA-256-Integritätshashes für Attachments +(#199, fail-closed beim Download). Keine Inhalts- oder +Backup-Verschlüsselung, kein eigenes Schlüsselmanagement — bewusst +(§52 VSA). -- 2.45.2 From d641c5dc8a1a3d0d103118c180fe5e2a852f25c4 Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Fri, 31 Jul 2026 09:57:16 +0200 Subject: [PATCH 2/3] #229: operations manual (install, update, backup/restore, deletion, roles) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/vs-nfd/70-betriebshandbuch.md: installation as run on the real stages (airgap variant explicitly pending #218-#221 with what already exists as groundwork), update/rollback incl. the no-down-migrations caveat, backup/restore with the ADR-0026 target allowlist and the rehearsed monthly restore drill (evidence: logs on #98), the full scheduler-job table (cadences verified against code), the deletion-and- destruction chapter built on the #228 copy list (per content type: what deletion reaches, what remains, immediate-destruction path, decommissioning), and role separation incl. the deliberate limits of a Site Admin and the honest note that Site Admin read-bypass makes the content/platform split non-absolute app-side. Every procedure carries its evidence level (erprobt / nicht geprobt / offen) — nothing claimed above what was actually executed. Co-Authored-By: Claude Fable 5 (1M context) --- docs/vs-nfd/20-massnahmenplan.md | 5 +- docs/vs-nfd/70-betriebshandbuch.md | 210 +++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 docs/vs-nfd/70-betriebshandbuch.md diff --git a/docs/vs-nfd/20-massnahmenplan.md b/docs/vs-nfd/20-massnahmenplan.md index 0d62aa9..6bb922e 100644 --- a/docs/vs-nfd/20-massnahmenplan.md +++ b/docs/vs-nfd/20-massnahmenplan.md @@ -189,8 +189,9 @@ nach sich zieht. Backup nur lokal · 3 AT · #227 - [x] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan, Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 → `60-sicherheitsdokumentation.md` -- [ ] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore, - Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 +- [x] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore, + Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 → `70-betriebshandbuch.md` + (Airgap-Kapitel verweist auf offene #218–#221) - [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung „Produkt / Betreiber / nicht anwendbar" · 3–4 AT · #230 - [ ] **Restrisikoliste** mit bewusst offenen Punkten · 1 AT · #231 diff --git a/docs/vs-nfd/70-betriebshandbuch.md b/docs/vs-nfd/70-betriebshandbuch.md new file mode 100644 index 0000000..061ec78 --- /dev/null +++ b/docs/vs-nfd/70-betriebshandbuch.md @@ -0,0 +1,210 @@ +# Betriebshandbuch (VS-NfD, Issue #229) + +Zweck: der Betreiber führt die Instanz ohne uns — auch an dem Tag, an +dem etwas ausfällt. Dieses Handbuch bündelt die betrieblichen +Prozeduren und verweist für Schrittfolgen auf die maßgeblichen +Runbooks im Repository, statt sie zu duplizieren. Das Kapitel mit dem +größten VS-Gewicht ist **§5 Löschung und Vernichtung**; es baut auf der +vollständigen Kopienliste der Sicherheitsdokumentation auf +(`60-sicherheitsdokumentation.md` §5). + +**Belegstufen.** Jede Prozedur nennt am Ende ihren Erprobungsstand: + +- ✅ _erprobt_ — vom Autor bzw. automatisiert mindestens einmal real + ausgeführt, mit Beleg. +- ⚠️ _Mechanik vorhanden, nicht geprobt_ — implementiert und getestet, + aber noch nie im Ernstfall/als Übung durchgespielt. +- ⏳ _offen_ — kommt mit dem genannten Issue. + +--- + +## 1 Installation + +Referenz: `deploy/stages.md` (maßgebliche Schrittfolge), +`docs/self-hosting/README.md`. + +1. Host-Voraussetzungen: Docker + Compose, ein Reverse Proxy mit TLS + (oder das mitgelieferte `caddy`-Compose-Profil). +2. `deploy/compose/docker-compose.yml` + `.env` (Vorlage + `.env.example`; **niemals** eine echte `.env` ins Repo — CI-Zaun + #198) auf den Host bringen; `COMPOSE_PROJECT_NAME`, Ports + (`WEB_PORT`/`API_PORT`/`COLLAB_PORT`), `POSTGRES_PASSWORD`, Secrets + setzen. +3. `docker compose pull && docker compose up -d` — die api wendet + Migrationen beim Start selbst an (`MIGRATE_ON_START`); es gibt + keinen separaten Migrationsschritt. +4. Erststart: der Setup-Wizard (Issue #80) legt das Admin-Konto an und + verriegelt sich danach dauerhaft; bis zum Abschluss ist nur + `/setup/*` erreichbar. +5. Reverse Proxy: `/` → web, `/api` → api, `/collab` → collab mit + WebSocket-Upgrade (Portliste: Sicherheitsdokumentation §2). +6. Für VS-NfD-Betrieb anschließend die Referenzkonfiguration aus dem + Härtungsleitfaden (`50-haertungsleitfaden.md`) anwenden und mit + dessen Checkliste verifizieren. + +Belegstufe: ✅ erprobt — die Stages Test/Int/Prod auf ONE sind exakt +nach dieser Prozedur aufgesetzt und laufen produktiv (`deploy/stages.md` +dokumentiert die realen Instanzen). + +**Airgap-/Offline-Variante:** ⏳ offen — Mirror-Verfahren (#218), +netzloser Build (#219), Testlauf in isolierter Umgebung (#220), +Offline-Update-Pfad (#221); Meilenstein M28. Bereits vorhanden als +Grundlage: alle Dritt-Images digest-gepinnt (#203), ein authoritativer +Node-Pin (#236), SBOMs je Release (#202). + +## 2 Update und Rollback + +Referenz: `docs/architecture/operations.md` §Update strategy, +`deploy/stages.md`. + +- **Stages:** Merge auf `main` → CD baut Images, deployt Test, führt + Smoke-Tests aus, promotet Int. CD synct **keine** Compose-Dateien — + Compose-Änderungen werden von Hand auf den Host übernommen, sonst + räumt `--remove-orphans` manuell ergänzte Container ab. + Belegstufe: ✅ erprobt (läuft bei jedem Merge; zuletzt CD-Lauf 568). +- **Produktion:** ausschließlich getaggte Releases. `git tag vX.Y.Z` + baut die Versions-Images und führt die Update-Simulation als Gate + aus; `git tag prod-vX.Y.Z-initial` pinnt die Version in der + Prod-`.env` und rollt aus (readyz-Poll). Migrationen laufen beim + api-Start; **Rückwärtsmigrationen gibt es nicht** — Rollback setzt + deshalb ein DB-kompatibles Vorgängerimage voraus (Migrations-Kaveat; + im Zweifel Restore aus dem Backup-Set der Vorversion). + Belegstufe: ✅ erprobt (alle Prod-Versionen bis v0.12.0 so + ausgerollt). +- **Rollback:** `git tag prod-v-rollback1` pinnt die Vorversion + zurück. Belegstufe: ⚠️ Mechanik vorhanden, nicht geprobt — ein realer + Rollback war bisher nie nötig; die Übung steht aus. +- **Dritt-Image-Digest heben:** Prozedur `deploy/stages.md` §5a + (imagetools inspect → Compose-Referenz ändern → CI bestätigt → Stage- + Composes von Hand nachziehen → `docker inspect` verifiziert). + Belegstufe: ✅ erprobt (Rollout #203 am 31.07.2026). +- **Offline-Update:** ⏳ offen (#221, M28). + +## 3 Backup und Restore + +Referenz: `docs/operations/restore-runbook.md` (maßgeblich), +`deploy/backup-basel.md`, ADR 0015/0026. + +- Nächtlich erzeugt der backup-Sidecar ein konsistentes Restore-Set + (`pg_dump -Fc` + Tar der Volumes `uploads`/`plugins`) auf dem + `backups`-Volume; Retention `BACKUP_RETENTION_DAYS` (Default 30, + Test/Int 7). Status in `status.json`, Fehler alarmieren per Mail + direkt via SMTP (bewusst nicht über die api — sie könnte das kaputte + Teil sein). +- **Zielbeschränkung (#192, ADR 0026):** Fernziele (rsync-Spiegel, + WebDAV) funktionieren nur gegen Hosts der **Deploy-Allowlist** + `BACKUP_ALLOWED_TARGETS`; eine leere Allowlist schaltet alle + Fernziele hart ab. Die Allowlist ist bewusst NICHT über die Admin-UI + änderbar (Rollentrennung, §6). +- **Restore:** `deploy/backup/restore.sh` fährt den Stack kontrolliert + herunter, spielt Dump + Volumes zurück und startet neu; Details und + Totalverlust-Szenario im Runbook. +- **Geprobter Restore:** ✅ erprobt — ein automatischer monatlicher + Restore-Drill (`drill-*`-Tag → `drill.yml`) stellt das jüngste + Prod-Set in einer Wegwerf-Umgebung wieder her und prüft Inhalte; das + Protokoll jedes Laufs steht als Kommentar an Issue #98. + Backups sind unverschlüsselt by design — Datenträgerschutz ist + Plattformsache (Abgrenzungserklärung). + +## 4 Wartungsjobs (Scheduler) + +Alle Jobs laufen in der api (in-app Scheduler, `jobs`-Tabelle), sind im +Site-Admin-Systempanel sichtbar und dort manuell auslösbar (auditiert +als `job.triggered`). Aktueller Bestand (9): + +| Job | Rhythmus | Wirkung | +| -------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `trash-purge` | täglich | endgültiges Löschen abgelaufener Papierkorb-Seiten **und** -Teiche (`trash.retentionDays`, #31/#193) | +| `orphan-file-sweep` | täglich | verwaiste Uploads entfernen (#194); trägt nachts auch den SHA-256-Backfill (#199) | +| `page-compaction` | stündlich | `page_updates`-Log in den Zustand mergen | +| `version-thinning` | täglich | automatische Versions-Snapshots ausdünnen | +| `audit-retention` | täglich | `audit_log` nach `audit.retentionDays` beschneiden (#196), Lücke selbst auditiert (`audit.pruned`) | +| `conversion-payload-prune` | täglich | Import-/Export-Payloads fertiger Jobs nullen (#233) | +| `mail-outbox-retention` | täglich | SENT/endgültig FAILED Outbox-Zeilen löschen (#234) | +| `data-export-purge` | stündlich | abgelaufene DSGVO-Datenexporte entfernen (#68) | +| `notification-digest` | alle 15 min (Versand nach Nutzer-Präferenz) | Benachrichtigungs-Digests versenden | + +Der e2e-Zaun `apps/web/e2e/system.spec.ts` pinnt diese Zahl — ein +neuer Job ohne Handbuch-/Zaun-Anpassung wird rot. + +## 5 Löschung und Vernichtung + +Grundsatz: „Gelöscht" heißt in Dorfteich erst dann gelöscht, wenn alle +Kopien aus `60-sicherheitsdokumentation.md` §5 erreicht sind. Die +Tabelle nennt je Inhaltstyp, was die Löschung tut, welche Kopien sie +erreicht und was **stehen bleibt**. + +| Vorgang | Wirkung | erreichte Kopien | Rückstände / Fristen | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Seite in den Papierkorb | Soft-Delete (`deleted_at`), aus Suchindex entfernt (#195) | Suchvektor sofort | Inhalt vollständig vorhanden, wiederherstellbar bis Purge | +| Seiten-Purge (automatisch nach `trash.retentionDays`, Default 30, oder manuell) | löscht Zustand, Update-Log, Content-Cache, Versionen, Kommentare, Attachments (Bytes + Quota), Watches; Kinder rücken auf | DB-Zeilen + `uploads`-Bytes | `page_links.target_slug` bleibt bewusst (#235, Restrisiko I-24); Backups/Spiegel bis Ablauf ihrer Retention; Export-/Endgeräte-Kopien organisatorisch | +| Teich-Purge (#193, automatisch/manuell, Site-Admin) | löscht alle Seiten samt Kaskade, Labels, Grants, Nutzungszähler, Plugin-Opt-ins, Conversion-Jobs, Files | wie oben, teichweit | wie oben | +| Nutzer löschen / DSGVO | Konto löschen bzw. Autorschaft pseudonymisieren (`user.pseudonymized`) | Identitätsdaten | von ihm erstellte Inhalte gehören dem Teich | +| Export-Payloads | `conversion-payload-prune` nullt `input`/`result` fertiger Jobs (Default 30 d) | DB | Job-Zeile bleibt für Status/Audit | +| Mail-Kopien | `mail-outbox-retention` löscht SENT/endgültig FAILED (Default 30 d) | DB | zugestellte Mails beim Empfänger (Restrisiko I-23) | +| Backup-Sets | Retention des Sidecars bzw. des Spiegels | `backups`-Volume, Spiegel | ein gelöschter Inhalt lebt maximal bis zum Ablauf der längsten Backup-Retention weiter — bei sofortigem Vernichtungsbedarf Sets manuell löschen (Host-Zugriff) und Spiegel bereinigen | +| Audit-Trail | `audit-retention` (Default 365 d) | DB | Metadaten, nie Inhalt | + +**Sofortige Vernichtung einzelner Inhalte** (über die Fristen hinaus): +Seite manuell purgen (Papierkorb → endgültig löschen), danach auf dem +Host die Backup-Sets der Aufbewahrungskette löschen bzw. den Spiegel +bereinigen und Endgeräte-/Exportkopien organisatorisch einsammeln. +Belegstufe Purge-Pfade: ✅ erprobt (laufen täglich produktiv; Purge- +Semantik durch Tests gepinnt). Belegstufe „Backup-Kette manuell +vernichten": ⚠️ nicht geprobt. + +**Außerbetriebnahme einer Instanz:** `docker compose down -v` entfernt +Container und **alle benannten Volumes** (`db-data`, `uploads`, +`plugins`, `secrets`, `backups`, Caddy-Volumes); anschließend Spiegel- +Bestände löschen und Host-Datenträger nach Betreiber-Vorgabe +vernichten — die physische Vernichtung ist Plattformsache +(Abgrenzungserklärung). Belegstufe: ✅ erprobt für den Stack-Teil (der +stillgelegte Alt-VPS wurde so zurückgebaut; Datenträger dort noch als +Rollback-Reserve vorhanden). + +## 6 Rollentrennung + +| Aufgabe | braucht | +| --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| Instanz-Settings, Nutzer-/Quota-Verwaltung, Plugins, Wartungsjobs anstoßen, Teich-Purge, Audit-Panel | **Site-Admin** (App-Rolle) | +| Deploy, `.env`/Compose ändern, Backup-Allowlist, Secrets-Volume, Restore, Backup-Sets vernichten, DB-Direktzugriff, Log-Collector | **Plattformzugriff** (Host) | +| Release nach Prod | Plattformzugriff + Freigabeprozess (Tag) | + +Was ein Site-Admin **nicht** kann (bewusste Grenzen): + +- die Backup-Zielliste ändern oder Fernziele aktivieren + (`BACKUP_ALLOWED_TARGETS` ist Deploy-Konfiguration, ADR 0026); +- Secrets lesen oder setzen, die im Secret-Store/der `.env` liegen + (die Settings-Tabelle trägt nie Secrets, security.md §Secrets); +- Deploy-/Composeänderungen, Image-Versionen, Migrationen auslösen; +- den Audit-Trail editieren (nur lesen; Retention läuft als Job und + protokolliert sich selbst); +- Backups herunterladen ist möglich (In-App-Restore-Pfad, #103), aber + ihre Vernichtung auf Host/Spiegel nicht. + +Zu beachten: ein Site-Admin **liest** per Rollen-Bypass jeden Inhalt +der Instanz (permissions.md) — die Trennung „wer darf Inhalte sehen" +von „wer betreibt die Plattform" ist damit App-seitig bewusst NICHT +absolut; wo das nicht tragbar ist, ist es organisatorisch zu regeln +(Vier-Augen-Prinzip bei Vergabe der Site-Admin-Rolle). Der Lesetrail +für eingestufte Inhalte (M29, #222–#225) macht solche Zugriffe +nachvollziehbar. + +## 7 Monitoring und Störung + +Referenz: `deploy/monitoring.md`, `docs/architecture/operations.md` +§Health & monitoring. + +- `GET /api/v1/readyz` prüft DB, Migrationen, Konverter, Renderer, + Backup-Status; `healthz` je Container. Externe Überwachung der + Referenzinstanzen: Uptime-Kuma mit Alarmierung. +- Logs: alle Dienste loggen JSON auf stdout (Docker json-file mit + Rotation); Audit-Ereignisse mit `msg`-Präfix `audit: ` und + `severity`-Feld — Weiterleitung an SIEM/Syslog übernimmt der + Collector des Betreibers (Katalog: + `docs/architecture/audit-events.md`, v1.1, mit + Kompatibilitätsversprechen). +- Integritätsalarm: `file.integrity_failed` (critical) = Download-Hash + ≠ Upload-Hash → Objekt als manipuliert/korrupt behandeln, Datei aus + Backup-Set wiederherstellen (Runbook), erneut laden; der + Audit-Eintrag trägt beide Hashes. -- 2.45.2 From 0dc9789cb1677987db44c04429dc9096b151d8bf Mon Sep 17 00:00:00 2001 From: Claude Fable 5 Date: Fri, 31 Jul 2026 09:59:27 +0200 Subject: [PATCH 3/3] #227: hardening guide with the VS-NfD reference configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/vs-nfd/50-haertungsleitfaden.md: one adoptable profile — every entry with the exact switch name, value, default and the reason, split into instance settings (registration closed, api/mcp off, feeds off, plugins off, classification defaults vs_nfd + upload block, svg reject, minimal extension list) and deploy-level configuration (empty BACKUP_ALLOWED_TARGETS enforces backup-local-only outside Site-Admin reach; tightened session hours; SMTP deliberately unconfigured with the consequence stated honestly). auth.local.enabled is listed as the one pending row (#216) with its compensation until then; the guide states the binding updated-in-same-PR rule for every future switch. Includes an operator verification checklist (four unauthenticated 404 curls + readyz + admin spot checks). Cross-referenced from the delimitation statement (file names made concrete) and consumed by the Grundschutz mapping (#230). Co-Authored-By: Claude Fable 5 (1M context) --- docs/vs-nfd/20-massnahmenplan.md | 6 +- docs/vs-nfd/40-abgrenzungserklaerung.md | 7 +- docs/vs-nfd/50-haertungsleitfaden.md | 94 +++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 docs/vs-nfd/50-haertungsleitfaden.md diff --git a/docs/vs-nfd/20-massnahmenplan.md b/docs/vs-nfd/20-massnahmenplan.md index 6bb922e..3dd34e8 100644 --- a/docs/vs-nfd/20-massnahmenplan.md +++ b/docs/vs-nfd/20-massnahmenplan.md @@ -184,9 +184,9 @@ nach sich zieht. - [x] **Abgrenzungserklärung §52 VSA** — welche Sicherheitsgrundfunktionen die Anwendung _nicht_ erbringt und wem sie zufallen. Wichtigstes Einzeldokument. · 3 AT · #226 → `40-abgrenzungserklaerung.md` -- [ ] **Härtungsleitfaden** mit Referenzkonfiguration „VS-NfD-Betrieb": - lokale Auth aus, Public-API aus, MCP aus, Feeds aus, Plugins aus, - Backup nur lokal · 3 AT · #227 +- [x] **Härtungsleitfaden** mit Referenzkonfiguration „VS-NfD-Betrieb": + lokale Auth aus (Zeile ⏳ bis #216), Public-API aus, MCP aus, Feeds aus, + Plugins aus, Backup nur lokal · 3 AT · #227 → \`50-haertungsleitfaden.md\` - [x] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan, Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 → `60-sicherheitsdokumentation.md` - [x] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore, diff --git a/docs/vs-nfd/40-abgrenzungserklaerung.md b/docs/vs-nfd/40-abgrenzungserklaerung.md index 21fc2bd..382b0cb 100644 --- a/docs/vs-nfd/40-abgrenzungserklaerung.md +++ b/docs/vs-nfd/40-abgrenzungserklaerung.md @@ -313,5 +313,8 @@ Slug-Residuum falls in #235 so entschieden), führt die Restrisikoliste gekürzt; die Erklärung ist erst dann uneingeschränkt gültig, wenn die Delta-Liste leer ist oder alle Restpositionen in der Restrisikoliste (#231) vom Betreiber gebilligt sind. -- Querbezüge: Härtungsleitfaden (#227), Sicherheitsdokumentation (#228), - Betriebshandbuch (#229), IT-Grundschutz-Zuarbeit (#230). +- Querbezüge: Härtungsleitfaden (#227, `50-haertungsleitfaden.md`), + Sicherheitsdokumentation (#228, `60-sicherheitsdokumentation.md`), + Betriebshandbuch (#229, `70-betriebshandbuch.md`), + IT-Grundschutz-Zuarbeit (#230, `80-grundschutz-mapping.md`), + Restrisikoliste (#231, `90-restrisiken.md`). diff --git a/docs/vs-nfd/50-haertungsleitfaden.md b/docs/vs-nfd/50-haertungsleitfaden.md new file mode 100644 index 0000000..6323c22 --- /dev/null +++ b/docs/vs-nfd/50-haertungsleitfaden.md @@ -0,0 +1,94 @@ +# Härtungsleitfaden — Referenzkonfiguration „VS-NfD-Betrieb" (Issue #227) + +Zweck: **eine** benannte Konfiguration, die ein Betreiber als Ganzes +übernehmen kann. Jeder Eintrag nennt den exakten Schalter, den Wert und +das **Warum** — wer abweicht, tut es wissentlich. Der Leitfaden macht +zugleich die in M1/M2/M4 gebauten Schalter prüfbar. + +**Pflegeregel (verbindlich):** Jeder PR, der einen neuen Instanz- oder +Deploy-Schalter einführt, ergänzt diesen Leitfaden **im selben PR** um +dessen Referenzwert. Ein Schalter ohne Leitfaden-Zeile gilt im Review +als unvollständig. (Gleiches Muster wie der Ereigniskatalog-Zaun #201.) + +Geltungsbereich: Konfiguration der Anwendung. Die Härtung der Plattform +(Betriebssystem, Netz, Reverse Proxy, Datenträger) ist Betreibersache +(Abgrenzungserklärung `40-abgrenzungserklaerung.md` — von dort wird +hierher verwiesen; das IT-Grundschutz-Mapping +`80-grundschutz-mapping.md` nimmt diese Referenzkonfiguration als +Produkt-Beleg). + +--- + +## 1 Referenzkonfiguration + +### 1.1 Instanz-Settings (Site-Admin → Einstellungen; Tabelle `instance_settings`) + +Nach jeder Änderung an Instanz-Settings die api neu starten — der +Settings-Cache ist in-process (operations.md). + +| Setting | Referenzwert | Default | Warum | +| ----------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `auth.registrationMode` | `closed` | `open` | Konten entstehen in einer VS-Umgebung nur kontrolliert; Selbstregistrierung öffnet den Nutzerkreis unkontrolliert. | +| `api.enabled` | `false` | `false` | Public REST API ist ein zusätzlicher Egress-Kanal; ohne dokumentierten Bedarf bleibt er zu (404 auf allen `/api/public/v1`-Routen). | +| `mcp.enabled` | `false` | `false` | gleiches Argument für den MCP-Endpoint (`/api/mcp`); unabhängiger Schalter. | +| `feeds.enabled` | `false` | `true` | **explizit setzen** — Atom-Feeds liefern Inhalte an Reader außerhalb der Kontrolle der Instanz (Feed-Token umgehen die Session); Kopien in Feed-Readern sind nicht einholbar (Kopienliste, Sicherheitsdokumentation §5). Schaltet Routen UND Feed-Token-Verwaltung auf 404. | +| `plugins.enabled` | `false` | `true` | **explizit setzen** — kein Fremdcode in der VS-Zone (#200): alle Plugin-Flächen 404, Dropzone quarantänisiert; bestehende Blöcke degradieren zu ihrem deklarierten Text-Fallback. Hash-Pinning ist verschoben (#232, Restrisikoliste) — der Kill-Switch deckt das Risiko für diesen Betriebsmodus vollständig. | +| `classification.newPageDefault` | `vs_nfd` | `unclassified` | in einer VS-NfD-Instanz beginnt nichts unmarkiert (#204); die Vererbung (#205) hält den Baum konsistent. | +| `classification.uploadPolicy` | `block` | `warn` | Anhänge können die Kennzeichnung im Inhalt nicht tragen (#212) — die Referenzkonfiguration lehnt Uploads auf eingestufte Seiten serverseitig ab (403 `classified_upload_blocked`, #213) statt nur zu warnen. | +| `upload.svgPolicy` | `reject` | `sanitize` | SVG ist aktiver Inhalt; die Sanitisierung ist gut getestet, aber Ablehnen ist die kleinere Angriffsfläche. Abweichung vertretbar, wenn SVG gebraucht wird. | +| `upload.allowedExtensions` | nur das dienstlich Nötige (z. B. `pdf`) | Standardliste | jede zusätzliche Endung vergrößert die Menge nicht prüfbarer Binärformate im Bestand. Bilder sind davon unabhängig immer erlaubt (Magic-Byte-geprüft). | +| `backup.nextcloud.enabled` | `false` | `false` | „Backup nur lokal": kein Anwendungs-Upload von Restore-Sets zu Drittdiensten. Fernspiegel regelt ausschließlich die Deploy-Allowlist (1.2). | +| `trash.retentionDays`, `audit.retentionDays`, `conversion.payloadRetentionDays`, `mail.outboxRetentionDays` | Defaults (30/365/30/30) | ebd. | Aufbewahrung bewusst begrenzt; Verkürzung nach Betreiber-Löschkonzept zulässig (Betriebshandbuch §5). | +| `legal.imprint`, `legal.privacyPolicy` | befüllt | leer | Betreiberpflicht; leere Seiten zeigen einen Warnbanner. | + +### 1.2 Deploy-Konfiguration (`.env` / Compose — nur Plattformzugriff, bewusst nicht per Admin-UI) + +| Variable | Referenzwert | Warum | +| ----------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BACKUP_ALLOWED_TARGETS` | leer lassen **oder** exakt der eine freigegebene Spiegel-Host | leere Allowlist schaltet ALLE Fernziele hart ab (ADR 0026, #192) — „Backup nur lokal" ist damit deploy-seitig erzwungen und vom Site-Admin nicht aufweichbar (Rollentrennung, Betriebshandbuch §6). | +| `SESSION_ABSOLUTE_HOURS` | `12` (Default 168) | eine Sitzung überdauert keinen Arbeitstag; Neuanmeldung am nächsten Tag ist der Preis. | +| `SESSION_IDLE_HOURS` | `2` (Default 72) | unbeaufsichtigte, noch angemeldete Arbeitsplätze fallen schnell zurück auf die Anmeldemaske. | +| `SMTP_HOST` etc. | **unkonfiguriert lassen** (oder internes Relay) | ohne SMTP verlassen keinerlei Inhaltstitel die Instanz per Mail (Digest-Restrisiko I-23 entfällt vollständig). Konsequenz ehrlich benannt: dann gibt es keine Verifikations- und Passwort-Reset-Mails — Kontenpflege läuft über den Site-Admin. Wer Mail braucht, nutzt ein internes Relay und akzeptiert I-23 (Restrisikoliste). | +| `WEB_PORT`/`API_PORT`/`COLLAB_PORT` | Defaults (127.0.0.1-gebunden) | Anwendungscontainer sind nie direkt exponiert; einzige Eintrittsstelle ist der Reverse Proxy (Sicherheitsdokumentation §2). | +| `LOG_LEVEL` | `info` | Audit-Zeilen (`audit: `-Präfix) müssen den Collector erreichen; `debug` nur zur Störungssuche. | + +### 1.3 Noch nicht verfügbar (Regel: landet hier im selben PR) + +| Schalter | Referenzwert (geplant) | Status | +| -------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `auth.local.enabled` | `false` — lokale Passwort-Auth aus, Anmeldung nur über die Fremdauthentisierung der Behörde | ⏳ kommt mit #216 (M27); bis dahin bleibt lokale Auth der einzige Anmeldeweg und `auth.registrationMode=closed` + Session-Verkürzung sind die Kompensation. Zeile wird im #216-PR scharfgestellt. | + +## 2 Verifikations-Checkliste + +Auf der laufenden Instanz (ersetze `HOST`); Erwartung jeweils dahinter. +Die vier 404-Prüfungen laufen unauthentifiziert: + +```sh +curl -s -o /dev/null -w '%{http_code}\n' https://HOST/api/public/v1/ponds # 404 (api.enabled=false) +curl -s -o /dev/null -w '%{http_code}\n' -X POST https://HOST/api/mcp # 404 (mcp.enabled=false) +curl -s -o /dev/null -w '%{http_code}\n' https://HOST/api/v1/public/IRGENDEIN-TEICH/feed.xml # 404 (feeds.enabled=false) +curl -s -o /dev/null -w '%{http_code}\n' https://HOST/api/v1/admin/plugins # 401/404, nie 200 ohne Session +curl -s https://HOST/api/v1/readyz # status ok +``` + +Als Site-Admin (UI → Administration bzw. `GET /api/v1/admin/settings`): + +- [ ] Registrierung „geschlossen"; neue Seite entsteht mit Kennzeichnung + (Einstufung neuer Seiten = VS-NfD); Upload auf eingestufte Seite + wird abgelehnt (403). +- [ ] Plugin-Verwaltung antwortet 404 (Kill-Switch aktiv). +- [ ] Backup-Panel zeigt keine aktiven Fernziele; auf dem Host ist + `BACKUP_ALLOWED_TARGETS` leer bzw. exakt der freigegebene Spiegel. +- [ ] Eine Testsitzung läuft nach `SESSION_IDLE_HOURS` Inaktivität ab. +- [ ] Kennzeichnungs-Stichprobe: eingestufte Seite zeigt den Aufdruck in + Web, Druckvorschau und PDF-Export (Konventionen: Kommentare auf + Issue #228 bzw. `60-sicherheitsdokumentation.md` §3.5). + +## 3 Querbezüge + +Abgrenzungserklärung (`40-abgrenzungserklaerung.md`, verweist hierher); +IT-Grundschutz-Mapping (`80-grundschutz-mapping.md`, nutzt dieses +Profil als Produkt-Beleg); Betriebshandbuch (`70-betriebshandbuch.md`, +Installation §1 wendet dieses Profil an); Restrisikoliste +(`90-restrisiken.md` — Abweichungen von der Referenzkonfiguration +gehören dorthin, wenn sie dauerhaft sind). -- 2.45.2