Files
bf-infra/docs/tp16.3-nautobot-mesh-only-runbook.md

187 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TP16.3 — Probe: `nautobot` aus dem CF-Ingress nehmen (mesh-only)
> **Stand 2026-08-19 · vorbereitet, nicht ausgeführt.** Die Recherche zu TP16.3 ist seit 2026-08-17
> durch; offen war nur die Umsetzung. Dieses Runbook macht daraus einen ausführbaren, in einer Minute
> zurücknehmbaren Schnitt.
---
## 1. Worum es geht (in drei Sätzen)
Der private Datenpfad läuft seit TP16.1 über **natives WireGuard auf dem Core** (`cf-mesh`), nicht über
Container — belegt, nicht vermutet: im TP5-Log-Lauf standen **137 Zeilen `in:cf-mesh`**. Für Dienste,
die **niemand ohne WARP im Browser** aufrufen muss, ist `cloudflared` damit überflüssiger Aufwand:
eine Ingress-Zeile, ein Connector-Paar, eine HA-Frage. Diese Probe nimmt **einen** solchen Dienst
heraus und schaut eine Woche zu.
### Warum ausgerechnet `nautobot`
| Kriterium | `nautobot` |
|---|---|
| Wer nutzt es | nur Christoph — kein Fremdgerät, kein Gast |
| Braucht es einen Client ohne Access-Login? | **nein** (das ist das TP16.2-Kriterium, an dem `vw` hängt) |
| Wie oft schon per Mesh angefasst | **62×** im TP5-Log — der Spitzenreiter |
| Was kostet ein Fehlschlag | nichts Produktives; im Zweifel greift man per IP zu |
---
## 2. 🔴 Die Verwechslungsfalle, die den ganzen TP kippen kann
**Mesh-Hostname-Routes sind Egress, nicht Publishing.** Cloudflare kennt seit 2026-07-02 auch
Hostname-Routes auf dem Mesh — die klingen so, als könnten sie den Ingress ersetzen. Sie können es
nicht: sie leiten *ausgehenden* Verkehr zu diesem Hostname über den Node und egressen mit dessen
öffentlicher IP. **Sie veröffentlichen nichts.**
Wer das verwechselt, baut den öffentlichen Strang ab und wundert sich, warum von außen nichts mehr geht.
→ **Diese Probe ersetzt nichts. Sie entfernt einen Dienst, der öffentlich gar nicht gebraucht wird.**
---
## 3. Vorbedingung — VOR dem Schnitt prüfen (read-only)
Alle drei Punkte müssen erfüllt sein, sonst wird nicht geschnitten:
1. **Die Mesh-Route steht.** Auf dem Core: die vnet-Route `10.40.0.0/16` zeigt auf `bf-core-mesh`
(TP16.1 §4.4, „Weg A"). Ohne sie ist Mesh-Zugriff auf `10.40.20.30` gar nicht möglich.
2. **Der Dienst antwortet über die private IP, mit aktivem WARP** — von einem Client, der *nicht* im
BF-LAN steht (das ist der einzige Test, der etwas beweist; im LAN geht es ohnehin):
```
http://10.40.20.30:8080
```
3. ⚠️ **ICMP nicht als Beweis nehmen, aber auch nicht als Gegenbeweis.** Der Mesh kann ICMP (das war
der Gewinn von TP16.1). Wenn Ping scheitert, TCP aber geht, ist zuerst zu prüfen, ob lokal noch ein
WARP-Client läuft — s. Memory `ref_warp_local_icmp_trap`. Umgekehrt ist ein erfolgreicher Ping kein
Beleg, dass der HTTP-Dienst erreichbar ist.
---
## 4. Der Schnitt
**Was:** die `nautobot`-Zeile aus der Ingress-Config des Tunnels **`bf-nas`** entfernen.
Die DNS-CNAME-Zeile bleibt zunächst stehen (Rollback bleibt so eine reine Ingress-Operation).
**Womit:** die Cloudflare-API mit `CF_API_TOKEN`.
> 🔴 **Nicht über den Cloudflare-MCP.** Der ist **strikt read-only** — Writes gehen ausschließlich über
> die API mit dem Token (Memory `ref_cf_mcp_readonly_token_writes`). Das Token ist **account-owned**,
> nutzt also einen anderen verify-Endpoint als ein User-Token, und **läuft am 2027-01-09 ab**.
**Wo die Werte stehen:** `mcp/cloudflare/.env` (chmod 600, gitignored) —
`CF_API_TOKEN`, `CF_ACCOUNT_ID`, `CF_BF_TUNNEL_ID`.
**Ablauf:**
1. **Ist-Config sichern** (das ist der Rollback):
`GET /accounts/{account_id}/cfd_tunnel/{tunnel_id}/configurations` → JSON wegschreiben, mit Datum.
2. Aus dem `ingress`-Array die `nautobot`-Zeile entfernen. ⚠️ Die abschließende Catch-all-Regel
(`service: http_status:404` ohne `hostname`) **muss die letzte bleiben** — sonst greift der Ingress
für alle übrigen Hostnames nicht mehr.
3. **Zurückschreiben:** `PUT` derselben Route mit dem gekürzten Array.
4. **Gegenprobe am Gerät, nicht am Statuscode:** ein `GET` der Config liest zurück, was wirklich steht.
*(Dieselbe Regel wie in TP22/TP23: ein „success" der API ist kein Wirksamkeitsnachweis.)*
---
## 5. Abnahme
| Prüfung | Erwartung |
|---|---|
| Öffentlicher FQDN `nautobot.…` ohne WARP | **nicht mehr erreichbar** (das ist der Zweck) |
| `http://10.40.20.30:8080` mit WARP, von außerhalb des LAN | erreichbar |
| Die übrigen Hostnames auf `bf-nas` (gitea, grafana, pve-mcp, vw, ds-bf) | **unverändert erreichbar** |
| Golden-Config-Deploys (Netmiko aus Nautobot) | laufen weiter |
🔴 **Der letzte Punkt ist der, den man übersieht:** Nautobot ist nicht nur eine Web-UI, sondern die
Deploy-Pipeline. Der Netmiko-Deploy kommt von **`10.40.20.31`** (`nautobot-worker`), nicht von `.20.30`
— das war einer der vier TP5-Funde. Der Schnitt betrifft nur den *eingehenden* öffentlichen Weg, aber
nach dem Schnitt gehört ein Job-Lauf zur Abnahme, nicht nur ein Browser-Aufruf.
---
## 6. Betriebsfenster und Rollback
- **Eine Woche fahren**, dann entscheiden: bleibt es so, oder kommt es zurück.
- **Rollback = die gesicherte Ingress-Config aus Schritt 1 zurückschreiben.** Eine Zeile, eine Minute.
- Danach ist zu entscheiden, ob weitere Dienste der rechten Spalte folgen (`pve`, `portainer`,
`grafana`, `pve-mcp`, `ha-mcp`, `gitea-mcp`, `ds-bf`, `ccu`, `iobroker`, `z2m25`).
### Was in dieser Probe ausdrücklich NICHT entschieden wird
- **`vw`, `hass`, `n8n` bleiben öffentlich** — harte Kriterien: Bitwarden-Clients (TP16.2-Constraint),
die HA-Companion-App (das 400-Muster), eingehende Webhooks von Dritten.
- **`gitea` ist offen** und wird hier nicht mitentschieden: Git-Clients/CI von außen können keinen
interaktiven Access-Login. Braucht eine eigene Prüfung, ob es solche Zugriffe real gibt.
---
## 7. Nebeneffekt, der den Fall stärkt
Die **TP16-HA-Degradierung** (`bf-nas` und `bf-pve` haben je nur **einen** Connector, Befund 2026-08-16)
verliert an Gewicht, je weniger Dienste an cloudflared hängen. Das ersetzt die Reparatur nicht —
aber es verkleinert, was der SPOF trifft.
---
## 8. ▶️ AUSGEFÜHRT 2026-08-24 — der Schnitt sitzt, Probe läuft
**Vorbedingungen (§3), alle erfüllt:**
| Prüfung | Ergebnis |
|---|---|
| vnet-Route `10.40.0.0/16` → `bf-core-mesh` | ✅ vorhanden (CF `teamnet/routes`) |
| `cf-mesh` am Core | ✅ `R`, last-handshake 33 s, 198 MiB rx / 738 MiB tx |
| `http://10.40.20.30:8080` **von außerhalb des LAN, WARP aktiv** | ✅ HTTP 302 in 0,18–0,36 s |
| ICMP zu `10.40.20.30` | ✅ 0 % Loss *(kein Beweis, aber es schließt die WARP-Falle aus)* |
🎯 **Der Testclient war zufällig genau der richtige:** die Session lief auf einem Host im
**Hotspot-Netz `172.20.10.13`** mit aktivem WARP — also *nicht* im BF-LAN. Das ist der einzige Aufbau,
der für Vorbedingung 2 etwas beweist; aus dem LAN heraus geht es ohnehin.
**Der Schnitt:** `nautobot.emsenhuber.net` aus dem Ingress von `bf-nas`
(`220c9958-401b-4111-9451-f3db09bba2d5`) entfernt, **8 → 7 Regeln**, Catch-all weiterhin letzte.
Rollback-Sicherung: **`docs/cf-backup/bf-nas-ingress-20260824-vor-tp16.3.json`**.
Gegenprobe **durch Zurücklesen der Config vom Dienst**, nicht am Statuscode: `nautobot noch drin? False`.
**Abnahme (§5):**
| Prüfung | Ergebnis |
|---|---|
| `10.40.20.30:8080` mit WARP von außerhalb | ✅ 302 in 0,16–0,17 s |
| `gitea` / `grafana` / `pve-mcp` / `vw` / `ds-bf` | ✅ je 3 Läufe, unverändert (200/302/302/200/302) |
| **Golden-Config-Pipeline** | ✅ Backup → Intended → Compliance, **104/104 compliant, 0 Drifts** |
| Öffentlicher FQDN nicht mehr bedient | ⚠️ **so nicht testbar — s.u.** |
### 🔴 Befund: die erste Abnahmezeile aus §5 ist als Test untauglich
`https://nautobot.emsenhuber.net/` antwortet **vor und nach** dem Schnitt mit **HTTP 302** — und zwar
identisch, weil der 302 **nicht vom Origin kommt, sondern von Cloudflare Access**:
`location: https://emsenhuber.cloudflareaccess.com/cdn-cgi/access/login/nautobot.emsenhuber.net`.
Access greift **vor** dem Tunnel; der Request erreicht den Ingress gar nicht, solange keine
Access-Session besteht. Im mitgelieferten JWT steht `"auth_status":"NONE"` bei gleichzeitig
`"is_warp":true` — **WARP-Identität allein erfüllt die Policy für `nautobot` nicht**, es braucht den
interaktiven Login.
⇒ **Konsequenzen, beide wichtig:**
1. **Der Statuscode des öffentlichen FQDN kann diesen Schnitt weder belegen noch widerlegen.** Der
gültige Nachweis ist das **Zurücklesen der Ingress-Config** (oben) — dieselbe Regel wie in
TP22/TP23: nicht die Antwort des Frontends zählt, sondern der Zustand am Dienst.
2. **Der öffentliche Weg war schon vorher zu.** Nautobot war nie ohne Access-Login erreichbar; der
Schnitt entfernt also die *zweite* von zwei Türen, nicht die erste. Das schmälert den Gewinn nicht
(eine Ingress-Zeile und ein Connector-Paar weniger), aber es korrigiert die Erwartung: **hier wurde
keine offene Tür geschlossen, sondern eine überflüssige entfernt.**
📌 **Nebenprüfung, die den Schnitt hätte kippen können:** `nautobot/gc-configplan.py` spricht
`http://10.40.20.30:8080/api` — die **private** IP. Kein Repo-Skript benutzt den öffentlichen Namen
(`grep` über `*.py`/`*.sh`/`*.yml`/`*.env`: kein Treffer). Die Werkzeugkette hängt also nicht am
gekappten Weg. **Das gehört vor jeden weiteren Schnitt der rechten Spalte geprüft.**
### ⏳ Offen: das Betriebsfenster
**Eine Woche fahren (bis ~2026-08-31), dann entscheiden.** Rollback = die gesicherte Config
zurückschreiben, eine Minute. Danach steht die Frage, ob `pve`, `portainer`, `grafana`, `pve-mcp`,
`ha-mcp`, `gitea-mcp`, `ds-bf`, `ccu`, `iobroker`, `z2m25` folgen.
### 📌 §7 ist überholt
Der dort beschriebene Nebeneffekt („die HA-Degradierung verliert an Gewicht") ist gegenstandslos:
**TP16 ist am selben Tag repariert** — alle drei bf-Tunnel laufen wieder auf 2 Connectoren
(`cf-tunnel-mapping.md §15`). Der Fall für TP16.3 steht damit auf dem verbliebenen Argument, und das
ist das eigentliche: **weniger bewegliche Teile für Dienste, die öffentlich nicht gebraucht werden.**