Files
bf-infra/docs/tp5-deny-by-default-runbook.md

15 KiB
Raw Permalink Blame History

TP5 — Deny-by-default auf crs326-core · Session-Runbook

Stand der Vorbereitung: 2026-08-14 (Live-Erhebung auf crs326-core via MikroMCP). Status: vorbereitet, nicht ausgeführt. Diese Datei ist der Einstieg für die TP5-Session — kanonischer Scope bleibt docs/teilprojekte.md → TP5.


0. Worum es geht (in einem Absatz)

Die forward-Kette auf dem Core endet heute ohne Drop: alles, was nicht von einer der 6 TP5-Hole-Regeln erfasst wird, läuft trotzdem durch. Die VLAN-Segmentierung ist damit dokumentiert, aber nicht durchgesetzt. TP5 schließt genau diese eine Lücke — plus die Whitelists, die es dafür braucht. Es ist der letzte Schritt vor TP6 (CAKE), dem eigentlichen Projektziel.

Das Risiko ist asymmetrisch: ein zu früh gesetzter Drop nimmt DHCP, DNS, Mgmt und das halbe Smart-Home gleichzeitig mit — und zwar auch den Pfad, über den man den Fehler zurücknehmen wollte. Deshalb ist unten Etappe 1 ein Log-Lauf ohne jede Blockierung. Wer direkt zum Drop springt, arbeitet blind.


1. Ist-Stand (live erhoben 2026-08-14, crs326-core)

1.1 forward — 13 Regeln, kein abschließender Drop

# Aktion Bedeutung
1 accept established,related,untracked defconf
2 fasttrack established,related defconf (hw-offload=true)
3 drop invalid defconf
4 drop in-list=WAN !dstnat defconf
5–6 accept in/out wg-hub TP10 (FM/DW-Spokes)
7–8 accept vlan10↔vlan40 TP5: MAIN↔MEDIA
9–10 accept vlan20↔vlan40 TP5: INFRA↔MEDIA
11–12 accept vlan90↔vlan40 TP5: GUEST↔MEDIA
— (nichts) ⬅️ hier fehlt der Deny-by-default

1.2 input — 12 Regeln, LAN pauschal offen

TP10-Seal komplett (est · inv · dhcp · icmp · lan · wandrop) + WG-Listen 51821 + 5× mDNS.

⚠️ TP10-seal-lan (in-interface-list=LAN, accept) nimmt jeden LAN-Input an — inkl. VLAN99-Mgmt, VLAN90-Guest und VLAN50. Die 5 TP5: mDNS in vlanXX-Regeln stehen dahinter und zählen daher 0 Pakete (seal-lan: 3,5 Mio.). Sie sind nicht kaputt, nur noch nicht dran.

1.3 NAT + Interface-Listen

  • srcnat: accept dst-address=10.40.0.0/16 (no-NAT inter-VLAN, steht vor masquerade) → masquerade out-list=WAN.
  • WAN = vlan100 · LAN = vlan10,20,30,40,50,90,99 (vlan99 ist Mitglied von LAN — relevant für 1.2 und für D4).

2. Was heute real über VLAN-Grenzen läuft

Erhoben aus /ip/firewall/connection (Momentaufnahme) + den kumulativen Regel-Countern. ⚠️ Beides zusammen lesen: conntrack zeigt nur, was gerade offen ist; die Counter zeigen, was jemals lief. Beispiel: INFRA→MEDIA hat 178.142 Pakete auf dem Zähler, stand aber im Snapshot auf 0 Verbindungen.

