TP16.1 4.1-4.4 durch: Mesh-Tunnel steht auf crs326-core

Prereqs: use_zt_virtual_ip=true, Device-Profil 'Cloudflare Mesh', Split-Tunnel
auf beiden Profilen (100.64/10 -> /11 + 100.112/12).
Geraet: cf-mesh, 100.96.0.6/32, Peer 162.159.193.2:2408, Handshake steht,
Route 100.96.0.0/12 aktiv. Backup vorher (Auflage #4).
4.4 Weg A hat funktioniert - vnet-Gotcha trat NICHT ein.

Zwei Werkzeug-Fallen dokumentiert: MikroMCP manage_wireguard_interface kann
keinen Privkey annehmen (CF liefert ihn aber), und manage_wireguard_peer
verarbeitet endpoint 'IP:Port' falsch (endpoint-port=0 -> kein Handshake).

Offen: Dashboard-only-Schalter 'Allow all Cloudflare One traffic to reach
enrolled devices' + Ping-Abnahme 4.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 12:08:36 +02:00
parent 7388cc8388
commit 44c1fbcca8
2 changed files with 89 additions and 4 deletions

View File

@@ -481,7 +481,9 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
### TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` ▸ 🟡 **§4.0 DURCH, läuft — §4.1 user-gated (2026-08-17)** — `docs/tp16.1-warp-mesh-native-crs326.md` ### TP16.1 — Private Access via native Cloudflare Mesh (WireGuard) auf `crs326-core` ▸ 🟡 **§4.0 DURCH, läuft — §4.1 user-gated (2026-08-17)** — `docs/tp16.1-warp-mesh-native-crs326.md`
- ✅ **§4.0 erledigt 2026-08-17: Mesh-Node angelegt.** `bf-core-mesh` = **`d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env` (`CF_MESH_REGISTER_TOKEN`, gitignored). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.** - ✅ **§4.0 erledigt 2026-08-17: Mesh-Node angelegt.** `bf-core-mesh` = **`d1da7c8a-2736-4cbb-8af1-32db5393fb9c`**, Register-Token in `mcp/cloudflare/.env` (`CF_MESH_REGISTER_TOKEN`, gitignored). ➡️ **Die seit 2026-07-17 offene Free-Plan-Frage ist beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh — der LXC-Fallback ist vom Tisch.**
- 🔑 **Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe).** Der alte User-Token hatte **zwei** fehlende Permissions (§4.0 *und* §4.1 — `devices/settings` war schon lesend `403`). Ersetzt durch einen **account-owned** Token (`9984078812bc…`, ⏰ **gültig bis 2027-01-09**). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an `/accounts/{id}/tokens/verify` — am alten `/user/tokens/verify` melden sie `1000 "Invalid API Token"`, **während alle echten Endpoints 200 liefern**; (2) Dashboard-Fehler `"Account tag in access policy must match tag in request uri"` = eine **Zone**-Permission (`DNS`) steckt in der Policy-Zeile mit **Account**-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in `docs/tp16.1-…md` §4.0. **Merke: Dashboard sagt „Edit", API sagt „Write".** - 🔑 **Der Weg dahin war ein Token-Problem, kein Plan-Problem (kostete 2 Anläufe).** Der alte User-Token hatte **zwei** fehlende Permissions (§4.0 *und* §4.1 — `devices/settings` war schon lesend `403`). Ersetzt durch einen **account-owned** Token (`9984078812bc…`, ⏰ **gültig bis 2027-01-09**). Zwei Fallen, beide sehen aus wie „Token kaputt": (1) Account-Tokens verifizieren an `/accounts/{id}/tokens/verify` — am alten `/user/tokens/verify` melden sie `1000 "Invalid API Token"`, **während alle echten Endpoints 200 liefern**; (2) Dashboard-Fehler `"Account tag in access policy must match tag in request uri"` = eine **Zone**-Permission (`DNS`) steckt in der Policy-Zeile mit **Account**-Ressourcen → braucht eine eigene Zeile. Verifiziertes Soll-Set + Permission-IDs in `docs/tp16.1-…md` §4.0. **Merke: Dashboard sagt „Edit", API sagt „Write".**
- ⏭️ **Als Nächstes §4.1** (Unique Device IP, Device-Profil „Cloudflare Mesh", Split-Tunnel `100.64.0.0/10` → `100.64.0.0/11` + `100.112.0.0/12`). Account-weit → **1× kurzer WARP-Reconnect auf allen eingebuchten Geräten**, daher user-gated. **Am `crs326-core` ist bis hierher nichts angefasst** — Ausnahme #4 noch nicht gezogen. - ✅ **§4.1–§4.4 durchgezogen 2026-08-17 (User-Freigabe „jetzt durchziehen").** Prereqs: `use_zt_virtual_ip=true`, Device-Profil „Cloudflare Mesh" (`4d3c0f57…`), Split-Tunnel auf **beiden** Profilen korrigiert (`100.64.0.0/10` → `/11` + `100.112.0.0/12`). Gerät: `cf-mesh` auf `crs326-core`, Mesh-IP **`100.96.0.6/32`**, Peer `162.159.193.2:2408`, **Handshake steht**, Route `100.96.0.0/12 → cf-mesh` aktiv; Backup `pre-tp16.1-mesh-20260817.backup` vorher (Auflage #4 ✅). CF meldet Node **healthy**. **§4.4: Weg A hat funktioniert — der befürchtete vnet-Gotcha trat NICHT ein**, `10.40.0.0/16` zeigt jetzt auf `bf-core-mesh` statt `bf-pve` (Rollback = 1 PATCH zurück, `bf-pve` unangetastet).
- ⚠️ **Zwei Werkzeug-Fallen, real getroffen (gehören in jede künftige WG-Arbeit):** (1) **MikroMCPs `manage_wireguard_interface` kann grundsätzlich keinen privaten Schlüssel annehmen** — RouterOS erzeugt ihn selbst; bei CF Mesh kommt er aber von CF → Key per SSH mit Interpolation aus der gitignoreten `.env` setzen, nicht über das Tool. (2) **`manage_wireguard_peer` schreibt `endpoint: "IP:Port"` komplett in `endpoint-address` und lässt `endpoint-port=0`** → **kein Handshake**, sieht aus wie ein Firewall-Problem, ist ein Parameter-Bug. Nach jedem Peer-Add `print detail` gegenlesen. Auch `persistent-keepalive` fehlt dem Tool.
- 🔴 **Offen, beide user-gated:** (1) **„Allow all Cloudflare One traffic to reach enrolled devices"** — laut CF-Docs Mesh-Prerequisite, aber **Dashboard-only** (kein API-Feld, in Spec *und* Profil-GET gegengeprüft) und entgegen unserem Runbook eine **Profil**- statt Account-Einstellung. (2) **§4.5 Ping-Abnahme** mit verbundenem WARP. Danach fällig: Auflage #4 — Ist-Stand als intended config zurück nach Nautobot.
- ✅ **Gate erfüllt (2026-08-16):** die Bedingung „gated bis TP21 den CRS326 abdeckt" ist eingelöst — `crs326-core` ist seit 2026-08-08 der produktive L3-Router und seit 2026-08-13 in Nautobot adoptiert (68/68 compliant). Runbook §3 neu gemessen, §4.3 von Platzhalter auf **echte Werte** gezogen. - ✅ **Gate erfüllt (2026-08-16):** die Bedingung „gated bis TP21 den CRS326 abdeckt" ist eingelöst — `crs326-core` ist seit 2026-08-08 der produktive L3-Router und seit 2026-08-13 in Nautobot adoptiert (68/68 compliant). Runbook §3 neu gemessen, §4.3 von Platzhalter auf **echte Werte** gezogen.
- 🧭 **Erwartung geraderücken (wichtig, weil leicht zu verwechseln): das Mesh ersetzt die CF-Tunnel NICHT.** TP16.1 löst ausschließlich den **privaten** Zugang (unterwegs → `10.40.0.0/16` per IP, **inklusive ICMP**) und tauscht dort den cloudflared-QUIC-Proxy gegen natives WireGuard auf dem Core. Die **öffentlichen FQDNs bleiben** auf dem 3-Tunnel-HA aus TP16. Beide Stränge laufen nebeneinander — die HA-Degradierung aus TP16 (2026-08-16) wird davon **nicht** geheilt und bleibt ein eigener offener Punkt. - 🧭 **Erwartung geraderücken (wichtig, weil leicht zu verwechseln): das Mesh ersetzt die CF-Tunnel NICHT.** TP16.1 löst ausschließlich den **privaten** Zugang (unterwegs → `10.40.0.0/16` per IP, **inklusive ICMP**) und tauscht dort den cloudflared-QUIC-Proxy gegen natives WireGuard auf dem Core. Die **öffentlichen FQDNs bleiben** auf dem 3-Tunnel-HA aus TP16. Beide Stränge laufen nebeneinander — die HA-Degradierung aus TP16 (2026-08-16) wird davon **nicht** geheilt und bleibt ein eigener offener Punkt.
- 📌 **Live-Befunde 2026-08-16 (read-only erhoben, kein Eingriff):** - 📌 **Live-Befunde 2026-08-16 (read-only erhoben, kein Eingriff):**

