TP5 E5: MAIN->IOT aufgemacht, Drucker und Shellys wieder erreichbar

Der Deny-by-default arbeitete korrekt — es gab nie eine vlan10->vlan30-Zeile.
Offen waren nur MAIN->MEDIA, MAIN->INFRA, MAIN->MGMT; IOT fehlte als einziges
Paar. Neue Regel voll einbahnig ohne Portfilter (User-Entscheid): MAIN steht zu
INFRA/MEDIA/MGMT ohnehin ungefiltert offen, und dort liegen NAS/Proxmox/Nautobot.
IOT->MAIN bleibt bewusst zu — dort sitzt der Wert der Segmentierung.

Merkregel aus dem Drop-Log: der Drucker faellt mit udp/161 auf, nicht mit 631.
macOS fragt zuerst den SNMP-Status ab und bricht ab, bevor es zu IPP kommt —
eine Whitelist auf 631 haette nichts repariert.

Abnahme von 10.40.10.82 (der Quelle aus dem Log): .30.5 ICMP 0% Loss,
snmpget sysName.0 -> P750W, tcp/631+9100+80 offen; Shellys .30.202/.203/.221
HTTP 200. Compliance 92/92, 0 Drifts. Seit dem Deploy keine neue
vlan10->vlan30-Zeile im Kollektor.

Nebenbei: gc-manual-plan.py --assert URL-encodet die Query jetzt. Ein Kommentar
mit Leerzeichen warf InvalidURL erst NACH dem erfolgreichen Deploy — man steht
mit einem Traceback da und weiss nicht, ob der Change gewirkt hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 16:02:29 +02:00
parent 777ac95c3f
commit 1eabd0434b
3 changed files with 84 additions and 10 deletions

View File

