From afc2f90dac011991920a137ac40b88b0fcaa6ddb Mon Sep 17 00:00:00 2001 From: nexxo Date: Tue, 4 Aug 2026 17:04:04 +0200 Subject: [PATCH] Entwurf: die oeffentlichen Seiten im Tunnel erreichbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Am Telefon mit aktivem VPN zeigt app.clupilot.com die Platzhalterseite. Nicht falsch eingestellt, sondern nie gebaut: der Tunnel traegt nur 10.66.0.0/24, und der interne Resolver biegt genau einen Namen um — den der Konsole. Portal, Website und Statusseite loesen oeffentlich auf und gehen am Tunnel vorbei. Der Gateway sucht seine Zertifikate kuenftig selbst und laesst Namen ohne Zertifikat aus, statt gar nicht zu starten. Sonst nimmt ein fehlendes Zertifikat fuer www. den Tunnel-Zugang zur Konsole mit — genau der Ausfall, vor dem update.sh heute warnt. files. bleibt absichtlich draussen: ein Server im Rettungssystem holt dort sein Archiv und ist per Definition nicht im Tunnel. Co-Authored-By: Claude Opus 5 --- ...04-oeffentliche-seiten-im-tunnel-design.md | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-04-oeffentliche-seiten-im-tunnel-design.md diff --git a/docs/superpowers/specs/2026-08-04-oeffentliche-seiten-im-tunnel-design.md b/docs/superpowers/specs/2026-08-04-oeffentliche-seiten-im-tunnel-design.md new file mode 100644 index 0000000..11770b9 --- /dev/null +++ b/docs/superpowers/specs/2026-08-04-oeffentliche-seiten-im-tunnel-design.md @@ -0,0 +1,149 @@ +# Die öffentlichen Seiten im Tunnel — Entwurf + +**Stand:** 2026-08-04 +**Zustand:** Vom Betreiber freigegeben. Namensumfang, Verhalten bei fehlendem +Zertifikat und Verdrahtungsweg sind entschieden. + +## Worum es geht + +Solange die Website verborgen ist (`site.public` aus), sieht nur die echte +Seite, wer aus einem Netz in der Freigabeliste kommt — sonst erscheint die +Platzhalterseite. Über das Management-VPN funktioniert das für die **Konsole**, +aber nicht für Portal und Website. + +Gemeldet: am Telefon, mit aktivem Tunnel (`10.66.0.2/32`, Handshake frisch), +zeigt `app.clupilot.com` die Platzhalterseite. Am Notebook über die Büroleitung +(`62.178.50.33`, in der Freigabeliste) erscheint die echte Seite. + +## Warum es nicht funktioniert + +Nicht falsch eingestellt — nicht gebaut. Zwei Zeilen erklären alles: + +- **Der Tunnel trägt nur das Management-Netz.** Im Client steht + `AllowedIPs = 10.66.0.0/24`. `app.clupilot.com` löst auf die öffentliche + Adresse des Servers auf, und dieser Verkehr geht am Tunnel vorbei. Der Server + sieht also die öffentliche Adresse des Telefons, nicht `10.66.0.2`. +- **Der interne Resolver biegt genau einen Namen um.** + [`docker-compose.yml:212`](../../../docker-compose.yml) — + `--address=/${VPN_INTERNAL_HOST}/${CLUPILOT_WG_HUB_ADDRESS}`. Alles andere + geht an `1.1.1.1`. Und der Tunnel-Gateway + ([`docker/caddy/vpn.Caddyfile`](../../../docker/caddy/vpn.Caddyfile)) hat + genau einen Site-Block, für denselben einen Namen. + +Die Konsole ist im Tunnel erreichbar, weil ihr Name dort auf `10.66.0.1` zeigt +und der Gateway ihn bedient. Für Portal, Website und Statusseite gilt beides +nicht. + +## Was entschieden ist + +| Frage | Entscheidung | +|---|---| +| Welche Namen | `app.`, `www.` (samt Aliassen) und `status.` | +| `files.` | **Nicht.** Ein Server im Rettungssystem holt dort sein Archiv und ist per Definition nicht im Tunnel | +| Fehlt ein Zertifikat | Diesen Namen auslassen, die übrigen bedienen | +| Wer verdrahtet | Der Gateway findet seine Zertifikate selbst | + +## Die Strecke + +### 1. Der Resolver — eine hosts-Datei, kein zweiter `--address` + +Ein Artisan-Befehl schreibt die konfigurierten Namen im hosts-Format in das +vorhandene `dns-hosts`-Volume: + +``` +10.66.0.1 app.clupilot.com +10.66.0.1 www.clupilot.com +10.66.0.1 status.clupilot.com +``` + +dnsmasq überwacht dieses Verzeichnis bereits (`--hostsdir`) und übernimmt +Änderungen ohne Neustart. `RegisterHostDns` schreibt dort schon die Host-Namen +hinein, und der `app`-Container hängt das Volume schreibbar ein — gleicher Ort, +gleicher Mechanismus, kein root, keine Compose-Änderung. + +Bewusst **keine** zweite `--address`-Zeile: die fängt zusätzlich jede +Unterdomain mit ab, und eine Compose-`command`-Zeile kann nicht über eine +variable Namensliste schleifen. + +Die Hub-Adresse kommt aus `CLUPILOT_WG_HUB_ADDRESS`, nicht als Literal — auf +der nächsten Installation ist sie eine andere. + +### 2. Der Gateway erzeugt seine Konfiguration beim Start + +Ein Startskript rendert die Caddy-Konfiguration aus zwei Quellen: den +konfigurierten Namen und den Zertifikaten, die im eingehängten Speicher +**tatsächlich liegen**. Ein Site-Block je Name mit `.crt` **und** `.key`; jeder +andere Name wird ausgelassen und beim Start benannt. + +Der Konsolenname steht zuerst. Damit kann „für `www.` gibt es noch kein +Zertifikat" den Tunnel-Zugang zur Konsole nie mitnehmen — genau der Ausfall, +vor dem [`update.sh:352`](../../../deploy/update.sh) heute warnt: + +> *„Both or neither: leaving the profile on with empty tls paths starts a Caddy +> that cannot load its configuration and crashes forever."* + +Das Zertifikat bleibt das, was der öffentliche Caddy ohnehin erneuert. Es hängt +am Namen, nicht an der Adresse, die ihn ausliefert — dieselbe Begründung, die im +Kopf der `vpn.Caddyfile` schon steht. + +Der Gesundheits-Port bleibt unverändert und unabhängig von jedem Zertifikat: er +ist das Signal, an dem die Bereitschaft des Tunnels hängt. + +### 3. `VPN_CERT_PATH` / `VPN_KEY_PATH` bleiben stehen + +Sie werden überflüssig, aber sie werden **nicht** entfernt. `install-agent.sh` +und `update.sh` schreiben, prüfen und löschen sie an fünf Stellen, und ein +Fehler darin sperrt den Betreiber aus dem Tunnel aus. + +Der Gateway ignoriert sie künftig und sucht selbst. Die eine nötige Änderung an +`update.sh`: es darf das VPN-Profil nicht mehr abschalten, bloß weil die Pfade +leer sind. Ob der Tunnel bereit ist, entscheidet weiterhin die +Gesundheitsprüfung, die es seit [`update.sh:263`](../../../deploy/update.sh) +ohnehin macht — die ist bereits probe-basiert und nicht pfad-basiert. + +### 4. `clupilot:bind-hosts` härtet die Eingabe + +Ein Wert, der kein Hostname sein kann, wird abgelehnt statt geschrieben — +geprüft je Name, auch in einer Komma-Liste, gegen dasselbe Muster, das der +root-Helfer in `apply-proxy-hosts` benutzt. + +Der Anlass ist konkret: In die `.env` der Produktivmaschine gelangte + +``` +SITE_HOST=[www.clupilot.com](https://www.clupilot.com) +``` + +— ein Markdown-Link, aus einem Kopiervorgang. `EnvFileEditor` prüft nur, ob die +Zeile `KEY=value` ist, also wurde er widerspruchslos geschrieben. Die Website +war danach an einen Namen gebunden, den keine Anfrage je trifft. Das Repo kennt +diese Falle bereits: `RestrictConsoleNetwork::isNetwork()` existiert mit der +Begründung, ein Eintrag, der nichts trifft, werde „stored happily and reports +success". + +## Was nicht angefasst wird + +`PublicSiteGate`, `RestrictConsoleNetwork` und die Freigabeliste. Sobald der +Verkehr durch den Tunnel kommt, ist die Quelladresse `10.66.0.x` — die steht in +`TRUSTED_RANGES` und wird geprüft, wie sie soll. Daran ist nichts zu ändern. + +## Das Risiko, das benannt gehört + +Der Resolver biegt diese Namen für **jeden** Tunnel-Teilnehmer um, nicht nur für +den Betreiber — auch für die Proxmox-Hosts. Die erreichen das Portal danach über +den Gateway statt über das öffentliche Netz. + +Das ist unschädlich und eher besser (der Weg verlässt die Maschine nicht), aber +es ist eine Verhaltensänderung, die niemand bestellt hat. Sie steht deshalb +hier und nicht in einer Überraschung. + +## Abnahme + +1. Am Telefon über VPN zeigen **`app.`, `www.` und `status.`** die echte Seite, + nicht die Platzhalterseite. +2. Ohne VPN und ohne Adresse in der Freigabeliste: weiterhin die + Platzhalterseite. +3. Ein Name ohne Zertifikat schaltet den Gateway nicht ab — die Konsole bleibt + im Tunnel erreichbar, und der ausgelassene Name wird beim Start gemeldet. +4. `clupilot:bind-hosts` lehnt einen Markdown-Link ab und schreibt nichts. +5. `files.` ist im Tunnel **nicht** umgebogen und bleibt öffentlich erreichbar. +6. Voller Testlauf grün.