View File

@@ -1,6 +1,13 @@
# TP16.1 — Private Access via native Cloudflare Mesh (WARP Connector) auf dem CRS326 Core-Switch # 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: > **🟡 Status 2026-08-17: §4.0–§4.4 DURCH, Mesh-Tunnel steht — Abnahme (§4.5) offen.** `cf-mesh` auf
> `crs326-core` handshaked mit dem CF-PoP, CF meldet den Node **healthy**, und die Route `10.40.0.0/16` zeigt
> bereits auf ihn statt auf `bf-pve`. **Zwei Dinge fehlen noch, beide erfordern dich:** (1) der Dashboard-only-
> Schalter „Allow all Cloudflare One traffic to reach enrolled devices" (§4.1 Schritt 2), (2) die Ping-Abnahme mit
> verbundenem WARP (§4.5). Danach ist die Auflage aus Ausnahme #4 fällig: Ist-Stand als intended config zurück
> nach Nautobot.
>
> *Vorstufe:* **§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` > **`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 > (`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.** > beantwortet: der Free-ZT-Plan erlaubt Cloudflare Mesh. Der LXC-Fallback (§6) ist vom Tisch.**
@@ -191,6 +198,32 @@ tunnel-spezifischen Doc-Seiten, die noch die alten Namen zeigen). Im Token-Edito
**„Cloudflare One Connector: WARP"** suchen (nicht „Tunnel"), **Edit** wählen. Für §4.4 zusätzlich **„Cloudflare One Connector: WARP"** suchen (nicht „Tunnel"), **Edit** wählen. Für §4.4 zusätzlich
**„Cloudflare One Networks"** → Edit (Routes/vnets). **„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" | 🔴 **OFFEN — Dashboard-only, kein API-Feld** (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 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).
> 📎 **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) ### 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 → 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"). Devices → Management → „Assign a unique IP address to each device").
@@ -211,7 +244,21 @@ tunnel-spezifischen Doc-Seiten, die noch die alten Namen zeigen). Im Token-Edito
nativ (kein Gateway-ICMP-Proxy im Pfad). **Trotzdem: nach dem Scharfschalten Ping gegengeprüft** (§4.5) — falls wider nativ (kein Gateway-ICMP-Proxy im Pfad). **Trotzdem: nach dem Scharfschalten Ping gegengeprüft** (§4.5) — falls wider
Erwarten doch tot, hier zuerst schauen. Erwarten doch tot, hier zuerst schauen.
### 4.2 WireGuard-Config extrahieren (einmalig, ephemer) </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): Auf macmini oder pve (Docker vorhanden):
``` ```
docker run --rm -v $(pwd):/app/output ghcr.io/animmouse/wgcf-connector <REGISTER_TOKEN> docker run --rm -v $(pwd):/app/output ghcr.io/animmouse/wgcf-connector <REGISTER_TOKEN>
@@ -222,7 +269,41 @@ ignorieren, wir bauen v4-only), **DNS** (CF-Resolver — ignorieren, ap1 hat eig
Registriert den Node via CF-API (macht Netz-Call), läuft danach nie wieder. **PrivateKey nach `mcp/cloudflare/.env` Registriert den Node via CF-API (macht Netz-Call), läuft danach nie wieder. **PrivateKey nach `mcp/cloudflare/.env`
o. `~/.secrets`** (nicht committen). o. `~/.secrets`** (nicht committen).
### 4.3 `crs326-core` nativ konfigurieren ✅ ausführungsreif (Werte finalisiert 2026-08-16) ### 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 > 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 > **`crs326-core`** (Mgmt `10.40.99.1`, MikroMCP-routerId `crs326-core`). Offen bleibt nur, was erst zur Laufzeit
@@ -259,6 +340,8 @@ o. `~/.secrets`** (nicht committen).
Ist-Stand als intended config zurück nach Nautobot. **Scope:** nur der Mesh-Bring-up auf `crs326-core`, nicht 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). 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? #### 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 Die Ausnahme soll ein **einmaliger Bootstrap** bleiben, kein Dauerzustand. Der Denkfehler, den man vermeiden