Files
bf-infra/docs/tp16.1-warp-mesh-native-crs326.md
Christoph Emsenhuber c9a413858b TP31 + TP32 angelegt; Startreihenfolge auf pve repariert
TP31 -- Caddy als Reverse Proxy fuer innen UND aussen, cloudflared von
sechs Containern auf einen. Anlass war Christophs Einwand, er habe den
Core unter der Praemisse gekauft, dass auch die Public-Apps darueber
laufen. Aktenlage geprueft: eine Zusage gibt es nicht, aber TP16.1
begruendet die Auswahl mit seinem Prinzip 'Router = einziger SPOF' --
und niemand hat je geprueft, ob der oeffentliche Strang ohne sechs
Container auskommt. Keine Taeuschung, eine ungepruefte Annahme.

- dst-nat-Variante geprueft und verworfen (ginge, Free-Tarif reicht),
  weil Christophs Tunnel-Variante drei Risiken spart: keine angreifbare
  Origin-IP, keine CF-IP-Allowlist, kein DDNS als Voraussetzung
- cloudflared auf ap1/ap2: live gemessen arm64, 95 MB Flash frei,
  690 MB RAM frei. Der Core kann es nie (arm 32-bit, 2,9 MB frei)
- Kriterium fuer Infrastruktur-Container: was sich selten aendert und
  wenig schreibt. Caddy nein (Config aendert staendig), AGH nein
  (Query-Log frisst NAND)

TP32 -- VMID-Systematik (Hunderter = Klasse, gemeinsamer Raum fuer VMs
und LXCs) + letztes Oktett je /24, uebertragbar auf andere Standorte.
🔮 Dabei gefunden und SOFORT REPARIERT: die Startreihenfolge war falsch.
Proxmox startet erst Gaeste mit explizitem startup order=, dann den Rest
nach VMID -- drei Gaeste hatten einen, die DBs nicht. Home Assistant
startete damit vor MariaDB, MQTT und z2m25. Jetzt fuenf Stufen
(Core/Daten/Platform/Broker/Apps). Erkenntnis: VMID = Klassifikation,
startup order = Abhaengigkeit -- die Umnummerierung muss den Startlauf
gar nicht reparieren.

Doku-Korrekturen aus den Messungen:
- CLAUDE.md: 'Container NICHT auf dem Router' war nie nachgemessen
- tp16.1: 'Connector als Container ❌' galt dem TUN-Bedarf des
  WARP-Connectors; cloudflared braucht kein TUN
- cf-tunnel-mapping Paragraph 16: die macvlan-Blindheit war mit einem
  Shim behebbar -- der Workaround war groesser als der Fix

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 01:03:54 +02:00

