diff --git a/CLAUDE.md b/CLAUDE.md index c004842..1ca68f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,8 @@ DNS/DHCP-Ablösung, WLAN via **CAPsMAN auf dem Core** (TP28, 2026-08-17 — der 1. **`docs/teilprojekte.md`** (Quick-Index-Block) — kanonischer Stand, Scope, Abhängigkeiten, **offene** TPs. **1 Read reicht.** 2. **`docs/handoff-*.md`** (neueste) — wo die letzte Session aufgehört hat. 3. Bei Live-Writes oder Inventur: **`docs/cf-tunnel-mapping.md`**, **`docs/teilprojekte-archiv.md`**. -4. Inventur-Befund 2026-08-27: [`docs/handoff-20260827-inventur-und-architektur.md`](docs/handoff-20260827-inventur-und-architektur.md) — Master-Struktur, Asymmetrie, Drift. +4. ap3-Bring-up 2026-08-30: [`docs/handoff-20260830-ap3.md`](docs/handoff-20260830-ap3.md) — sechs Funde zu Werks-MikroTiks, CAPsMAN und Golden-Config-Lücken. +5. Inventur-Befund 2026-08-27: [`docs/handoff-20260827-inventur-und-architektur.md`](docs/handoff-20260827-inventur-und-architektur.md) — Master-Struktur, Asymmetrie, Drift. ## Was du sofort wissen musst (Inventur 2026-08-27) @@ -59,9 +60,9 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, | ~~Omada Access-Switch (sw2)~~ | — | ❌ **abgebaut 2026-07-23** (TP15 Branch-Tausch) — durch `crs326-branch` in Technik OG ersetzt, physisch entfernt | | **crs326-branch** (CRS326, Technik OG) | **10.40.99.2** (VLAN99) · Bootstrap Rev4 2026-08-08 | RouterOS **7.24** · `admin`/`~/.secrets` (`CRS326_BRANCH_*`) · REST `http://10.40.99.2/rest` · MikroMCP `crs326-branch` ✅ **live** (2026-08-16 verifiziert) · **TP15 ✅: ersetzt sw2** (L2-Switch), volle Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap2) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 | | **crs326-core** (CRS326, Technik UG) | **10.40.99.1** (VLAN99-SVI = L3-GW; same-subnet-Fallback `10.40.10.1`) · Identity `crs326-core` | RouterOS **7.24** · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` ✅ **live** (2026-08-16 verifiziert) · **TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router** (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (**DoH zum CF-Gateway mit `verify-doh-cert=yes`**, TP23 Stufe 1 ✅ 2026-08-18; `servers=1.1.1.1` nur noch Bootstrap) · **NTP-Server für den Standort**/NAT/WG-Hub (fm+dw live); WAN `vlan100`=Liwest `90.146.33.177` · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 | -| hAP ax² #1 (ap1) | **10.40.99.11** (VLAN99) · Identity `ap1` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **TP28 ✅ 2026-08-17: reiner CAP** (Manager = `crs326-core`, `cap enabled=yes`, `discovery-interfaces=vlan99`) — Uplink `ether1` (PoE-Port, `poe-out=off`) an crs326-core `ether15`; SSIDs/Kanäle kommen vom Manager, ch36/ch1 · MAC D0:EA:11:4D:40:E0 · Config-Referenz `routeros/.secrets/ap1-prov-base.rsc` | +| hAP ax² #1 (ap1) | **10.40.99.11** (VLAN99) · Identity `ap1` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` ✅ live · **TP28 ✅ 2026-08-17: reiner CAP** (Manager = `crs326-core`) · 🔴 **FUNKSTILL seit 2026-08-30**: keine Provisioning-Regel am Manager, weil ap3 daneben steht und denselben Kanal 5180 fährt. Gerät bleibt erreichbar. Beim Umzug ans Sideboard → `5260/Ce` (Band B, DFS) und Uplink auf `core ether3` — Uplink `ether1` (PoE-Port, `poe-out=off`) an crs326-core `ether15`; SSIDs/Kanäle kommen vom Manager, ch36/ch1 · MAC D0:EA:11:4D:40:E0 · Config-Referenz `routeros/.secrets/ap1-prov-base.rsc` | | hAP ax² #2 (ap2) | **10.40.99.12** (VLAN99) · Identity `ap2` | RouterOS **7.24** · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **TP28 ✅ 2026-08-17: reiner CAP** — Uplink `ether1` (PoE) an crs326-branch `ether24`; ch44/ch11 (TP25: ch149 war unter ETSI leistungsbegrenzt) · MAC D0:EA:11:4D:40:3A · Config `routeros/.secrets/ap2-prov-base.rsc` | -| **cAP ax (ap3)** | **10.40.99.13** (VLAN99, Ziel) · Identity `ap3` | ⏳ **Bring-up offen (TP28-Nachtrag, 2026-08-30)** · Modell `cAPGi-5HaxD2HaxD`, Werkszustand · Uplink `ether1` → **core `ether21`** (Trunk seit 2026-08-30 ✅; Gerät hängt und hat Strom — `link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01) · Kanäle **`5180/Ce` + `2437`** (TP25-Plan 08-24) · 🔴 **nur 2 Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5` wie am hAP ax² · Dateien `routeros/tp28-ap3-bringup.rsc` + `-lifeline-abbau.rsc` | +| **cAP ax (ap3)** | **10.40.99.13** (VLAN99) · Identity `ap3` | ✅ **LIVE seit 2026-08-30** (TP28-Nachtrag) · `cAPGi-5HaxD2HaxD`, RouterOS **7.24.1** + RouterBOOT 7.24.1 (Flotte sonst 7.24 — Differenz bewusst, s. TP27) · reiner CAP, Manager `crs326-core` · Uplink `ether1` → **core `ether21`** (Trunk) · Kanäle **`5180/ax/Ce/I`** (5 GHz) + **`2437/ax`** (ch6) nach TP25-Plan · 🔴 **nur 2 Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out, **kein** Bridge-Port) → `ap.extra_ports=[]` · `device-mode=basic` (scheduler/fetch/romon **erlaubt**, anders als ap1/ap2) · Dateien `routeros/tp28-ap3-phase{1,2}-*.rsc` + `-service-accounts.rsc` + `-werksreste.rsc` | | **🔑 Einheitliches Admin-PW (2026-08-08)** | alle 4 MikroTik (core/branch/ap1/ap2) | user `admin`, gleiches PW (`~/.secrets/credentials.env` → `ROUTER_*_PASS`/`CRS326_*_PASS`; „Überlebensversicherung", User-Vorgabe). Werks-Defaults ap1/ap2 damit obsolet. | | NAS (nas-bf, DS923) | 10.40.20.10 | SSH `adm.christoph@10.40.20.10` (kein PW nötig) · Docker (Portainer endpointId=14) | | platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅ 2026-07-16: PG16, DB `nautobot`, Creds `postgres/.secrets`** | @@ -104,6 +105,41 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`, ## Kritische Gotchas - 📜 **~~ALLNET-Gotchas (1 Telnet-VTY, `save` nicht vergessen)~~ — gegenstandslos seit 2026-08-08** (Gerät ausgebaut). Nur noch historisch relevant, s. `switch/README.md`. +- 🔑 **CAPsMAN verteilt KEINE Login-Passwörter — nur WLAN-PSKs.** Zwei verschiedene Dinge heißen + „Passwort": die **WPA-Passphrase** (`/interface wifi security`) liegt am Manager und wird an die + CAPs verteilt ✅; der **Geräte-Login** (`/user`) ist eine **lokale Benutzer-DB auf jedem Gerät** ❌. + Beleg: ap1 trägt `admin`, `ha`, `nautobot` lokal, mit gerätelokalen `last-logged-in`-Zeitstempeln. + Das einheitliche Admin-PW ist eine **von Hand angewendete Konvention**, keine Automatik — und + logisch kann es nicht anders sein: ein Gerät *wird* erst zum CAP, nachdem sich jemand eingeloggt hat. +- 🔴 **Ein neuer CAP braucht `configuration.manager=capsman` auf JEDEM Master-Radio.** + `/interface wifi cap set enabled=yes` allein reicht nicht — ohne das Feld bietet der CAP seine + Radios dem Manager gar nicht erst an. **Symptom (2026-08-30, ap3):** `remote-cap print` zeigt + `state=Ok`, aber `/interface wifi radio print` am Manager listet für dieses Gerät **nichts**, und + weil die Provisioning-Regel dann nicht matcht, sucht man den Fehler in der `radio-mac`. Im + Werkszustand ist das Feld leer (= `local`). +- 🔌 **Werks-MikroTik am Trunk: `ether1` ist WAN, und das sieht aus wie ein Netzproblem.** Beim + cAP ax (und den hAPs) steht `ether1` per defconf in der Interface-Liste `WAN`. Folge: Discovery + aus (**kein MNDP → in `/ip neighbor` unsichtbar**), Input-TCP verworfen (22/80/8291/8728 **zu**), + **ICMP aber erlaubt** — es *pingt*, was sich wie „erreichbar" liest. Kein Registry-Eintrag und + kein MikroMCP hilft, denn Port 80 ist zu. **Der Weg rein ist die LAN-Seite** (`ether2` oder das + Werks-WLAN); dort `/interface list member remove [find where interface=ether1]` — das ist der Fix. +- 🔀 **`pvid` ist INGRESS, `tagged`/`untagged` ist EGRESS.** Ein Port mit `pvid=99`, der in der + *tagged*-Liste von VLAN99 steht, nimmt untagged an und antwortet **getaggt**. Symptom + (2026-08-30): ein DHCP-Lease hing ewig auf `offered` und ARP auf `failed` — der DISCOVER kam an, + der ACK nie. Für ein Werksgerät gehört der Port **untagged** in die VLAN-Zeile. +- 🕳️ **Ein neues Gerät ohne `secrets_group` erzeugt eine LÜCKE, keine Fehlermeldung.** Ohne + Secrets Group scheitert der Golden-Config-**Backup**-Job still; die *intended* config wird + trotzdem sauber gerendert, aber ohne Backup entstehen **keine Compliance-Einträge** — und die + Gesamtzahl bleibt unauffällig (bei uns 104/104), weil ein Gerät ohne Ergebnisse auch keine Drift + meldet. 🔴 **Merkregel: eine gleichbleibende Compliance-Zahl nach dem Hinzufügen eines Geräts ist + ein BEFUND, kein Erfolg.** Erst `130/130` bewies, dass ap3 wirklich geprüft wird. Zweite + Voraussetzung: die dedizierten Service-Accounts `nautobot`/`ha` (TP-PWD) existieren auf einem + Werksgerät nicht. +- 🔢 **Firewall-Regelnummern aus `print where` sind Handles, keine Positionen.** Nach einem + `place-before` zeigte `/ip firewall filter print terse where comment~"..."` die Nummer **60**, + während `drop` auf **59** stand — die Regel lag trotzdem davor. Beweis ist der **Zähler**: eine + Regel hinter dem `drop` kann nicht zählen. Wer nach der Zahl geht statt nach `print stats`, + „repariert" eine funktionierende Regel kaputt. - 🔌 **`crs326-core` hat KEIN PoE-out** (`/interface ethernet poe` existiert dort gar nicht, CRS326-24G-2S+). Ein AP an einem Core-Port braucht eigene Stromversorgung. Der **cAP ax** bringt dafür **Netzteil UND Gigabit-PoE-Injector im Karton** mit (Produktseite, 2026-08-30) — die diff --git a/docs/handoff-20260830-ap3.md b/docs/handoff-20260830-ap3.md new file mode 100644 index 0000000..f29be83 --- /dev/null +++ b/docs/handoff-20260830-ap3.md @@ -0,0 +1,82 @@ +# Handoff 2026-08-30 — ap3 (cAP ax) live, TP28 wieder geöffnet und durchgezogen + +## Was jetzt gilt + +**ap3 funkt.** MikroTik cAP ax (`cAPGi-5HaxD2HaxD`), `10.40.99.13/24` auf `vlan99`, Uplink +`ether1` → **`crs326-core ether21`**, reiner CAP unter `crs326-core`. Kanäle **`5180/ax/Ce/I`** +(5 GHz) und **`2437/ax`** (ch6) — am Gerät nachgemessen, nicht aus dem Plan abgeschrieben. + +**ap1 ist funkstill.** Es steht bis zu seinem Umzug ans Sideboard direkt neben ap3 und würde auf +5180 kollidieren. Es hat deshalb **keine Provisioning-Regel** am Manager; das Gerät bleibt +erreichbar, die Objekte `ch-*-ap1`/`cfg-main-*-ap1` bleiben für den Umzug stehen. + +**Compliance: 130/130, 0 Drifts über 5 Geräte.** + +## Was noch offen ist + +1. **TP25-Messrunde** — `bash docs/tp25-runde.sh ap3-bringup`, dann `docs/tp25-walk-messung.sh` + für den Gehweg Technik UG ↔ OG. Braucht einen Rundgang mit dem Telefon. + Leitfrage: **deckt ap3 allein ab, was ap1 abgedeckt hat, oder klafft bis zum Umzug eine Lücke?** +2. **Passwort-Rotation (TP30)** — das gemeinsame MikroTik-Admin-PW ist in dieser Session + verbrannt (ungefilterte Ausgabe des `mikromcp`-`env`-Blocks aus `~/.claude.json`). ap3 trägt es + bereits, hängt also in der Rotation mit drin. Fehlen zusätzlich in `~/.secrets/exports.zsh`: + `MCP_MIKROMCP_ROUTER_AP3_PASS`, `MCP_MIKROMCP_CRS326_CORE_PASS`, `MCP_MIKROMCP_CRS326_BRANCH_PASS`. +3. **MikroMCP-Neustart** — Registry (jetzt `bf-infra`, mit ap3) wird nur beim Start gelesen. +4. **Versions-Differenz** — ap3 läuft auf **7.24.1**, die anderen vier auf **7.24**. Bewusst: + 7.20.8 (Januar, long-term) wäre ein halbes Jahr Abstand zur `/interface wifi`-Implementierung + des Managers gewesen. Die Angleichung der Flotte ist ein eigener TP27-Durchgang — der + Core-Reboot ist ein Standort-Ausfall und damit Terminentscheidung. +5. **Kabel `ap3 ether2` → `core ether22`** kann raus. Seit Phase 2 ist `ether2` kein Bridge-Port + mehr, das Kabel ist inert. +6. **Offene Frage aus TP22:** auf den APs deckt **keine ComplianceRule** `/interface wifi` ab. + Genau dort sitzt `configuration.manager=capsman` — ein Feld, dessen Fehlen das WLAN eines APs + still verhindert (s. unten). Ob dafür eine Regel sinnvoll ist, ist noch nicht entschieden. + +## Die sechs Funde, die Zeit gekostet haben + +1. **`ether1` ist am Werksgerät WAN.** Discovery aus (kein MNDP), Input-TCP verworfen + (22/80/8291/8728 zu) — **aber ICMP erlaubt**. Es *pingt*, und das liest sich wie „erreichbar". + MikroMCP hilft nicht: es ist ein HTTP-Client auf Port 80, und der ist zu. Gegenprobe, dass der + Core nicht schuld war: Regel 44 `MAIN->MGMT` ist `action=accept` **ohne** Port-Einschränkung. +2. **`pvid` ist Ingress, `tagged`/`untagged` ist Egress.** Ein DHCP-Lease hing ewig auf `offered`, + ARP auf `failed`: `pvid=99` nahm den DISCOVER an, aber der Port stand in der *tagged*-Liste von + VLAN99 — der ACK ging getaggt raus, ein Werksgerät versteht das nicht. +3. **`cap enabled=yes` reicht nicht.** Jedes Master-Radio braucht zusätzlich + `configuration.manager=capsman`, sonst bietet der CAP seine Radios dem Manager gar nicht an: + `remote-cap print` zeigt `state=Ok`, `/interface wifi radio print` bleibt leer — und man sucht + den Fehler in der `radio-mac`. +4. **Ein neues Gerät ohne `secrets_group` erzeugt eine Lücke, keine Fehlermeldung.** Der Backup-Job + scheitert still, die *intended* config wird trotzdem gerendert, und ohne Backup entstehen keine + Compliance-Einträge. Die Zahl blieb bei **104/104** — unauffällig. 🔴 **Eine gleichbleibende + Compliance-Zahl nach dem Hinzufügen eines Geräts ist ein Befund, kein Erfolg.** Zweite + Voraussetzung: die Service-Accounts `nautobot`/`ha` gibt es auf einem Werksgerät nicht. +5. **CAPsMAN verteilt keine Login-Passwörter.** Zentral sind die **WLAN-PSKs**; der Geräte-Login + (`/user`) ist eine lokale DB pro Gerät. Das einheitliche Admin-PW ist eine von Hand angewendete + Konvention. +6. **Firewall-Regelnummern aus `print where` sind Handles, keine Positionen.** Nach `place-before` + zeigte die TEMP-Regel **60** neben `drop` auf **59** — lag aber davor. Beweis ist der Zähler + (`print stats`), nicht die Zahl. + +## Zwei Korrekturen an bestehender Doku + +- **Der Kanalplan war schon da.** TP25 hatte ihn am **2026-08-24** für genau dieses Gerät gerechnet + (DFS vom User genehmigt). Ich hatte ihn zunächst neu erfunden und wäre bei ch1 statt **ch6** + gelandet. Merkregel: den TP-Body lesen, nicht nur den Quick-Index. +- **Der Port war `ether21`, nicht `ether22`.** Der Befund lag in den Daten: in der dynamischen + `pvid`-VLAN1-Zeile stand `current-untagged=…,ether21`. Gegenprobe ist + `/interface bridge host print where interface=etherN` — dort steht die MAC des Gegenübers. +- **ap1 stand in Nautobot auf „Wohnzimmer Sideboard"**, physisch aber in **Technik UG**. Der + Eintrag war der Ziel-Standort nach dem geplanten Umzug. Korrigiert. + +## Dateien + +| Datei | Zweck | +|---|---| +| `routeros/tp28-ap3-phase1-erreichbar.rsc` | über die LAN-Seite: `ether1` aus WAN, Mgmt auf `vlan99` | +| `routeros/tp28-ap3-phase2-cap.rsc` | über `vlan99`: Werks-WLAN weg, CAP-Modus, `vlan-filtering=yes` | +| `routeros/tp28-ap3-service-accounts.rsc` | `nautobot`/`ha` + Gruppen (sonst kein GC-Backup) | +| `routeros/tp28-ap3-werksreste.rsc` | die drei Drifts aus dem ersten Compliance-Lauf | +| `nautobot/ingest-ap3-device.py` | DeviceType, Device, 16 Interfaces, IP, SecretsGroup | + +Alle vier `.rsc` tragen Platzhalter statt Secrets; die echten Dateien werden zur Laufzeit ins +Scratchpad generiert und danach gelöscht. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 3a22dec..03c31f5 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -70,12 +70,15 @@ liest, liest ihn als Erstes: er wird bei jeder Änderung mitgezogen, nicht nur d (an core `ether15`), ap2 **`10.40.99.12`** (an branch `ether24`), beide **reine CAPs** — `cap enabled=yes`, `discovery-interfaces=vlan99`. Die APs halten **keine eigene WLAN-Config** mehr; Kanäle, SSIDs, Security und Steering kommen vom Manager. 802.11r/k/v aktiv, `ft-over-ds=yes`. - 🆕 **Dritter AP `ap3` (cAP ax) in Arbeit seit 2026-08-30** — **TP28 dafür wieder geöffnet**. - Core-seitig fertig: **`ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`) und im - GC-Template nachgezogen, Compliance weiter **104/104 / 0 Drifts**. ap3 hängt bereits am Kabel und hat Strom (`link-ok`, MAC `D0:EA:11:FF:16:9D` = Label-E01), - der Bring-up steht aber noch aus. Ziel-IP **`10.40.99.13`**, Kanäle **`5180/Ce` + `2437`** nach dem - TP25-Kanalplan vom 2026-08-24. ⚠️ **ap1s Radios gehen vorher aus** (steht bis zum Umzug neben ap3); - ap1 kehrt später auf `5260/Ce` (DFS) zurück, ap2 auf `5300/Ce`. + 🆕 **Dritter AP `ap3` (cAP ax) ✅ LIVE seit 2026-08-30** (TP28 dafür wieder geöffnet): + `10.40.99.13`, Uplink `ether1` → **core `ether21`** (Trunk), reiner CAP, **RouterOS 7.24.1** + (die anderen vier auf 7.24 — Angleichung ist ein eigener TP27-Durchgang). Kanäle + **`5180/ax/Ce/I`** + **`2437/ax`** (ch6) nach dem TP25-Plan vom 2026-08-24, am Gerät nachgemessen. + 🔴 **ap1 ist funkstill** — es steht bis zu seinem Umzug ans Sideboard direkt neben ap3 und würde + auf 5180 kollidieren; es hat deshalb bewusst **keine Provisioning-Regel** am Manager, bleibt aber + erreichbar. Beim Umzug: ap1 → `5260/Ce` (Band B, DFS, **Frequenz-Liste statt Einzelwert**), + ap2 → `5300/Ce`, Uplink ap1 → `core ether3`. + **Compliance jetzt 130/130 / 0 Drifts über 5 Geräte** (vorher 104/104 über 4). - **SSDP-Relay** (TP7.1, **nachgebessert 08-25**): LXC **108 `mcast-relay`** auf pve, zweibeinig `10.40.10.21` (VLAN10) + `10.40.40.21` (VLAN40) — dafür trägt `core ether13` beide VLANs; mDNS macht weiterhin der Core. **Läuft seit 08-25 ohne `--ssdpUnicastAddr`** — die Flagge verwarf die M-SEARCH @@ -242,7 +245,7 @@ nachmessen und dann entscheiden**. Blocker gibt es keine mehr. TP5 ist in der Na > | TP23 | ✅ | DNS-Policy (Stufe 1+2 ✅) | 2026-08-26 | > | TP24 | 🔜 | DC-USV | — | > | TP25 | ✅ | WLAN-Stabilisierung (Kern erledigt) | 2026-08-24 | -> | TP28 | ⏳ | WLAN/CAPsMAN — **wieder geöffnet**: dritter AP `ap3` (cAP ax) | Kern ✅ 2026-08-18 | +> | TP28 | ✅ | WLAN/CAPsMAN — dritter AP `ap3` (cAP ax) **live** | 2026-08-30 | > | TP29 | 💡 | Anwesenheitserkennung (Idee) | — | > | TP30 | 🔜 | Credentials (Etappe 1 ✅, Rest gebündelt) | — | > | TP31 | ✅ | Reverse Proxy Caddy (Schritt 1–4 ✅) | — | @@ -1345,7 +1348,7 @@ die Redundanzfrage oben. -### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ⏳ **WIEDER GEÖFFNET 2026-08-30: dritter AP `ap3`** +### TP28 — WLAN über CAPsMAN auf `crs326-core` ▸ ✅ **ap3 LIVE (2026-08-30)** — wieder geöffnet und durchgezogen > **Der CAPsMAN-Kern ist und bleibt ✅ KOMPLETT (2026-08-18)** — der Abschnitt darunter ist > unverändert der Archivtext inkl. Post-Mortem. Wieder geöffnet wurde das TP am 2026-08-30, @@ -1503,19 +1506,36 @@ Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört > Bridge-Host-Tabelle stellen.** `/interface bridge host print where interface=etherN` nennt die > MAC des Gegenübers — hier `D0:EA:11:FF:16:9D`, exakt die `E01` vom ap3-Label. -##### Stand 2026-08-30 (diese Session) -- ✅ Backups core+ap1 (`pre-ap3-20260830.backup`/`.rsc` auf den Geräten), Compliance-Ausgangsstand - **104/104, 0 Drifts**. -- ✅ **`core ether21` ist Trunk**: tagged in 10/20/30/40/50/90/99, Muster = `ether15` (ap1). - `pvid=1`/`frame-types=admit-all` unverändert; das VLAN1-Bein entsteht dynamisch aus dem pvid. -- ✅ **GC-Template nachgezogen und gepusht** (Commit `e25d654b`): `ether21` im Core-L2-Block, und - der AP-Zweig rendert die Bridge-Ports jetzt aus **`ap.extra_ports`** statt hartkodiert - `ether3/4/5`. **Compliance danach wieder 104/104, 0 Drifts** — das beweist, dass ap1/ap2 - zeichengleich rendern. -- ✅ Bring-up-Dateien geschrieben: [`routeros/tp28-ap3-bringup.rsc`](../routeros/tp28-ap3-bringup.rsc) - + [`routeros/tp28-ap3-lifeline-abbau.rsc`](../routeros/tp28-ap3-lifeline-abbau.rsc). -- 🔜 **Offen:** physischer Bring-up (Strom + `/import`), ap1-Radios aus, CAPsMAN-Objekte für ap3, - Nautobot-Adoption, PW-Rotation, MikroMCP-Registry, Messrunde. +##### Stand 2026-08-30 — ✅ DURCH +- ✅ **`core ether21` ist Trunk** (10/20/30/40/50/90/99 tagged, Muster `ether15`), GC-Template + nachgezogen; AP-Zweig rendert Bridge-Ports aus **`ap.extra_ports`** statt hartkodiert. +- ✅ **Zugang über `ap3 ether2` → `core ether22`** (Patchkabel). Über `ether1` war ap3 + grundsätzlich nicht administrierbar, s. Gotchas unten. Loop-Schutz: `ether21` war während + Phase 1 abgeschaltet, danach im selben Befehl getauscht (`ether22` aus, `ether21` an). +- ✅ **Bring-up in zwei Phasen**: Phase 1 (`ether1` aus der WAN-Liste, `10.40.99.13/24` auf + `vlan99`, Flotten-PW) über das Kabel, Phase 2 (Werks-WLAN weg, CAP-Modus, + `slaves-datapath`, `vlan-filtering=yes`) über `vlan99`. +- ✅ **RouterOS 7.20.8 → 7.24.1 + RouterBOOT 7.24.1** (zwei Reboots, TP27-Kriterium + `current-firmware = upgrade-firmware`). ⚠️ Die anderen vier stehen auf **7.24**; die + Angleichung auf 7.24.1 ist ein eigener TP27-Durchgang (Core-Reboot = Standort-Ausfall). +- ✅ **ap1 funkstill** (Provisioning-Regeln entfernt + `remote-cap provision`), gegengeprüft + **am CAP**: nur noch `wifi1`/`wifi2` mit `running=false`, keine dynamischen VAPs. +- ✅ **ap3 funkt**: `cap-wifi5` = **`5180/ax/Ce/I`**, `cap-wifi6` = **`2437/ax`**, voller + VAP-Satz (2 Master + 7 Slaves), alle `running=true`. +- ✅ **Datapath bewiesen**: 20 IOT-Clients auf ap3, Ping über VLAN30 mit **0 % Loss**. + *(Bewusst nicht über die Registration-Table abgenommen — die zeigte im 14-h-Ausfall + 25 zufriedene Clients. Bei statisch reservierten Shellys erzeugt ein Roam keine neue + DHCP-Lease, deshalb ist hier Datenverkehr das belastbare Signal.)* +- ✅ **Nautobot**: DeviceType `cAP ax`, Device `ap3` mit 16 Interfaces, `primary_ip4`, + `secrets_group`, ConfigContext (`5g=ch36 2g=ch6 extra_ports=[]`). + Nebenkorrektur: ap1 stand auf „Wohnzimmer Sideboard", steht aber in **Technik UG**. +- ✅ **Compliance 130/130, 0 Drifts über 5 Geräte.** Die drei Werksreste, die der erste + Lauf mit ap3 gemeldet hat (defconf-DNS, 11 defconf-Firewallregeln, fehlende + `/ip service`-Härtung), sind mit `routeros/tp28-ap3-werksreste.rsc` abgeräumt. +- 🔜 **Offen:** TP25-Messrunde (`bash docs/tp25-runde.sh ap3-bringup`) — braucht einen + Rundgang. Leitfrage: deckt ap3 allein ab, was ap1 abgedeckt hat? +- 🔜 **Offen:** Passwort-Rotation (TP30) — das gemeinsame Admin-PW ist in dieser Session + verbrannt; ap3 trägt es bereits mit, hängt also in der Rotation mit drin. ##### Was am cAP ax anders ist als am hAP ax² — und was daran wehtut - 🔴 **Nur ZWEI Ethernet-Ports** (`ether1` PoE-in, `ether2` PoE-out) — kein `ether3/4/5`. Der diff --git a/nautobot/ingest-ap-configcontext.py b/nautobot/ingest-ap-configcontext.py index 6655e72..2b8fe56 100644 --- a/nautobot/ingest-ap-configcontext.py +++ b/nautobot/ingest-ap-configcontext.py @@ -97,9 +97,22 @@ DEVICE_CHANNELS = { # 5 GHz mit 40 MHz: ap1 ch36 belegt 5170-5210 (36+40), ap2 ch44 belegt 5210-5250 # (44+48) -> benachbart, ueberlappungsfrei, beide mit /I-Indoor-Freigabe und 18 dBm. # ap2 war bis 2026-08-17 auf ch149 (5745, ETSI-Band C) und dort auf 9 dBm gedrosselt. - # 2,4 GHz: ch1/ch11 mit 20 MHz -> ueberlappungsfrei, ch6 bleibt fuer die Nachbarschaft frei. + # ap3 (cAP ax, seit 2026-08-30) uebernimmt in Technik UG ch36; ap1 steht bis zu seinem + # Umzug daneben und ist deshalb FUNKSTILL (keine Provisioning-Regel am Manager) -- + # seine Kanaele bleiben hier stehen, weil sie beim Umzug wieder gebraucht werden. + # 2,4 GHz: mit ap3 auf ch6 ist 1/6/11 erstmals vollstaendig ueberlappungsfrei. "ap1": {"5g": 36, "2g": 1}, "ap2": {"5g": 44, "2g": 11}, + "ap3": {"5g": 36, "2g": 6}, +} + +# --- Per-Device: Abweichungen vom ap-common-Default ----------------------------------- +# Nur eintragen, was wirklich abweicht. Nautobot merged Dicts TIEF, Listen ERSETZEND -- +# eine leere Liste hier loescht den Default, genau das ist beim cAP ax gewollt. +DEVICE_OVERRIDES = { + # cAP ax hat nur ether1 (PoE-in) + ether2 (PoE-out); ether2 ist bewusst kein + # Bridge-Port. Es bleibt also KEIN zusaetzlicher Bridge-Port uebrig. + "ap3": {"extra_ports": []}, } CTX_NAME = "ap-common" @@ -201,14 +214,15 @@ def main(): if not d: print(f" !! Device {dev} nicht gefunden") continue - want = {"ap": {"channels": ch}} + ov = "".join(f" {k}={v}" for k, v in DEVICE_OVERRIDES.get(dev, {}).items()) + want = {"ap": {"channels": ch, **DEVICE_OVERRIDES.get(dev, {})}} if d.get("local_config_context_data") == want: - print(f" = {dev} 5g=ch{ch['5g']} 2g=ch{ch['2g']} (unveraendert)") + print(f" = {dev} 5g=ch{ch['5g']} 2g=ch{ch['2g']}{ov} (unveraendert)") elif DRY: - print(f" ~ {dev} -> 5g=ch{ch['5g']} 2g=ch{ch['2g']} (dry-run)") + print(f" ~ {dev} -> 5g=ch{ch['5g']} 2g=ch{ch['2g']}{ov} (dry-run)") else: req("PATCH", f"/dcim/devices/{d['id']}/", {"local_config_context_data": want}) - print(f" ~ {dev} -> 5g=ch{ch['5g']} 2g=ch{ch['2g']}") + print(f" ~ {dev} -> 5g=ch{ch['5g']} 2g=ch{ch['2g']}{ov}") if DRY: print("\nDRY-RUN - nichts geschrieben. Mit --apply ausfuehren.") diff --git a/nautobot/ingest-ap3-device.py b/nautobot/ingest-ap3-device.py new file mode 100644 index 0000000..cd8054c --- /dev/null +++ b/nautobot/ingest-ap3-device.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +r"""BF-Netzwerk -> Nautobot: ap3 (cAP ax) als Device anlegen. TP28-Nachtrag, 2026-08-30. + +WOZU +---- +ap3 ist der dritte Access Point und laeuft seit dem 2026-08-30 als reiner CAP unter +`crs326-core`. Damit Golden Config ihn rendern und pruefen kann, braucht er ein +DCIM-Modell: DeviceType, Device, Interfaces, Mgmt-IP. + +MUSTER = ap1 (per GraphQL gelesen, nicht geraten -- `tagged_vlans` liest sich per REST +als null, das war schon in `ingest-ap-rebuild.py` eine Falle). + +DELTAS ZU ap1/ap2 (hAP ax2), und beide sind der Grund fuer die ganze Uebung: + - cAP ax hat nur ZWEI Ethernet-Ports (ether1 PoE-in, ether2 PoE-out). Kein ether3/4/5. + Deshalb `ap.extra_ports = []` in local_config_context_data; das GC-Template rendert + die Bridge-Ports seit 2026-08-30 aus diesem Schluessel statt hartkodiert. + - ether2 ist bewusst KEIN Bridge-Port (Zielzustand wie ap2). + +NEBENKORREKTUR: ap1 stand in Nautobot auf "BF -> Wohnzimmer Sideboard", steht physisch +aber in Technik UG (User-Bestaetigung 2026-08-30). Der Sideboard-Eintrag war der ZIEL- +Standort nach dem geplanten Umzug, nicht der Ist-Zustand. Wird hier mitkorrigiert; beim +tatsaechlichen Umzug wandert er zurueck auf Sideboard. + + export NAUTOBOT_TOKEN=... + python3 nautobot/ingest-ap3-device.py # Dry-Run + python3 nautobot/ingest-ap3-device.py --apply # schreiben +""" +import json, os, sys, urllib.error, urllib.parse, urllib.request + +BASE = "http://10.40.20.30:8080/api" +TOKEN = os.environ.get("NAUTOBOT_TOKEN", "") +DRY = "--apply" not in sys.argv + +DEV = "ap3" +MODEL = "cAP ax" # board-name, wie "hAP ax2" bei ap1/ap2 +MANUFACTURER = "MikroTik" +LOCATION = "BF → Technik UG" +ROLE = "Wireless AP" +PLATFORM = "RouterOS" +MGMT_IP = "10.40.99.13/24" +TRUNK_VLANS = [10, 20, 30, 40, 50, 90, 99] + +# Interfaces in Anlegereihenfolge: bridge zuerst (die anderen referenzieren sie). +# radio-mac steht in der description, weil die CAPsMAN-Provisionierung am Core genau +# daran haengt -- wer das Geraet tauscht, findet hier, was er am Manager nachziehen muss. +IFACES = [ + ("bridge", "bridge", None, None, [], None, None, "vlan-filtering=yes; VLAN1 untagged, 10-99 tagged"), + ("ether1", "1000base-t", "tagged", 1, TRUNK_VLANS, None, None, "Uplink-Trunk (PoE-in) -> crs326-core ether21"), + ("ether2", "1000base-t", None, None, [], None, None, "PoE-out, frei. KEIN Bridge-Port - cAP ax hat nur 2 Ports"), + ("lo", "virtual", None, None, [], None, None, "loopback"), + ("vlan99", "virtual", "tagged", None, [99], None, "bridge", "Mgmt SVI (VLAN99)"), + ("wifi1", "ieee802.11ax", "access", 10, [], "bridge", None, "5GHz master - dirtyvader-main (radio-mac D0:EA:11:FF:16:9F)"), + ("wifi2", "ieee802.11ax", "access", 10, [], "bridge", None, "2.4GHz master - dirtyvader-main (radio-mac D0:EA:11:FF:16:A0)"), + ("w5-infra", "ieee802.11ax", "access", 20, [], "bridge", None, "5G slave - dirtyvader-infra"), + ("w5-media", "ieee802.11ax", "access", 40, [], "bridge", None, "5G slave - dirtyvader-media"), + ("w5-untrusted", "ieee802.11ax", "access", 50, [], "bridge", None, "5G slave - dirtyvader-untrusted"), + ("w5-guest", "ieee802.11ax", "access", 90, [], "bridge", None, "5G slave - dirtyvader-guest"), + ("w2-infra", "ieee802.11ax", "access", 20, [], "bridge", None, "2.4G slave - dirtyvader-infra"), + ("w2-iot", "ieee802.11ax", "access", 30, [], "bridge", None, "2.4G slave - dirtyvader-iot (nur 2.4GHz)"), + ("w2-media", "ieee802.11ax", "access", 40, [], "bridge", None, "2.4G slave - dirtyvader-media"), + ("w2-untrusted", "ieee802.11ax", "access", 50, [], "bridge", None, "2.4G slave - dirtyvader-untrusted"), + ("w2-guest", "ieee802.11ax", "access", 90, [], "bridge", None, "2.4G slave - dirtyvader-guest"), +] + + +def req(method, path, data=None): + url = path if path.startswith("http") else f"{BASE}{path}" + body = json.dumps(data).encode() if data is not None else None + r = urllib.request.Request(url, data=body, method=method) + r.add_header("Authorization", f"Token {TOKEN}") + r.add_header("Content-Type", "application/json") + r.add_header("Accept", "application/json") + try: + with urllib.request.urlopen(r, timeout=30) as resp: + raw = resp.read() + return json.loads(raw) if raw else {} + except urllib.error.HTTPError as e: + print(f"!! {method} {url}\n -> {e.code} {e.read().decode()[:500]}") + raise + + +def find(path, **params): + q = "&".join(f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items()) + res = req("GET", f"{path}?{q}&limit=2") + return res["results"][0] if res.get("count") else None + + +def main(): + if not TOKEN: + sys.exit("!! NAUTOBOT_TOKEN nicht gesetzt") + mode = "DRY-RUN" if DRY else "APPLY" + print(f"== ap3 -> Nautobot ({mode}) ==\n") + + status_active = find("/extras/statuses", name="Active") + role = find("/extras/roles", name=ROLE) + plat = find("/dcim/platforms", name=PLATFORM) + loc = find("/dcim/locations", name="Technik UG") + manu = find("/dcim/manufacturers", name=MANUFACTURER) + # 🔴 OHNE SecretsGroup kein Backup. Das Geraet landet trotzdem in der GC-DynamicGroup, + # die intended config wird sauber gerendert -- nur der Backup-Job kann sich nicht + # anmelden, und ohne Backup entstehen KEINE Compliance-Eintraege. Die Gesamtzahl + # bleibt dann unauffaellig (104/104), weil ein Geraet ohne Ergebnisse auch keine + # Drift meldet. Aufgeschlagen am 2026-08-30 bei ap3. + secrets = find("/extras/secrets-groups", name="RouterOS Nornir") + for label, obj in [("Status Active", status_active), (f"Role {ROLE}", role), + (f"Platform {PLATFORM}", plat), ("Location Technik UG", loc), + (f"Manufacturer {MANUFACTURER}", manu), + ("SecretsGroup 'RouterOS Nornir'", secrets)]: + if not obj: + sys.exit(f"!! {label} nicht gefunden") + vlans = {v["vid"]: v["id"] for v in req("GET", "/ipam/vlans/?limit=50")["results"]} + + # --- DeviceType --- + dt = find("/dcim/device-types", model=MODEL) + if dt: + print(f" = DeviceType {MODEL!r}") + elif DRY: + print(f" + DeviceType {MODEL!r} (dry-run)") + else: + dt = req("POST", "/dcim/device-types/", {"manufacturer": manu["id"], "model": MODEL, "u_height": 0}) + print(f" + DeviceType {MODEL!r}") + + # --- Device --- + dev = find("/dcim/devices", name=DEV) + if dev: + print(f" = Device {DEV}") + elif DRY: + print(f" + Device {DEV} (dry-run)") + else: + dev = req("POST", "/dcim/devices/", { + "name": DEV, "device_type": dt["id"], "role": role["id"], "platform": plat["id"], + "location": loc["id"], "status": status_active["id"], "secrets_group": secrets["id"], + "comments": "cAP ax (cAPGi-5HaxD2HaxD), reiner CAP unter crs326-core. Bring-up 2026-08-30 (TP28-Nachtrag).", + }) + print(f" + Device {DEV}") + + if DRY and not dev: + print("\n (Interfaces/IP koennen im Dry-Run nicht geplant werden, solange das Device fehlt)") + print("\nDRY-RUN - nichts geschrieben. Mit --apply ausfuehren.") + return + + # SecretsGroup auch nachziehen, wenn das Device schon existierte + if dev and not DRY: + cur = req("GET", f"/dcim/devices/{dev['id']}/") + if (cur.get("secrets_group") or {}).get("id") != secrets["id"]: + req("PATCH", f"/dcim/devices/{dev['id']}/", {"secrets_group": secrets["id"]}) + print(" ~ SecretsGroup 'RouterOS Nornir' gesetzt") + + # --- Interfaces --- + print(f"\n Interfaces ({len(IFACES)}):") + have = {i["name"]: i for i in req("GET", f"/dcim/interfaces/?device={DEV}&limit=100")["results"]} + ids = {n: o["id"] for n, o in have.items()} + for name, typ, mode_, untag, tagged, bridge, parent, descr in IFACES: + payload = { + "device": dev["id"], "name": name, "type": typ, "status": status_active["id"], + "enabled": True, "description": descr, + } + if mode_: + payload["mode"] = mode_ + if untag is not None: + payload["untagged_vlan"] = vlans[untag] + if tagged: + payload["tagged_vlans"] = [vlans[v] for v in tagged] + if bridge: + payload["bridge"] = ids.get(bridge) + if parent: + payload["parent_interface"] = ids.get(parent) + if name in have: + print(f" = {name}") + continue + if DRY: + print(f" + {name:<13} type={typ} mode={mode_} untag={untag} tagged={tagged} (dry-run)") + continue + new = req("POST", "/dcim/interfaces/", payload) + ids[name] = new["id"] + print(f" + {name:<13} type={typ} mode={mode_} untag={untag} tagged={tagged}") + + # --- Mgmt-IP + primary_ip4 --- + print("\n Mgmt-IP:") + ip = find("/ipam/ip-addresses", address=MGMT_IP.split("/")[0]) + if not ip: + if DRY: + print(f" + {MGMT_IP} (dry-run)") + else: + ns = find("/ipam/namespaces", name="Global") + ip = req("POST", "/ipam/ip-addresses/", { + "address": MGMT_IP, "status": status_active["id"], "type": "host", + "namespace": ns["id"], "description": "ap3 Mgmt (VLAN99)"}) + print(f" + {MGMT_IP}") + else: + print(f" = {MGMT_IP}") + + if not DRY and ip and ids.get("vlan99"): + assigned = req("GET", f"/ipam/ip-address-to-interface/?ip_address={ip['id']}&interface={ids['vlan99']}") + if assigned.get("count"): + print(" = Zuordnung vlan99") + else: + req("POST", "/ipam/ip-address-to-interface/", {"ip_address": ip["id"], "interface": ids["vlan99"]}) + print(" + Zuordnung vlan99") + cur = req("GET", f"/dcim/devices/{dev['id']}/") + if (cur.get("primary_ip4") or {}).get("id") != ip["id"]: + req("PATCH", f"/dcim/devices/{dev['id']}/", {"primary_ip4": ip["id"]}) + print(" ~ primary_ip4 gesetzt") + else: + print(" = primary_ip4") + + # --- Nebenkorrektur ap1-Standort --- + print("\n Nebenkorrektur ap1-Standort:") + ap1 = find("/dcim/devices", name="ap1") + if ap1: + cur = req("GET", f"/dcim/devices/{ap1['id']}/?depth=1") + curloc = (cur.get("location") or {}).get("name") + if curloc == "Technik UG": + print(" = ap1 steht bereits auf Technik UG") + elif DRY: + print(f" ~ ap1: {curloc!r} -> 'Technik UG' (dry-run)") + else: + req("PATCH", f"/dcim/devices/{ap1['id']}/", {"location": loc["id"]}) + print(f" ~ ap1: {curloc!r} -> 'Technik UG'") + + if DRY: + print("\nDRY-RUN - nichts geschrieben. Mit --apply ausfuehren.") + + +if __name__ == "__main__": + main() diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2 index 9862ba3..2b76313 100644 --- a/nautobot/mikrotik_routeros.j2 +++ b/nautobot/mikrotik_routeros.j2 @@ -184,6 +184,12 @@ /interface wifi channel add band=5ghz-ax frequency=5220 name=ch-5g-ap2 width=20/40mhz /interface wifi channel add band=2ghz-ax frequency=2412 name=ch-2g-ap1 width=20mhz /interface wifi channel add band=2ghz-ax frequency=2462 name=ch-2g-ap2 width=20mhz +# ap3 (cAP ax, Technik UG) seit 2026-08-30. Kanaele aus dem TP25-Plan vom 2026-08-24: +# Band A (5150-5250) ist das einzige non-DFS-Band mit vollen 23 dBm; ap3 bekommt es, weil +# seine Flaeche von keinem anderen AP mitgetragen wird. 2437 = ch6 -> mit ap1 (ch1) und +# ap2 (ch11) ist 2,4 GHz erstmals vollstaendig ueberlappungsfrei. +/interface wifi channel add band=5ghz-ax frequency=5180 name=ch-5g-ap3 width=20/40mhz +/interface wifi channel add band=2ghz-ax frequency=2437 name=ch-2g-ap3 width=20mhz /interface wifi datapath add bridge=bridge name=dp-main vlan-id=10 /interface wifi datapath add bridge=bridge name=dp-infra vlan-id=20 /interface wifi datapath add bridge=bridge name=dp-iot vlan-id=30 @@ -208,12 +214,22 @@ /interface wifi configuration add channel=ch-5g-ap2 country=Austria datapath=dp-main mode=ap name=cfg-main-5g-ap2 security=sec-main ssid=dirtyvader-main steering=st-main /interface wifi configuration add channel=ch-2g-ap1 country=Austria datapath=dp-main mode=ap name=cfg-main-2g-ap1 security=sec-main ssid=dirtyvader-main steering=st-main /interface wifi configuration add channel=ch-2g-ap2 country=Austria datapath=dp-main mode=ap name=cfg-main-2g-ap2 security=sec-main ssid=dirtyvader-main steering=st-main +/interface wifi configuration add channel=ch-5g-ap3 country=Austria datapath=dp-main mode=ap name=cfg-main-5g-ap3 security=sec-main ssid=dirtyvader-main steering=st-main +/interface wifi configuration add channel=ch-2g-ap3 country=Austria datapath=dp-main mode=ap name=cfg-main-2g-ap3 security=sec-main ssid=dirtyvader-main steering=st-main /interface wifi configuration add country=Austria datapath=dp-guest mode=ap name=cfg-guest security=sec-guest ssid=dirtyvader-guest steering=st-guest /interface wifi capsman set ca-certificate=auto certificate=auto enabled=yes interfaces=vlan99 upgrade-policy=none -/interface wifi provisioning add action=create-dynamic-enabled comment="ap1 5GHz" master-configuration=cfg-main-5g-ap1 radio-mac=D0:EA:11:4D:40:E5 slave-configurations=cfg-media,cfg-untrusted,cfg-guest -/interface wifi provisioning add action=create-dynamic-enabled comment="ap1 2.4GHz" master-configuration=cfg-main-2g-ap1 radio-mac=D0:EA:11:4D:40:E6 slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest +# 🔴 ap1 hat BEWUSST keine Provisioning-Zeile (2026-08-30). ap3 hat in Technik UG seinen +# Platz uebernommen und funkt dort auf 5180; ap1 steht bis zu seinem Umzug ans Sideboard +# direkt daneben und wuerde auf demselben Kanal kollidieren. Ohne passende Regel legt +# CAPsMAN fuer ein Radio gar nichts an -> es bleibt still, das Geraet aber erreichbar. +# Die Objekte `ch-5g-ap1`/`ch-2g-ap1` und `cfg-main-*-ap1` bleiben absichtlich stehen: +# beim Umzug bekommt ap1 laut TP25 `5260/Ce` (Band B, DFS) -- dann werden nur diese +# zwei Zeilen wieder eingesetzt und das Channel-Objekt auf eine Frequenz-LISTE gestellt +# (mit nur einem Wert waere eine Radar-Erkennung ein SPOF). /interface wifi provisioning add action=create-dynamic-enabled comment="ap2 5GHz" master-configuration=cfg-main-5g-ap2 radio-mac=D0:EA:11:4D:40:3E slave-configurations=cfg-media,cfg-untrusted,cfg-guest /interface wifi provisioning add action=create-dynamic-enabled comment="ap2 2.4GHz" master-configuration=cfg-main-2g-ap2 radio-mac=D0:EA:11:4D:40:3F slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest +/interface wifi provisioning add action=create-dynamic-enabled comment="ap3 5GHz" master-configuration=cfg-main-5g-ap3 radio-mac=D0:EA:11:FF:16:9F slave-configurations=cfg-media,cfg-untrusted,cfg-guest +/interface wifi provisioning add action=create-dynamic-enabled comment="ap3 2.4GHz" master-configuration=cfg-main-2g-ap3 radio-mac=D0:EA:11:FF:16:A0 slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest # [6c] WLAN ACCESS-LIST — der Soll-Zustand ist LEER, und das ist eine Aussage. # diff --git a/routeros/tp28-ap3-phase2-cap.rsc b/routeros/tp28-ap3-phase2-cap.rsc index 489680c..f60dcc9 100644 --- a/routeros/tp28-ap3-phase2-cap.rsc +++ b/routeros/tp28-ap3-phase2-cap.rsc @@ -64,6 +64,15 @@ # Das Objekt traegt bewusst KEINE vlan-id: die liefert der Manager pro VAP. /interface wifi datapath add name=dp-caps-slaves bridge=bridge comment="TP28-fix: bridge fuer CAPsMAN-Slave-VAPs" /interface wifi cap set enabled=yes discovery-interfaces=vlan99 slaves-datapath=dp-caps-slaves +# 🔴 `cap enabled=yes` REICHT NICHT. Jedes Master-Radio muss zusaetzlich selbst auf +# `configuration.manager=capsman` stehen, sonst BIETET der CAP seine Radios dem +# Manager gar nicht an. Symptom (2026-08-30 live aufgeschlagen): `remote-cap print` +# zeigt `state=Ok`, aber `/interface wifi radio print` am Manager listet fuer dieses +# Geraet NICHTS -- und weil die Provisioning-Regel dann nicht matcht, sucht man den +# Fehler in der radio-mac statt an dieser Stelle. ap1/ap2 tragen es seit dem +# TP28-Cutover, im Werkszustand ist es leer (= local). +/interface wifi set [find name=wifi1] configuration.manager=capsman +/interface wifi set [find name=wifi2] configuration.manager=capsman # --- (6) ZULETZT scharf schalten. Vorher waere der Rest der Datei nicht angekommen. --- /interface bridge set [find name=bridge] vlan-filtering=yes diff --git a/routeros/tp28-ap3-service-accounts.rsc b/routeros/tp28-ap3-service-accounts.rsc new file mode 100644 index 0000000..87397c3 --- /dev/null +++ b/routeros/tp28-ap3-service-accounts.rsc @@ -0,0 +1,36 @@ +# ===================================================================================== +# ap3 — dedizierte Service-Accounts nachziehen. TP28-Nachtrag, 2026-08-30. +# +# BEFUND: nach dem Bring-up rendert Golden Config die intended config fuer ap3 sauber, +# aber der BACKUP-Job scheitert -- Nautobot kommt per SSH nicht auf das Geraet. Ursache +# ist kein Netz- und kein Firewall-Problem: ap1/ap2/core/branch tragen seit TP-PWD +# (2026-08-23) dedizierte Service-Accounts `nautobot` und `ha`, ein Werksgeraet hat nur +# `admin`. Ohne den User gibt es kein Backup, ohne Backup keine Compliance-Eintraege -- +# und die Gesamtzahl bleibt unauffaellig bei 104/104, weil ein Geraet ohne Ergebnisse +# eben auch keine Drift meldet. 🔴 Merkregel: eine gleichbleibende Compliance-Zahl nach +# dem Hinzufuegen eines Geraets ist ein BEFUND, kein Erfolg. +# +# Policies 1:1 von ap1 uebernommen (live gelesen, nicht abgeschrieben): +# grp-ha = read,test,api,rest-api + alles andere explizit aus +# grp-nautobot = ssh,read,write,test,sensitive + alles andere explizit aus +# (write, weil Golden Config auch deployen koennen muss) +# +# `__HA_PW__` / `__NB_PW__` sind PLATZHALTER. Die echte Datei wird zur Laufzeit aus +# ~/.secrets/credentials.env (MIKROTIK_HA_PASS / MIKROTIK_NAUTOBOT_PASS) ins Scratchpad +# generiert und danach geloescht -- die Werte stehen nie in diesem Repo. +# ===================================================================================== + +:if ([:len [/user group find where name="grp-ha"]]=0) do={ + /user group add name=grp-ha policy=read,test,api,rest-api,!local,!telnet,!ssh,!ftp,!reboot,!write,!policy,!winbox,!password,!web,!sniff,!sensitive,!romon +} +:if ([:len [/user group find where name="grp-nautobot"]]=0) do={ + /user group add name=grp-nautobot policy=ssh,read,write,test,sensitive,!local,!telnet,!ftp,!reboot,!policy,!winbox,!password,!web,!sniff,!api,!romon,!rest-api +} +:if ([:len [/user find where name="ha"]]=0) do={ + /user add name=ha group=grp-ha password="__HA_PW__" comment="TP-PWD 2026-08-23: dedizierter Service-Account" +} else={ /user set [find name="ha"] group=grp-ha password="__HA_PW__" } +:if ([:len [/user find where name="nautobot"]]=0) do={ + /user add name=nautobot group=grp-nautobot password="__NB_PW__" comment="TP-PWD 2026-08-23: dedizierter Service-Account" +} else={ /user set [find name="nautobot"] group=grp-nautobot password="__NB_PW__" } + +:log info ("tp28-ap3: service-accounts, users=" . [:tostr [:len [/user find]]] . " groups=" . [:tostr [:len [/user group find]]]) diff --git a/routeros/tp28-ap3-werksreste.rsc b/routeros/tp28-ap3-werksreste.rsc new file mode 100644 index 0000000..9d6a232 --- /dev/null +++ b/routeros/tp28-ap3-werksreste.rsc @@ -0,0 +1,39 @@ +# ===================================================================================== +# ap3 — die drei Werksreste abraeumen, die die Compliance nach dem Bring-up gemeldet hat. +# TP28-Nachtrag, 2026-08-30. +# +# Befund aus dem ersten Compliance-Lauf MIT ap3 (127/130): +# ip-dns extra -> defconf-Eintrag router.lan -> 192.168.88.1 (die alte +# Werks-LAN-IP, die es seit Phase 2 gar nicht mehr gibt) +# ip-firewall-filter extra -> 11 defconf-Regeln. Ein reiner AP routet nicht, hat also +# keine forward-Kette zu schuetzen; der Mgmt-Zugang haengt +# an /ip service, nicht an der input-Kette (TP26). +# ip-service missing -> die Haertung war nie gesetzt +# +# 🔴 `[find !dynamic]` waere ein SYNTAXFEHLER -- korrekt ist `[find where !dynamic]`. +# Und Syntaxfehler schlagen beim PARSEN zu, `on-error` faengt sie NICHT. Symptom: +# kein Fehler im Log, keine Wirkung, Plan meldet trotzdem Erfolg. (TP26, 2026-08-17.) +# 🔴 Das Filtern auf `!dynamic` ist zusaetzlich noetig, weil `remove [find]` auch die +# dynamische "special dummy rule to show fasttrack counters" erfasst -- RouterOS +# verweigert deren Entfernung, das GESAMTE Kommando wirft, und es wird NICHTS entfernt. +# +# ⚠️ Kein Lockout-Risiko: `available-from=10.40.0.0/16` deckt alle BF-VLANs ab, also +# auch den Mgmt-Weg ueber vlan99 und die Nautobot-Quelle 10.40.20.30. +# ===================================================================================== + +# [1] /ip service haerten (Soll aus dem GC-Template, platform-weit) +/ip service set ftp disabled=yes +/ip service set ssh available-from=10.40.0.0/16,100.96.0.0/12 +/ip service set telnet disabled=yes +/ip service set www available-from=10.40.0.0/16,100.96.0.0/12 +/ip service set winbox available-from=10.40.0.0/16,100.96.0.0/12 +/ip service set api available-from=10.40.0.0/16,100.96.0.0/12 +/ip service set api-ssl disabled=yes + +# [2] defconf-Firewall abraeumen (TP26-Muster, identisch zu ap1/ap2) +:do { /ip firewall filter remove [find where !dynamic] } on-error={ :log info "tp28-ap3: firewall-remove warf" } + +# [3] defconf-DNS-Eintrag auf die tote Werks-IP +:do { /ip dns static remove [find where address="192.168.88.1"] } on-error={ :log info "tp28-ap3: dns-remove warf" } + +:log info ("tp28-ap3-werksreste: fw-statisch=" . [:tostr [:len [/ip firewall filter find where !dynamic]]] . " dns-static=" . [:tostr [:len [/ip dns static find]]])