@@ -3,34 +3,68 @@
> **Eigenständiges Arbeitspaket.** Hier definieren wir die **finalen statischen IPs** je Gerät in den
> Ziel-VLANs. Voraussetzung (✅ erfüllt 2026-06-16): Routing VLAN1 ↔ übrige VLANs steht (statische Route
> auf der FB → `10.40.41.1`), ap1-NAT nimmt LAN-internes aus (`srcnat accept 10.40.0.0/16`).
> Erst **bestätigte** Zeilen werden migriert. Status: 🟡 Entwurf — Vorschläge zum Festklopfen.
> Erst **bestätigte** Zeilen werden migriert. Status: ~~🟡 Entwurf — Vorschläge zum Festklopfen.~~
---
## ✅ Migration abgeschlossen (Stand 2026-08-19) — dieses Dokument ist Historie
> **Lies zuerst das hier, nicht die Tabelle.** Der Plan ist umgesetzt; die Tabelle unten ist das
> *Arbeitsblatt von damals* und bleibt bewusst erhalten (Konvention: fortschreiben, nicht überschreiben).
> Mehrere ihrer Zeilen beschreiben eine Topologie, die es **seit 2026-08-08 nicht mehr gibt** —
> insbesondere „ap1 = Router/GW" und `sw1` als lebendes Gerät.
>
> **Kanonischer Ist-Stand: `docs/teilprojekte.md` (Ist-Stand-Block) und `CLAUDE.md`.**
**Wie es heute wirklich aussieht:**
| Rolle | Gerät | Adresse |
|---|---|---|
| L3-Router + WAN-Edge + DHCP/DNS/NTP/WG-Hub | **`crs326-core`** | GW **`.1` je VLAN**, Mgmt-SVI `10.40.99.1`, WAN `vlan100` |
| L2-Switch Technik OG | **`crs326-branch`** | `10.40.99.2` |
| AP (reiner CAP unter CAPsMAN) | **ap1** | `10.40.99.11` |
| AP (reiner CAP unter CAPsMAN) | **ap2** | `10.40.99.12` |
| ~~ALLNET sw1~~ | — | ❌ ausgebaut 2026-08-08 |
| ~~Omada sw2~~ | — | ❌ ausgebaut 2026-07-23 |
| ~~FritzBox 6660~~ | — | ❌ ausgebaut 2026-07-01 (Arris im Bridge-Mode) |
**Was daraus für die Tabelle folgt:** die Zeilen `ap1` / `ap2` / `sw1` / `crs326-*` stehen unten noch mit
den *damals geplanten* Werten und ⬜-Status. Sie sind erledigt bzw. gegenstandslos — die Marker sind
2026-08-19 nachgezogen, die ursprüngliche Planung bleibt darunter lesbar.
**Ebenfalls überholt:** die Konventionszeile „GW je VLAN = `.1` (**ap1**)" und der Arbeitsschritt
„(3) DNS `bf.lan`-Eintrag **auf ap1** updaten" — beides ist seit dem Core-Cutover Sache von
`crs326-core`. **VLAN1 existiert seit TP19 (2026-08-18) gar nicht mehr** als Client-Netz, die Spalte
„aktuell (VLAN1)" ist damit reine Herkunftsangabe.
---
## Konventionen
- **Host-Oktett möglichst beibehalten** (weniger Churn in DNS/Configs): z.B. NAS `.10` → `10.40.20.10`.
- GW je VLAN = `.1` (ap1). Pools `.100–.200` = DHCP → statische Geräte **außerhalb** des Pools (`.2–.99`).
- GW je VLAN = `.1` (~~ap1~~ **`crs326-core`**, seit 2026-08-08). Pools `.100–.200` = DHCP → statische Geräte **außerhalb** des Pools (`.2–.99`).
- Zuordnung: **VLAN20 INFRA** = NAS/VMs/Docker/HA/Services **+ Hypervisor** (User-Entscheid 2026-06-16:
pve/DS923 sind Infra *und* Hypervisor → INFRA) · **VLAN99 MGMT** = nur Netz-Gear-Mgmt (Switch/AP) ·
**VLAN10 MAIN** = trusted Clients · **VLAN40 MEDIA** = Sonos/AppleTV/TV.
- Pro migriertem Gerät: (1) IP am Gerät selbst umstellen, (2) DHCP-Reservierung/Static pflegen,
(3) DNS `bf.lan`-Eintrag auf ap1 updaten, (4) Reachability prüfen.
(3) DNS `bf.lan`-Eintrag auf ~~ap1~~ **`crs326-core`** updaten, (4) Reachability prüfen.
## Geräte-Tabelle (Vorschlag → bestätigen)
| Gerät | aktuell (VLAN1) | Rolle | Ziel-VLAN | **Vorschlag IP** | bestätigt | Notizen |
|---|---|---|---|---|---|---|
| ap1 (Router/hAP) | 10.40.41.1 | Router/GW | alle (.1) + Mgmt | bleibt `.1` je VLAN; Mgmt `10.40.99.1` | ⬜ | ist GW, kein „Umzug" |
| ap2 (hAP) | 10.40.41.2 | AP | 99 MGMT | `10.40.99.2` | ⬜ | |
| sw1 ALLNET (Core) | 10.40.41.11 | Switch-Mgmt | 99 MGMT | `10.40.99.11` | ⬜ | Mgmt-VLAN-Wechsel = Lockout-Risiko, sorgsam |
| ap1 (hAP ax²) | 10.40.41.1 | ~~Router/GW~~ → **reiner AP (CAP)** | 99 MGMT | ~~bleibt `.1` je VLAN; Mgmt `10.40.99.1`~~ → **`10.40.99.11`** | ✅ **erledigt, anders als geplant** | ⚠️ **Rolle seit 2026-08-08 auf `crs326-core`** (TP21.1) — kein DHCP/DNS/NAT/WG mehr. Seit TP28 (08-17) reiner CAP unter dem Manager auf dem Core |
| ap2 (hAP ax²) | 10.40.41.2 | AP (CAP) | 99 MGMT | ~~`10.40.99.2`~~ → **`10.40.99.12`** | ✅ **live** | TP19.1 2026-08-14 (Mgmt-Cutover); `.99.2` ging an `crs326-branch`. Uplink an branch `ether24` |
| ~~sw1 ALLNET (Core)~~ | 10.40.41.11 | — | — | — | ❌ **ausgebaut** | 2026-08-08 durch `crs326-core` ersetzt, physisch entfernt. Die hier geplante `.99.11` ging an **ap1**. Telnet-Runner unter `switch/` = Archiv |
| ~~sw2 Omada (ES208G)~~ | — | — | — | — | ❌ **abgebaut** | TP15 2026-07-23: physisch entfernt, durch crs326-branch ersetzt (Technik OG) |
| **crs326-branch** (CRS326) | 192.168.88.1 (Werk) | Switch-Mgmt (Technik OG) | 99 MGMT | `10.40.99.13` (Ziel `.99.12` nach Core-Tausch) | ✅ **live** | TP15 2026-07-23: ersetzt sw2; Bring-up-VLAN1-Temp `.41.213`; über sw1 #8 |
| **crs326-core** (CRS326) | 192.168.88.1 (Werk) | Switch-Mgmt/künftig L3-Router (Technik UG) | 99 MGMT | `10.40.99.11` | 🟡 **grundkonf.** | TP15 2026-07-23: grundkonfiguriert, nicht produktiv (ersetzt sw1, eigene Session); VLAN1-Temp `.41.211`, an sw1 #12 |
| **crs326-branch** (CRS326) | 192.168.88.1 (Werk) | L2-Switch (Technik OG) | 99 MGMT | ~~`10.40.99.13` (Ziel `.99.12`)~~ → **`10.40.99.2`** | ✅ **live** | TP15 2026-07-23: ersetzt sw2. ⚠️ **Anders gekommen als geplant.** Uplink `ether1` → **`crs326-core ether8`** (nicht mehr sw1). Bring-up-Reste `.41.213` + SVI vlan10 entfernt 08-16 |
| **crs326-core** (CRS326) | 192.168.88.1 (Werk) | **L3-Router + WAN-Edge** (Technik UG) | 99 MGMT + GW je VLAN | ~~`10.40.99.11`~~ → **`10.40.99.1`** + **`.1` in jedem VLAN** | ✅ **produktiv** | TP21.1 2026-08-08: Cutover vollzogen, ersetzt ap1 als L3. Routing/DHCP (7 Server)/DNS (DoH)/NAT/NTP/WG-Hub. WAN `vlan100` = Liwest `90.146.33.177`. VLAN1-Temp `.41.211` entfernt |
| NAS nas-bf (DS923) | ~~10.40.41.10~~ | INFRA | 20 INFRA | `10.40.20.10` | ✅ **live** | TP14.1 2026-07-10; DNS `nas.bf.lan` nachgezogen 2026-07-16 |
| ~~Omada-Controller~~ | — | Service (macvlan/DS923) | — | — | ❌ **entfernt** | User hat den Container 2026-07-16 gelöscht (overkill für 1 Switch auf der Abschussliste, s. TP2.1/TP15). DNS-Eintrag `omada.bf.lan` entfernt |
| ~~omada-mcp~~ | — | Service (pve-LXC) | — | — | ❌ **entfernt** | mit dem Controller zusammen gelöscht 2026-07-16 (MCP-Server verwaist, in Claude-Settings noch registriert — Aufräumen offen). DNS-Eintrag `omada-mcp.bf.lan` entfernt |
| pve-mcp | 10.40.20.173 | Service (pve) | 20 INFRA | `10.40.20.173` | ✅ **live** | TP14.1; NAS-Stack (vlan20-only) |
| Docker-LXC | 10.40.20.126 | Docker-Host (pve) | 20 INFRA | `10.40.20.126` | ✅ **live** | TP14.2 2026-07-10: LXC 400 net0 tag=20; Docker `vlan20`=ipvlan parent `veth20` |
| Proxmox pve | 10.40.20.100 | Hypervisor+INFRA | 20 INFRA | `10.40.20.100` | ✅ **live** | TP14 2026-07-13: vmbr0.20 Host-IP + Default-GW-Flip, SSH-Key ✓, ap1-DNS+CF-Ingress(bf-pve) nachgezogen; `.41.100` bleibt interim ohne Route (wie sw1/sw2/ap2-Mgmt) |
| Mac mini | 10.40.41.167 | INFRA/Server | 20 INFRA | `10.40.20.167` | ✅ **live** | umgezogen; Omada P7 + Access-INFRA (VLAN20, ohne VLAN1) ✅ |
| Mac mini | ~~10.40.41.167~~ | INFRA/Server | 20 INFRA | `10.40.20.167` | ✅ **live** | umgezogen. ⚠️ Die alte `.41.167` geisterte bis 2026-08-16 in `CLAUDE.md` — dort korrigiert. Portangabe „Omada P7" ist Historie (sw2 ausgebaut) |
| ~~FritzBox 6660~~ | — | WAN/Transfer | — | — | ❌ **physisch entfernt** | Liwest-Kabelmodemtausch 2026-07-01 (Edge-Cutover), kein Zurück — Arris-Kabelmodem im Bridge-Mode ersetzt sie komplett |
| platform-pg (LXC 105, pve) | — (neu) | Platform-Postgres (TP20), PG16, DB `nautobot` | 20 INFRA | `10.40.20.20` | ✅ **live** | TP20 2026-07-16: erstellt via `pct create`, Ubuntu 24.04 (native PG16, kein PGDG-Repo nötig), UTF8/en_US.UTF-8, `wal_level=replica`, `archive_mode` bewusst noch aus (s. Memory) bis pgBackRest/Garage-S3 da ist; Creds `postgres/.secrets` |