Richtung Snapshot Was genau
v20 → v30 24 HA (.20.105) pollt 22 Shellys auf tcp/80 + .30.137:6638
v10 → v30 224 223× ICMP von .10.82 („Mac") auf alle Shellys + 1× udp/3722
v10 → v20 10 .10.82/.10.183 → NAS .20.10:6690 (Synology Drive), .20.173:3000 (pve-mcp), .20.167:49152
v20 → v10 4 Mac mini .20.167 ↔ iPad .10.180 udp/3722; HA → .10.6:161 (SNMP)
v30 → v20 3 .30.249 → HA-API .20.105:8123; udp/5540 (Matter) → HA; .30.201 → .20.167:49152
v30 → v10 4 Rückrichtungen zu .10.82/.10.180/.10.183 (ephemere Ports)
v10 → v99 1 .10.82 → core .99.1 tcp/22 + tcp/80 (= Mgmt-Zugriff)
v40 → * 0 MEDIA initiiert nichts nach außerhalb
v50 / v90 0 komplett leer — Internet-only-Pinning ist hier risikofrei

2.1 Drei Funde, die vor dem Drop eine Entscheidung brauchen

  1. 📱 Ein iPhone sitzt in VLAN30 (IOT): 10.40.30.201, Hostname iPhone, und es spricht mit dem Mac mini (udp/3722, Apple-Continuity-Muster). Entweder hat sich jemand bewusst ins dirtyvader-iot-SSID gehängt, oder das ist ein Ausrutscher. Ein IOT-Deny trennt dieses Gerät — vorher klären, nicht danach.
  2. ✅ .10.82 („Mac") pingt alle ~30 Shellys — GEKLÄRT (User 2026-08-14): das ist lanscan.app, also ein Ad-hoc-Scan vom eigenen Arbeitsplatz, kein Dauerdienst. Konsequenz: keine permanente MAIN→IOT-ICMP-Regel bauen — der Scan gehört in die JIT-Admin-Gruppe (address-list mit timeout, s. §3/D3). Beim Log-Lauf erzeugt er sonst viel Rauschen: entweder während Etappe 1 nicht scannen, oder die Treffer beim Auswerten bewusst ausfiltern.
  3. 🔑 VLAN99 ist Mitglied der LAN-Liste → TP10-seal-lan erlaubt Mgmt-Input aus jedem VLAN. „MGMT isolieren" (TP5-Ziel) heißt also: seal-lan verengen — das ist derselbe Griff, der die 5 mDNS-Regeln scharf schaltet. Beides passiert zusammen oder gar nicht.

3. Offene Entscheidungen (vor der Session klären)

# Frage Vorschlag
D1 MAIN→INFRA: welche Dienste? Beobachtet: NAS 6690, pve-mcp 3000, macmini 49152, 3722. Vorschlag: erst mit dem Log-Lauf (Etappe 1) vervollständigen, dann Whitelist — nicht jetzt raten.
D2 iPhone .30.201 in IOT ✅ ENTSCHIEDEN (2026-08-14): ins MAIN-SSID umhängen. Hands-on bei Christoph (SSID wechseln + dirtyvader-iot vergessen lassen). Beleg, dass es dasselbe Telefon ist: die statische Reservierung 10.40.10.196 „iPhone" trägt eine andere private MAC — iOS vergibt pro SSID eine eigene Private Address. Genau darum wäre eine Whitelist auf .30.201 instabil.
D3 ICMP-Sweep .10.82→IOT ✅ ENTSCHIEDEN (2026-08-14): lanscan.app, Ad-hoc vom Arbeitsplatz. Keine Dauerregel — läuft über die JIT-Admin-Gruppe (selbstverfallend). Deckt sich mit D4.
D4 Wer darf auf VLAN99-Mgmt? Vorschlag: nur MAIN (vlan10), Rest drop. seal-lan entsprechend verengen.
D5 IOT→INFRA-Whitelist Doku nennt MQTT 1883/8883, HA-API, Influx 8086. Beobachtet real: 8123 (HA), 5540/udp (Matter). Vorschlag: die beobachteten + MQTT.
D6 UNTRUSTED/GUEST Beide tragen null Inter-VLAN-Verkehr → Internet-only sofort und gefahrlos pinnbar.

4. Umsetzung in 4 Etappen

Governance (TP22): alles über Nautobot Golden Config, plan_type=manual, feature-scoped — kein Direkt-Write, kein „full intended" (Regel 1: <<REDACTED>>-Sektionen nie pushen). Vor jedem Push /system backup auf dem Core (Regel 3). Und: nur die Gegenprüfung am Gerät zählt, Job- und Plan-Status sind kein Wirksamkeitsnachweis (zweimal belegt, TP21.1/TP22).

Push-Werkzeug + Fallen (aus dem Etappe-1-Rollout, 2026-08-14 am Gerät belegt)

Werkzeug: nautobot/gc-manual-plan.py --device <name> --commands-file <datei> --change-id <id> [--dry-run|--deploy]. Es legt den manual Plan an, prüft plan.device gegen den Namen, gibt frei, deployt und liest den Plan-Status zurück (nicht den Job-Status). Kommentar-/Leerzeilen werden abgestreift.

  1. 🔴 Bindestriche in name= werden abgelehnt. add name="tp5-remote" und add name="tp5-abc" werfen, add name="tp5syslog" geht — Quoten hilft nicht. Das hat vier Deploys gekostet, weil die naheliegende Erklärung („remote= darf nicht ins selbe add wie target=remote") falsch war: set remote=… remote-port=… in einem Statement läuft anstandslos. Bei neuen benannten Objekten also Namen ohne Bindestrich wählen, oder das Gegenteil erst beweisen.
  2. 🔴 Ein gescheiterter Netmiko-Deploy hinterlässt einen TEIL-Zustand. Die TP22-Notiz „gescheiterter Deploy = sauberer Fehlschlag" galt dem NAPALM-Weg, der scheiterte, bevor er das Gerät anfasste. Netmiko schickt Zeile für Zeile und lässt Abgesetztes stehen: der erste Push hier hinterließ Zeile 3+4 ohne 1+2. Nach jedem Fehlschlag den Ist-Stand am Gerät prüfen.
  3. Keine print-Zeilen in einen Config Plan. Ein /system identity print als Kanarienzeile hat die unmittelbar folgende Zeile verschluckt und eine Fehldiagnose erzeugt.
  4. Selbstprotokollierende Pläne sind die Diagnosemethode. Der Deploy-Job liefert nur E1028: found bad/failed/failure in output und zeigt den Geräte-Output nicht. Jedes Kommando in :do {…} on-error={ :log info "…" } wickeln und danach eine Zählzeile :log info ("… count=" . [:tostr [:len [… find …]]]) setzen — anschließend per get_log zeilengenau auslesen. Damit ist Root-Cause-Analyse ohne Direkt-Write möglich.

Etappe 1 — Log-Lauf (blockiert nichts, Dauer: einige Tage) ▸ 🟢 SAMMELPHASE LÄUFT (seit 2026-08-16)

Historie: am 2026-08-14 vor der Sammelphase pausiert, weil 76 % des Verkehrs .41.x-Altlast war. Nach dem VLAN1-Rückbau (TP19) am 2026-08-16 wieder scharf — routeros/tp5-etappe1-resume.rsc.

✅ Abnahme bestanden: out:bridge = 0 über ein 10-Minuten-Fenster (Logdatei vorher geleert). Rate von ~166 auf ~34 Zeilen/min gefallen. Die beobachtete Matrix steht in docs/teilprojekte.md → TP5 und ist ab jetzt das Arbeitsmaterial für Etappe 2.

Zwei Regeln stehen damit schon fest und brauchen keinen weiteren Beleg aus dem Log:

  1. accept out-interface-list=WAN — ohne sie nimmt der Drop das ganze Haus vom Netz.
  2. Der Management-Pfad 10.40.20.105 → 10.40.99.{2,11,12}:8728 (HA-mikrotik-Integrationen) plus MikroMCP vom Arbeitsplatz — sonst sperrt der Drop die Netzverwaltung aus.
  3. Same-VLAN-mDNS (vlan10 → vlan10, 5353, vom Repeater auf dem Core erzeugt) erreicht die forward-Kette und würde von einem blanken Drop getroffen.

Ausgerollt über Nautobot GC (manual Config Plans, nautobot/gc-manual-plan.py), Quelle routeros/tp5-etappe1-log-lauf.rsc. Auf dem Gerät:

/ip firewall filter add chain=forward action=log log-prefix="TP5-WOULD-DROP" \
    comment="TP5: log-only, Vorstufe zum deny-by-default" out-interface-list="!WAN"

Das protokolliert exakt die Pakete, die der spätere Drop treffen würde — inklusive der Dinge, an die niemand denkt (Drucker-Discovery, Backup-Jobs, ein Gerät, das nur einmal pro Woche funkt). Diese Liste ist die Whitelist, nicht eine am Schreibtisch ausgedachte. Danach D1/D5 final beantworten.

Log-Ziel steht: syslog-ng-Container tp5-syslog auf dem Docker-LXC, 10.40.20.18:514, Dateien unter /opt/tp5-syslog/log/ (tp5-would-drop.log gefiltert + Host-Dateien). Auf dem Core: Action tp5syslog + logging add topics=firewall action=tp5syslog. Disk-Logging auf dem Core scheidet aus (nur ~3 MB freier Flash), der Memory-Ring (1000 Zeilen) wickelt sich in Minuten um. firewall ist aus dem Memory-Ring genommen (topics=info,!firewall), damit der On-Box-Log während des mehrtägigen Laufs für andere Fehlersuche brauchbar bleibt — am Ende von Etappe 1 auf topics=info zurückbauen. ⚠️ Der LXC hat nur ~2,5 GB frei; /usr/local/bin/tp5-log-guard.sh (Cron, alle 10 min) kappt bei 500 MB je Datei per Truncate-in-place. Ein vollgelaufenes Dateisystem risse dort auch adguard/cloudflared mit.

🔴 out-interface-list="!WAN" — warum die Regel nicht bare steht

Bare gefasst fing sie auch den gesamten Internet-Verkehr (out:vlan100): in einer ruhigen Minute 242 Zeilen/min, davon ¼ WAN-bound, unter Last deutlich mehr. LAN→WAN ist aber eine garantierte Whitelist-Zeile — ohne sie hat kein VLAN Internet, auch die „Internet-only"-VLANs 50/90 nicht. Es wäre also Rauschen genau in der Matrix, die Etappe 2 ableiten soll.

Merkposten für Etappe 3: der Drop darf aus demselben Grund nicht bare ans Kettenende — accept out-interface-list=WAN gehört davor, sonst ist das ganze Haus offline. Diese Zeile ist bekannt und braucht keinen Beleg aus dem Log.

Auswertung

ssh root@10.40.20.100 'pct exec 400 -- grep -o "in:vlan[0-9]* out:[a-z0-9]*" /opt/tp5-syslog/log/tp5-would-drop.log | sort | uniq -c | sort -rn'

Etappe 2 — Whitelists bauen (noch immer ohne Drop)

Die aus Etappe 1 abgeleiteten Regeln vor der Log-Regel einfügen: MAIN→INFRA (D1), IOT→INFRA (D5), ggf. MAIN→IOT-ICMP (D3). Danach muss die Log-Regel verstummen — solange sie noch Nutzverkehr sieht, ist die Whitelist unvollständig. Das ist das Abbruchkriterium für Etappe 3.

Etappe 3 — Drop scharf schalten

action=log → action=drop (bzw. Log behalten und Drop dahinter). Erst wenn Etappe 2 still ist.

🛟 Sicherung — auf dem Core möglich, anders als auf den hAPs: crs326-core läuft nicht in device-mode=home, also stehen scheduler und fetch zur Verfügung. Vor dem Drop einen Auto-Revert scharfstellen, der die Regel nach n Minuten selbst wieder abschaltet, wenn niemand ihn abbestellt:

/system scheduler add name=tp5-autorevert interval=10m on-event="/ip firewall filter set [find comment~\"TP5: deny-by-default\"] disabled=yes; /system scheduler remove tp5-autorevert"

Läuft alles sauber → Scheduler manuell entfernen. Läuft es nicht → das Netz heilt sich in ≤10 Minuten selbst. Diese Absicherung gibt es auf ap1/ap2 nicht — dort ist sie durch device-mode=home blockiert (TP22-Regel 2).

Etappe 4 — UNTRUSTED/GUEST + MGMT (D4/D6)

vlan50/vlan90 auf Internet-only pinnen (risikofrei, s. §2), seal-lan auf MAIN verengen → MGMT isoliert, und als Nebeneffekt werden die 5 mDNS-input-Regeln endlich wirksam.


5. Verifikation (nach jeder Etappe, immer am Gerät)

  1. list_firewall_rules chain=forward/input → Regel existiert, disabled=false, Kommentar korrekt.
  2. Funktionstest quer durchs Haus: Sonos aus MAIN steuern · HA erreicht die Shellys (v20→v30:80) · Shelly meldet an HA (v30→v20:8123) · NAS-Zugriff aus MAIN (6690) · Drucker · AirPlay/Cast · Gast-WLAN kommt ins Internet und sonst nirgendwohin.
  3. Counter der neuen Regeln lesen — eine Drop-Regel, die gar nichts zählt, ist genauso verdächtig wie eine, die zu viel zählt.
  4. Nach der Geräteänderung: Backup → Intended → Compliance in dieser Reihenfolge (GC vergleicht gegen das letzte Backup, nicht gegen das Live-Gerät — TP22-Falle).

6. Rollback

  • Sofort: disabled=yes auf der Drop-Regel (bzw. Auto-Revert greift von selbst).
  • Ganz zurück: /system backup von vor der Session einspielen.
  • 🚨 Wenn der Mgmt-Pfad weg ist: Rettung ist der Kabelweg, nicht WLAN. crs326-core steht in Technik UG; VLAN10-Fallback 10.40.10.1 existiert zusätzlich zu .99.1.
  • ⚠️ WARP-Falle: „Ping auf alles tot, TCP geht" ist kein Firewall-Befund, sondern ein aktiver WARP-Client. Vor jeder Firewall-Fehlersuche zuerst WARP trennen (ref_warp_local_icmp_trap).

7. Anschluss

TP5.1 (DNS-Redirect + Torch für Low-Trust) hängt am UNTRUSTED-Pinning aus Etappe 4. Danach ist der Weg frei für TP6 — CAKE, das Projektziel.