From 81a0ebaf9d44378280a9202979da79794e18d8d366a389b168ffda2d61a340b1 Mon Sep 17 00:00:00 2001 From: wlfb Date: Fri, 24 Jul 2026 09:45:06 +0200 Subject: [PATCH] docs: Reverse-Proxy-Doku auf reales Live-Setup (fbwl.creature-go.com via sv005/Traefik) Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 14 ++- docs/reverse-proxy.md | 285 +++++++++++++++++++++++++++++++++--------- 2 files changed, 235 insertions(+), 64 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6a2e6cd..fa0ca6b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -131,11 +131,15 @@ App muss sie **in-place** überschreiben (`open` im Modus `r+`, `flock`, `truncate` — siehe `services/admin.py::_rewrite_env_file`), niemals über Temp-Datei+`rename` (neuer Inode, vom laufenden Mount nicht mehr gesehen). Host-seitige `sed`-Edits in `create_pod_finance.sh` selbst sind unkritisch, -weil das Skript die Container ohnehin bei jedem Lauf neu erstellt. Für den -Betrieb hinter einem Reverse-Proxy (eine Subdomain, Grafana als -`/grafana/`-Unterpfad) genügen zwei optionale `.env`-Variablen -(`FB_GRAFANA_PUBLIC_URL`, `FB_SESSION_COOKIE_SECURE`) — siehe -`docs/reverse-proxy.md`. +weil das Skript die Container ohnehin bei jedem Lauf neu erstellt. Das Tool ist +**produktiv über den Reverse-Proxy erreichbar** unter +`https://fbwl.creature-go.com/` (App) + `/grafana/` (Grafana-Unterpfad); +aktiviert rein über die zwei optionalen `.env`-Variablen +(`FB_GRAFANA_PUBLIC_URL`, `FB_SESSION_COOKIE_SECURE`), leer ⇒ Lokalbetrieb. +Kette sv005 (Apache/TLS) → WireGuard → sv006 Traefik → Pod, **vollständig +dokumentiert in `docs/reverse-proxy.md`** (dort auch die Regel: Traefik-Backends +`10.0.2.2:PORT`, nicht `127.0.0.1`). Achtung: mit `FB_SESSION_COOKIE_SECURE=true` +geht Browser-Login nur noch über die HTTPS-Domain (curl-Tests weiter ok). **Disaster Recovery = `BIND_DIR`-Backup + Repo + Skript.** Für vollständige Wiederherstellung werden **beide** gebraucht: ein Backup von diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index 37961bc..0b73d6d 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -1,91 +1,258 @@ -# Reverse-Proxy-Betrieb (Ausbaustufe 10) +# Reverse-Proxy-Betrieb (Ausbaustufe 10) — LIVE -Ziel: Erreichbarkeit über **eine** Subdomain -`https://fb.wolfundlaemmlein.de/`, Grafana als Unterpfad `/grafana/`. +Das Finanzberatungs-Tool ist öffentlich erreichbar unter +**`https://fbwl.creature-go.com/`** (App/GUI) mit **Grafana als Unterpfad +`https://fbwl.creature-go.com/grafana/`**. Aktiviert wird das ausschließlich +über zwei `.env`-Schalter auf sv006; ohne sie läuft der unveränderte +Lokalbetrieb auf `127.0.0.1` weiter (per Live-Redeploy + Sub-Pfad-Smoke belegt). -Kette: Internet → **sv003** (Apache, TLS-Terminierung) → WireGuard-VPN → -**sv006** (Traefik, ohne TLS, `10.8.0.6:8080`) → finance_pod -(`127.0.0.1:8096` GUI/API, `127.0.0.1:8097` Grafana). +Die Proxy-Konfiguration (sv005 Apache, sv006 Traefik) liegt außerhalb dieses +Repos — dieses Dokument hält den realen Stand fest, damit Änderungen am Tool +die Kette nicht brechen. -Die Proxy-Konfiguration selbst (Traefik, Apache) liegt **außerhalb** dieses -Repos — die folgenden Snippets sind Vorlagen. Tool-seitig genügt es, in der -`.env` unter `~/.local/share/finance_pod/.env` zwei Variablen zu setzen und -`./create_pod_finance.sh` erneut auszuführen. +## Kette (Datenfluss) -## `.env`-Schalter (single-quoted!) - -```sh -FB_GRAFANA_PUBLIC_URL='https://fb.wolfundlaemmlein.de/grafana/' -FB_SESSION_COOKIE_SECURE='true' +``` +Browser ──HTTPS──► sv005 (DesTEngSsv005) + Virtualmin/Apache, TLS-Terminierung, WireGuard-IP 10.8.0.1 + ServerName fbwl.creature-go.com ──ProxyPass──► http://10.8.0.6:8080 + ──WireGuard (wg0)──► + sv006 (DesTEngSsv006 — DIESER Rechner) + Traefik: rootless-Podman-Pod "traefik_pod" (User trf), + Entrypoint "wghttp" published auf 10.8.0.6:8080 + Router Host(fbwl.creature-go.com) ──► http://10.0.2.2:8096 (App/API) + Router Host(fbwl.creature-go.com) && /grafana ──► http://10.0.2.2:8097 (Grafana) + finance_pod: 127.0.0.1:8096 (App), 127.0.0.1:8097 (Grafana) ``` -- `FB_GRAFANA_PUBLIC_URL` — öffentliche Grafana-Basis-URL (mit `/grafana/`, - abschließender Slash empfohlen). Bewirkt: - - `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` + - `GF_SERVER_SERVE_FROM_SUB_PATH=true`; - - interner Health-Check und interne Grafana-API-URL (`FB_GRAFANA_URL`, für - die GUI-Passwortänderung) erhalten automatisch den `/grafana`-Präfix; - - alle GUI-Links/iframes zeigen auf die öffentliche URL statt `:8097`. - - **Leer lassen** = unveränderter Direktbetrieb auf `127.0.0.1`. -- `FB_SESSION_COOKIE_SECURE='true'` — Session-Cookie nur über HTTPS. Vor der - Internet-Freigabe setzen. Default (leer/`false`) für lokalen HTTP-Zugriff. +**Warum `10.0.2.2`?** Traefik läuft in einem *rootless*-Podman-Pod mit +`slirp4netns:allow_host_loopback=true`. Aus Sicht des Traefik-Containers ist +`127.0.0.1` der **Container selbst**; die auf dem Host an `127.0.0.1:8096/8097` +veröffentlichten Pod-Ports erreicht er über die slirp4netns-Host-Loopback-Adresse +**`10.0.2.2`**. Backend-URLs im Traefik-Router deshalb IMMER `10.0.2.2:PORT`, +niemals `127.0.0.1:PORT`. -Nach dem Editieren: `./create_pod_finance.sh` (übernimmt die `.env` -unverändert, baut Pod/Container neu). HTTPS/HSTS und Login-Rate-Limiting sind -Proxy-Sache (Apache auf sv003 bzw. fail2ban) — außerhalb dieses Repos. +**Warum funktioniert `X-Forwarded-Proto` sauber?** sv005 hat die WireGuard-IP +`10.8.0.1`, und Traefiks Entrypoint `wghttp` vertraut per +`forwardedHeaders.trustedIPs: 10.8.0.1/32` genau dieser Quelle. Das von sv005 +gesetzte `X-Forwarded-Proto: https` wird also übernommen und (via uvicorn +`--proxy-headers`) bis zur App durchgereicht. -## Traefik (sv006) — Datei-Provider, Beispiel +## Teil 1 — finance_pod (`.env` auf sv006) -Kein Prefix-Stripping für `/grafana` (Grafana serviert dank -`serve_from_sub_path` selbst unter dem Sub-Pfad): +In `~/.local/share/finance_pod/.env` (alle Werte **single-quoted**): +```sh +FB_GRAFANA_PUBLIC_URL='https://fbwl.creature-go.com/grafana/' +FB_SESSION_COOKIE_SECURE='true' +``` +Übernehmen: `./stop_finance_pod.sh` → `.env` editieren → `./create_pod_finance.sh`. +Wirkung von `FB_GRAFANA_PUBLIC_URL` (leer ⇒ Lokalbetrieb, Grafana-Fallback +`http://:8097`, Grafana an der Wurzel): +- `create_pod_finance.sh` startet Grafana mit `GF_SERVER_ROOT_URL` + + `GF_SERVER_SERVE_FROM_SUB_PATH=true`; +- der **Pfadanteil** der URL (`/grafana`) wird zum Präfix für den internen + Grafana-Health-Check UND für die interne `FB_GRAFANA_URL` + (`http://localhost:3000/grafana`, für die GUI-Passwortänderung) — beides in + `create_pod_finance.sh` automatisch abgeleitet; +- alle GUI-Links/iframes bilden sich aus dieser URL (Jinja-Global + `grafana_public_base`, siehe `app/routers/gui.py`). + +**Achtung Secure-Cookie:** Mit `FB_SESSION_COOKIE_SECURE='true'` akzeptiert ein +**Browser** das Session-Cookie nur über HTTPS — ein direkter Login über +`http://127.0.0.1:8096` funktioniert dann nicht mehr, nur noch über die +HTTPS-Domain. Automatisierte Tests mit `curl` (`-c/-b`) laufen weiter, weil curl +das Secure-Flag ignoriert (bewährt bei Redeploy-Smoke-Tests). + +## Teil 2 — Traefik-Router (sv006, verwaltet als User `trf`) + +Traefik ist ein **geteilter** rootless-Podman-Pod `traefik_pod` (User `trf`, +Config unter `/home/trf/.local/share/traefik_pod/`; als `wlfb` NICHT lesbar). +Statik: Entrypoint `wghttp` auf `:8080` (published `10.8.0.6:8080`), +`forwardedHeaders.trustedIPs: 10.8.0.1/32`; File-Provider `dynamic/` mit +`watch: true` (Änderungen laden automatisch, kein Neustart, andere Dienste +unberührt). + +**Wichtig — Black Hole:** Es gibt einen Catch-all-Router +(`HostRegexp('{any:.*}')`, `priority: 1`, Middleware `ipAllowList 127.0.0.1/32`), +der jeden **nicht explizit gerouteten** Host sofort mit **403** abweist. Jeder +öffentliche Dienst braucht daher einen eigenen Router mit +`entryPoints: ["wghttp"]` und `priority > 1`. + +Router-Datei `dynamic/fbwl.yml`: ```yaml -# /etc/traefik/dynamic/finance.yml http: routers: finance-grafana: - rule: "Host(`fb.wolfundlaemmlein.de`) && PathPrefix(`/grafana`)" - priority: 20 + rule: "Host(`fbwl.creature-go.com`) && PathPrefix(`/grafana`)" + priority: 120 # ueber dem Black Hole (priority 1) service: finance-grafana - entryPoints: [web] + entryPoints: ["wghttp"] finance-app: - rule: "Host(`fb.wolfundlaemmlein.de`)" - priority: 10 + rule: "Host(`fbwl.creature-go.com`)" + priority: 110 service: finance-app - entryPoints: [web] + entryPoints: ["wghttp"] services: finance-grafana: loadBalancer: servers: - - url: "http://127.0.0.1:8097" + - url: "http://10.0.2.2:8097" # slirp4netns-Host-Loopback, NICHT 127.0.0.1 finance-app: loadBalancer: servers: - - url: "http://127.0.0.1:8096" + - url: "http://10.0.2.2:8096" ``` -## Apache (sv003) — VHost, Beispiel +## Teil 3 — sv005 Apache (Virtualmin, Domain fbwl.creature-go.com) -TLS terminiert hier; Weiterleitung an Traefik über die WireGuard-IP. `X- -Forwarded-Proto https` ist wichtig, damit uvicorn (mit `--proxy-headers`) das -öffentliche Schema erkennt: +Virtualmin → Server `fbwl.creature-go.com` → „Edit Directives" (bearbeitet +`/etc/apache2/sites-available/fbwl.creature-go.com.conf`). Aufgebaut nach dem +Muster des bestehenden `affine.creature-go.com`-vhosts: `/.well-known` lokal +(ACME/Let's-Encrypt-Erneuerung), HTTP→HTTPS-Redirect, dann Weiterleitung der +ganzen Domain an Traefik (`10.8.0.6:8080`) mit erhaltenem Host-Header. TLS-Cert +verwaltet Virtualmin/Let's Encrypt. +### Port 80 ```apache - - ServerName fb.wolfundlaemmlein.de - - SSLEngine on - SSLCertificateFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/fullchain.pem - SSLCertificateKeyFile /etc/letsencrypt/live/fb.wolfundlaemmlein.de/privkey.pem - - ProxyPreserveHost On - RequestHeader set X-Forwarded-Proto "https" - ProxyPass / http://10.8.0.6:8080/ - ProxyPassReverse / http://10.8.0.6:8080/ - +SuexecUserGroup #1015 #1011 +ServerName fbwl.creature-go.com +ServerAlias www.fbwl.creature-go.com +ServerAlias mail.fbwl.creature-go.com +ServerAlias webmail.fbwl.creature-go.com +ServerAlias admin.fbwl.creature-go.com +DocumentRoot /home/fbwl/public_html +ErrorLog /var/log/virtualmin/fbwl.creature-go.com_error_log +CustomLog /var/log/virtualmin/fbwl.creature-go.com_access_log combined +ScriptAlias /cgi-bin/ /home/fbwl/cgi-bin/ +DirectoryIndex index.php index.htm index.html + + Options -Indexes +IncludesNOEXEC +SymLinksIfOwnerMatch +ExecCGI + Require all granted + AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch + AddHandler fcgid-script .php + AddHandler fcgid-script .php8.4 + FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php + FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php8.4 + + + Require all granted + AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch + +ProxyPass /.well-known ! +RewriteEngine on +RewriteCond %{HTTP_HOST} =webmail.fbwl.creature-go.com +RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:20000/ [R=301,L] +RewriteCond %{HTTP_HOST} =admin.fbwl.creature-go.com +RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:10000/ [R=301,L] +# --- Finanzberatungs-Tool: HTTP -> HTTPS (ausser ACME-Challenge) --- +RewriteCond %{HTTPS} off +RewriteCond %{REQUEST_URI} !^/\.well-known/ +RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L] +RemoveHandler .php +RemoveHandler .php8.4 +FcgidMaxRequestLen 1073741824 ``` -## Zurück in den Lokalbetrieb +### Port 443 +```apache +SuexecUserGroup #1015 #1011 +ServerName fbwl.creature-go.com +ServerAlias www.fbwl.creature-go.com +ServerAlias mail.fbwl.creature-go.com +ServerAlias webmail.fbwl.creature-go.com +ServerAlias admin.fbwl.creature-go.com +DocumentRoot /home/fbwl/public_html +ErrorLog /var/log/virtualmin/fbwl.creature-go.com_error_log +CustomLog /var/log/virtualmin/fbwl.creature-go.com_access_log combined +ScriptAlias /cgi-bin/ /home/fbwl/cgi-bin/ +DirectoryIndex index.php index.htm index.html + + Options -Indexes +IncludesNOEXEC +SymLinksIfOwnerMatch +ExecCGI + Require all granted + AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch + AddHandler fcgid-script .php + AddHandler fcgid-script .php8.4 + FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php + FCGIWrapper /home/fbwl/fcgi-bin/php8.4.fcgi .php8.4 + + + Require all granted + AllowOverride All Options=ExecCGI,Includes,IncludesNOEXEC,Indexes,MultiViews,SymLinksIfOwnerMatch + +ProxyPass /.well-known ! +RewriteEngine on +RewriteCond %{HTTP_HOST} =webmail.fbwl.creature-go.com +RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:20000/ [R=301,L] +RewriteCond %{HTTP_HOST} =admin.fbwl.creature-go.com +RewriteRule ^/(?!\.well-known)(.*)$ https://fbwl.creature-go.com:10000/ [R=301,L] +# --- Finanzberatungs-Tool: Weiterleitung an Traefik (sv006, wghttp) --- +ProxyPreserveHost On +RequestHeader set X-Forwarded-Proto "https" +# WebSocket (Grafana Live) an Traefik durchreichen +RewriteCond %{HTTP:Upgrade} websocket [NC] +RewriteCond %{HTTP:Connection} upgrade [NC] +RewriteRule .* ws://10.8.0.6:8080%{REQUEST_URI} [P,L] +# Haupt-Weiterleitung (Traefik trennt App vs. /grafana selbst) +ProxyPass / http://10.8.0.6:8080/ +ProxyPassReverse / http://10.8.0.6:8080/ +RemoveHandler .php +RemoveHandler .php8.4 +FcgidMaxRequestLen 1073741824 +SSLEngine on +SSLCertificateFile /etc/ssl/virtualmin/17848735353695059/ssl.cert +SSLCertificateKeyFile /etc/ssl/virtualmin/17848735353695059/ssl.key +SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 +SSLCACertificateFile /etc/ssl/virtualmin/17848735353695059/ssl.ca +``` -`FB_GRAFANA_PUBLIC_URL` und `FB_SESSION_COOKIE_SECURE` aus der `.env` -entfernen (oder leeren) und `./create_pod_finance.sh` erneut ausführen. +Hinweis: `www.`/`mail.fbwl…` gehen (wie bei affine) über den Proxy an Traefik +und landen dort mangels Router im Black Hole (403) — fürs Tool irrelevant. + +## Cutover-Reihenfolge (falls die Kette neu aufgebaut wird) + +1. DNS `fbwl.creature-go.com` → sv005; Cert via Virtualmin/Let's Encrypt. +2. Traefik-Router (Teil 2) ablegen — `watch` lädt automatisch. +3. sv005 Apache (Teil 3) eintragen — Apache reload. +4. Testen (unten), solange Tool noch im Lokalmodus (`/grafana` kommt erst mit Schritt 5). +5. `.env`-Cutover (Teil 1) + Redeploy — **`FB_SESSION_COOKIE_SECURE='true'` zuletzt**. + +## Verifikation + +```bash +# Von aussen: +curl -sI https://fbwl.creature-go.com/login | head -1 # -> 200 +curl -sI https://fbwl.creature-go.com/grafana/api/health | head -1 # -> 200 +curl -I http://fbwl.creature-go.com/ 2>&1 | head -3 # -> 301 auf https + +# Auf sv006 (Kette bis Traefik, Host-Header simuliert): +for p in /login /grafana/api/health; do + curl -sS --max-time 5 -o /dev/null -w "$p -> %{http_code}\n" \ + -H 'Host: fbwl.creature-go.com' "http://10.8.0.6:8080$p" +done +``` +Der Grafana-iframe erfordert eine Grafana-Session (Embedding an, anonym aus): in +der GUI einmal „Grafana anmelden" (→ `…/grafana`) mit dem gemeinsamen Passwort. + +## Für künftige Claude-Sessions — was das für Änderungen am Tool bedeutet + +- **Domain/Links des Tools hängen ausschließlich an `FB_GRAFANA_PUBLIC_URL`** + (+ Templates via `grafana_public_base`). Kein Hostname im Code/Image — ein + Domainwechsel ist eine reine `.env`-Änderung + Redeploy (plus je ein Eintrag + in Traefik-Router und sv005-Apache). +- **Neuer öffentlicher Pfad/Dienst hinter derselben Domain**: Traefik-Router in + `dynamic/` mit `entryPoints: ["wghttp"]`, `priority > 1`, Backend + `http://10.0.2.2:PORT`; an sv005 meist nichts nötig, weil `ProxyPass /` die + ganze Domain weiterreicht und Traefik nach Pfad trennt. +- **Neuer Port am Pod**: in `create_pod_finance.sh` als `127.0.0.1:PORT` + veröffentlichen → Traefik erreicht ihn als `10.0.2.2:PORT`. +- **Grafana-Sub-Pfad**: `GF_SERVER_ROOT_URL` + `serve_from_sub_path` und die + präfixierten internen URLs werden in `create_pod_finance.sh` aus + `FB_GRAFANA_PUBLIC_URL` abgeleitet — den Pfad (`/grafana`) nicht ohne Grund + ändern; er steckt auch im Health-Check und in `FB_GRAFANA_URL`. +- **Lokaler Test trotz Live-Proxy**: `.env`-Schalter leeren (echter Lokalmodus) + ODER mit `curl` testen; ein echter Browser braucht die HTTPS-Domain, weil das + Session-Cookie `Secure` ist. +- **Traefik ist geteilt** (User `trf`): dessen Config ist als `wlfb` nicht + editierbar. Router-/Entrypoint-Änderungen müssen als `trf` erfolgen; den + Black-Hole-Router und fremde Dienste nicht anfassen.