View File

@@ -253,6 +253,32 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
### TP5 — Firewall & Inter-VLAN-Policy ▸ ✅ **DURCH (2026-08-18): deny-by-default in BEIDEN Ketten scharf** — 📋 `docs/tp5-deny-by-default-runbook.md`
- ✅ **ETAPPE 5 (2026-08-19): `MAIN → IOT` aufgemacht — `routeros/tp5-e5-main-iot.rsc`, Backup `core-pre-tp5-e5-main-iot-20260819.backup`, Compliance danach 92/92, 0 Drifts.**
- **Auslöser (User):** Brother P750W und die Shellys aus VLAN10 nicht erreichbar. Der Drop arbeitete
korrekt — es gab schlicht **nie** eine `vlan10→vlan30`-Zeile. Offen waren nur `MAIN→MEDIA`,
`MAIN→INFRA`, `MAIN→MGMT`; IOT fehlte als einziges Paar in der Liste.
- 🔴 **Merkregel: der Drucker fällt mit `udp/161` auf, NICHT mit `631`.** Im Drop-Log stehen
40 Zeilen SNMP nach `10.40.30.5` und kein einziges IPP — macOS fragt zuerst den SNMP-Status ab
und bricht ab, bevor es überhaupt zu IPP kommt. **Wer nur 631 whitelistet, repariert nichts.**
- **Log-Matrix `in:vlan10 out:vlan30`:** 497 ICMP · 489 `udp/5353` · 489 `udp/137` ·
237 `udp/3722` (Apple-Continuity → `.30.201`) · 40 `udp/161`. Quellen: `.10.183` (1596 Zeilen,
Sweep über alle 254 Adressen), `.10.180` (118), `.10.82` (38).
- **Entscheid (User): voll einbahnig, kein Portfilter.** MAIN steht zu INFRA/MEDIA/MGMT ohnehin
ungefiltert offen — und INFRA hält NAS, Proxmox und Nautobot, also deutlich mehr Wert als die
Shellys. MAIN→IOT eng zu filtern wäre Symbolik, kostet aber bei jedem neuen IoT-Gerät eine Runde
Fehlersuche. **Der Sicherheitswert sitzt in der Gegenrichtung:** `IOT→MAIN` bekommt bewusst
**kein** `accept` und bleibt gedroppt; Antworten laufen über `defconf: accept est,rel,untracked`.
- **Abnahme am Gerät** (von `10.40.10.82`, also der Quelle aus dem Log): `.30.5` ICMP 0 % Loss,
`snmpget sysName.0` → `P750W`, tcp/631+9100+80 offen; `.30.202/.203/.221` HTTP **200**.
`.30.201` bleibt still — kein Firewall-Thema mehr (die Regel ist ungefiltert), Gerät aus.
- ⚠️ **Bewusste Nebenwirkung:** der Subnetz-Sweep von `.10.183` läuft ab jetzt durch statt im
Drop-Log aufzulaufen. Betriebsneutral, aber der Log wird an dieser Stelle leiser — **nicht mit
„Problem gelöst" verwechseln**, wenn später jemand die Log-Rate als Metrik liest.
- 🔧 **Werkzeug-Fix nebenbei: `gc-manual-plan.py --assert` URL-encodet die Query jetzt.** Ein
Kommentar mit Leerzeichen (`TP5-E5: MAIN->IOT`) warf `http.client InvalidURL` — **nach** dem
erfolgreichen Deploy, d.h. der Change war durch und nur die Gegenprüfung crashte. Das ist die
schlechtere Reihenfolge: man steht mit einem Traceback da und weiß nicht, ob es gewirkt hat.
- ✅ **ETAPPE 3 + 4 DURCH (2026-08-18, nachts).** `forward` und `input` enden jetzt mit `drop`, die
Log-Regel steht in beiden Ketten **davor** und protokolliert weiter, was der Drop frisst.
Artefakte: `routeros/tp5-etappe2-nachtrag.rsc`, `-etappe3-drop.rsc`, `-etappe4-input-mgmt.rsc`,

View File

@@ -58,6 +58,7 @@ import re
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
@@ -203,7 +204,20 @@ def rest_call(name, path, query, post=False):
k: v for k, _, v in (p.partition("=") for p in query.split("&") if p)
}).encode()
elif query:
url += f"?{query}"
# Werte URL-encoden. Ein Kommentar wie "TP5-E5: MAIN->IOT" traegt ein
# Leerzeichen; roh angehaengt wirft http.client InvalidURL ("URL can't
# contain control characters") — der Deploy ist dann laengst durch und
# nur die Gegenpruefung crasht, was schlimmer ist als kein Assert.
# (2026-08-19 bei TP5-E5 aufgeschlagen.) Trennzeichen & und = bleiben.
parts = []
for p_ in query.split("&"):
if not p_:
continue
k, sep, v = p_.partition("=")
parts.append(f"{urllib.parse.quote(k, safe='')}{sep}"
f"{urllib.parse.quote(v, safe='')}" if sep
else urllib.parse.quote(k, safe=""))
url += "?" + "&".join(parts)
auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
req = urllib.request.Request(
url, data=body, method="POST" if post else "GET",