Datenbankzugang (Soll-Architektur)
Ist vs. Soll
Soll (live): Compose-Profil mtls (docker-compose.pgbouncer.yml), CA unter scripts/pki/, QGIS sslmode=verify-full. PostGIS unpublished. Siehe ADR-0002.
Ziel (ADR-0031): Control-DB linetra (Login, tenants); Fachdaten in linetra_<slug>; Cert-CN {slug}_edit / {slug}_read. Lokal LINETRA_SHARED_DB=true darf Control und Dev-Mandant dieselbe DB teilen.
Ist (abgeschaltet): Traefik TCP :3401 auf WireGuard — docker-compose.override.yml ist nicht mehr der QGIS-Pfad (ADR-0003).
Kurzantwort
Die sicherste Variante ohne VPN ist nicht „Passwort über TLS im Internet“, sondern:
- PostGIS nie öffentlich (kein Port 3401 ins Netz, nur Docker-Netz).
- Apps (Editor, Dashboard, FastAPI) verbinden sich nur intern zur DB — Clients sehen Postgres nicht.
- QGIS und andere Postgres-Clients gehen über einen Auth-Proxy, der das Postgres-Protokoll spricht, mit gegenseitigem TLS (mTLS) und pro Person/Gerät einem Client-Zertifikat (kurzlebig, widerrufbar).
Ja: Keys + Auth-Proxy, der auf PostGIS weiterleitet, ist genau dieses Muster. Die „Keys“ sind X.509-Client-Zertifikate, keine API-Keys im HTTP-Sinn — QGIS kann keine Authorization-Header auf Port 5432 senden.
Ist vs. Soll
flowchart LR
subgraph gone [Ist — abgeschaltet]
Q1[QGIS] -->|plain TCP + Passwort| WG[WireGuard]
WG --> T1[Traefik TCP 3401]
T1 --> PG1[(PostGIS)]
end
flowchart TB
subgraph public [Öffentlich HTTPS / TLS]
ED[Linetra Editor]
DASH[Dashboard]
QGIS[QGIS]
end
ED -->|Session / HTTPS| API[FastAPI]
DASH -->|Session / HTTPS| API
QGIS -->|TLS 1.3 + Client-Zertifikat| PROXY[PgBouncer]
API -->|nur Docker-Netz| PG[(PostGIS)]
PROXY -->|nur Docker-Netz| PG
| Weg | Wer | Auth | Sieht Postgres-Port? |
|---|---|---|---|
| FastAPI intern | Backend, Editor, Dashboard | App-User / Session | nein |
| Auth-Proxy | QGIS, psql, GIS-Tools | Client-Zertifikat (mTLS) | nur den Proxy |
| Direkt 5432/3401 | niemand von außen | — | nein |
VPN darf als zusätzliche Hülle für andere NAS-Dienste bleiben, ist aber keine Voraussetzung für QGIS oder die Apps.
Warum nicht nur TLS + Passwort?
| Variante | Bewertung ohne VPN |
|---|---|
| Passwort, Port öffentlich, TLS | Besser als plain, aber: geteiltes linetra_edit, Passwort leakt, brute-force auf dem Port, kein Gerätebezug |
| Langes API-Token im Proxy | Gleicher Fehlertyp wie Passwort, und QGIS kann das Token nicht nativ senden |
| mTLS, Zertifikat pro Client, Proxy, DB privat | Identität am Gerät, Widerruf über CA/CRL, Postgres unsichtbar, Audit am Proxy |
| Nur API, kein Live-Postgres für QGIS | Noch enger (INT-01 Export statt Live-DB) — für den Übergang reicht der Proxy |
Editor und Dashboard brauchen keinen DB-Proxy: Stackregel ist HTTPS zur FastAPI (Lastenheft Q-02). Einfache Editor-Nutzer bekommen nur ein Web-Konto; sie schreiben über die API in PostGIS, ohne Client-Zertifikat. Siehe Linetra Editor nutzen.
Was der Proxy tun muss
QGIS spricht Postgres-Wire-Protocol. Traefik-HTTP, OAuth2-Proxy oder nginx-HTTP helfen hier nicht.
Der Proxy muss:
- SSLRequest / TLS von libpq verstehen (oder Postgres 17
sslnegotiation=direct) - Client-Zertifikat prüfen (eigene CA)
- Verbindung nur dann nach intern zu PostGIS öffnen
- das Zertifikat auf eine Mandanten-DB-Rolle abbilden: CN =
{slug}_editoder{slug}_read(ADR-0031)
Live: PgBouncer (client_tls_sslmode=verify-full, auth_type=cert). Ungeeignet: Traefik TLS-Termination auf einem TCP-Entrypoint — libpq sendet zuerst SSLRequest, kein TLS-ClientHello.
QGIS: Host=… Port=5432 SSL=verify-full
SSL-Zertifikat + Schlüssel + CA
→ PgBouncer (TLS in PgBouncer, Direct-Publish)
→ postgres:5432 (Compose-Netz, unpublished)
PostGIS: nur Compose-Netz. Superuser postgres nicht über den Proxy.
Zertifikate („Keys“)
| Gut | Schlecht |
|---|---|
| Eine interne CA, Client-Zertifikat pro Person oder Gerät | Ein Team-Zertifikat für alle |
| Kurze Laufzeit (Tage), Neuausstellung nach Login/Admin | Unbefristetes Zertifikat neben dem Passwort |
| Widerruf (CRL/OCSP) wenn Laptop weg ist | Nur Passwort drehen, Zertifikat weiter gültig |
CN ist {slug}_edit oder {slug}_read |
Proxy akzeptiert jedes Zertifikat und nutzt ein Shared-Login ohne Cert; globales linetra_edit auf allen DBs |
QGIS: Verbindung → SSL → Zertifikat, Schlüssel, CA-Datei. Das ist der native Key-Weg. Ausstellung: ./scripts/pki/issue-client-cert.sh edit <name>.
Passwörter intern zwischen Proxy und Postgres zusätzlich behalten — nicht statt mTLS nach außen. pgbouncer.get_auth liefert denselben HMAC-Klartext wie die Mandanten-Rollen (nicht den SCRAM-Hash aus pg_authid); sonst antwortet PgBouncer mit wrong password type. Shared-Rollen linetra_edit / linetra_read stehen weiter als Klartext in userlist.txt.
Abnahme (Lastenheft INF-04–INF-06)
Siehe Lastenheft Infrastruktur. Kurz:
- Von außen kein Login an PostGIS ohne gültiges Client-Zertifikat (bzw. ohne App-Session auf der API).
- Port der Datenbank ist nicht im Internet.
- Entzug eines Zertifikats sperrt den Client, ohne das VPN-Gitter umzubauen.
Übergang
- Proxy + CA intern, QGIS testweise mit Zertifikat zusätzlich zum VPN.
sslmode=verify-fullverbindlich.- Erledigt: Öffentliches Publish von
3401/ Traefik-TCP aufwg0abgeschaltet; der Proxy trägt. - Geteiltes QGIS-Passwort abschaffen, sobald Rollen am Zertifikat hängen.
Lokale Entwicklung: Compose darf Postgres weiter auf localhost:3401 legen — nicht das Server-Soll.
Gleiche Live-DB von der Workstation: lokale FastAPI darf denselben PgBouncer-Pfad nutzen wie QGIS (DATABASE_HOST = öffentlicher Hostname, DATABASE_SSLMODE=verify-full, Client-Zertifikat). Production-FastAPI bleibt intern postgres:5432. Siehe ADR-0013.
Erstmalige Mandanten-Migration
Bestehende GIS-/Stammdaten liegen heute in der Control-DB linetra. Einmalig in den ersten Mandanten ziehen, danach Fachtabellen aus der Control-DB entfernen:
- Control-Alembic inkl.
0011_tenants:docker compose exec backend uv run alembic upgrade head. PgBouncer:./scripts/create_app_roles.sh(legtpgbouncer.get_authan) und Wildcard*inpgbouncer/pgbouncer.ini. - Ersten Kunden anlegen mit Kopie der Fachdaten:
POST /api/v1/tenantsmit{ "slug": "…", "name": "…", "copy_control_data": true }oder./scripts/provision_tenant.sh <slug> --copy-dataplus Catalog-POST. - Web-User legen selbst ein Organisationskonto an (
POST /tenants);app_users.tenant_idwird gesetzt. Plattform-Admins dürfentenant_idnull behalten. - QGIS neu verbinden: nach API-Login liefert
/auth/medbname=linetra_<slug>und CN{slug}_edit. Cert-ZIP neu ausstellen. - Wenn die Mandanten-DB die Fachdaten hält:
./scripts/strip_control_fachdaten.sh(löscht Fachtabellen inlinetra, behältapp_users/tenants/ Tokens). - Weitere Mandanten ohne Kopie:
POST /tenants(copy_control_datafalse) klontlinetra_tenant_template. Schema-Updates:./scripts/alembic_upgrade_all.sh.
Lokal bleibt LINETRA_SHARED_DB=true (Compose-Dev): eine physische DB, logischer Mandant dev.