Files
bf-infra/docs/tp16.1-warp-mesh-native-crs326.md
Christoph Emsenhuber 7388cc8388 TP16.1 4.0 DURCH: Mesh-Node bf-core-mesh angelegt, Free-Plan-Gate offen
POST /warp_connector erfolgreich -> d1da7c8a-... Der LXC-Fallback ist vom Tisch.
Blocker war der Token, nicht der Plan: alter User-Token hatte 2 fehlende
Permissions, ersetzt durch account-owned Token (gueltig bis 2027-01-09).
Zwei Fallen dokumentiert: account-owned Tokens verifizieren an
/accounts/{id}/tokens/verify (am /user-Pfad melden sie faelschlich
'Invalid API Token'), und Zone-Permissions brauchen eine eigene Policy-Zeile
('Account tag in access policy must match tag in request uri').
Verifiziertes Permission-Set inkl. IDs in 4.0. Am Geraet weiterhin nichts angefasst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 11:58:51 +02:00

330 lines
29 KiB
Markdown
Raw 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.1 — Private Access via native Cloudflare Mesh (WARP Connector) auf dem CRS326 Core-Switch
> **✅ Status 2026-08-17: §4.0 DURCH — Plan-Gate offen, Mesh-Node existiert.** `POST /warp_connector` erfolgreich:
> **`bf-core-mesh` = `d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env`
> (`CF_MESH_REGISTER_TOKEN`, gitignored, `chmod 600`). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist damit
> beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh. Der LXC-Fallback (§6) ist vom Tisch.**
> **Am Gerät ist weiterhin nichts angefasst** (kein Backup, kein `cf-mesh`, keine Route) — nächster Schritt ist
> §4.1, und der ist wegen des account-weiten WARP-Reconnects **user-gated**.
>
> *Historie dieses Anlaufs: 2026-08-16 blockiert auf Token-Scope (zwei fehlende Permissions, s. §4.0) →
> 2026-08-17 Token neu aufgesetzt, jetzt account-owned.* `crs326-core` ist seit 2026-08-08 der produktive L3-Router und seit 2026-08-13 in Nautobot adoptiert (68/68 compliant) — die Bedingung „gated bis TP21 den CRS326 abdeckt" ist **erfüllt**. §3 am 2026-08-16 neu gemessen, §4.3 von Platzhalter auf **ausführungsreif** gezogen, **Governance-Ausnahme #4 erteilt** (WG-Zeilen direkt, weil ein Privkey über Golden Config strukturell nicht deploybar ist). Scharfschalten bleibt eine **eigene Session** — es fasst account-weite WARP-Einstellungen an.
> *Historie: Konzept + Runbook 2026-07-16 · Recherche verifiziert + gehärtet 2026-07-17 · Ziel-Device von ap1 auf CRS326 retargeted 2026-07-22.*
> ⚠️ **Retarget-Hinweis (2026-07-22, Stand 2026-08-16 aufgelöst):** Alles unten war ursprünglich für **ap1** geschrieben und wurde auf den **CRS326 Core-Switch** ummünzt. ~~Der CRS326 ist aktuell noch Werkszustand — Management-IP/Interface-Zuordnung TBD, §4.3 ist ein Platzhalter.~~ → **überholt:** `crs326-core` ist produktiver L3-Router mit Mgmt `10.40.99.1`, §4.3 trägt jetzt echte Werte. Grund für den Retarget: TP15-Endstand (kein RB5009, Router bleibt ap1/ap2 rein für L3/WLAN) + TP21s Modell "ganzer Kabel-Fabric via RouterOS" — der CRS326 wird ohnehin Nautobot-verwaltet, ap1 bleibt Public-Edge/WLAN-Anker.
> Selbst-tragend: eine frische Session kann das mit diesem Doc + `docs/cf-tunnel-mapping.md` straight bauen — vorausgesetzt der CRS326-Bring-up (TP15) und die Nautobot-Adoption (TP21) stehen.
> SSoT-Kontext: `docs/cf-tunnel-mapping.md` §13/§14, Memory `project_cf_tunnel_1033`.
> Härtungs-Delta 2026-07-17 (gilt unabhängig vom Zielgerät): Split-Tunnel auf BEIDEN Profilen (§4.1.4), vnet-Gotcha + Weg B beim Route-Umzug (§4.4),
> v4-only-Entscheid (IPv6-site-to-site laut Guide broken), Quellen verifiziert (§2). **Korrektur:** ICMP-Proxy ist bereits AN
> (fm-Ping `10.101.0.0/24` beweist es) — kein To-Do, s. §4.1.5.
## 1. Warum überhaupt / Root Cause
**Incident 2026-07-16 (unterwegs):** keine BF-IPs über WARP. Diagnose:
- **TCP über den CF-WARP-Pfad funktioniert** (SSH, alle Web-UIs, MCP-Endpoints erreichbar) —
- **ICMP ist strukturell kaputt.** Dreiländer-Ping-Befund: fm **36 % Loss** (ICMP-Proxy über QUIC-Connector,
funktioniert aber mies), bf **100 %** (macmini-Connector läuft seit dem http2-Fix von heute = QUIC-los →
ICMP-unfähig; zusätzlich cloudflared-Container ohne Ping-Sockets), dw **100 %** (192.168.0.0/16 steht in der
WARP-Split-Tunnel-Exclude → Pings betreten den Tunnel gar nicht).
**Kernursache:** cloudflared ist **kein** WireGuard, sondern ein QUIC/HTTP2-L7/L4-Proxy. ICMP darüber ist
Zusatz-Gefrickel (Account-ICMP-Flag + QUIC-Transport + `ping_group_range`-sysctl je Host) und war nie
zuverlässig eingerichtet. **Der WARP-Client selbst spricht WireGuard** — die passende Gegenstelle für einen
*sauberen* privaten L3-Zugang ist deshalb **Cloudflare Mesh (WARP Connector)**, ebenfalls WireGuard.
## 2. Entscheidungsweg (was verworfen wurde und warum)
- **WG-Roadwarrior (eigene WG-Peers je Gerät):** ❌ verworfen — User will KEINE Extra-App/kein Profil pro Gerät.
„Ein Client = WARP für alles" bleibt Anforderung.
- **Alle ZT-Anbieter mit Helper-Host** (CF WARP Connector als Daemon-Host, Tailscale, NetBird, Twingate):
brauchen dauerhaft einen Linux-Host → verletzt „Router = einziger SPOF, keine Abhängigkeit zu anderen Rechnern".
- **Connector als RouterOS-Container:** ❌ nicht möglich — RouterOS-7-Container haben **kein `/dev/net/tun`-
Passthrough**; der WARP-Connector-Daemon braucht ein TUN-Device. Gilt auf **jeder** Router-/Switch-HW (hAP ax²/ax³/CRS326/RB5009 — RB5009 mittlerweile ohnehin nicht mehr geplant, s. TP15).
→ **hAP ax³ löst das NICHT** (und ist zudem Wi-Fi 6, nicht 7 — kein WLAN-Gewinn ggü. ax²). HW-Kauf ist vom
Tunnel-Fix **entkoppelt**.
- **✅ Gewählt: Cloudflare Mesh als NATIVES WireGuard-Interface auf dem CRS326 Core-Switch** (Retarget 2026-07-22, ursprünglich ap1). Cloudflare Mesh spricht WireGuard,
RouterOS kann natives WireGuard (der TP10-Hub auf ap1 *ist* schon einer, Muster überträgt sich). Ein Mesh-Node wird als WG-Peer zu einem
CF-PoP registriert — **kein Container, kein TUN, kein LXC, keine neue HW.** Der CRS326 ist der zentrale Kabel-Fabric-Knoten
und sieht ohnehin alle VLANs; ap1 bleibt Public-Edge/WLAN-Anker, übernimmt aber nicht mehr die Mesh-Node-Rolle. ICMP/TCP/UDP nativ.
Quelle/Vorlage: animmouse „Setup Cloudflare Mesh on MikroTik" + „Setup Cloudflare Mesh using WireGuard".
Ehrliche Einschränkung: **von uns noch nicht gebaut** — starker, technisch schlüssiger Lead; echter Beweis = bauen+pingen.
**✅ Recherche verifiziert (2026-07-17, Primärquellen gelesen + offizielle CF-Docs + OpenAPI-Spec):**
- **Produkt-Status:** „WARP Connector" heißt seit **2026-04-14 offiziell „Cloudflare Mesh"** (Rebranding, kein Migrationszwang);
Node-Limit **50/Account**; neues Dashboard unter **Networking → Mesh** (interaktive Map, Route-Config, Setup-Wizard).
Die API-Pfade heißen weiterhin `/accounts/{id}/warp_connector` — im OpenAPI-Spec bestätigt: POST (create),
GET `…/{tunnel_id}/token` (Register-Token), dazu sogar **HA-Endpoints** (`…/configurations` active-passive-Replicas,
`…/failover`) — für uns nicht nötig (ap1 = bewusster SPOF), aber gut zu wissen.
- **CIDR-Routes für Mesh-Nodes** laufen über dieselbe `teamnet/routes`-API wie cloudflared (`POST {network, tunnel_id: <mesh_node_id>}`,
Token-Scope `Cloudflare One Networks Write` oder `Cloudflare Tunnel Write`) — unser `CF_API_TOKEN`-Pfad passt.
- **animmouse-MikroTik-Rezept bestätigt** (alle Kommandos gegengelesen, s. §4.3): CF-Wellknown-PublicKey, Endpoint-Substitution
`162.159.192.x → 162.159.193.x` (Latenz), MTU 1420, `100.96.0.0/12 → cf-mesh`-Route. Guide nutzt `persistent-keepalive=1m`
(unser Entwurf sagte 25s — beides ok, 25s konservativer hinter NAT; wir sind Public-Edge → 1m reicht).
- **IPv6-Befund (animmouse):** Mesh-site-to-site-IPv6 war beim Guide-Test **broken** (NAT66-Workaround nötig) → **wir bauen bewusst v4-only**
(kein v6 auf `cf-mesh`, kein NAT66 — BF nutzt intern eh kein v6, s. Memory `project_ipv6_state`).
- **Sources:** [Setup Cloudflare Mesh on MikroTik](https://www.animmouse.com/p/setup-cloudflare-mesh-on-mikrotik/) ·
[Setup Cloudflare Mesh using WireGuard](https://www.animmouse.com/p/setup-cloudflare-warp-connector-using-wireguard/) ·
[wgcf-connector (GitHub)](https://github.com/AnimMouse/wgcf-connector) ·
[CF-Changelog „Introducing Cloudflare Mesh"](https://developers.cloudflare.com/changelog/post/2026-04-14-cloudflare-mesh/) ·
[CF-Docs Mesh Routes](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-mesh/routes/)
## 3. Ist-Stand Account (**neu gemessen 2026-08-16**, read-only — CF-API + MikroMCP)
> Die Messung vom 2026-07-16 ist auf der **CF-Seite unverändert** geblieben; auf der **Geräteseite** hat
> sich alles geändert (Core-Cutover 2026-08-08, Nautobot-Adoption 2026-08-13). Beides unten getrennt.
**Cloudflare (alle Werte am 2026-08-16 gegengeprüft, identisch zu 2026-07-16):**
- Team-Domain: **`emsenhuber.cloudflareaccess.com`** → Mesh-Node-Identität = `warp_connector@emsenhuber.cloudflareaccess.com`
- **Noch KEIN** `warp_connector`-Tunnel (`GET /accounts/…/warp_connector` = `[]`). Wichtig: der GET **antwortet**
(kein 403) — die Plan-Frage entscheidet sich also erst am `POST` (§4.0).
- `devices/settings`: **`use_zt_virtual_ip: false`** → Unique-Device-IP weiterhin **AUS**, muss für Mesh AN.
`gateway_proxy_enabled: true`, `gateway_udp_proxy_enabled: true` (ICMP-Flag ist Dashboard-only, s. §4.1.5).
- **Genau EIN Device-Profil** (das Default-Profil `0197d1b3-d98a-7c24-b70c-a4d89df7583b`, `service_mode_v2: warp`).
➡️ Der „auf BEIDEN Profilen"-Hinweis in §4.1.4 meint deshalb: **Default jetzt, Mesh-Profil sobald es in Schritt 3
angelegt ist** — es gibt aktuell kein zweites, auf dem man etwas vergessen könnte.
- Split-Tunnel-Modus = **Exclude**; die Liste enthält weiterhin **`100.64.0.0/10`** (schluckt die Mesh-Overlay-Range
`100.96.0.0/12`) und **kein** `10.0.0.0/8` → `10.40.0.0/16` läuft schon heute über WARP (= warum TCP geht).
Nebenbefund: `192.168.0.0/16` steht drin — das ist die Erklärung für die 100 % Ping-Loss nach **dw**, unabhängig von diesem TP.
- vnet-Route `10.40.0.0/16` (vnet `1480b545…` = `vnetemsenhuber`, **default**, Route-ID
**`5546dcb6-5901-4da6-8d91-b17f94703f71`**) zeigt weiterhin auf **cfd_tunnel `bf-pve`** (`f7fdf3a6…`).
Bleibt der **Fallback**, bis Mesh bewiesen ist — vorher nichts löschen.
⚠️ **Neu zu wissen:** `bf-pve` fährt aktuell nur **einen** Connector (statt zwei, s. TP16-HA-Befund 2026-08-16) —
der private Pfad hängt damit heute an einem einzelnen cloudflared-Prozess. Ein weiteres Argument für das Mesh,
aber **kein** Ersatz für die HA-Reparatur der öffentlichen Tunnel.
- `CF_API_TOKEN` ist `active` (verify OK). Ob er die separate Permission **„Cloudflare One Connector: WARP"** trägt,
lässt sich nur durch den `POST` in §4.0 feststellen — genau dort schlug es am 2026-07-17 mit `10000` fehl.
**Geräteseite — die alten Zeilen hier waren überholt, Neufassung 2026-08-16:**
- ~~„CRS326 = Werkszustand, keine WireGuard-Config, keine Management-IP"~~ → **falsch seit dem Core-Cutover.**
`crs326-core` ist der **produktive L3-Router** (RouterOS 7.19.5): Mgmt `10.40.99.1` (vlan99), Gateway `.1` in
**jedem** VLAN, WAN `vlan100` mit der öffentlichen `90.146.33.177`, NAT, DHCP, DNS.
- ~~„ap1-WireGuard: 1 Interface `wg-hub`"~~ → **wg-hub liegt seit 2026-08-08 auf dem Core**, nicht mehr auf ap1
(listen 51821, MTU 1412, Peers `fm` 10.101.0.0/24 + `dw` 10.95.10.0/24, beide handshaken). Das ist TP10 und
bleibt unangetastet — aber es ist jetzt das **direkte Vorbild auf demselben Gerät**: das Mesh-Interface entsteht
daneben, nicht auf einem fremden Blatt.
- ✅ **Die offene Frage „Rückweg-Routing TBD" aus §4.3 ist damit beantwortet:** der Core *ist* der L3-Router aller
VLANs, Rückverkehr aus `10.40.0.0/16` zum Mesh-Interface braucht **keine** Zusatzroute.
- `crs326-core` ist seit 2026-08-13 in Nautobot adoptiert und compliance-verwaltet (68/68) — damit ist das
Gate „gated bis TP21 den CRS326 abdeckt" **erfüllt**.
## 4. Runbook (nächste Session — Reihenfolge)
### 4.0 Plan-Probe (billig, reversibel) — ZUERST
`POST /accounts/{account_id}/warp_connector {name:"bf-ap1-mesh"}` via `CF_API_TOKEN` (curl/Bearer — der `cloudflare`-
MCP ist read-only, s. §11 der Mapping-Doc). Erfolgt der Create → Mesh ist im Free-ZT-Plan erlaubt. Scheitert er mit
Plan-/Auth-Fehler → **Stopp, Fallback = LXC-auf-pve-WARP-Connector** (alter Plan, ebenfalls dokumentiert).
Token danach: `GET /accounts/…/warp_connector/{tunnel_id}/token` → das ist das `eyJhIjoi…In0=`-Register-Token.
**🔴 STAND 2026-08-16: Plan-Probe erneut ausgeführt — identisch gescheitert, Blocker jetzt vollständig vermessen.**
`POST /accounts/…/warp_connector {"name":"bf-core-mesh"}` → `{"success":false,"errors":[{"code":10000,
"message":"Authentication error"}]}`, HTTP **403**. Der Token (`455e4f8e…`) ist `active`. Gegenproben mit
**demselben** Token, um Scope von Plan-Gate zu trennen:
| Endpoint | Methode | Ergebnis | Bedeutet |
|---|---|---|---|
| `warp_connector` | GET | **200** (`[]`) | Endpoint existiert für den Account, Feature nicht wegge-403-t |
| `warp_connector` | POST | **403** `10000` | ⛔ §4.0 — Permission `Cloudflare One Connector: WARP` fehlt |
| `cfd_tunnel` | GET | 200 | `Cloudflare Tunnel`-Scope vorhanden (TP16-Strang unberührt) |
| `teamnet/routes` | GET | 200 | §4.4-Pfad grundsätzlich da (PATCH-Edit 2026-07-17 bewiesen) |
| `devices/settings` | GET | **403** `9109` | ⛔ **NEU: §4.1 ist ebenfalls blockiert** — `Zero Trust`-Scope fehlt |
| `user/tokens/{id}` · `tokens/permission_groups` | GET | 403 `9109` | Token kann sich selbst nicht introspizieren → Scope-Liste nur im Dashboard |
⚠️ **Der wichtigste neue Befund: das Runbook nannte bisher NUR die eine fehlende Permission (§4.0).** Die Probe
zeigt, dass auch **§4.1 komplett scheitern würde** — `devices/settings` ist für den Token schon lesend dicht.
Ohne diesen Nachtrag hätte die Scharfschalt-Session den Token **zweimal** nachziehen müssen. Beides in **einem**
Edit erledigen.
**Gegenprobe, dass es NICHT das Free-Plan-Gate ist:** derselbe `devices/settings`-GET liefert über den
`cloudflare`-**MCP** (OAuth, read-only, volle Account-Sicht) sauber `200` mit
`{gateway_proxy_enabled:true, gateway_udp_proxy_enabled:true, use_zt_virtual_ip:false}`. Der Account *kann* das
also — nur der Token darf nicht. ➡️ **Die Free-Plan-Frage aus §4.0 bleibt damit weiterhin unentschieden**: sie
beantwortet sich erst beim Retry des `POST` mit korrekt gescoptem Token. Erst ein dann *immer noch* fehlschlagender
POST wäre das Plan-Gate und würde den LXC-Fallback (§6) auslösen.
**✅ AUFGELÖST 2026-08-17 — Token neu aufgesetzt, alle Proben 200.** Der Weg dahin hatte zwei Stolpersteine, die
man kennen muss, weil beide *aussehen* wie „Token kaputt", es aber nicht sind:
1. **Der neue Token ist ACCOUNT-OWNED** (id `9984078812bc18d4ebb6a64b570144f2`, gültig bis **2027-01-09** ⏰).
Account-Tokens verifizieren an einem **anderen** Endpoint: `GET /accounts/{account_id}/tokens/verify`.
⚠️ Am alten `GET /user/tokens/verify` antwortet ein gültiger Account-Token mit **`1000 "Invalid API Token"`/401** —
zeitgleich liefern *alle* echten Endpoints 200. **Nicht als abgelaufen fehldeuten.**
2. **Dashboard-Fehler `"Account tag in access policy must match tag in request uri"`** (real erlebt, mehrfach):
tritt auf, wenn eine **Zone**-Permission (hier `DNS`) in dieselbe Policy-Zeile gepackt wird, die **Account**-
Ressourcen trägt. Zone-Permissions brauchen eine **eigene Policy-Zeile** mit Zone-Ressourcen. Halb richtig
gesetzt = Permission sichtbar, aber jede Zone `403`, weil keine Zone eingeschlossen ist.
**Verifiziertes Soll-Set (alle am 2026-08-17 mit 200 gegengeprüft) — Dashboard sagt „Edit", die API sagt „Write":**
| Dashboard | API-Name | Permission-ID | braucht |
|---|---|---|---|
| Cloudflare One Connector: WARP → Edit | `Cloudflare One Connector: WARP Write` | `5b5c774a5d174ca88d046c8889648b3f` | §4.0 |
| Zero Trust → Edit | `Zero Trust Write` | `b33f02c6f7284e05a6f20741c0bb0567` | §4.1 |
| Cloudflare One Networks → Edit | `Cloudflare One Networks Write` | `e2980d9241cf4939bbbd74fdc43b9651` | §4.4 |
| Cloudflare Tunnel → Edit | `Cloudflare Tunnel Write` | `c07321b023e944ff818fec44d8203567` | TP16 |
| Access: Apps and Policies → Edit | `Access: Apps and Policies Write` | — | Bestand |
| Access: Organizations, IdP, and Groups → Edit | `… Write` | — | Bestand |
| **Zone** DNS → Edit (`emsenhuber.net`) | `DNS Write` | — | Tunnel-CNAMEs |
⚠️ **Namensfalle:** `Cloudflare One Connectors Write` (Plural, Sammelgruppe) ist **nicht** die Mesh-Permission.
Ebenso heißt „Cloudflare Tunnel" in neueren Listen zusätzlich „Cloudflare One Connector: cloudflared" — beide
Gruppen existieren parallel. Vollständige Liste (396 Gruppen) via MCP: `GET /user/tokens/permission_groups`.
<details><summary>Historischer Klickpfad (2026-08-16, vor der Auflösung)</summary>
**➡️ Was zu tun ist (Dashboard, nur der User — Token-Bearbeitung ist keine Agent-Aktion):**
`dash.cloudflare.com` → My Profile → API Tokens → Token `455e4f8e…` → Edit → **Account**-Permissions ergänzen:
1. **„Cloudflare One Connector: WARP" → Edit** (§4.0: `POST /warp_connector` + `/token`)
2. **„Zero Trust" → Edit** (§4.1: `devices/settings` PATCH, `devices/policy` POST, Split-Tunnel PUT)
3. **„Cloudflare One Networks" → Edit** (§4.4 Routes/vnets — GET geht heute, Edit war 2026-07-17 ok; sicherheitshalber mitnehmen)
Danach neuen Wert nach `mcp/cloudflare/.env` **und** `~/.secrets/credentials.env` (beide tragen `CF_API_TOKEN`).
</details>
**⚠️ Token-Permission-Gotcha (2026-07-17, real erlebt — Ursachenanalyse, weiterhin gültig):** `POST /warp_connector` schlug mit `code 10000
"Authentication error"` (HTTP 403) fehl, obwohl der `CF_API_TOKEN` gültig ist und `teamnet/routes` PATCHen kann.
Ursache: Cloudflare hat beim WARP-Connector→Mesh-Rebranding (2026-04-14) eine **eigene, separate Permission-Group**
eingeführt — `Cloudflare One Connector: WARP` (Read/Edit) — „Grants write access to Cloudflare Mesh nodes". Das ist
**nicht** dasselbe wie `Cloudflare Tunnel Write` (nur für cloudflared) oder `Cloudflare One Connectors Write`
(Sammel-Permission, deckt aber laut Doku nicht zuverlässig Mesh ab). Verifiziert gegen
`developers.cloudflare.com/fundamentals/api/reference/permissions/` (aktuelle Tabelle, nicht die älteren
tunnel-spezifischen Doc-Seiten, die noch die alten Namen zeigen). Im Token-Editor unter **Account**-Permissions nach
**„Cloudflare One Connector: WARP"** suchen (nicht „Tunnel"), **Edit** wählen. Für §4.4 zusätzlich
**„Cloudflare One Networks"** → Edit (Routes/vnets).
### 4.1 CF-Prerequisites setzen (⚠️ account-weit, trifft LIVE-Geräte → 1× kurzer WARP-Reconnect einplanen)
1. **Unique Device IP AN:** `PATCH /accounts/…/devices/settings {use_zt_virtual_ip:true}` (Dashboard: Team & Resources →
Devices → Management → „Assign a unique IP address to each device").
2. **„Allow all Cloudflare One traffic to reach enrolled devices" AN** (gleiche Seite).
3. **Device-Profile „Cloudflare Mesh"** anlegen: Match `identity.email == warp_connector@emsenhuber.cloudflareaccess.com`,
Tunnel-Protokoll = **WireGuard** (`POST /accounts/…/devices/policy`).
4. **Split-Tunnel-Exclude anpassen** (`PUT /accounts/…/devices/policy/exclude` — ⚠️ ersetzt die GANZE Liste, erst GET,
dann editiert zurückschreiben): `100.64.0.0/10` **raus**, dafür `100.64.0.0/11` + `100.112.0.0/12` **rein**
(= 100.64/10 minus 100.96/12 → Mesh-Overlay `100.96.0.0/12` läuft nun durch WARP statt als „local" behandelt zu werden).
⚠️ **Auf BEIDEN Profilen** (Default **und** dem neuen „Cloudflare Mesh"-Profil aus Schritt 3 — per-Profile-Endpoint
`…/devices/policy/{policy_id}/exclude`); animmouse macht das explizit auf beiden.
5. **ICMP-Proxy — ✅ bereits AN, KEIN To-Do** (Korrektur 2026-07-17, User): Die offiziellen Mesh-Docs listen „Gateway proxy
mit TCP, UDP, ICMP" als Prerequisite. TCP+UDP sind an (`gateway_proxy_enabled`/`gateway_udp_proxy_enabled`=true);
**ICMP ist ebenfalls schon angehakt** — **Beweis: `ping` ins FM-Netz `10.101.0.0/24` funktioniert** (über den fm-QUIC-Connector).
Das ICMP-Flag ist nur nicht im public OpenAPI-Spec sichtbar (Dashboard-only), daher hatte ich es fälschlich als „fehlt" geführt.
→ **Nichts zu klicken.** Dass bf-Ping im Incident 100 % verlor lag NICHT am Flag, sondern daran, dass die macmini-Connectoren
nach dem http2-Fix QUIC-los sind (cloudflared-ICMP-Proxy braucht QUIC). Mit dem nativen WG-Mesh-Node ist ICMP ohnehin
nativ (kein Gateway-ICMP-Proxy im Pfad). **Trotzdem: nach dem Scharfschalten Ping gegengeprüft** (§4.5) — falls wider
Erwarten doch tot, hier zuerst schauen.
### 4.2 WireGuard-Config extrahieren (einmalig, ephemer)
Auf macmini oder pve (Docker vorhanden):
```
docker run --rm -v $(pwd):/app/output ghcr.io/animmouse/wgcf-connector <REGISTER_TOKEN>
```
→ erzeugt `wgcf-connector-<registration_id>.conf` mit: **PrivateKey**, **Address** (`100.96.x.y/32` **+ eine IPv6** —
ignorieren, wir bauen v4-only), **DNS** (CF-Resolver — ignorieren, ap1 hat eigenes DNS), **MTU 1420**, Peer-**PublicKey**
(= CF-Wellknown `bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=`), **Endpoint** (`162.159.193.x:2408`).
Registriert den Node via CF-API (macht Netz-Call), läuft danach nie wieder. **PrivateKey nach `mcp/cloudflare/.env`
o. `~/.secrets`** (nicht committen).
### 4.3 `crs326-core` nativ konfigurieren ✅ ausführungsreif (Werte finalisiert 2026-08-16)
> Der frühere Platzhalter-Vorbehalt („Management-IP/Interface-Zuordnung fehlt") ist erledigt: Zielgerät ist
> **`crs326-core`** (Mgmt `10.40.99.1`, MikroMCP-routerId `crs326-core`). Offen bleibt nur, was erst zur Laufzeit
> existiert: der PrivateKey und die `100.96.x.y`-Adresse aus §4.2.
```
/interface wireguard add name=cf-mesh mtu=1420 private-key="<PrivateKey aus .conf>" comment="TP16.1 Cloudflare Mesh"
/interface wireguard peers add interface=cf-mesh name=cf-pop \
public-key="bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" \
endpoint-address=162.159.193.1 endpoint-port=2408 \
allowed-address=0.0.0.0/0,::/0 persistent-keepalive=25s
/ip address add address=<100.96.x.y>/32 interface=cf-mesh comment="TP16.1 Mesh-Overlay"
/ip route add dst-address=100.96.0.0/12 gateway=cf-mesh comment="TP16.1 Mesh"
```
- **Endpoint:** falls die Config `162.159.192.x` liefert → `162.159.193.x` nehmen (Latenz-Tuning laut Guide).
- **MTU:** 1420 aus dem Guide. Direkter Vergleichswert steht jetzt **auf demselben Gerät**: `wg-hub` läuft mit
**1412**. Bei Path-MTU-Symptomen (TCP baut auf, große Transfers hängen) zuerst auf 1412 angleichen.
- ✅ **Rückweg-Routing — erledigt, keine Zusatzarbeit.** `crs326-core` ist der L3-Router **aller** VLANs
(`vlan10/20/30/40/50/90/99` mit `.1`), Rückverkehr nach `10.40.0.0/16` läuft also ohne Zusatzroute. Das war
beim ursprünglichen CRS326-Retarget die einzige echte Unbekannte.
- **Namenskollision vermeiden:** auf dem Gerät existiert bereits `wg-hub` (TP10). Das neue Interface heißt
`cf-mesh` — ⚠️ **Bindestrich in `name=` hat RouterOS beim GC-Push schon einmal abgelehnt** (Memory
`ref_gc_push_gotchas_routeros`); beim Direkt-Write über MikroMCP war das nie ein Problem, aber falls die Zeilen
später doch durch die GC laufen, ist `cfmesh` der sichere Name.
- **Firewall:** die `forward`-Kette endet derzeit **ohne Drop** (TP5 steht aus) → der Mesh-Verkehr passiert ohne
Zutun. ⚠️ **Für TP5 vormerken:** sobald deny-by-default greift, braucht es explizit
`forward accept 100.96.0.0/12 ↔ 10.40.0.0/16`, sonst kappt die Firewall den privaten Zugang. Gehört in die
TP5-Whitelist, nicht hierher.
- ✅ **Governance — Ausnahme #4 erteilt (User, 2026-08-16).** Die frühere Zeile („läuft regulär über Nautobot GC")
trägt **nicht mehr**: TP22-Regel 1 schließt jede Sektion mit `<<REDACTED>>` generell vom Push aus, und
`/interface wireguard` ist genau so eine Sektion — ein Privkey ist über GC gar nicht deploybar. Diese vier Zeilen
dürfen daher **direkt** (MikroMCP `plan_changes` dryRun → `apply_plan`). **Auflage wie bei Ausnahme #2:** danach
Ist-Stand als intended config zurück nach Nautobot. **Scope:** nur der Mesh-Bring-up auf `crs326-core`, nicht
spätere WG-Änderungen, nicht andere Geräte. Vorher `/system backup` (TP22-Regel 3).
#### Und wie hält man Secrets langfristig aus der Config raus?
Die Ausnahme soll ein **einmaliger Bootstrap** bleiben, kein Dauerzustand. Der Denkfehler, den man vermeiden
muss, ist, einen privaten Schlüssel als *Konfiguration* zu behandeln — er ist **Geräte-Identität**, wie ein
SSH-Hostkey. Die branchenübliche Regel:
> Der private Teil entsteht auf dem Gerät und verlässt es nie. Nur der **öffentliche** Teil geht in die Source of
> Truth — und wird von dort in die Configs der **Gegenstellen** gerendert.
| Was | Klasse | Wo verwaltet |
|---|---|---|
| PrivateKey | Geräte-Identität | nur am Gerät — nie in Git, nie in der intended config, nie in einem ConfigPlan |
| PublicKey | öffentliche Information | Nautobot (Custom Field/Data) → in die Peer-Configs gerendert |
| `listen-port`, `mtu`, `comment`, Peers, `allowed-address`, Routen, Firewall | Konfiguration | Golden Config, ganz normal |
RouterOS unterstützt das direkt: `/interface wireguard add` **erzeugt den Privkey selbst**, `print` gibt den
Pubkey aus. Compliance bleibt trotzdem grün, weil die `ConfigReplace`-Regel aus TP22 Schritt 0 den Privkey im
Backup auf **byte-identisch dieselbe** Maske normalisiert, die das Template emittiert.
⚠️ **Vorbehalt genau hier:** bei Cloudflare Mesh erzeugt **CF** das Schlüsselpaar (§4.2 holt es per `wgcf-connector`).
Wenn der Flow keinen eigenen Pubkey akzeptiert, fällt die On-Device-Erzeugung weg — das Prinzip („nie in Git, nie
in der intended config") hält trotzdem, der Key wird dann einmalig eingespielt und lebt danach nur am Gerät.
**Stufe 2, falls Secrets doch verwaltet werden müssen** (mehr Geräte, Rotation, PSKs): `nautobot-secrets-providers`
nachinstallieren (Vault/AWS SM/Azure KV/1Password; Core kann schon Env-Var + Text-File) und den echten Wert erst
**bei der ConfigPlan-Erzeugung** einsetzen — gespeicherte intended config behält den Platzhalter, der Plan wird
nach dem Deploy gelöscht. Preis: Klartext liegt transient in Postgres, braucht Retention-Disziplin. Bei vier
Geräten und einem Key lohnt das nicht; **das ist ein TP für die Zeit nach TP18/TP20.1.**
*(Nicht verwechseln: `CredentialsNautobotSecrets` + SecretsGroup „RouterOS Nornir" sind schon im Einsatz — das
sind die **Login**-Credentials für Nornir, nicht Secrets *im Config-Inhalt*.)*
**Sofort fälliger Modellierungs-Nachzug, unabhängig vom Mesh:** die WG-Peers `fm` und `dw` stehen heute mit
hartkodierten `public-key="…"`-Werten im Jinja-Template (`nautobot/mikrotik_routeros.j2`). Public Keys sind Daten,
keine Templatezeilen — die gehören nach Nautobot und von dort gerendert.
### 4.4 Subnetz-Route + vnet-Route umziehen
- Ziel: die CIDR-Route `10.40.0.0/16` zeigt auf den **neuen Mesh-Node** statt auf `bf-pve`.
- **Weg A (zuerst versuchen):** `PATCH /accounts/…/teamnet/routes/5546dcb6… {tunnel_id: <warp_connector_id>}` —
gleiche `teamnet/routes`-API wie cloudflared, per CF-Docs auch für Mesh-Nodes gültig.
- ⚠️ **Neu-Fund vnet-Gotcha:** CF-Docs sagen „**Virtual networks are only supported for cloudflared tunnels**" —
unsere Route hängt am vnet `1480b545…` (`vnetemsenhuber`, ist aber das **default**-vnet des Accounts). Möglich,
dass der PATCH auf einen `warp_connector`-Tunnel deshalb mit 4xx abgelehnt wird.
**Weg B (Fallback):** `DELETE` der alten Route `5546dcb6…` + `POST /teamnet/routes {network:"10.40.0.0/16",
tunnel_id:<mesh_node_id>}` **ohne** vnet-Angabe (landet im default). Kurze Lücke (Sekunden) dazwischen ist ok —
Reihenfolge DELETE→POST zwingend (CIDR-Kollision verhindert POST-vor-DELETE).
- **Rollback jederzeit:** PATCH (bzw. DELETE+POST) zurück auf `f7fdf3a6…` (bf-pve) — cloudflared/bf-pve bleibt die
ganze Zeit bestehen. **Wichtig fürs Remote-Arbeiten:** alle CF-API-Writes laufen über die öffentliche API
(api.cloudflare.com), NICHT über WARP → Rollback funktioniert auch, wenn der private Pfad gerade tot ist.
### 4.5 Verifikation (Abnahme)
- MacBook, WARP **connected**: `ping 10.40.20.105`, `ping 10.40.10.1`, `ping 10.40.99.12` → **verlustfrei**
(Regressions-Gegenprobe zu heute: fm 36 % / bf 100 %).
- TCP-Gegenprobe inkl. NAS-macvlan-IP direkt: `ssh 10.40.20.167`, `https://10.40.20.100:8006`, `10.40.20.157:3000`
(gitea) mehrfach → Beweis „keine Blindheit".
- FQDN-Smoke (Stichprobe gitea/hass/pve über `*.emsenhuber.net`) → der cloudflared-Public-Teil (3-Tunnel-HA) blieb unberührt.
## 5. Danach (Cleanup, teils ins „vertagt"-Gespräch)
- Nach Mesh-Beweis: `warp-routing` auf bf-nas/bf-pve/bf-mm ggf. auf `false` (Hygiene — Public-Tunnel bedienen keine
privaten Netze mehr). CF-vnet-Route-Historie bereinigen.
- **Vertagt (User „müssen wir noch reden, hat Zeit"):** 4 tote Tunnel (TeilzeitCharlie/coolify/johnny2/openclaw),
Ghost-Connector-Session bf-pve (arm64-Leiche vom 12.07.), `gateway-dw`-CNAME.
## 6. Risiken / Fallbacks
- Einziger Eingriff mit Nutzer-Impact = vnet-Route-Umzug → Rollback = 1 PATCH zurück auf bf-pve.
- CF-Prerequisites sind account-weit (Unique-IP-ReIP, Split-Tunnel) → 1× kurzer WARP-Reconnect; alle reversibel.
- Plan-Gate: wenn `warp_connector`-Create scheitert → LXC-auf-pve-WARP-Connector als Fallback (TUN-Passthrough
`c 10:200 rwm`, `warp-cli connector new`, ap1-Static-Route `100.96.0.0/12`→LXC).
- ax³/ZeroTier/Tailscale = separate Eval-Fußnoten, nicht Teil dieses Fixes.