Initial import from bf-network-r6.opencode — opencode migration

This commit is contained in:
2026-08-26 12:44:21 +02:00
commit 5ee0716dda
297 changed files with 72324 additions and 0 deletions

View File

@@ -0,0 +1,186 @@
# 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.**