552 lines
44 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
> # ✅ **TP16.1 ABGESCHLOSSEN — 2026-08-17**
> Der private Zugang lĂ€uft ĂŒber **natives WireGuard auf `crs326-core`** statt ĂŒber den cloudflared-QUIC-Proxy.
> **ICMP ist damit repariert: 0 % Loss auf alle BF-VLANs (vorher 100 %)** — genau das Symptom aus dem Incident
> vom 2026-07-16, das dieses TP ausgelöst hat. Die öffentlichen FQDNs blieben unberĂŒhrt auf dem 3-Tunnel-HA
> (TP16). Auflage aus Ausnahme #4 erfĂŒllt: **68/68 compliant, keine Drifts.**
>
> **Die drei Erkenntnisse, die Zeit gekostet haben und beim nÀchsten Mal Zeit sparen:**
> 1. Der Blocker war nie der Free-Plan, sondern der **API-Token** (§4.0) — Mesh ist im Free-ZT-Plan erlaubt.
> 2. **`/ip service address=10.40.0.0/16` sperrt das Router-Mgmt aus dem Mesh aus** (§4.5) — Ping geht,
> Management nicht. FĂŒr `branch`/`ap1`/`ap2` noch offen.
> 3. **MikroMCP taugt fĂŒr WireGuard nur halb** (§4.3): kein Privkey möglich, und `endpoint="IP:Port"` wird
> falsch zerlegt → kein Handshake.
>
> ✅ **Nachlauf am selben Tag, ebenfalls abgeschlossen:** die **Mgmt-ACL wurde via Golden Config auf alle vier
> GerĂ€te ausgerollt** (neue Compliance-Feature `ip-service`, **72/72 compliant**) — s. §4.7. Und der
> Dashboard-Schalter ist **recherchiert und bewusst AUS** — s. §4.1 Schritt 2. **TP16.1 hat damit keine
> offenen Punkte mehr**, außer den weiterhin getrennt gefĂŒhrten Fremdthemen (TP16-HA, TP5-Firewall). `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 — ✅ **ERLEDIGT 2026-08-17, bis auf einen Dashboard-Klick**
| Schritt | Stand |
|---|---|
| 1. Unique Device IP | ✅ `use_zt_virtual_ip: true` gesetzt (PATCH `devices/settings`) |
| 2. „Allow all Cloudflare One traffic to reach enrolled devices" | ✅ **entschieden: bleibt AUS** — nicht nötig (s.u.) |
| 3. Device-Profil „Cloudflare Mesh" | ✅ `4d3c0f57-a717-49ac-ab92-b420e33e6380`, match `identity.email == "warp_connector@emsenhuber.cloudflareaccess.com"`, `tunnel_protocol=wireguard`, precedence 100 |
| 4. Split-Tunnel-Exclude | ✅ auf **beiden** Profilen: `100.64.0.0/10` raus, `100.64.0.0/11` + `100.112.0.0/12` rein (14→15 bzw. 16→15 EintrĂ€ge) |
| 5. ICMP-Proxy | ✅ war schon an, nichts zu tun (s. §4.1.5) |
> ✅ **Schritt 2 — recherchiert 2026-08-17, Entscheid: bleibt AUS.** Die CF-**Settings-Referenz** fĂŒhrt ihn unter
> „Global device client settings" (also **global**, nicht per Profil — die Mesh-Get-Started-Seite behauptet das
> Gegenteil, **die Docs widersprechen sich**). VerfĂŒgbar in **allen PlĂ€nen**, alle OS, Client-Mode „Traffic and
> DNS". **Was er tut:** er lĂ€sst Traffic aus Mesh/WAN **zu den eingebuchten GerĂ€ten hin** — Zugriff auf
> *Device-IPs*, also die **Client↔Client**-Richtung. **Unser Anwendungsfall ist die andere Richtung**
> (GerĂ€t → Mesh-Node → `10.40.0.0/16`), und der funktioniert bewiesenermaßen ohne ihn (§4.5). Die CF-Docs
> nennen **keinen** MenĂŒpfad, und es gibt **kein API-Feld** (Spec, `devices/settings`-PATCH und Profil-GET
> gegengeprĂŒft). **Nach User-Vorgabe „im Zweifel ohne Schalter" nicht gesetzt.** Erst dann relevant, wenn
> Zugriff **auf ein EndgerÀt** statt auf das Netz gebraucht wird.
<details><summary>UrsprĂŒngliche Handlungsanweisung (vor der Recherche)</summary>
> 🔮 **Schritt 2 ist der einzige verbliebene Handgriff und NUR im Dashboard machbar.** Weder
> `PATCH /devices/settings` noch `POST /devices/policy` kennen ein entsprechendes Feld — im OpenAPI-Spec **und** in
> der GET-Antwort des Default-Profils taucht es nicht auf (gegengeprĂŒft 2026-08-17). Es verhĂ€lt sich damit genau
> wie das ICMP-Flag: Dashboard-only. **CF-Docs fĂŒhren es als Mesh-Prerequisite („Mesh connectivity")**, es ist also
> nicht optional. Pfad: **Team & Resources → Devices → Device settings → Profil → „Allow all Cloudflare One traffic
> to reach enrolled devices"** einschalten. ⚠ Laut CF-Doc ist das eine **Profil**-Einstellung — unser Runbook
> fĂŒhrte sie fĂ€lschlich als Account-Einstellung („gleiche Seite" wie Unique Device IP).
</details>
### 4.7 Mgmt-ACL via Golden Config auf alle vier GerĂ€te — ✅ **ERLEDIGT 2026-08-17, `72/72 compliant`**
Der `/ip service`-Fix aus §4.5 war zunÀchst nur am Core und **direkt** gesetzt. Nachgezogen als sauberer
GC-Rollout auf **alle vier** GerÀte.
**Der Befund war das Gegenteil der Erwartung.** §4.5 schloss mit „fĂŒr `branch`/`ap1`/`ap2` noch offen — die
haben eigene `/ip service`-BeschrĂ€nkungen". **Falsch:** sie hatten **gar keine** — `ssh`, `www`, `winbox`,
`api`, `ftp`, `telnet` alle aktiv und **ohne** Address-BeschrÀnkung. Die dynamischen EintrÀge zeigten sogar
`REMOTE 100.96.0.1`, waren also lÀngst per Mesh erreichbar. Und **`winbox` war auf allen vier offen**, auch am
Core. Damit wurde aus dem erwarteten Erreichbarkeits-Fix eine **HĂ€rtung**.
| | Sollzustand (einheitlich, im Template) |
|---|---|
| `ssh` · `www` · `winbox` · `api` | `address=10.40.0.0/16,100.96.0.0/12` |
| `ftp` · `telnet` · `api-ssl` | `disabled=yes` |
- **Neue Compliance-Feature `ip-service`** (`match_config=/ip service`, `config_type=cli`,
`config_ordered=false`) → 17→18 Regeln, **68→72 Checks**. Ohne die Regel stĂŒnde die ACL zwar im Template,
wĂŒrde aber **nie verglichen** — der Rollout wĂ€re nicht durchsetzbar.
- **Ablauf regelkonform:** `/system backup` auf allen Zielen → `--job mgmt-acl` (Dry-Run) →
`--apply --no-deploy` → **gestaffelter** Deploy: erst `ap2`, am GerĂ€t gegengeprĂŒft, dann `ap1`/`branch`/`core`
→ GerĂ€te-Gegenprobe → `--refresh-compliance`.
- **Kein Lockout-Risiko:** der Deploy lĂ€uft von Nautobot (`10.40.20.30`) aus, also aus `10.40.0.0/16` — in jeder
gesetzten ACL enthalten. RouterOS wendet `address=` ohnehin erst auf **neue** Verbindungen an.
- ⚠ **Scope-Erweiterung, bewusst:** `ftp`/`telnet`/`api-ssl` auf branch/ap1/ap2 **abgeschaltet**. Die
Alternative wĂ€re gewesen, „telnet aktiv" als **Sollzustand** in die SoT zu schreiben. Nichts hier nutzt sie
(der ALLNET-Telnet-Runner ist Archiv, das GerÀt ausgebaut).
- 📌 **Schreibweise:** `/ip service set ssh 
` mit dem **Dienstnamen**, NICHT `[find name=ssh]` — letzteres
trifft auch den dynamischen Verbindungs-Eintrag → `failure: this is configured elsewhere`.
- đŸ§Ș **Mess-Falle am Rande:** `[/ip service get 
 address]` in einem `:put`-Ausdruck verketten erzeugt bei
**zwei** ACL-EintrĂ€gen das **Kreuzprodukt** (16 Zeilen statt 1) — `address` ist eine Liste. Zum PrĂŒfen
`/ip service print detail` nehmen, nicht String-Verkettung.
Job liegt als `mgmt-acl` in `nautobot/gc-configplan.py` (idempotent, wiederholbar).
> 📎 **Nachtrag zu Schritt 3/4 aus den CF-Docs (gelesen 2026-08-17):** CF empfiehlt fĂŒr Mesh-*Nodes* eigentlich ein
> Profil im **Include**-Modus mit `100.96.0.12` — das gilt fĂŒr Nodes, die den **Cloudflare One Client** fahren.
> Unser Node ist **natives RouterOS-WireGuard** und konsumiert ĂŒberhaupt kein Device-Profil; fĂŒr ihn zĂ€hlt
> ausschließlich die Routing-Config aus §4.3. Das Profil legen wir trotzdem an (Guide-konform, schadet nicht).
> Relevant ist die Exclude-Korrektur fĂŒr die **Client**-GerĂ€te (MacBook/iPhone) — und die ist gesetzt.
<details><summary>UrsprĂŒngliche Schrittliste</summary>
### 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.
</details>
### 4.4 Route-Umzug — ✅ **ERLEDIGT 2026-08-17, Weg A hat funktioniert**
`PATCH /teamnet/routes/5546dcb6-5901-4da6-8d91-b17f94703f71 {tunnel_id: d1da7c8a-
}` → `success: true`.
**Der befĂŒrchtete vnet-Gotcha ist NICHT eingetreten** — CF hat den PATCH auf einen `warp_connector`-Tunnel
anstandslos angenommen, obwohl die Route am vnet `1480b545
` hĂ€ngt. Weg B (DELETE+POST) war nicht nötig und
bleibt nur als Fallback dokumentiert. `10.40.0.0/16` zeigt jetzt auf **`bf-core-mesh`** statt auf `bf-pve`.
**Rollback** unverĂ€ndert 1 PATCH zurĂŒck auf `f7fdf3a6
` — `bf-pve` lĂ€uft weiter, nichts gelöscht.
### 4.2 WireGuard-Config extrahieren (einmalig, ephemer) — ✅ erledigt 2026-08-17
> **As-built:** gelaufen in **LXC 400 auf pve** (x86_64; der macmini ist arm64 — bei den cloudflared-Connectoren
> hatten wir dort schon eine arm64-Leiche). Ergebnis s. §4.3. Der `.conf` mit dem PrivateKey wurde nach dem
> Auslesen auf dem LXC **geshreddert** (`shred -u`) — der SchlĂŒssel liegt jetzt nur noch in
> `mcp/cloudflare/.env` (`CF_MESH_PRIVKEY`, gitignored, `chmod 600`) und am GerÀt.
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 — ✅ **AS-BUILT 2026-08-17**
**Ist-Stand am GerÀt (verifiziert):**
| | |
|---|---|
| Interface | `cf-mesh`, MTU **1420**, `running`, listen-port 13279 (auto) |
| PublicKey (GerÀt) | `vGkiAuQt8iHGtNkllTxMxjlgQhN9RtSTjC8qHwnccAw=` |
| Mesh-IP | **`100.96.0.6/32`** (v4-only, die mitgelieferte v6 `2606:4700:cf1:1000::6/128` bewusst ignoriert) |
| Peer | `bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=` @ **`162.159.193.2:2408`**, allowed `0.0.0.0/0`, keepalive 25s |
| Handshake | ✅ steht (`last-handshake` < 10s, rx/tx laufen) |
| Route | `100.96.0.0/12 → cf-mesh`, Flags **`A s`** (active/static) |
| Registrierung | `reg_id 01a00f2d-2316-12f6-90f6-c3f344469fb3`, CF-Node-Status **healthy**, 1 Connection |
| Backup vor dem Eingriff | `pre-tp16.1-mesh-20260817.backup` (Auflage Ausnahme #4 ✅) |
**Endpoint-Hinweis erledigt:** die Config lieferte direkt `162.159.193.2` — die im Guide beschriebene
Substitution `162.159.192.x → 162.159.193.x` war **nicht nötig**.
> ⚠ **Zwei Werkzeug-Fallen, beide real getroffen — fĂŒr die nĂ€chste WG-Arbeit wichtig:**
>
> 1. **MikroMCPs `manage_wireguard_interface` kann prinzipiell KEINEN privaten SchlĂŒssel annehmen**
> („RouterOS generates the private key on create — it is never passed in"). Bei CF Mesh kommt der Key aber
> **von CF** — das Tool ist fĂŒr diesen Fall also strukturell ungeeignet. Weg: `sshpass`+SSH mit
> `$CF_MESH_PRIVKEY` aus der gitignoreten `.env` **interpoliert**, damit der SchlĂŒssel weder durch den
> Agent-Kontext noch ins MCP-Journal wandert; Fehlerausgabe vorher durch `sed` maskieren (Memory
> `feedback_redact_router_passwords_in_tracebacks`). Peer/IP/Route liefen danach regulĂ€r ĂŒber
> `plan_changes` → `apply_plan`.
> 2. **`manage_wireguard_peer` verarbeitet `endpoint: "IP:Port"` falsch:** es schreibt den *ganzen* String nach
> `endpoint-address` und lĂ€sst `endpoint-port=0` → **kein Handshake**, rx/tx bleiben 0. Das sieht aus wie ein
> Netz-/Firewall-Problem, ist aber ein Parameter-Bug. Fix: `/interface/wireguard/peers/set [find 
]
> endpoint-address=<IP> endpoint-port=<Port>`. **Nach jedem Peer-Add per MikroMCP `print detail` gegenlesen.**
> 3. Ebenso ohne Parameter im Tool: **`persistent-keepalive`** → separat per `run_command` setzen.
<details><summary>UrsprĂŒnglicher Plan (Stand 2026-08-16)</summary>
> 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).
</details>
#### 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 — ✅ **BESTANDEN 2026-08-17** (MacBook am iPhone-Hotspot, WARP connected)
**Ping — der eigentliche Zweck des TPs. Vorher 100 % Loss, jetzt:**
| Ziel | VLAN | Loss | avg RTT |
|---|---|---|---|
| `10.40.10.1` | 10 | **0 %** | 47 ms |
| `10.40.20.105` (HA) | 20 | **0 %** | 56 ms |
| `10.40.99.12` (ap2) | 99 | **0 %** | 57 ms |
| `10.40.20.10` (NAS) | 20 | **0 %** | 63 ms |
| `10.40.99.1` (core) | 99 | **0 %** | 58 ms |
**TCP-Gegenprobe (echter Datenfluss, nicht nur Connect):** `https://10.40.20.100:8006` → 200 ·
`http://10.40.20.30:8080` → 302 · `ssh macmini` → ok. **FQDN-Smoke** (gitea/hass/pve/nautobot/vw ĂŒber
`*.emsenhuber.net`): 200/200/302/302/200 in 0,2–0,3 s → **der cloudflared-Public-Teil blieb unberĂŒhrt.**
*(`10.40.20.10:5000/5001` antwortet nicht — die DSM-Web-Ports lauschen dort schlicht nicht; SSH und 443 auf
demselben Host sind offen. Kein Mesh-Befund.)*
> 🔮🔮 **DIE FALLE DIESES TPs — `/ip service` sperrt das Router-Management aus dem Mesh aus.**
>
> Unmittelbar nach dem WARP-Connect war **`crs326-core` mgmt-seitig tot**: MikroMCP brach mit
> `Connection lost before handshake` ab, SSH mit `kex_exchange_identification: Connection reset`, REST mit
> **HTTP 502** — **wĂ€hrend Ping zu genau derselben IP verlustfrei lief** und alle Hosts *hinter* dem Core
> (pve, Nautobot, macmini) normal antworteten.
>
> **Ursache:** auf dem Core steht `/ip service set ssh,www address=10.40.0.0/16`. Der Mesh-Client kommt aus
> **`100.96.0.0/12`** und fĂ€llt aus der ACL. ICMP kennt keine Service-ACL → geht. Und `nc -z` meldete
> „offen", weil der **CF-Gateway-Proxy** den TCP-Handshake terminiert, bevor das Origin ĂŒberhaupt antwortet —
> ein TCP-Connect ist ĂŒber WARP also **kein** Reachability-Beweis.
>
> **Fix:** `/ip service set [find name=ssh] address=10.40.0.0/16,100.96.0.0/12` (analog `www`).
> Danach SSH und REST ĂŒber den Mesh sauber. **Merkregel: wer per Mesh ans Netz kommt, ist noch lange nicht
> am Router.** Jede Mgmt-ACL, die auf `10.40.0.0/16` steht, muss die Mesh-Range mitnehmen —
> fĂŒr `crs326-branch`/`ap1`/`ap2` ist das noch **offen** (die haben eigene `/ip service`-BeschrĂ€nkungen).
>
> ⚠ Zwei Fehlspuren, die ich unterwegs verfolgt habe und die man sich sparen kann: (1) **MTU** — große Pings
> ab ~1350 B scheitern, das ist aber das **WARP-Client-Interface** (`utun` MTU 1380), nicht `cf-mesh`;
> normaler TCP-Verkehr ist davon nicht betroffen. (2) **SSH-Session-Erschöpfung** — naheliegend bei
> `Connection reset`, war es aber nicht. Der Test, der es entschied: **Banner-Grab vom macmini aus**
> (`nc 10.40.99.1 22` → `SSH-2.0-ROSSSH`) — von innen gesund, also Pfad/ACL statt GerĂ€t.
>
> â„č Nebenbefund beim Setzen: `/ip service set [find name=ssh] 
` trifft **auch den dynamischen
> Verbindungs-Eintrag** (`D c ssh 
 local 10.40.99.1`) und quittiert mit `failure: this is configured
> elsewhere`. **Harmlos** — der statische Eintrag wird trotzdem gesetzt. Nicht als Fehlschlag werten.
### 4.6 Auflage Ausnahme #4 — ✅ **ERLEDIGT 2026-08-17: Ist-Stand ist intended config, 68/68 compliant**
Ablauf: `--refresh-compliance` zeigte zuerst **65/68** mit drei `extra`-Drifts (interface-wireguard,
ip-address, ip-route) — exakt die Mesh-Zeilen. Die Zeilen **byte-genau aus dem Nautobot-Backup** (nicht aus
dem Runbook!) ins Jinja-Template ĂŒbernommen, ins GC-Repo `bf-golden-config` gepusht, Repo-Sync, erneut
compliance: **68/68, keine Drifts.**
- Der Privkey bleibt draußen: das Template emittiert `private-key="<<REDACTED>>"` — byte-identisch zur
`ConfigReplace`-Maske aus TP22 Schritt 0, genau wie bei `wg-hub`. Secret-Gegenprobe vor jedem Push gelaufen.
- ⚠ **Gotcha beim Push:** Nautobot schreibt Backups *und* intended configs in **dasselbe** Repo → der zweite
Push wurde `rejected (fetch first)`, weil die Compliance-LĂ€ufe dazwischen committen. `git pull --rebase`
vor jedem Push einplanen.
- `/ip service` steht **nicht** im Template → die ACL-Erweiterung erzeugt keine Drift, ist damit aber auch
**nicht** in der SoT abgebildet. Bewusst so belassen; gehört zu TP5, wenn die Mgmt-Ebene modelliert wird.
<details><summary>UrsprĂŒngliche Abnahme-Kriterien</summary>
### 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.
</details>
## 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.
---
## Nachtrag 2026-08-25 — „Connector als RouterOS-Container ❌" galt dem TUN-Bedarf, nicht jedem Container
§2 schließt „Connector als RouterOS-Container" aus, weil **RouterOS-7-Container kein `/dev/net/tun`
durchreichen** und der WARP-Connector-Daemon ein TUN-Device braucht. **Das stimmt weiterhin — fĂŒr den
WARP-Connector.**
🔮 **Es wurde damals aber nicht getrennt, und die Verallgemeinerung hat sich festgesetzt:**
**`cloudflared` braucht kein TUN.** Es ist ein Userspace-HTTP/QUIC-Proxy und lÀuft in einem
RouterOS-Container problemlos — auf arm64 oder x86, `container`-Paket und `device-mode` vorausgesetzt.
**Live gemessen 2026-08-25:** ap1/ap2 sind **arm64** mit **95 MB freiem Flash** und **690 MB freiem RAM**
— genug fĂŒr einen Connector. `crs326-core` ist **arm 32-bit mit 2,9 MB frei von 16 MB** und kommt fĂŒr
Container **nie** in Frage.
⇒ Die Zeile „verletzt *Router = einziger SPOF, keine AbhĂ€ngigkeit zu anderen Rechnern*" trug fĂŒr den
privaten Strang — fĂŒr den **öffentlichen** hat sie nie jemand geprĂŒft. Genau das holt **TP31** nach.