diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 07e25a2..1373dee 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -486,8 +486,12 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". - ⚠️ **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. - ✅ **§4.5 ABNAHME BESTANDEN 2026-08-17** (MacBook am Hotspot, WARP connected): **Ping 0 % Loss** auf `10.40.10.1` / `10.40.20.105` / `10.40.99.12` / `10.40.20.10` / `10.40.99.1` (47–63 ms) — **vorher 100 %**. TCP mit echtem Datenfluss (pve 8006 → 200, Nautobot → 302, `ssh macmini` → ok). FQDN-Smoke gitea/hass/pve/nautobot/vw → 200/200/302/302/200 ⇒ **cloudflared-Public-Teil unberührt.** - ✅ **Auflage Ausnahme #4 erfüllt:** Ist-Stand als intended config zurück nach Nautobot → zuerst **65/68** (drei `extra`-Drifts = die Mesh-Zeilen), Zeilen **byte-genau aus dem Backup** ins Jinja-Template, Push nach `bf-golden-config`, Repo-Sync → **68/68, keine Drifts**. Privkey bleibt draußen (`<>`-Maske wie bei `wg-hub`). ⚠️ **Push-Gotcha:** Nautobot schreibt Backups+intended in **dasselbe** Repo → `git pull --rebase` vor jedem Push, sonst `rejected (fetch first)`. -- 🔴🔴 **DIE FALLE DIESES TPs — `/ip service` sperrt das Router-Mgmt aus dem Mesh aus.** Direkt nach dem WARP-Connect war `crs326-core` mgmt-tot: MikroMCP `Connection lost before handshake`, SSH `kex_exchange_identification: Connection reset`, REST **502** — **bei gleichzeitig verlustfreiem Ping auf dieselbe IP**, und alle Hosts *hinter* dem Core normal. Ursache: `/ip service ssh,www address=10.40.0.0/16`, der Mesh-Client kommt aus `100.96.0.0/12`. ICMP kennt keine Service-ACL. **Und `nc -z` meldet „offen", weil der CF-Gateway-Proxy den TCP-Handshake terminiert — ein TCP-Connect ist über WARP KEIN Reachability-Beweis.** Fix: `address=10.40.0.0/16,100.96.0.0/12`. **Merkregel: wer per Mesh ans Netz kommt, ist noch lange nicht am Router.** ⚠️ **Für `crs326-branch`/`ap1`/`ap2` noch OFFEN** — deren eigene `/ip service`-ACLs sind ungeprüft. Entscheidender Diagnose-Schritt war der **Banner-Grab vom macmini** (`nc 10.40.99.1 22` → `SSH-2.0-ROSSSH`): von innen gesund ⇒ Pfad/ACL, nicht Gerät. Fehlspuren, die man sich sparen kann: MTU (die großen Pings scheitern am **WARP-Client**-`utun` MTU 1380, nicht an `cf-mesh`) und SSH-Session-Erschöpfung. -- 🟡 **Einziger Restpunkt, user-gated:** Dashboard-only-Schalter **„Allow all Cloudflare One traffic to reach enrolled devices"** (kein API-Feld — in Spec *und* Profil-GET gegengeprüft; entgegen unserem Runbook eine **Profil**- statt Account-Einstellung). **Der Mesh läuft nachweislich ohne ihn**; vermutlich zählt er erst für **Client↔Client**. Setzen, wenn das gebraucht wird. +- ✅ **Mgmt-ACL via GC ausgerollt 2026-08-17 (Nachlauf, alle 4 Geräte) — `72/72 compliant`.** Neue Compliance-Feature **`ip-service`** (`match_config=/ip service`, `config_ordered=false`) → 17→18 Regeln, 68→**72 Checks**. 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`. Ablauf regelkonform: Backups auf allen Zielen → `--job mgmt-acl --apply --no-deploy` → **gestaffelter** Deploy (ap2 zuerst, am Gerät gegengeprüft, dann ap1/branch/core) → Geräte-Gegenprobe → Compliance. + - 🔎 **Der Befund war das Gegenteil der Erwartung:** ich hatte „branch/ap1/ap2 sperren den Mesh aus" befürchtet — tatsächlich hatten sie **gar keine** Address-Beschränkung (ssh/www/winbox/api/ftp/telnet **komplett offen**), und die dynamischen Einträge zeigten `REMOTE 100.96.0.1`, waren also längst per Mesh erreichbar. **`winbox` war auf ALLEN vier unbeschränkt**, auch am Core. Aus dem Erreichbarkeits-Fix wurde damit eine Härtung. + - ⚠️ **Scope-Erweiterung, bewusst und meldepflichtig:** `ftp`/`telnet`/`api-ssl` wurden auf branch/ap1/ap2 **abgeschaltet** (Klartext bzw. ungenutzt; der Core hatte das bereits). Begründung: die Alternative wäre gewesen, „telnet aktiv" als Sollzustand in die SoT zu schreiben. Nichts hier nutzt telnet/ftp — der ALLNET-Telnet-Runner ist Archiv, das Gerät ausgebaut. + - 📌 **Schreibweise (Falle):** `/ip service set ssh …` mit dem **Dienstnamen**, NICHT `[find name=ssh]` — letzteres trifft auch den dynamischen Verbindungs-Eintrag und quittiert mit `failure: this is configured elsewhere`. +- 🔴🔴 **DIE FALLE DIESES TPs — `/ip service` sperrte das Router-Mgmt aus dem Mesh aus** (Core, vor dem Rollout oben): Direkt nach dem WARP-Connect war `crs326-core` mgmt-tot: MikroMCP `Connection lost before handshake`, SSH `kex_exchange_identification: Connection reset`, REST **502** — **bei gleichzeitig verlustfreiem Ping auf dieselbe IP**, und alle Hosts *hinter* dem Core normal. Ursache: `/ip service ssh,www address=10.40.0.0/16`, der Mesh-Client kommt aus `100.96.0.0/12`. ICMP kennt keine Service-ACL. **Und `nc -z` meldet „offen", weil der CF-Gateway-Proxy den TCP-Handshake terminiert — ein TCP-Connect ist über WARP KEIN Reachability-Beweis.** Fix: `address=10.40.0.0/16,100.96.0.0/12`. **Merkregel: wer per Mesh ans Netz kommt, ist noch lange nicht am Router.** ⚠️ **Für `crs326-branch`/`ap1`/`ap2` noch OFFEN** — deren eigene `/ip service`-ACLs sind ungeprüft. Entscheidender Diagnose-Schritt war der **Banner-Grab vom macmini** (`nc 10.40.99.1 22` → `SSH-2.0-ROSSSH`): von innen gesund ⇒ Pfad/ACL, nicht Gerät. Fehlspuren, die man sich sparen kann: MTU (die großen Pings scheitern am **WARP-Client**-`utun` MTU 1380, nicht an `cf-mesh`) und SSH-Session-Erschöpfung. +- ✅ **Schalter „Allow all Cloudflare One traffic to reach enrolled devices" — recherchiert 2026-08-17, ENTSCHIEDEN: bleibt AUS.** Laut CF-Settings-Referenz ist es ein **globales** Device-Client-Setting („Global device client settings"), alle Pläne, alle OS, Client-Mode „Traffic and DNS". **Funktion: es lässt Traffic aus Mesh/WAN *zu den eingebuchten Geräten hin* — also Zugriff auf Device-IPs.** Das ist die **Client↔Client**-Richtung und für unseren Fall (Gerät → Mesh-Node → `10.40.0.0/16`) nicht nötig; der Mesh läuft bewiesenermaßen ohne. **Kein API-Feld** (in Spec, `devices/settings`-PATCH *und* Profil-GET gegengeprüft), und die CF-Docs nennen **keinen** Menüpfad. ⚠️ **Die Docs widersprechen sich:** die Mesh-Get-Started-Seite nennt es eine *Profil*-Einstellung, die Settings-Referenz eine *globale*. Nach User-Vorgabe („im Zweifel ohne Schalter") **nicht gesetzt** — nichts gebaut, nichts geklickt. Erst relevant, wenn ein Zugriff **auf ein Endgerät** (statt auf das Netz) gebraucht wird. - 📌 **Bleibt gültig:** die HA-Degradierung der öffentlichen Tunnel (TP16, `bf-pve` fährt nur einen Connector) wird davon **nicht** geheilt — eigener offener Punkt. Und für **TP5** vormerken: bei deny-by-default braucht der Mesh explizit `forward accept 100.96.0.0/12 ↔ 10.40.0.0/16`. - ✅ **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. diff --git a/docs/tp16.1-warp-mesh-native-crs326.md b/docs/tp16.1-warp-mesh-native-crs326.md index ea534cc..d76cece 100644 --- a/docs/tp16.1-warp-mesh-native-crs326.md +++ b/docs/tp16.1-warp-mesh-native-crs326.md @@ -13,10 +13,10 @@ > 3. **MikroMCP taugt für WireGuard nur halb** (§4.3): kein Privkey möglich, und `endpoint="IP:Port"` wird > falsch zerlegt → kein Handshake. > -> 🔴 **Einziger verbliebener Punkt, user-gated:** der Dashboard-only-Schalter „Allow all Cloudflare One traffic -> to reach enrolled devices" (§4.1 Schritt 2). **Der Mesh läuft nachweislich auch ohne ihn** — CF führt ihn als -> Prerequisite, für Device→Node/Subnetz war er offenkundig nicht nötig. Vermutlich zählt er erst für -> **Client↔Client** (Gerät zu Gerät). Setzen, wenn das gebraucht wird. `POST /warp_connector` erfolgreich: +> ✅ **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.** @@ -212,11 +212,23 @@ tunnel-spezifischen Doc-Seiten, die noch die alten Namen zeigen). Im Token-Edito | 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.) | +| 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. + +
Ursprüngliche Handlungsanweisung (vor der Recherche) + > 🔴 **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 @@ -225,6 +237,43 @@ tunnel-spezifischen Doc-Seiten, die noch die alten Namen zeigen). Im Token-Edito > 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). +
+ +### 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 diff --git a/nautobot/gc-configplan.py b/nautobot/gc-configplan.py index 9014820..520d493 100644 --- a/nautobot/gc-configplan.py +++ b/nautobot/gc-configplan.py @@ -281,7 +281,56 @@ def job_drift_cleanup(): ] -JOBS = {"drift-cleanup": job_drift_cleanup} +def job_mgmt_acl(): + """TP16.1 (2026-08-17): Mgmt-Dienste + Address-ACL auf allen vier Geraeten. + + WARUM: seit dem Cloudflare-Mesh kommt der Remote-Zugriff aus 100.96.0.0/12. Eine ACL, + die nur 10.40.0.0/16 kennt, laesst Ping durch und sperrt SSH/REST/MikroMCP aus -- genau + das ist auf dem Core passiert. Umgekehrt fuhren branch/ap1/ap2 ssh/www/winbox/api/ftp/ + telnet komplett OHNE Address-Beschraenkung; winbox war auf allen vier offen. + + KEIN LOCKOUT-RISIKO: der Deploy laeuft von Nautobot (10.40.20.30) aus, also aus + 10.40.0.0/16 -- die Adresse bleibt in jeder gesetzten ACL enthalten. RouterOS wendet + `address=` ohnehin erst auf NEUE Verbindungen an, die laufende Session ueberlebt. + + SCHREIBWEISE: `/ip service set ssh ...` mit dem Dienstnamen, NICHT `[find name=ssh]` -- + letzteres trifft auch den dynamischen Verbindungs-Eintrag (`D c ssh ... local `) + und quittiert mit `failure: this is configured elsewhere` (TP16.1-Befund). + """ + acl = "10.40.0.0/16,100.96.0.0/12" + + def line(svc, attr): + return ( + f":do {{ /ip service set {svc} {attr} }} " + f'on-error={{ :log info "mgmt-acl: {svc} hat geworfen" }}' + ) + + # Erst die Abschaltungen (Angriffsflaeche runter), dann die ACLs. + full_set = "\n".join( + [line(s, "disabled=yes") for s in ("ftp", "telnet", "api-ssl")] + + [line(s, f"address={acl}") for s in ("ssh", "www", "winbox", "api")] + + [ + ':log info ("mgmt-acl: ssh=" . [/ip service get [find name=ssh and !dynamic] address]' + ' . " winbox=" . [/ip service get [find name=winbox and !dynamic] address]' + ' . " telnet-disabled=" . [:tostr [/ip service get [find name=telnet and !dynamic] disabled]])' + ] + ) + # Der Core hat ftp/telnet/api-ssl bereits aus und ssh/www bereits korrekt -- + # ihm fehlen nur winbox (war offen) und die Mesh-Range auf api. + core_set = "\n".join( + [line(s, f"address={acl}") for s in ("winbox", "api")] + + [ + ':log info ("mgmt-acl: winbox=" . [/ip service get [find name=winbox and !dynamic] address]' + ' . " api=" . [/ip service get [find name=api and !dynamic] address])' + ] + ) + return [ + (["crs326-branch", "ap1", "ap2"], full_set, "TP16.1-mgmt-acl-mesh"), + (["crs326-core"], core_set, "TP16.1-mgmt-acl-mesh-core"), + ] + + +JOBS = {"drift-cleanup": job_drift_cleanup, "mgmt-acl": job_mgmt_acl} # ------------------------------------------------------------------------ main diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2 index ea20ff5..9b1e718 100644 --- a/nautobot/mikrotik_routeros.j2 +++ b/nautobot/mikrotik_routeros.j2 @@ -11,6 +11,23 @@ /system identity set name={{ obj.name }} +# MGMT-DIENSTE + ADDRESS-ACL (TP16.1, 2026-08-17) — fuer ALLE vier Geraete identisch. +# Warum die Mesh-Range dazu MUSS: seit TP16.1 kommt der Remote-Zugriff aus 100.96.0.0/12 +# (Cloudflare Mesh). Eine ACL, die nur 10.40.0.0/16 kennt, laesst Ping durch, sperrt aber +# SSH/REST/MikroMCP aus — das Symptom hat auf dem Core eine halbe Stunde gekostet. +# Warum ftp/telnet/api-ssl aus: Klartext-Protokolle bzw. ungenutzt. Der Core hatte das +# bereits, branch/ap1/ap2 fuhren sie offen und OHNE jede Address-Beschraenkung. +# winbox war auf ALLEN vier unbeschraenkt. +# ⚠️ Lockout-Regel: 10.40.0.0/16 (LAN) UND 100.96.0.0/12 (Mesh) muessen beide drin sein — +# das sind die zwei Wege, ueber die dieses Netz administriert wird. Nie einen davon kappen. +/ip service set ftp disabled=yes +/ip service set ssh address=10.40.0.0/16,100.96.0.0/12 +/ip service set telnet disabled=yes +/ip service set www address=10.40.0.0/16,100.96.0.0/12 +/ip service set winbox address=10.40.0.0/16,100.96.0.0/12 +/ip service set api address=10.40.0.0/16,100.96.0.0/12 +/ip service set api-ssl disabled=yes + {% if obj.name == "crs326-core" %} {#- VLAN-Tabelle: Site-Konstante. name = RouterOS-comment, lease/pool je VLAN. -#} {%- set vlans = [