From da452ce1f3cb85d50d1bf7e93845dfa2661318ce Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Tue, 18 Aug 2026 04:52:40 +0200 Subject: [PATCH] TP23 Stufe 1 (DoH) + TP5 Etappe-2-Nachtrag ausgerollt, Etappe-3-Drop vorbereitet Drei Skripte aus der Folgesession. Stand am Geraet zum Commit-Zeitpunkt (per REST gegengeprueft, nicht aus den Dateikoepfen abgeschrieben): - tp23-stufe1-doh-core.rsc -> LIVE. use-doh-server auf den Cloudflare-Gateway-Resolver, verify-doh-cert=true. Nur der GTS Root R4 importiert statt eines Bundles: der Core hat ~2,8 MB freien Flash, und die Kette endet nachweislich dort. Der Root wurde per SFTP hochgeladen, nicht per fetch geholt -- fetch haette check-certificate=no gebraucht und damit genau die Pruefung ausgehebelt, die hier aufgebaut wird. Schliesst zugleich TP21.1 TODO 1. - tp5-etappe2-nachtrag.rsc -> LIVE. 17 TP5-E2-Whitelists (11 + 6). Vier Luecken, die der Kollektor ueber dreieinhalb Stunden gezeigt hat: Matter (udp/5540, 92 der 119 Zeilen -- die E2-Regel war protocol=tcp und liess Matter komplett durchfallen), der Nautobot-Deploy-Pfad (kommt von .20.31 = worker, nicht von .20.30 = web; jetzt als address-list, weil der scheduler dieselben Jobs faehrt), und zwei Rueckwege aus abgelaufenen Conntrack-Eintraegen. Dazu eine Vorratsregel fuer AdGuard, damit das spaetere DNS-Umstellen ein reiner DHCP-Change bleibt. Korrigiert meine Aussage vom Vortag: "die Log-Regel ist verstummt" beruhte auf einem 70-Sekunden-Fenster und war so nicht haltbar. Genau deshalb verlangt das Runbook ein mehrtaegiges Fenster. - tp5-etappe3-drop.rsc -> VORBEREITET, NICHT AUSGEROLLT. Kein Drop und kein Scheduler auf dem Geraet. Der Auto-Revert steht im Skript bewusst VOR dem Drop, sonst gibt es ein Fenster ohne Sicherung; der Drop kommt HINTER die Log-Regel, damit der Kollektor weiter sieht, was gefressen wird. Gate fuer Etappe 3: die Log-Regel steht aktuell auf 0 Paketen. Co-Authored-By: Claude Opus 5 --- routeros/tp23-stufe1-doh-core.rsc | 48 +++++++++++++++++++++++ routeros/tp5-etappe2-nachtrag.rsc | 65 +++++++++++++++++++++++++++++++ routeros/tp5-etappe3-drop.rsc | 44 +++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 routeros/tp23-stufe1-doh-core.rsc create mode 100644 routeros/tp5-etappe2-nachtrag.rsc create mode 100644 routeros/tp5-etappe3-drop.rsc diff --git a/routeros/tp23-stufe1-doh-core.rsc b/routeros/tp23-stufe1-doh-core.rsc new file mode 100644 index 0000000..1a06d22 --- /dev/null +++ b/routeros/tp23-stufe1-doh-core.rsc @@ -0,0 +1,48 @@ +# TP23 Stufe 1 — DoH auf crs326-core (schliesst zugleich TP21.1 TODO 1). +# +# WARUM NUR EIN ROOT UND KEIN CA-BUNDLE +# ------------------------------------ +# Die Kette von px35ynlol3.cloudflare-gateway.com ist +# Leaf(CN=cloudflare-gateway.com) -> GTS WE1 -> GTS Root R4 +# (am 2026-08-17 per openssl s_client verifiziert). Fuer verify-doh-cert=yes +# reicht der Root. Das ist hier kein Geiz, sondern Notwendigkeit: der Core hat +# nur ~2,8 MB freien Flash, ein Mozilla-Bundle (~230 KB) waere Verschwendung +# und muesste gepflegt werden. Der Root laeuft bis 2036, das Leaf rotiert +# darunter ohne Zutun. +# +# Datei liegt schon auf dem Geraet: gtsrootr4.pem (per SFTP hochgeladen statt +# per fetch geholt — fetch haette check-certificate=no gebraucht, also genau +# die Pruefung ausgehebelt, die wir gerade aufbauen wollen). +# SHA-256 des Roots, lokal gegengeprueft: +# 34:9D:FA:40:58:C5:E2:63:12:3B:39:8A:E7:95:57:3C:4E:13:13:C8:3F:E6:8F:93:55:6C:D5:E8:03:1B:3C:7D +# +# servers=1.1.1.1 BLEIBT stehen: RouterOS loest damit den DoH-Hostnamen selbst +# auf (Bootstrap). Ohne das haette man ein Henne-Ei. Alles andere laeuft danach +# ueber DoH. +# +# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \ +# --commands-file routeros/tp23-stufe1-doh-core.rsc \ +# --change-id TP23-Stufe1-DoH --deploy \ +# --assert '/ip/dns|use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query' \ +# --assert '/ip/dns|verify-doh-cert=true' +# Backup davor: core-pre-tp23-doh-20260817.backup +# +# Rollback: /ip dns set use-doh-server="" — eine Zeile, sofort wirksam. + +# 1) Import. Idempotent: nur wenn der Root noch nicht da ist. +:do { :if ([:len [/certificate find where common-name="GTS Root R4"]] = 0) do={ /certificate import file-name=gtsrootr4.pem passphrase="" } } on-error={ :log info "tp23s1: 01 import warf" } + +# 2) Explizit vertrauen. Der Import allein setzt trusted nicht zuverlaessig, +# und ohne trusted=yes scheitert verify-doh-cert=yes stumm. +:do { /certificate set [find where common-name="GTS Root R4"] trusted=yes } on-error={ :log info "tp23s1: 02 trust warf" } + +# 3) DoH scharf. verify-doh-cert=yes ist der ganze Punkt der Uebung — ohne +# Pruefung waere DoH nur Verschleierung, kein Vertrauensanker. +:do { /ip dns set use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes } on-error={ :log info "tp23s1: 03 doh-set warf" } + +# 4) Cache leeren, damit die naechste Antwort nachweislich ueber DoH kommt und +# nicht aus dem Bestand von vorher. +:do { /ip dns cache flush } on-error={ :log info "tp23s1: 04 flush warf" } + +# Selbstprotokoll — der Deploy-Job zeigt den Geraete-Output nicht. +:log info ("tp23s1: ENDE roots=" . [:tostr [:len [/certificate find where common-name="GTS Root R4"]]] . " trusted=" . [:tostr [/certificate get [find where common-name="GTS Root R4"] trusted]] . " doh=" . [/ip dns get use-doh-server]) diff --git a/routeros/tp5-etappe2-nachtrag.rsc b/routeros/tp5-etappe2-nachtrag.rsc new file mode 100644 index 0000000..ebf2bac --- /dev/null +++ b/routeros/tp5-etappe2-nachtrag.rsc @@ -0,0 +1,65 @@ +# TP5 Etappe 2 — NACHTRAG. Vier Luecken, die der Kollektor nach dem ersten +# Etappe-2-Deploy gezeigt hat, plus eine Vorratsregel. +# +# QUELLE: /opt/tp5-syslog/log/tp5-would-drop.log auf dem Docker-LXC, +# Fenster 2026-08-17 15:00 .. 18:38 (nach dem E2-Deploy um 14:17:16). +# 119 Zeilen, aggregiert: +# 92 vlan30->vlan20 udp 10.40.30.227:5540 -> 10.40.20.105 +# 18 vlan20->vlan99 tcp 10.40.20.31 -> .99.2/.99.11/.99.12:22 +# 8 vlan99->vlan20 tcp .99.x:8728 -> 10.40.20.105 +# 1 vlan30->vlan20 tcp 10.40.30.225:80 -> 10.40.20.105 +# +# Die Behauptung "die Log-Regel ist verstummt" stammte aus einem 70-Sekunden- +# Fenster. Ueber dreieinhalb Stunden gemessen stimmt sie nicht. Das ist genau +# der Grund, warum das Runbook ein mehrtaegiges Fenster verlangt. +# +# BLOCKIERT NICHTS: ausschliesslich action=accept. +# +# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \ +# --commands-file routeros/tp5-etappe2-nachtrag.rsc \ +# --change-id TP5-Etappe2-Nachtrag --deploy \ +# --assert '/ip/firewall/filter?comment=TP5-E2: IOT->INFRA Matter|count=1' +# --------------------------------------------------------------------------- + +# 1) MATTER. 92 der 119 Zeilen. 10.40.30.227 = SwitchBot-Stehlampe (DHCP- +# Reservierung "SwitchBot-floor-lamp"), die per Matter ueber udp/5540 an HA +# pusht. Die E2-Regel "IOT->INFRA Dienste" ist protocol=tcp — Matter faellt +# komplett durch. Ohne diese Zeile toetet Etappe 3 die Matter-Anbindung. +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: IOT->INFRA Matter"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan30 out-interface=vlan20 protocol=udp dst-port=5540 comment="TP5-E2: IOT->INFRA Matter" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 01 Matter warf" } + +# 2) NAUTOBOT-DEPLOY-PFAD. Der Netmiko-Deploy kommt NICHT von 10.40.20.30 +# (Web-Container), sondern von 10.40.20.31 = nautobot-worker. Beleg: die +# E2-Regel "Nautobot->MGMT ssh" auf .20.30 steht auf 0 Paketen, waehrend im +# Log 18 SYNs von .20.31 auf .99.{2,11,12}:22 stehen. +# Address-List statt Einzel-IP, weil der scheduler (.20.32) dieselben Jobs +# faehrt und ein Stack-Redeploy die Rollen tauschen kann. +# ⚠️ Ohne diese Zeile sperrt Etappe 3 den einzigen legitimen Config-Weg aus — +# also genau das Werkzeug fuer den Rueckzieher. +:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.30]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.30 comment="nautobot web" } } on-error={ :log info "tp5n: 02a al-30 warf" } +:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.31]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.31 comment="nautobot worker = der Deploy-Pfad" } } on-error={ :log info "tp5n: 02b al-31 warf" } +:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.32]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.32 comment="nautobot scheduler" } } on-error={ :log info "tp5n: 02c al-32 warf" } +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: Nautobot->MGMT ssh (list)"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=nautobot out-interface=vlan99 protocol=tcp dst-port=22 comment="TP5-E2: Nautobot->MGMT ssh (list)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 02d regel warf" } + +# 3) RUECKRICHTUNG DES MGMT-PFADS. .99.x:8728 -> HA, als connection-state:new +# im Log. Das sind Pakete einer TCP-Sitzung, deren Conntrack-Eintrag +# abgelaufen ist, waehrend HA weiterpollt. Ein blanker Drop wuerde sie +# verschlucken und die mikrotik-Integrationen ins Timeout laufen lassen. +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: MGMT->INFRA api-rueckweg"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan99 out-interface=vlan20 protocol=tcp src-port=8728 comment="TP5-E2: MGMT->INFRA api-rueckweg" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 03 api-rueckweg warf" } + +# 4) SHELLY-ANTWORT auf demselben Muster (tcp/80 -> HA, als new gesehen). +# Nur 1 Zeile im Fenster, aber dieselbe Ursache wie 3 und bei 22 gepollten +# Shellys jederzeit wieder faellig. +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: IOT->INFRA http-rueckweg"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan30 out-interface=vlan20 protocol=tcp src-port=80 comment="TP5-E2: IOT->INFRA http-rueckweg" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 04 http-rueckweg warf" } + +# 5) VORRATSREGEL DNS -> AdGuard (10.40.20.16). +# AGH ist per Christophs Vorgabe eingerichtet, aber NICHT scharf geschaltet: +# die DHCP-Scopes liefern weiter 10.40.X.1 aus. Diese Zeile aendert daran +# nichts. Sie sorgt dafuer, dass das spaetere Umstellen ein reiner +# DHCP-Change bleibt und die dann scharfe Firewall nicht angefasst werden +# muss. Ohne sie waere die Reihenfolge eine Falle: DNS umgestellt, Drop +# scharf, Haus ohne Namensaufloesung. +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->AGH dns (Vorrat)"]] = 0) do={ /ip firewall filter add chain=forward action=accept dst-address=10.40.20.16 protocol=udp dst-port=53 comment="TP5-E2: LAN->AGH dns (Vorrat)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 05a dns-udp warf" } +:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->AGH dns-tcp (Vorrat)"]] = 0) do={ /ip firewall filter add chain=forward action=accept dst-address=10.40.20.16 protocol=tcp dst-port=53 comment="TP5-E2: LAN->AGH dns-tcp (Vorrat)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 05b dns-tcp warf" } + +# Zaehlzeile: 17 TP5-E2-Regeln erwartet (11 aus Etappe 2 + 6 hier). +:log info ("tp5n: ENDE e2-regeln=" . [:tostr [:len [/ip firewall filter find comment~"TP5-E2:"]]] . " nautobot-liste=" . [:tostr [:len [/ip firewall address-list find list=nautobot]]]) diff --git a/routeros/tp5-etappe3-drop.rsc b/routeros/tp5-etappe3-drop.rsc new file mode 100644 index 0000000..a004840 --- /dev/null +++ b/routeros/tp5-etappe3-drop.rsc @@ -0,0 +1,44 @@ +# TP5 Etappe 3 — Deny-by-default am Ende von forward. +# +# WAS HIER PASSIERT +# ----------------- +# Die forward-Kette endet heute nach der Log-Regel ohne Drop: alles, was keine +# Whitelist trifft, laeuft trotzdem durch. Diese Datei schliesst genau das. +# Der Drop kommt HINTER die Log-Regel, nicht an ihre Stelle — dann protokolliert +# der Kollektor weiter exakt das, was der Drop frisst. Ohne diese Sicht waere +# jede spaetere Fehlersuche Raten. +# +# VORBEDINGUNGEN, die am 2026-08-18 erfuellt sind: +# - 17 Whitelist-Regeln (11 aus Etappe 2 + 6 aus dem Nachtrag) stehen VOR der +# Log-Regel; Positionen 14..30, Log auf 31, live gegengeprueft. +# - Der Nachtrag hat die vier Luecken geschlossen, die der Kollektor nach dem +# ersten E2-Deploy gezeigt hat (Matter, nautobot-worker .20.31, die beiden +# Rueckwege). Ohne sie haette dieser Drop den GC-Deploy-Pfad selbst gekappt. +# +# ⚠️ REIHENFOLGE IM SKRIPT IST ABSICHT: erst der Auto-Revert-Scheduler, DANN der +# Drop. Andersherum gibt es ein Fenster, in dem der Drop scharf ist und die +# Sicherung noch nicht steht. +# +# 🛟 AUTO-REVERT: schaltet den Drop nach 10 Minuten selbst wieder ab und entfernt +# sich. Wer zufrieden ist, loescht den Scheduler von Hand — dann bleibt der Drop. +# Name OHNE Bindestrich: RouterOS lehnt "tp5-autorevert" ab (bekannte Falle, +# hat schon vier Deploys gekostet). +# +# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \ +# --commands-file routeros/tp5-etappe3-drop.rsc \ +# --change-id TP5-Etappe3-Drop --deploy +# Backup davor: core-pre-tp5-etappe3-20260818.backup +# +# Rollback sofort: /ip firewall filter set [find comment="TP5: deny-by-default"] disabled=yes +# --------------------------------------------------------------------------- + +# 1) Sicherung zuerst. +:do { :if ([:len [/system scheduler find name="tp5autorevert"]] = 0) do={ /system scheduler add name="tp5autorevert" interval=10m on-event="/ip firewall filter set [find comment=\"TP5: deny-by-default\"] disabled=yes; /system scheduler remove [find name=\"tp5autorevert\"]; :log warning \"tp5e3: AUTO-REVERT hat den Drop abgeschaltet\"" } } on-error={ :log info "tp5e3: 01 scheduler warf" } + +# 2) Der Drop. Hinter die Log-Regel, damit der Kollektor weiterlaeuft. +# KEIN place-before: ein add haengt ans Kettenende an, und dort steht die +# Log-Regel bereits als letzte. Ein place-before auf die Log-Regel wuerde den +# Drop DAVOR setzen -- dann sieht der Kollektor nie wieder, was gedroppt wird. +:do { :if ([:len [/ip firewall filter find comment="TP5: deny-by-default"]] = 0) do={ /ip firewall filter add chain=forward action=drop comment="TP5: deny-by-default" } } on-error={ :log info "tp5e3: 02 drop warf" } + +:log info ("tp5e3: ENDE drop=" . [:tostr [:len [/ip firewall filter find comment="TP5: deny-by-default"]]] . " autorevert=" . [:tostr [:len [/system scheduler find name="tp5autorevert"]]])