Compare commits
17 Commits
5bcd0b6989
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c2d9c0ea7 | |||
| 5c8824850f | |||
| 359ad666c4 | |||
| 4d4779e400 | |||
| 4b307315f0 | |||
| 81cd7bfb7e | |||
| c5086db223 | |||
| a3f2ade4e0 | |||
| 52a8ac462c | |||
| 9638a03e4b | |||
| fb415ae52d | |||
| f63149a769 | |||
| 3601a2b6cb | |||
| 75bd43d1f7 | |||
| 315f1e79e7 | |||
| 6e2a7cfb47 | |||
| 5ad71b37c0 |
@@ -122,7 +122,9 @@ GW je VLAN = `.1` (crs326-core L3) · Mgmt VLAN99: core `.99.1`, branch `.99.2`,
|
||||
🔴 **Ein GC-Recovery von null erzeugt einen Core, in den Nautobot nicht mehr per SSH kommt** — die
|
||||
Pipeline wäre tot. Gleiche Lücke wie die DHCP-Reservierungen. **Merkregel: was auf dem Gerät existiert,
|
||||
aber nicht im Template, ist eine Recovery-Lücke, auch wenn Compliance 140/140 grün meldet** — die Zahl
|
||||
prüft nur, was gerendert wird. Ablösung durch Komodo (native MCP) geplant.
|
||||
prüft nur, was gerendert wird. Systematisch erfasst in **[TP48](docs/teilprojekte.md#tp48)** (Gitea #75),
|
||||
der auch prüft, ob Ansible Nautobot als SSoT ablösen kann. ⚠️ **Nicht Komodo** — es verwaltet Container,
|
||||
nicht RouterOS-Config (frühere Notiz hier war eine Kategorie-Verwechslung).
|
||||
|
||||
---
|
||||
|
||||
@@ -181,6 +183,10 @@ Verhaltensregeln für nachfolgende AI-Sessions — kleiner, klarer, weniger Drif
|
||||
- **opencode-Skills: Default-Pfad kopieren, NICHT symlinken** (TP35 Schicht 6). Claude-Skill `~/.claude/skills/<name>/SKILL.md` ist die Quelle; für opencode `cp` nach `~/.config/opencode/skills/<name>/SKILL.md` (kein `skills.paths`-Eintrag nötig). Symlink verletzt Christophs „opencode = eigene Pfade"-Direktive. Lehre: opencode-Verhalten nicht raten, Doku lesen.
|
||||
- **Skills sind IMMER global, niemals pro Projekt** (Christoph-Direktive). Nur `~/.claude/skills/` und `~/.config/opencode/skills/`. Kein Repo-Override. Wer überschreiben will, editiert die globale Datei oder schreibt einen zweiten Skill.
|
||||
- **Skills sind seit 2026-09-25 versioniert** (Befund: sie waren reiner Home-Ordner-State, kein Git, kein Backup — bei 22 Skills über mehrere Monate gewachsen). Master = Gitea **`admin/claude-skills`** (privat), `~/.claude/skills/` ist dessen **Arbeitsbaum**. **Ablauf ab jetzt: editieren → committen → pushen → `cp -R ~/.claude/skills/<name> ~/.config/opencode/skills/<name>`.** ⚠️ Der `cp`-Schritt ist der, den man vergisst — genau so entsteht stille Drift zwischen den beiden Kopien. Secret-Gebot gilt auch hier: keine Tokens/Keys in Skills, Platzhalter (`REPLACE_WITH_…`) sind okay.
|
||||
- **Projekt-Memories sind seit 2026-10-06 versioniert** — dasselbe Muster wie die Skills, aus demselben Befund (reiner Home-Ordner-State). Master = Gitea **`admin/claude-memories`** (privat), Arbeitsbaum **`~/dev/claude-memories/`**. Ein Ordner je Projekt, Name = Projektpfad ohne das Präfix `-Users-christophemsenhuber-`; **führender Unterstrich = projektübergreifend** (`_dev` = `~/dev`, `_home` = `~`). Anbindung per **Symlink**, nicht Kopie: `~/.claude/projects/<kodiertes-projekt>/memory/` → `~/dev/claude-memories/<projekt>/` (eine zweite Kopie wäre genau die Drift, die das Repo verhindern soll). **Ablauf: editieren → `MEMORY.md`-Zeile ergänzen → committen → pushen.**
|
||||
- 🔴 **`name:` muss dem Dateinamen entsprechen** — die `[[wikilinks]]` adressieren den Slug, nicht den Pfad. Laufen beide auseinander, brechen Verweise **still** (kein Fehler, kein Hinweis). Belegt am 2026-10-06: 12 Links zeigten ins Leere, weil 8 Dateien einen abweichenden `name:` trugen.
|
||||
- 🔴 **`MEMORY.md` ist ein Index, kein Log** — nur das Index-File wird in jede Session des Projekts geladen, die Einzeldateien nicht. Statusberichte/Chronologie gehören in eine eigene, nicht geladene Datei (Muster: `bf-network-r6/CHRONIK.md`). 67 Log-Zeilen hatten den r6-Index auf 16,4 KB getrieben — mehr als alle anderen elf Indizes zusammen.
|
||||
- Keine Secrets in Memories — sie werden in jede Session geladen und liegen versioniert. Platzhalter sind okay.
|
||||
- **Kommentar in Code ≠ Verhalten.** `mcp/shelly/.env` trug „gitignored", es gab aber kein `.gitignore`. Behauptungen in Kommentaren per `git check-ignore -v <datei>` verifizieren, bevor man darauf vertraut.
|
||||
- **opencode-TUI: führendes Leerzeichen vor `/command` manchmal nötig** (bekannter Bug, anomalyco/opencode#27528, „Not planned"). Workaround (führendes Leerzeichen) ist der offizielle Pfad.
|
||||
- **Manuell gepflegte Doku verliert gegen die Realität, wenn sie lang wird.** Wer eine Stelle ändert, muss die anderen mitlesen (Quick-Index, TP-Abschnitt, Ist-Stand). Strukturelle Antwort = TP36 (SSoT via Nautobot).
|
||||
|
||||
@@ -13,6 +13,45 @@ zeigt die Einträge, die seit dem letzten `/news`-Lauf dazugekommen sind (Rest a
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-12 — Airfryer „airschreier" ist FUSION/Cloud-only — lokale Anbindung endgültig tot
|
||||
**Betrifft:** TP5.1 (Airfryer-Fall, DNS-Redirect + Live-Torch), die UNTRUSTED-Einordnung des Geräts
|
||||
(`10.40.30.180`, VLAN30) und jede Session, die den Airfryer als „Low-Trust-Gerät mit lokalem OTA-Problem"
|
||||
behandelt.
|
||||
**Aktion:** Die Begründung für den DHCP-Override `dns 1.1.1.1` auf **dieser** Lease („OTA geht nicht über
|
||||
den DoH-Gateway-Pfad") ist **erledigt** — das Gerät hat die FUSION-Migration bereits vollzogen und hängt
|
||||
sowieso an der Cloud. **Der Override-Mechanismus selbst bleibt bestehen** und ist für künftige Low-Trust-
|
||||
Kandidaten weiter definiert (Entscheidung 2026-10-12) — er wird **nicht** abgebaut, sondern behält seinen
|
||||
Wert als Werkzeug, falls wieder ein Gerät mit OTA-DoH-Latenzproblemen auftaucht. Das gilt ausdrücklich
|
||||
auch für die in TP5.1 skizzierte DNS-Redirect-Variante (a).
|
||||
|
||||
**Aber:** Bei FUSION ist die **Cloud-Verbindung die einzige** Steuerungsroute — das Gerät darf **nicht**
|
||||
ins internetlose UNTRUSTED-VLAN (40) wandern, sonst ist es komplett unsteuerbar (auch aus HA). Der
|
||||
frühere UNTRUSTED-Move-Kandidat aus TP5.1 ist damit **zurückzustellen**, nicht umzusetzen.
|
||||
|
||||
**Steuerungsentscheidung (2026-10-12):** Die Cloud-Integration **bleibt** — der Airfryer wird bewusst
|
||||
über `philips_homeid` / Cloud-Relay betrieben. Kein Rückbau, keine weitere Diskussion nötig.
|
||||
|
||||
**Befund (aus HA verifiziert, 2026-10-12):**
|
||||
- Config-Entry `philips_homeid`, Titel **„Airschreier 5000 Series XXL (Cloud)"** — das `(Cloud)` im
|
||||
Titel ist die FUSION-Markierung der Integration (Cloud-Relay statt lokal).
|
||||
- Firmware-Sensoren: **NCP `1.1.14` / Host `0.5.6`** → entspricht der dokumentierten FUSION-Liste
|
||||
(`1.1.14/0.5.6`; vgl. HD9285-Fälle `1.6.2/0.6.8`, `1.1.8/0.5.6`). Achtung: Die HomeID-App zeigt die
|
||||
Reihenfolge **umgedreht** an (`0.5.6/1.1.14`) — nicht als SPECTRE fehldeuten.
|
||||
- Modellnummer-Sensor: `HD9285` (SPECTRE wäre die lokale Variante, ist es aber nicht mehr).
|
||||
- Lokaler Port 80/443 antwortet **nicht** (aus MAIN getestet); mDNS announce't aber weiter
|
||||
`PHILIPS_HD9285_2_1FB281._http._tcp.local.` → Port 80. Das Gerät läuft also, hat aber keine
|
||||
lokalen Credentials mehr.
|
||||
- **Kein Weg zurück:** Laut Integrations-Doku ist die FUSION-Migration einwegig („There is no way back"),
|
||||
weil die Zuordnung cloud-seitig über `externalDeviceId` verankert ist. Firmware-Downgrade hilft nicht,
|
||||
Custom-Firmware existiert nicht (geschlossener ESP-Stack, signierter Bootloader).
|
||||
- Steuerung **funktioniert** über die Cloud (Live-Werte plausibel: Kochstatus `setting`, 180 °C,
|
||||
`fresh_fries`; Buttons für Start/Pause/Stopp/Warmhalten vorhanden).
|
||||
- Nebenbefund: `dns 1.1.1.1` = anonymer Consumer-Resolver → **DNS-Sicht weiter blind**, Gerät umgeht
|
||||
die Filterung. Redet nachweislich mit Versuni → AWS `eu-west-1` (IoT). Bleibt ein Beobachtungspunkt,
|
||||
aber ohne OTA-Rechtfertigung.
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-03 — TP-Header tragen keinen Status mehr (SSoT-Drift geschlossen)
|
||||
**Betrifft:** alle, die `docs/teilprojekte.md` oder `teilprojekte-archiv.md` lesen oder editieren, und
|
||||
alle, die ein TP anlegen/abschließen.
|
||||
|
||||
@@ -303,6 +303,30 @@ Tokens: nur im Container-Env + jederzeit via `GET /accounts/…/cfd_tunnel/<id>/
|
||||
|
||||
**🔴 NEUER FUND (wichtiger als vw!) — Wildcard `*.emsenhuber.net` (`16aaec78`) zu offen:** Policy `christoph` hat `include: email:christoph OR login_method:github OR login_method:google`. **`login_method:<idp>` als include matcht JEDEN, der sich via GitHub/Google einloggt** — nicht nur christoph. D.h. **jeder GitHub-/Google-User erreicht nach Login alle internen Dienste** (hass, grafana, portainer, pve, gitea, …). Fix = die zwei `login_method`-Includes raus (nur `email:christoph` + ggf. weitere erlaubte Mails + `non_identity bf4` + `claude-mcp`). **NICHT einseitig geändert** (ändert Login-Verhalten überall) → User-Entscheidung. War im Memory schon als „Policy christoph verschlanken" geflaggt.
|
||||
|
||||
> ✅ **BESTÄTIGT und BEHOBEN am 2026-10-06 — der Fund oben war RICHTIG.**
|
||||
> Christoph hat mit einem **zweiten GitHub-Account** (fremde Adresse) getestet: **er kam durch.**
|
||||
> `login_method:github` im **`include`** ist ein **OR** — die danebenstehende `email`-Zeile war
|
||||
> eine **Alternative**, keine Bedingung. Jeder GitHub-Account kam hinein.
|
||||
>
|
||||
> **Fix (ausgeführt):** `login_method:github` von `include` nach **`require`** verschoben
|
||||
> ⇒ **`email AND github`**. Zurückgelesen über beide Endpunkte, Fix bestätigt.
|
||||
> ⚠️ Nebenwirkung: der E-Mail-Einmalcode allein reicht auf GitHub-Seite **nicht mehr**.
|
||||
>
|
||||
> 🔴 **Zwischenzeitliche Fehlspur (dokumentiert, weil lehrreich):** eine frühere Fassung dieser
|
||||
> Notiz erklärte den Fund am selben Tag für **widerlegt** — auf Basis eines **Google**-Tests
|
||||
> (`testus@emsenhuber.com` → abgelehnt) plus eines **Schlusses** auf GitHub. Der Schluss war
|
||||
> falsch: der Google-Test zeigt nur Googles Verhalten. Die IdP-Configs sind konfigurationsgleich
|
||||
> (beide nur `client_id` + `redirect_url`) — **das unterscheidende Feld steht nicht in der Config.**
|
||||
> **Lehre: pro IdP einzeln messen, nicht den ersten Test auf den zweiten übertragen.**
|
||||
>
|
||||
> **Bleibt gültig:** TP41 hat den internen Wildcard auf Access-Groups (`zt-humans`/`zt-home`/
|
||||
> `zt-automation`) umgestellt — dort steht heute kein `login_method` mehr. Der Sweep über alle
|
||||
> Apps fand `login_method` nur noch in `Terpenfinder v3` (`christoph`: github+onetimepin;
|
||||
> `geli`: nur onetimepin = harmlos).
|
||||
>
|
||||
> Details, Gotchas (`12130` reusable-Policy-Endpunkt, MCP read-only) und die Grenze
|
||||
> „kein GitHub-Username-Selector" → `docs/teilprojekte.md` → `### TP49`.
|
||||
|
||||
**⏸️ `hass` 400 / `ha-mcp` — zwei getrennte Layer:**
|
||||
- **Access-Layer (remote):** HA-Companion-App kann kein interaktives Access-Login → Fix = iPhone in **WARP** enrollen (`Warp Login App` `6c32fe23` `allow:christoph` existiert → christoph darf enrollen). Client-seitig, braucht User am Gerät. Vom Heim-Netz (bf4-IP) ohnehin ohne Login erreichbar.
|
||||
- **Origin-Layer:** `hass.emsenhuber.net` gibt **vom Heim-Netz** (Access passt via bf4-IP) trotzdem **400** (CF `server:cloudflare`, aber Origin-verursacht) = **HA lehnt den Request ab** — HA-`http:`-Config (`use_x_forwarded_for` + `trusted_proxies` inkl. cloudflared-Range, ggf. Host-Allowlist). **Separates HA-Thema, NICHT Tunnel/Access** — offen.
|
||||
|
||||
@@ -43,7 +43,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP7.1](#tp7-1) | SSDP-Relay (macOS-Sonos-App über VLAN-Grenze) | ✅ LÄUFT (2026-08-18) |
|
||||
| [TP7.2](#tp7-2) | HA↔Sonos static-host binding + VLAN1/GUEST-Discovery | ✅ FERTIG (2026-07-03) |
|
||||
| [TP9](#tp9) | Gateway-/Router-HA | ❌ OBSOLET (User-Entscheid 2026-08-08) |
|
||||
| [TP10](#tp10) | Multi-Site VPN | ✅ KERN DURCH (2026-06-27) |
|
||||
| [TP10](#tp10) | Multi-Site VPN | ✅ ABGESCHLOSSEN (2026-10-05) — Mesh-Quell-NAT + DDNS-Updater |
|
||||
| [TP14](#tp14) | Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) | ✅ DURCH (2026-07-13; live re-verifiziert 2026-08-08) |
|
||||
| [TP14.1](#tp14-1) | Session A: NAS fertigstellen | ✅ DURCH (2026-07-10); Omada-Inform/sw2 VERTAGT |
|
||||
| [TP14.2](#tp14-2) | Session B: pve-Docker-LXC (.126) → VLAN20 | ✅ DURCH (2026-07-10) (ipvlan) |
|
||||
@@ -61,7 +61,7 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP25](#tp25) | WLAN-Stabilisierung | ✅ FERTIG (2026-09-15) — Platzierung final (ap1 Sideboard · ap2 UG · ap3 OG), Fund E geschlossen |
|
||||
| [TP26](#tp26) | `/ip firewall filter` unter Drift-Kontrolle | ✅ DURCH (2026-08-17) |
|
||||
| [TP27](#tp27) | RouterBOOT-Firmware auf 7.24 nachziehen | ✅ DURCH (2026-08-17/18) |
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ↩️ **wieder geöffnet 2026-08-30** (ap3) — Volltext in [teilprojekte.md](teilprojekte.md#tp28) |
|
||||
| [TP28](#tp28) | WLAN über CAPsMAN auf `crs326-core` | ✅ FERTIG (2026-10-04) — ap1 Sideboard · ap2 UG · ap3 OG, Kanalplan committed, Compliance 29/29 |
|
||||
| [TP-R](#tp-r) | Router-Capabilities-Sweep | FERTIG (docs/research/routeros-capabilities.md) |
|
||||
| [TP39](#tp39) | LXC-Hygiene pve: Resolver-Altlasten, `networking.service`, DB-Reverse-Lookup | ✅ ABGESCHLOSSEN (2026-09-01) — pve+NAS-Sweep grün |
|
||||
| [TP34](#tp34) | Caddyfile via Nautobot/GC (generische Reverse-Proxy-Schnittstelle) | ❌ VERWORFEN (2026-09-08) — Caddyfile bleibt manuell · **Fortsetzung: TP34.2 (2026-09-22)** |
|
||||
@@ -70,6 +70,8 @@ teuerste Eigenschaft der Datei zerstört, um ihr billigstes Problem zu lösen. B
|
||||
| [TP43](#tp43) | dw-Standort-DNS-Uplink (site-spezifische Overrides) | ♻️ REOPENED (2026-09-11) — Volltext zurück in teilprojekte.md; interne BF-Namen über WG am dw offen |
|
||||
| [TP42](#tp42) | Namens-Vereinheitlichung: ein interner Suffix bf.emsenhuber.net | ✅ DURCH (2026-09-02) — `.bf.lan` raus, `.16` abgebaut, Compliance 140/140 |
|
||||
|
||||
| [TP47](#tp47) | Core egress via cf-mesh und WARP-Exit-Validierung | ✅ ABGESCHLOSSEN (2026-10-05) — Egress-Weg verifiziert: 15/15 über cf-mesh, Heim-IP blockt |
|
||||
|
||||
Dazu am Ende: **[TP25 (Kondensat)](#tp25)** + **[TP25 (Langfassung)](#tp25-lang)** — TP25 ist
|
||||
✅ abgeschlossen (2026-09-15); der aktive Stand (Kondensat) und die 394-Zeilen-Herleitung
|
||||
(Messchronik A–G) liegen beide hier.
|
||||
@@ -710,6 +712,84 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
- **Offen:** DDNS-Auto-Updater für `bf4.emsenhuber.net` auf NAS/pve, damit der A-Record der Public-IP folgt
|
||||
(falls Liwest sie tauscht). Aktuell manuell in Cloudflare gepflegt.
|
||||
|
||||
<a id="tp10-ddns"></a>
|
||||
### TP10 · Gitea #49 — Multi-Site VPN (WG-Hub FM/DW) — ✅ ABGESCHLOSSEN 2026-10-05 (Mesh-Quell-NAT + DDNS-Updater)
|
||||
|
||||
> **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).**
|
||||
> Research: `docs/research/vpn-comparison.md`. Prosa-Historie: `teilprojekte-archiv.md#tp10`.
|
||||
> **Zurück ins Archiv am 2026-10-05** — beide Close-Bedingungen erfüllt (DDNS-Updater live,
|
||||
> Mesh-NAT-Regel im täglichen GC-Lauf konform). Erneut geöffnet war es am 2026-10-03 für den
|
||||
> Mesh-Quell-NAT-Befund (Konvention: geschlossenes Ticket, kein versiegeltes).
|
||||
|
||||
**Bestand (aus dem Archiv, unverändert gültig):** beide WG-Tunnel (fm `10.101.0.0/24`, dw
|
||||
`10.95.10.0/24`) laufen mit Hub auf `crs326-core`; `wg-hub` UDP/51821, MTU 1412, **keine
|
||||
Interface-IP** (bewusst so, s.u.). Public-Edge direkt, kein Double-NAT.
|
||||
|
||||
#### 🔴 Neuer Befund (2026-10-03) — Mesh-Quelle außerhalb der Spoke-AllowedIPs
|
||||
WARP-Clients erreichen die Spoke-Netze über **`cf-mesh`** (TP16.1) mit Quell-IP **`100.96.0.0/12`**.
|
||||
Die Spoke-FritzBoxen führen am WG-Peer aber nur **`10.40.0.0/16`** als AllowedIPs → sie **verwerfen**
|
||||
den Verkehr. Live bewiesen am Core:
|
||||
|
||||
| Quelle | → Spoke | Ergebnis |
|
||||
|---|---|---|
|
||||
| `src=100.96.0.6` (cf-mesh) | fm/dw | ❌ 100 % Loss — **das ist der Fehler** |
|
||||
| `src=10.40.20.1` (BF-IP) | fm/dw | ✅ 0 % Loss |
|
||||
|
||||
**Fix (live seit 2026-10-03, Commit `1fddf5d`):** eine `src-nat`-Regel auf dem `wg-hub`-**Egress**
|
||||
legt die Quelle in den AllowedIPs-Raum:
|
||||
`/ip firewall nat add action=src-nat chain=srcnat comment="TP10: NAT mesh source to WG spokes (AllowedIPs)" out-interface=wg-hub src-address=100.96.0.0/12 to-addresses=10.40.10.1`
|
||||
Soll im GC-Template (`nautobot/mikrotik_routeros.j2` Abschnitt `[7] NAT`), Deploy idempotent über den
|
||||
Job **`tp10-mesh-nat`** (`nautobot/gc-configplan.py`, manueller Plan, **nie** full-intended).
|
||||
|
||||
**Warum `src-nat` und nicht `masquerade`:** `wg-hub` hat **keine Interface-IP** — masquerade hätte
|
||||
keine Quelle zum Einsetzen. Die feste IP `10.40.10.1` hält zudem das Connection-Tracking: die Antwort
|
||||
an `10.40.10.1` wird im prerouting auf die Mesh-IP zurückübersetzt und via `cf-mesh` geroutet.
|
||||
|
||||
**Abnahme (live 2026-10-03):** Regel-Zähler steigt (6 Pakete/456 Bytes bei der ersten Prüfung),
|
||||
Core → `10.95.10.246` ✅ 0 % Loss (vorher 100 %), Mac/WARP → `10.95.10.1` ✅.
|
||||
|
||||
#### ⚠️ Bewusste Grenze
|
||||
Alle Mesh-Clients erscheinen an der FritzBox als **eine** IP (`10.40.10.1`) — unterscheidbar nur
|
||||
über Connection-Tracking am Core. Christoph-Entscheid 2026-10-03: **bewusst so**, Erreichbarkeit war
|
||||
das Ziel. Eine Unterscheidbarkeit pro Client ist **nicht** Teil dieses TP.
|
||||
|
||||
#### ❌ Verworfene Diagnose — nicht wieder aufgreifen
|
||||
Ein Handoff vom 2026-09-28 behauptete, `wg-hub`s fehlende Interface-IP sei ein **Defekt**, der
|
||||
Core-eigene Traffic unerreichbar mache, und schlug `preferred-source` auf den zwei TP10-Routen vor.
|
||||
**Beides ist falsch:** (a) die fehlende IF-IP ist **beabsichtigt** und im Archiv dokumentiert;
|
||||
(b) der Core-eigene Ping ist **kein** Betriebsproblem — normaler Client-Traffic liegt mit seiner
|
||||
Quell-IP ohnehin im erlaubten Raum. Die Datei wurde gelöscht; die Erkenntnis steht hier.
|
||||
|
||||
#### Offen / Close-Bedingung
|
||||
- [x] **DDNS-Updater für `bf4.emsenhuber.net`** — **LIVE seit 2026-10-03** auf `crs326-core`:
|
||||
Script `tp10-ddns-bf4` installiert, Trigger: DHCP-Client `client1` (Lease-Event),
|
||||
Token `cfDdnsToken` in `/system script environment` (53 Zeichen, SSoT = `CF_DNS_API_TOKEN`).
|
||||
Positiv-Test bestanden: Test-IP 1.2.3.4 → Script-Lauf → Record zurück auf 90.146.33.177,
|
||||
Comment `2026.10.03-18:08 by crs326-core`, Idempotenz-Variable `CFWanIP4Cur` gesetzt.
|
||||
- [x] **Mesh-NAT-Regel im täglichen GC-Lauf** — `tp10-mesh-nat` manueller ConfigPlan,
|
||||
im Template `nautobot/mikrotik_routeros.j2` Abschnitt `[7] NAT` abgebildet, wird beim
|
||||
nächsten `backup → intended → compliance` Zyklus (täglich 03:30 UTC) deployed.
|
||||
- [x] **Zweiter Spoke prüfen** — Fix an fm/dw gemeinsam verifiziert (fm & dw gleicher
|
||||
AllowedIPs-Raum, dieselbe src-nat-Regel greift für beide).
|
||||
|
||||
#### ✅ Abschluss-Nachweis (2026-10-05)
|
||||
- **DDNS Positiv-Test:** A-Record auf `1.2.3.4` gesetzt → `/system script run tp10-ddns-bf4` →
|
||||
Record zurück auf `90.146.33.177`, Comment `2026.10.03-18:08 by crs326-core`, `CFWanIP4Cur` gesetzt.
|
||||
- **GC-Compliance:** `nautobot_golden_config` → `ip-firewall-nat` = **true** für crs326-core;
|
||||
Regel steht in der **Intended Config** (Z.425), nicht nur im Backup; zwei tägliche
|
||||
`AllDevicesGoldenConfig`-Läufe nach dem Fix grün (2026-10-04, 2026-10-05).
|
||||
- **Commits:** `6e2a7cf` (Global-Scope-Fix) + `75bd43d` (Abschluss/Doku) → `main` gepusht.
|
||||
- Gitea **#49 geschlossen** (Label `status:done`).
|
||||
|
||||
### ⚠️ Offener Restpunkt (bewusst, eigener TP)
|
||||
**Recovery-Lücke:** das GC-Template rendert **kein `/system script` / `/system scheduler`**.
|
||||
Ein Recovery von null hätte den DDNS-Updater nicht. Nicht Teil von TP10 —
|
||||
**übernommen von [TP48](teilprojekte.md#tp48)** (Gitea #75, 2026-10-05), der die ganze Klasse
|
||||
inventarisiert und zugleich prüft, ob Ansible Nautobot als SSoT ablösen kann.
|
||||
*(Frühere Notiz „Ablösung durch Komodo geplant" ist damit korrigiert: Komodo verwaltet Container,
|
||||
nicht RouterOS-Config — es kann diese Lücke nicht schließen.)*
|
||||
|
||||
|
||||
<a id="tp14"></a>
|
||||
|
||||
### TP14 · Gitea #50 — Host/Container-IP-Migration VLAN1→VLAN20 (Docker-Cutover NAS+pve) — live re-verifiziert 2026-08-08) — `docs/nas-vlan20/RUNBOOK.md`, Handoff `docs/nas-vlan20/handoff/`
|
||||
@@ -1277,11 +1357,222 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
|
||||
### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core`
|
||||
|
||||
> Der Volltext steht seit dem 2026-08-30 wieder in
|
||||
> [`teilprojekte.md` → TP28](teilprojekte.md#tp28), weil mit **ap3** (cAP ax) ein dritter
|
||||
> Access-Point dazukommt. **Ein archiviertes TP ist ein geschlossenes Ticket — es darf wieder
|
||||
> geöffnet werden** (Christoph-Direktive 2026-08-30); der Abschnitt wandert zurück, nicht nur
|
||||
> ein Nachtrag. Hier bleibt dieser Zeiger stehen, damit alte Links nicht ins Leere gehen.
|
||||
> ✅ **Abgeschlossen 2026-10-04.** ap1 Sideboard · ap2 Technik UG · ap3 Technik OG (Platzierung
|
||||
> final). Kanalplan committed, `wifi-cap`-Rest auf dem Core bereinigt, Compliance 29/29. Der
|
||||
> ap3-Nachtrag unten ist damit erledigt; die zwei 🔜-Reste sind Cross-TP (TP25 ✅ 09-15, TP30).
|
||||
|
||||
|
||||
> **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,
|
||||
> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP
|
||||
> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder
|
||||
> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".**
|
||||
|
||||
- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
|
||||
Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
|
||||
war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
|
||||
Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
|
||||
**der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
|
||||
`-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
|
||||
**statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
|
||||
- **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
|
||||
`/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
|
||||
`/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
|
||||
`vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
|
||||
Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
|
||||
- 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
|
||||
Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
|
||||
`torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
|
||||
Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
|
||||
— der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
|
||||
- ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
|
||||
(30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
|
||||
dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
|
||||
**frische DHCP-Lease pro SSID**, nicht die Registration-Table.
|
||||
- **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
|
||||
mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
|
||||
zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
|
||||
- ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
|
||||
ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
|
||||
durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
|
||||
sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
|
||||
kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
|
||||
wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
|
||||
`last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
|
||||
- ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
|
||||
antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
|
||||
gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@<ap> "/system reboot"`.
|
||||
- ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
|
||||
`nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
|
||||
→ Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
|
||||
auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
|
||||
allein erreicht Nautobot nicht.
|
||||
- ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
|
||||
Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
|
||||
als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
|
||||
|
||||
- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
|
||||
jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
|
||||
(`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
|
||||
- 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
|
||||
je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
|
||||
sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
|
||||
Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
|
||||
defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
|
||||
- ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
|
||||
Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
|
||||
bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
|
||||
referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
|
||||
`configuration=`-Verweis, der beim Loeschen folgenlos blieb.
|
||||
- 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
|
||||
`/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
|
||||
`/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
|
||||
**nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
|
||||
- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
|
||||
(der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
|
||||
welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
|
||||
`discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
|
||||
**Ergebnis: 92/92 compliant, 0 Drifts.**
|
||||
- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<<REDACTED>>`
|
||||
im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
|
||||
Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
|
||||
Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
|
||||
als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
|
||||
drei Bring-up-Dateien, die voneinander abgeschrieben sind.
|
||||
- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
|
||||
wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
|
||||
> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
|
||||
> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
|
||||
> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
|
||||
|
||||
- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
|
||||
Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
|
||||
TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
|
||||
das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
|
||||
Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
|
||||
waren die Funkparameter aus TP25.
|
||||
- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
|
||||
4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
|
||||
TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
|
||||
media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
|
||||
verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
|
||||
sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
|
||||
- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
|
||||
Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
|
||||
deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
|
||||
- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
|
||||
`/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
|
||||
einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
|
||||
vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
|
||||
- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
|
||||
iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
|
||||
drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
|
||||
Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
|
||||
✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
|
||||
`wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
|
||||
sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
|
||||
- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
|
||||
`wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
|
||||
toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
|
||||
Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
|
||||
*(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
|
||||
„KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
|
||||
gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
|
||||
die Punkte darunter mitziehen.**)*
|
||||
- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
|
||||
Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
|
||||
nur `admin logged in/out via api`).
|
||||
- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
|
||||
Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
|
||||
|
||||
|
||||
#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30
|
||||
|
||||
**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**.
|
||||
**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am
|
||||
Gerät wie ap1/ap2 — **keine** DHCP-Reservierung.
|
||||
|
||||
**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die
|
||||
gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die
|
||||
bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten").
|
||||
|
||||
**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:**
|
||||
|
||||
| AP | 5 GHz | Band | DFS | 2,4 GHz | wann |
|
||||
|---|---|---|---|---|---|
|
||||
| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** |
|
||||
| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug |
|
||||
| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug |
|
||||
| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — |
|
||||
|
||||
🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum
|
||||
Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein
|
||||
**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück.
|
||||
|
||||
> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der
|
||||
> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile
|
||||
> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat.
|
||||
> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die
|
||||
> 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 — ✅ 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 Angleichung der übrigen vier ist am
|
||||
2026-08-31 erfolgt** (Christoph, WebFig) — die Flotte ist einheitlich, TP27 erledigt.
|
||||
- ✅ **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
|
||||
GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*.
|
||||
Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`).
|
||||
**Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.**
|
||||
- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax
|
||||
bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz
|
||||
„kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu.
|
||||
- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus,
|
||||
wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port
|
||||
(VLAN1 bleibt leer, TP19 ist geschlossen).
|
||||
- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity +
|
||||
Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`,
|
||||
das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24).
|
||||
|
||||
##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen)
|
||||
1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit
|
||||
genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und
|
||||
hat keinen zweiten → 5 GHz dieses APs bleibt unten.
|
||||
2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf
|
||||
`5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt,
|
||||
9 dBm gesendet.
|
||||
3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen,
|
||||
der Port ist untagged-Member von VLAN40 bei `pvid=1`.
|
||||
|
||||
<a id="tp-r"></a>
|
||||
|
||||
@@ -2498,3 +2789,75 @@ Gegen die Geräte gemessen (MikroMCP + dig), nicht aus dem Handoff:
|
||||
**Status:** ✅ **Namensraum vereinheitlicht (2026-09-02)** — ein interner Suffix `bf.emsenhuber.net`,
|
||||
`.bf.lan` raus, `.16` abgebaut, Compliance 140/140. Offen nur der TP37-gekoppelte Nautobot-Doku-Teil.
|
||||
Handoff: [`docs/handoff-20260901-tp42-naming.md`](handoff-20260901-tp42-naming.md).
|
||||
|
||||
---
|
||||
<a id="tp47"></a>
|
||||
|
||||
### TP47 · Gitea #74 — Core egress via cf-mesh und WARP-Exit-Validierung
|
||||
> **Christoph:** Validierung des Core-Egress-Weges via Cloudflare Mesh. Ausgangsprüfung gegen neutralen Host
|
||||
> und Ziel `164.90.191.7` (DigitalOcean). Korrektur früherer Behauptung: **WARP war am Mac gar nicht
|
||||
> verbunden** — `warp=off`, `ip=90.146.33.177` (Heim-IP). Die zuvor gemessene `104.28.207.39` stammte aus
|
||||
> einem Test mit nur der `1.1.1.1/32`-Regel, daher über die Heim-IP (403). Beweis: Regel aktiviert
|
||||
> `164.90.191.7/32 → egress-cannaleo` → Ziel erreicht mit `200` (168 KiB). Template-Änderung in
|
||||
> `nautobot/mikrotik_routeros.j2` Abschnitt `[10b]` (commit `c600c62e`). Doku-Korrektur
|
||||
> `docs/warp-egress-native-crs326.md` §6.3.
|
||||
>
|
||||
> **Bestandsaufnahme (2026-09-27):**
|
||||
> - Ziel `164.90.191.7` (DigitalOcean-Origin, kein Cloudflare im Pfad) bereitet `403` bei Heim-IP
|
||||
> (`90.146.33.177`, WARP aus), `200` über Core-Routing (`cf-mesh`, `egress-cannaleo`-Table).
|
||||
> - `cf-mesh` erreicht den Origin mit `200` (verifiziert: `cdn-cgi/trace` → `ip=104.28.207.39`,
|
||||
> `warp=on`, `colo=VIE`). Keine Dedicated Egress IP nötig.
|
||||
> - User-Cave: kein Zugriffsmuster, das Heim-IP und diesen Ausgang gemeinsam gegen den Origin zeigt.
|
||||
> - Template-Drift: 0 (Secret-Guard bestanden, Push `c600c62e`).
|
||||
>
|
||||
> #### Durchführung (in einem Durchgang)
|
||||
> 1. TP anlegen, Prosa in `teilprojekte.md` schreiben, commit + push
|
||||
> 2. Core-Routing-Objekte (`egress-cannaleo` Table, Route, Rule) bereits live auf `crs326-core`
|
||||
> 3. Doku `docs/warp-egress-native-crs326.md` §6.3 korrigiert (früherer 403 = Messfehler)
|
||||
> 4. Mineral: nicht abzudrehen (seit 21.09. still)
|
||||
> 5. `10.101.0.254` taugt nicht als GW (100 % Loss; FM = Liwest; Cave erfüllt)
|
||||
|
||||
#### Bestandsaufnahme (live 2026-09-21) — es gibt eine Doku-SSoT, aber keinen Master
|
||||
| Was | Wo | Stand |
|
||||
|---|---|---|
|
||||
| Tunnel, WARP-Connector, Workers, Access-Apps, Hyperdrive, VPC, teamnet-Routen | Nautobot `nautobot.cloud`, Job **`CF Estate Sync`** (`jobs/cf_estate_sync.py`) | ✅ 4 `CloudNetwork` · 16 `CloudService` live |
|
||||
| **Ingress-Origin-Ziele** | **nirgends** | 🔴 `_ingress()` speichert nur `ingress_hostnames`, **nicht** das Origin |
|
||||
| **DNS-Records** | **nirgends** | 🔴 nicht erfasst — live 24 Records / 8 proxied |
|
||||
| **Access-Policies** | **nirgends** | 🔴 nur die Apps selbst |
|
||||
| Richtung | CF → Nautobot | 🔴 **Pull-only** („CF bleibt operativer Master") |
|
||||
| `docs/cf-tunnel-mapping.md` | Repo | §1–16 eingefrorene As-Built-Historie · §0 = Live-Zeiger (altert: nennt `bf-public` „2 conns", live **12** = 3 AP-Connectoren × 4) |
|
||||
|
||||
**Antwort auf „full setup/recovery from scratch": heute nicht möglich.** Es fehlt (a) der Push-Weg,
|
||||
(b) Ingress-Origins, (c) DNS, (d) Access-Policies. Und ein frisch erzeugter Tunnel liefert **neue ID
|
||||
und neues Secret** → das **Token-Handoff an die Connectoren** (ap1/ap2/ap3) ist **Teil der Recovery**,
|
||||
kein Nebenschritt.
|
||||
|
||||
#### Umfang
|
||||
Tunnel + Ingress **inkl. Origin-Ziele** + DNS-Records + Access-Apps/Policies vollständig nach Nautobot;
|
||||
**Push-Renderer** (Dry-Run + Diff); **Recovery-Runbook** für den Aufbau von null.
|
||||
|
||||
🔴 **Vor dem ersten Schritt prüfen, nicht annehmen:** welches Nautobot-Objekt DNS-Records tragen kann
|
||||
(Kern-`ipam` vs. eigenes Modell), und ob das Ingress-Origin als `extra_config`-Feld reicht oder ein
|
||||
eigenes Modell braucht.
|
||||
|
||||
- **Abh.:** TP44 Etappe 1/2 ✅ (native `nautobot.cloud`-Modelle + Pull-Job stehen). **Abgrenzung:**
|
||||
Tunnel-**GC/Namensgebung** läuft in TP45 Schicht D; **CF-Writes** aus Nautobot sind hier *das Ziel*,
|
||||
in TP37 ausdrücklich ausgeschlossen — die Grenze verschiebt sich also bewusst mit diesem TP.
|
||||
|
||||
#### ✅ ABGESCHLOSSEN (2026-10-05) — Egress-Weg verifiziert
|
||||
|
||||
**Nachmessung vom Core aus** (`crs326-core`, via MikroMCP; nicht vom Client — der User war zum
|
||||
Messzeitpunkt extern via WARP verbunden, Client-Messungen sind für diesen Pfad wertlos):
|
||||
|
||||
| Regel-Zustand | Stichprobe | Ergebnis |
|
||||
|---|---|---|
|
||||
| **AN** (`egress-cannaleo` → `cf-mesh`) | 15 | **15/15 → 200** (168,1 KiB) |
|
||||
| **AUS** (Fallback Heim-IP) | 15 | **14/15 → 403**, 1 Ausreißer |
|
||||
|
||||
Der Weg ist **deterministisch**, nicht intermittierend (frühere Formulierung war falsch und ist
|
||||
zurückgenommen). Der Ausreißer sitzt reproduzierbar am **ersten** Sample nach dem Umschalten —
|
||||
Connection-/Cache-Nachlauf der Vorverbindung, keine Pfad-Instabilität. 🔴 **Merkregel für künftige
|
||||
A/B-Messungen: nie ein Einzelsample werten, und die erste Anfrage nach einem Routing-Umschalten
|
||||
verwerfen.** Live-Objekte am Core: Routing-Tabelle `egress-cannaleo`, Route `0.0.0.0/0 → cf-mesh`
|
||||
(active, distance=1), Regel `164.90.191.7/32 → lookup-only-in-table` (`disabled:false`) — alle drei
|
||||
deckungsgleich mit Template `[10b]` in `nautobot/mikrotik_routeros.j2` (Drift 0).
|
||||
|
||||
@@ -73,7 +73,8 @@ Mgmt (VLAN99, Ist 2026-08-16): core `.99.1` · branch `.99.2` · ap1 `.99.11` ·
|
||||
- **Kontext (2026-07-08/09):** Airfryer „airschreier" (Philips/Versuni, ESP, `10.40.30.180`) bekam per DHCP-Option 6 `dns 1.1.1.1` als Override, weil das OTA-Update über den ap1-DoH-Gateway-Pfad nicht durchging (DoH-Latenz beim primitiven ESP-Stack — **kein** Content-Block, via Gateway-Logs bewiesen, s. `memory/ref_esp_ota_doh_not_block`). Nebenwirkung: public **1.1.1.1 = anonymer Consumer-Resolver ≠ accountgebundener Gateway-DoH** (`px35ynlol3.cloudflare-gateway.com`) → **DNS-Sicht auf das Gerät weg**, Gerät umgeht zudem die Filterung.
|
||||
- **(a) DNS-Redirect statt Geräte-Override** (der dauerhafte Fix): dst-nat „UDP/53 (+TCP/53) von Low-Trust-Src → auf einen *plain* Resolver umbiegen" gibt **Logging zurück UND** funktionierendes OTA (kein langsamer DoH-Pfad fürs Gerät). Redirect-Ziel-Optionen: (i) ap1-Resolver mit *nicht*-DoH-Upstream für dieses Src-Set, (ii) direkt public 1.1.1.1 (dann nur Flow-Logging), (iii) eigener plain-Resolver. Sauberer als „Gerät verschwindet auf 1.1.1.1". Verortung: Teil des UNTRUSTED-Internet-only-Pinnings (TP5-„Offen"); Entscheidung v50-DNS-Policy (gefiltert vs. plain) hängt dran.
|
||||
- **(b) Live-Torch als Ad-hoc-Observability:** DNS ist bei direktem 1.1.1.1 blind, **Flows nicht** (NATen über ap1). `torch` auf `vlan30`/`vlan50` gefiltert auf die Geräte-IP zeigt Ziel-IPs live (Versuni → AWS `eu-west-1` IoT erkennbar). Voraussetzung: Gerät aktiv. Merkregel: Airfryer nur online wenn er kocht → Capture ans Einschalten koppeln.
|
||||
- **Abh.:** UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50) + TP5-Deny-by-default. Kein Blocker.
|
||||
- **Abh.:** ~~UNTRUSTED-Move des Airfryers (Kandidat, s. VLAN50)~~ + TP5-Deny-by-default. Kein Blocker.
|
||||
- ⚠️ **Nachtrag 2026-10-12 — Gerät ist FUSION/Cloud-only, UNTRUSTED-Move zurückgestellt:** Der Airfryer hat die Philips-FUSION-Migration bereits vollzogen (HA-Config-Entry `philips_homeid`, Titel „Airschreier 5000 Series XXL **[Cloud]**"; NCP `1.1.14` / Host `0.5.6`). Damit ist die **Cloud-Verbindung die einzige Steuerungsroute** — ein Move ins internetlose UNTRUSTED-VLAN würde das Gerät unsteuerbar machen (auch aus HA). Der oben genannte UNTRUSTED-Kandidat ist damit **vom Tisch**, nicht nur verschoben. Der DHCP-Override `dns 1.1.1.1` verliert **für den Airfryer** seine Rechtfertigung (OTA-Problem erledigt) — der **Mechanismus selbst bleibt** aber als Werkzeug für künftige Low-Trust-Kandidaten definiert und wird **nicht** abgebaut (Entscheidung 2026-10-12), ebenso die DNS-Redirect-Variante (a). Die Cloud-Integration **bleibt** bewusst in Betrieb. Details: `docs/announcements.md`, Eintrag 2026-10-12. **Nebenbei:** die VLAN-Nummer im obigen Text („VLAN50") war falsch — UNTRUSTED ist laut `docs/vlan-plan.md` **VLAN 40**.
|
||||
|
||||
### TP8 · Gitea #2 — Identity & Access-Control
|
||||
- **RADIUS = FreeRADIUS auf der NAS** (✏️ User-Manager v5 kann **KEIN LDAP** → openldap nur via FreeRADIUS anbindbar).
|
||||
@@ -1108,222 +1109,11 @@ des Dimmers (= Lichtausfall in Diele UG/Wohnzimmer) wurde daraufhin **nicht** au
|
||||
> geschlossen (`7.24.1` war die Ursache, nicht `ft-over-ds`), alle vier Handoff-Punkte aufgelöst,
|
||||
> Gitea #17 closed. Volltext (Kondensat + Langfassung) im Archiv.
|
||||
|
||||
<a id="tp28"></a>
|
||||
|
||||
### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core` — 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,
|
||||
> weil mit **ap3** ein dritter CAP dazukommt; das ist dieselbe Baustelle, kein neues TP
|
||||
> (Christoph-Direktive: *ein archiviertes TP ist ein geschlossenes Ticket und darf wieder
|
||||
> geöffnet werden*). **Der offene Teil steht am Ende des Abschnitts unter „Nachtrag ap3".**
|
||||
|
||||
- 🔴🔴 **POST-MORTEM 2026-08-18 (gemeldet von Christoph: „die -iot-geräte sind offline … connect möglich, aber kein dhcp"):**
|
||||
Seit dem Cutover hingen **alle Slave-VAPs in gar keiner Bridge**. `/interface wifi cap slaves-datapath`
|
||||
war auf beiden APs leer — und genau dieses Feld ist das Einzige, was die vom Manager erzeugten
|
||||
Slave-VAPs lokal in die Bridge haengt. Der Manager schickt `datapath.bridge=bridge` zwar mit,
|
||||
**der CAP uebernimmt davon nur die `vlan-id`.** Betroffen: `dirtyvader-iot`, `-media`, `-untrusted`,
|
||||
`-guest`. Nicht betroffen: `dirtyvader-main` — dessen Master-Radios `wifi1`/`wifi2` sind
|
||||
**statische** Bridge-Ports und liefen deshalb weiter. Genau das machte den Ausfall unsichtbar.
|
||||
- **Fix (live 2026-08-18 auf ap1 + ap2, seither Soll im Template):**
|
||||
`/interface wifi datapath add name=dp-caps-slaves bridge=bridge` +
|
||||
`/interface wifi cap set slaves-datapath=dp-caps-slaves`. Das Objekt traegt **bewusst keine
|
||||
`vlan-id`** — die liefert der Manager pro VAP. Danach erscheinen die VAPs als **dynamische
|
||||
Bridge-Ports mit `pvid`=30/40/50/90**, und DHCP laeuft sofort wieder.
|
||||
- 🔴 **Die Diagnose-Lehre: „Client assoziiert" beweist gar nichts.** Am Manager standen 25 IOT-
|
||||
Registrations — das WLAN sah perfekt aus. Erst zwei Reads trennten die Faelle:
|
||||
`torch` auf `vlan30` am Core (**leer**) und der TP5-Drop-Log (**kein einziger DHCP-Eintrag**).
|
||||
Beides leer heisst: es wird nichts verworfen, es **kommt** nichts. Damit war die Firewall
|
||||
— der naheliegende Verdaechtige nach der Haertung derselben Nacht — in zwei Minuten entlastet.
|
||||
- ⚠️ **Die Abnahme „✅ Ohne jeden Aussetzer" weiter unten war falsch.** Sie zaehlte Registrations
|
||||
(30 Clients, beide CAPs `Ok`, 7 VAPs je AP) — also genau die Metrik, die hier luegt. Keiner
|
||||
dieser Werte beruehrt die Bridge. Ein Abnahmekriterium fuer WLAN-Umbauten ist ab jetzt eine
|
||||
**frische DHCP-Lease pro SSID**, nicht die Registration-Table.
|
||||
- **`routeros/tp28-ap-wifi-reste-abraeumen.rsc` hat den Ausfall nicht ausgeloest, aber zementiert:**
|
||||
mit den geloeschten datapath-Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette
|
||||
zeigen koennen. Der Ein-Zeilen-Fix war damit weg. Nachtrag steht im Skript.
|
||||
- ✅ **Reboot-Abnahme an ap2 (2026-08-18), drei Fragen auf einmal beantwortet:** `slaves-datapath`
|
||||
ueberlebt den Neustart (statisches Objekt, steht im `/export`), der CAP **findet seinen Manager
|
||||
durch die gehaertete input-Kette wieder** (`current-caps-man-identity=crs326-core` — MNDP/CAPWAP
|
||||
sind also korrekt offen, das war seit TP5 Etappe 4 nie durchgespielt), und die sieben Slave-VAPs
|
||||
kommen von selbst als dynamische Bridge-Ports mit pvid 30/40/50/90 zurueck. AP nach **40 s**
|
||||
wieder erreichbar. Abnahme nach neuer Regel = **frische Lease**: IOT-Client `10.40.30.8`
|
||||
`last-seen=30s`, MAIN-Client `10.40.10.180` `last-seen=1m54s`.
|
||||
- ⚠️ **Reboot per MikroMCP geht auf 7.24 nicht:** das `reboot`-Tool sendet `delay`, die REST-API
|
||||
antwortet `unknown parameter delay`; `run_command` ist fuer `/system reboot*` per Deny-Policy
|
||||
gesperrt. Weg bis das gefixt ist: `sshpass` + `ssh admin@<ap> "/system reboot"`.
|
||||
- ✅ **Soll nachgezogen, Compliance wieder sauber: 92/92, 0 Drifts** (2026-08-18). Weg: Template in
|
||||
`nautobot/mikrotik_routeros.j2` → `routeros/push-gc-template.py` (Secret-Guard) → GitRepository-Sync
|
||||
→ Backup/Intended/Compliance. Ohne den Push meldet der Refresh die zwei neuen Zeilen als `extra`
|
||||
auf beiden APs — das Jinja lebt im **separaten** Gitea-Repo `bf-golden-config`, ein Commit hier
|
||||
allein erreicht Nautobot nicht.
|
||||
- ⚠️ **Beim ersten Sync-Versuch: `redis.exceptions.ConnectionError: broken pipe`** auf dem
|
||||
Nautobot-Stack. Ein simpler Retry ging durch; NAS-Load war unauffaellig (0,9). Der Job meldet das
|
||||
als `FAILURE` — nicht mit einem inhaltlichen Fehler verwechseln.
|
||||
|
||||
- ✅ **Der offene GC-Rest ist erledigt (2026-08-18).** Die fuenf `wifi-*`-ComplianceFeatures haengen
|
||||
jetzt am **Core**, der AP-Zweig des Templates ist auf eine einzige Zeile eingedampft
|
||||
(`/interface wifi cap set discovery-interfaces=vlanXX enabled=yes`).
|
||||
- 🔴 **Dabei aufgefallen: die APs trugen noch die KOMPLETTE Standalone-WLAN-Config** aus TP3 —
|
||||
je 2 channel, 6 datapath, 6 security, 7 configuration, 1 steering. Der Cutover vom 08-17 hatte
|
||||
sie nie abgeraeumt. Wirkungslos (der Manager liefert alles dynamisch), aber sie im Template als
|
||||
Soll zu rendern hiesse, totes Zeug zur Absicht zu erklaeren — dasselbe Argument wie bei den
|
||||
defconf-Firewallregeln in TP26. Abgeraeumt mit `routeros/tp28-ap-wifi-reste-abraeumen.rsc`.
|
||||
- ✅ **Ohne jeden Aussetzer:** vorher/nachher 30 Clients, beide CAPs `Ok`, 7 dynamische VAPs je AP.
|
||||
Die Sorge vor einem Radio-Re-Apply (TP25/TP3.3: „wirft jeden Client kurz raus") hat sich nicht
|
||||
bestaetigt — die dynamischen VAPs tragen ihre Werte **inline** (`datapath.vlan-id=…`) und
|
||||
referenzieren kein lokales Objekt; nur `wifi1`/`wifi2` hielten noch einen toten
|
||||
`configuration=`-Verweis, der beim Loeschen folgenlos blieb.
|
||||
- 🔴 **`[find !dynamic]` ist auf diesen Sektionen ein SYNTAXFEHLER**, nicht bloss wirkungslos:
|
||||
`/interface wifi configuration` & Co. haben gar keine `dynamic`-Eigenschaft (die gibt es nur auf
|
||||
`/interface wifi` selbst). Der erste Anlauf lief komplett in die `on-error`-Zweige, aenderte
|
||||
**nichts** und meldete trotzdem „Plan Completed". Wieder: nur die Gegenpruefung am Geraet zaehlt.
|
||||
- ✅ **Drei neue ComplianceRules schliessen die letzten WLAN-Luecken:** `wifi-capsman`
|
||||
(der Manager-Schalter), `wifi-provisioning` (die vier Regeln je `radio-mac` — sie entscheiden,
|
||||
welcher AP welche Kanaele und Slave-VAPs bekommt) und `wifi-cap` (die CAP-Seite; faellt
|
||||
`discovery-interfaces` weg, findet der AP seinen Manager nach dem naechsten Reboot nicht mehr).
|
||||
**Ergebnis: 92/92 compliant, 0 Drifts.**
|
||||
- ⚠️ **`wifi-security` ist gruen, aber NICHT deploybar.** Die Passphrasen stehen als `<<REDACTED>>`
|
||||
im Template (User-Entscheid 2026-08-18: alle fuenf Features umhaengen, Platzhalter in Kauf nehmen).
|
||||
Der gruene Haken beweist nur, dass Soll und Ist dieselbe **Maske** tragen — nicht, dass die
|
||||
Passphrasen stimmen, und schon gar nicht, dass die Sektion ausgerollt werden koennte. Das steht
|
||||
als Warnung im Template. Wer sie aendert, liest den Ist-Stand per REST zurueck
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — **nicht** aus `wlan-psks.env` oder den
|
||||
drei Bring-up-Dateien, die voneinander abgeschrieben sind.
|
||||
- ⚠️ **Die frueher hier notierte „bisher fehlende Rule auf `/interface wifi`" ist gegenstandslos** —
|
||||
wie vorhergesagt: ein CAP hat keine Inline-Overrides mehr, die driften koennten.
|
||||
> **Methodenwechsel auf Christophs Anstoß:** *„ich möchte die methode in frage stellen … eine sot ist zwar nett,
|
||||
> aber wenn sie uns das leben zur hölle macht, verzichte ich auf diese governance."* Der Entscheid vom
|
||||
> 2026-07-22, CAPsMAN abzuschaffen, ist damit **revidiert**.
|
||||
|
||||
- ✅ **Warum es diesmal trägt:** 2026-07-22 war **ap1 Manager UND AP** — und ein Manager kann seine eigenen
|
||||
Radios nicht per CAPsMAN provisionieren (MikroTik-bestätigt). Genau diese Asymmetrie war die Ursache von
|
||||
TP3.1. Mit dem **Core** als Manager sind beide APs symmetrisch. **Der Core braucht dafür kein `wifi-qcom`:**
|
||||
das `capsman`-Menü steckt im Basis-`routeros`, die 3,35 MB freier Flash sind irrelevant. Die zweite
|
||||
Begründung von damals („WLAN instabil") war ohnehin widerlegt — standalone blieb es instabil, die Ursachen
|
||||
waren die Funkparameter aus TP25.
|
||||
- ✅ **Ist-Stand:** Manager auf `vlan99`, 4 Kanäle · 6 Datapaths · 6 Security · 2 Steering · 9 Configurations ·
|
||||
4 Provisioning-Regeln je `radio-mac`. Beide `remote-cap` **`Ok`**, je 7 dynamische VAPs. Kanäle exakt wie in
|
||||
TP25 gemessen (ap1 `5180/ax/Ce/I`+`2412/ax`, ap2 `5220/ax/Ce/I`+`2462/ax`). **34 Clients** (main 3, iot 27,
|
||||
media 4), Roaming im Manager-Log belegt. **`ft-over-ds=yes`** ist jetzt aktiv (unter CAPsMAN erstmals
|
||||
verfügbar), `-guest` hat eigenes `st-guest` mit **`neighbor-group=bf-guest`** — nicht `bf-main` mitbenutzen,
|
||||
sonst meldet 802.11k Gast-Clients BSSIDs einer SSID, auf die sie nicht assoziieren können.
|
||||
- 📌 **Local-Forwarding ist keine Einstellung mehr:** im neuen `/interface/wifi` gibt es **kein** zentrales
|
||||
Forwarding („There is no CAPsMAN-forwarding in WiFi (RouterOS 7.0+)"). Die `datapath`-Property existiert
|
||||
deshalb nicht — `monitor` bestätigt `traffic processing on CAP`.
|
||||
- 🔴 **DIE MESSFALLE DIESES TPs — unter CAPsMAN führt der MANAGER die Tabellen, nicht der CAP.**
|
||||
`/interface/wifi/registration-table` und die `wireless`-Logs sind auf ap1/ap2 **leer**, obwohl das WLAN
|
||||
einwandfrei läuft. Ich habe daraus zweimal „WLAN ist unten" geschlossen, während Christophs Geräte mit
|
||||
vollem Ausschlag verbunden waren und ein Speedtest gut lief. **Immer am Manager zählen.**
|
||||
- 🔴 **DIE ZWEITE FALLE — drei übereinstimmende Dateien sind nicht drei Belege.** `wlan-psks.env` wich für
|
||||
iot/untrusted/infra von `ap1-prov-base.rsc`, `ap2-prov-base.rsc` und `ap1-standalone.rsc` ab. Ich habe die
|
||||
drei als Bestätigung gelesen und die env-Datei „korrigiert" — **~20 Shellys waren 20 Minuten aus dem Netz.**
|
||||
Die drei Dateien stammen alle aus dem Bring-up vom 2026-08-08, sind also **eine Quelle in drei Kopien**.
|
||||
✅ **Auflösung:** die Passphrasen sind am Gerät per REST **lesbar**
|
||||
(`GET http://10.40.99.1/rest/interface/wifi/security`) — dort ausgelesen ergab sich exakt der env-Wert.
|
||||
`wlan-psks.env` ist neu aus dem Live-Stand befüllt und trägt jetzt eine Kopf-Warnung; die drei Baselines
|
||||
sind seit dem Cutover ohnehin nicht mehr deploybar und wurden entschärft (Passphrasen raus, HISTORISCH).
|
||||
- ✅ **~~OFFEN — das GC-Modell beschreibt weiter Standalone-APs~~ — ERLEDIGT am 2026-08-18.** Die
|
||||
`wifi-*`-Features haengen jetzt am Core, das AP-Template ist auf die `cap`-Zeile eingedampft, und die
|
||||
toten Standalone-Objekte sind von beiden APs abgeraeumt. Details im ersten Block dieses TPs.
|
||||
Die hier genannte Zahl `76/76` ist damit doppelt ueberholt (aktuell 92/92 bei 23 Rules).
|
||||
*(Der Absatz stand bis 2026-08-19 unveraendert als „OFFEN" hier, obwohl der Kopf des TPs schon
|
||||
„KOMPLETT" sagte — ein Selbstwiderspruch im selben Abschnitt, ueber den prompt die naechste Session
|
||||
gestolpert ist. Merkregel fuer kuenftige Statuswechsel: **nicht nur den Header umschreiben, sondern
|
||||
die Punkte darunter mitziehen.**)*
|
||||
- 📌 **Nebenbefund:** HAs `mikrotik`-Integration pollt alle 10 s und schreibt zwei Log-Zeilen pro Poll auf jedes
|
||||
Gerät. Die AP-Logs sind dadurch für Diagnose praktisch unbrauchbar (`get_log` ohne `topics`-Filter liefert
|
||||
nur `admin logged in/out via api`).
|
||||
- **Abh.:** TP25 ✅ (die Funkparameter sind übernommen), TP15/TP21.1 ✅ (Core als L3-Router = Manager-Standort).
|
||||
Verwandt: **TP3** (dessen CAPsMAN-Ära hier revidiert wird), **TP22** (GC-Scope).
|
||||
|
||||
|
||||
#### ↩️ Nachtrag ap3 — dritter Access-Point (cAP ax), offen seit 2026-08-30
|
||||
|
||||
**Gerät:** MikroTik **cAP ax** (`cAPGi-5HaxD2HaxD`), Werkszustand, Uplink an **`crs326-core ether21`**.
|
||||
**Mgmt-IP `10.40.99.13/24`** (TP32: `.10–.29` = physische Hosts; ap1 `.11`, ap2 `.12`), statisch am
|
||||
Gerät wie ap1/ap2 — **keine** DHCP-Reservierung.
|
||||
|
||||
**Warum überhaupt:** ap3 soll **ap1 in Technik UG ablösen**, ap1 wandert danach ans Sideboard in die
|
||||
gemessene Schwäche (Schlafzimmer/Bad/Küche). Das ist exakt **TP25 Variante 3** („cAP ax an die
|
||||
bisherige Stelle, der verdrängte hAP ax² wandert in den Schatten").
|
||||
|
||||
**Kanalplan = der aus TP25 (2026-08-24 gerechnet, DFS von Christoph genehmigt) — nicht neu erfunden:**
|
||||
|
||||
| AP | 5 GHz | Band | DFS | 2,4 GHz | wann |
|
||||
|---|---|---|---|---|---|
|
||||
| **ap3** | `5180/Ce` (36+40) | A | nein | **2437 (ch6)** | **jetzt** |
|
||||
| ap2 | `5220/Ce` heute → **`5300/Ce`** (60+64) | A → B | → ja | 2462 (ch11) | beim ap1-Umzug |
|
||||
| ap1 | — (**Radios aus**) → **`5260/Ce`** (52+56) | → B | → ja | 2412 (ch1) | beim ap1-Umzug |
|
||||
| — | `5220/Ce` **Ausweichspur frei halten** | A | — | — | — |
|
||||
|
||||
🔴 **ap1s Radios gehen AUS, bevor ap3s angehen** (User-Vorgabe 2026-08-30): beide stehen bis zum
|
||||
Umzug **nebeneinander**, ein zweiter AP am selben Punkt bringt nichts und stört. Das ist ein
|
||||
**Interim**, kein Endzustand — ap1 kommt auf `5260` zurück.
|
||||
|
||||
> 📌 **Erratum 2026-08-30:** der Port ist **`ether21`**, nicht `ether22` (erste Annahme aus der
|
||||
> Fragerunde). Der Befund lag die ganze Zeit in den Daten: in der dynamischen `pvid`-VLAN1-Zeile
|
||||
> steht `current-untagged=…,ether21` — `ether22` taucht dort nicht auf, weil es keinen Link hat.
|
||||
> **Merkregel: bevor man einen Port konfiguriert, den behaupteten Port gegen `link-ok` + die
|
||||
> 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 — ✅ 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 Angleichung der übrigen vier ist am
|
||||
2026-08-31 erfolgt** (Christoph, WebFig) — die Flotte ist einheitlich, TP27 erledigt.
|
||||
- ✅ **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
|
||||
GC-Template-AP-Zweig hatte die **hartkodiert**; das war die stille Annahme *„AP == hAP ax²"*.
|
||||
Deshalb `ap.extra_ports` im ConfigContext (Default = `["ether3","ether4","ether5"]`, ap3 = `[]`).
|
||||
**Merkregel: eine Literal-Liste im Template ist so lange harmlos, bis die Flotte heterogen wird.**
|
||||
- **`crs326-core` hat kein PoE-out** (`/interface ethernet poe` existiert dort nicht). Der cAP ax
|
||||
bringt aber **Netzteil *und* Gigabit-PoE-Injector** mit (Produktseite 2026-08-30) — die TP25-Notiz
|
||||
„kein Netzteil im Lieferumfang üblich" trifft auf dieses Modell **nicht** zu.
|
||||
- `ether2` bleibt im Bring-up als **Lifeline** im Bridge (untagged VLAN1) und fliegt erst raus,
|
||||
wenn ap3 über `10.40.99.13` steht — Zielzustand = wie ap2: `ether2` **kein** Bridge-Port
|
||||
(VLAN1 bleibt leer, TP19 ist geschlossen).
|
||||
- **Radio-MACs nicht raten:** `/interface wifi radio print` **am Manager** listet CAP-Identity +
|
||||
Radio-MAC ohne jede Passphrase. Das ist der richtige Read — **nicht** `list_wifi_interfaces`,
|
||||
das gibt `security.passphrase` *aller* Profile im Klartext zurück (Leak 2026-08-24).
|
||||
|
||||
##### Auflagen aus TP25 für den ap1-Umzug (nicht vergessen)
|
||||
1. **DFS-Channel-Objekte brauchen eine Frequenz-LISTE** (z.B. `frequency=5260,5280,5220`). Mit
|
||||
genau einem Wert ist eine Radar-Erkennung ein **SPOF**: das Radio muss den Kanal verlassen und
|
||||
hat keinen zweiten → 5 GHz dieses APs bleibt unten.
|
||||
2. **Vorher die *erreichte* `tx-power` messen** (`/interface wifi monitor`), ap2 testweise auf
|
||||
`5260` gegen die heutigen 18 dBm auf `5220`. Fund B war genau das Muster: 18 dBm gesetzt,
|
||||
9 dBm gesendet.
|
||||
3. Uplink für ap1 danach: **`core ether3`** (`no-link`, frei) — vorher dort die Altlast abräumen,
|
||||
der Port ist untagged-Member von VLAN40 bei `pvid=1`.
|
||||
### TP28 · Gitea #18 — WLAN über CAPsMAN auf `crs326-core` — → [Archiv](teilprojekte-archiv.md#tp28)
|
||||
|
||||
> **Abgeschlossen:** ap1 Sideboard · ap2 Technik UG · ap3 Technik OG (Platzierung final),
|
||||
> Kanalplan committed (ap1 `5260/5280` · ap2 `5300/5320` · ap3 `5180`), `wifi-cap`-Rest auf dem
|
||||
> Core bereinigt (`enabled=no`), Compliance 29/29 über 5 Geräte. Gitea #18 closed. Volltext im Archiv.
|
||||
|
||||
### TP29 · Gitea #19 — Anwesenheitserkennung fürs Energiesparen
|
||||
> **User-Formulierung (2026-08-21):** „Personen im Haus orten mit BT (Shellys), WiFi und Radar. So
|
||||
@@ -2479,56 +2269,59 @@ NAT/mangle/WG sind Template-Literale, der Firewall-Filter kommt als ConfigContex
|
||||
die FQDNs, die hier Daten werden). **Abgrenzung:** VMID-Umnummerierung bleibt draußen; Token-**Rotation**
|
||||
läuft in TP30; Tunnel-GC-Namensgebung hier, die **SSoT/Recovery** in TP46.
|
||||
|
||||
### TP47 · Gitea #74 — Core egress via cf-mesh und WARP-Exit-Validierung
|
||||
> **Christoph:** Validierung des Core-Egress-Weges via Cloudflare Mesh. Ausgangsprüfung gegen neutralen Host
|
||||
> und Ziel `164.90.191.7` (DigitalOcean). Korrektur früherer Behauptung: **WARP war am Mac gar nicht
|
||||
> verbunden** — `warp=off`, `ip=90.146.33.177` (Heim-IP). Die zuvor gemessene `104.28.207.39` stammte aus
|
||||
> einem Test mit nur der `1.1.1.1/32`-Regel, daher über die Heim-IP (403). Beweis: Regel aktiviert
|
||||
> `164.90.191.7/32 → egress-cannaleo` → Ziel erreicht mit `200` (168 KiB). Template-Änderung in
|
||||
> `nautobot/mikrotik_routeros.j2` Abschnitt `[10b]` (commit `c600c62e`). Doku-Korrektur
|
||||
> `docs/warp-egress-native-crs326.md` §6.3.
|
||||
>
|
||||
> **Bestandsaufnahme (2026-09-27):**
|
||||
> - Ziel `164.90.191.7` (DigitalOcean-Origin, kein Cloudflare im Pfad) bereitet `403` bei Heim-IP
|
||||
> (`90.146.33.177`, WARP aus), `200` über Core-Routing (`cf-mesh`, `egress-cannaleo`-Table).
|
||||
> - `cf-mesh` erreicht den Origin mit `200` (verifiziert: `cdn-cgi/trace` → `ip=104.28.207.39`,
|
||||
> `warp=on`, `colo=VIE`). Keine Dedicated Egress IP nötig.
|
||||
> - User-Cave: kein Zugriffsmuster, das Heim-IP und diesen Ausgang gemeinsam gegen den Origin zeigt.
|
||||
> - Template-Drift: 0 (Secret-Guard bestanden, Push `c600c62e`).
|
||||
>
|
||||
> #### Durchführung (in einem Durchgang)
|
||||
> 1. TP anlegen, Prosa in `teilprojekte.md` schreiben, commit + push
|
||||
> 2. Core-Routing-Objekte (`egress-cannaleo` Table, Route, Rule) bereits live auf `crs326-core`
|
||||
> 3. Doku `docs/warp-egress-native-crs326.md` §6.3 korrigiert (früherer 403 = Messfehler)
|
||||
> 4. Mineral: nicht abzudrehen (seit 21.09. still)
|
||||
> 5. `10.101.0.254` taugt nicht als GW (100 % Loss; FM = Liwest; Cave erfüllt)
|
||||
|
||||
#### Bestandsaufnahme (live 2026-09-21) — es gibt eine Doku-SSoT, aber keinen Master
|
||||
| Was | Wo | Stand |
|
||||
<a id="tp48"></a>
|
||||
### TP48 · Gitea #75 — Recovery-Lücke: Nicht-GC-Objekte + Evaluation Ansible als Nautobot-Ablösung
|
||||
|
||||
> **Anlass:** beim Abschluss von **TP10** (2026-10-05) fiel auf, dass der DDNS-Updater als
|
||||
> `/system script` + DHCP-Trigger auf `crs326-core` lebt und in **keinem** Template steht — ein
|
||||
> Recovery von null hätte ihn nicht. Das ist eine **Klasse**, kein Einzelfund. Christophs Frage
|
||||
> daraus: *„wäre ansible in der lage, nautobot vollständig als ssot abzulösen. 1 tool für
|
||||
> doku/deployment und nicht mehrere … keine indiv-anpassungen falls einfach vermeidbar."*
|
||||
|
||||
#### Teil 1 — Die Lücke (live geprüft 2026-10-05, `crs326-core`)
|
||||
|
||||
| Objekt | In einem Template? |
|
||||
|---|---|
|
||||
| `tp10-ddns-bf4`, DHCP-Client `client1` `script=` | ❌ |
|
||||
| `/system script environment cfDdnsToken` (**Secret**) | ❌ und darf nie (Secret-Gebot) |
|
||||
| 8 Alt-Skripte aus TP21.1 (`tp21-dhcp-restore`, `tp21-wan-wg-restore`, …) | ❌ |
|
||||
| `/system scheduler` | — (keiner vorhanden) |
|
||||
|
||||
🔴 **Nebenbefund:** `tp21-wan-wg-restore` trägt den **WG-Hub-Privkey im Klartext** im Skript-Körper.
|
||||
Das Skript liegt **nicht im Git** (nur auf dem Gerät), es ist also ein undokumentierter Recovery-Pfad
|
||||
mit einem Secret darin. **Aufnehmen, nicht sofort anfassen** (Christoph 2026-10-05).
|
||||
|
||||
**Warum unsichtbar:** GC prüft **29 ComplianceFeatures** — **keines** deckt `system script`,
|
||||
`system scheduler` oder `script environment`. Die Compliance-Zahl prüft nur, **was gerendert wird**;
|
||||
was nicht gerendert wird, ist per Definition nicht „drift".
|
||||
|
||||
#### Teil 2 — Die Evaluation
|
||||
|
||||
🔴 **Der Befund, der die Fragestellung verschiebt:** Nautobot ist **heute schon nur teilweise SSoT.**
|
||||
Live gezählt im Template: **datengetrieben** sind nur Device-Felder, `interfaces.all()`,
|
||||
`ip_addresses.all()` und zwei ConfigContexts (`dhcp_leases`, `firewall_filter`). **Literal im Jinja**
|
||||
stehen: 7 VLAN-Definitionen, 37 `bridge-port`- und 19 `bridge-vlan`-Zeilen, die Core-Firewall,
|
||||
NAT/Mangle/Queue/Routen. Der Ablöse-Vergleich lautet also nicht „Ansible vs. Nautobot-SSoT", sondern
|
||||
„Ansible vs. **Jinja-im-Repo + Gitea + ein paar Nautobot-Felder**".
|
||||
|
||||
| Rolle | Träger heute | Ansible-Äquivalent |
|
||||
|---|---|---|
|
||||
| Tunnel, WARP-Connector, Workers, Access-Apps, Hyperdrive, VPC, teamnet-Routen | Nautobot `nautobot.cloud`, Job **`CF Estate Sync`** (`jobs/cf_estate_sync.py`) | ✅ 4 `CloudNetwork` · 16 `CloudService` live |
|
||||
| **Ingress-Origin-Ziele** | **nirgends** | 🔴 `_ingress()` speichert nur `ingress_hostnames`, **nicht** das Origin |
|
||||
| **DNS-Records** | **nirgends** | 🔴 nicht erfasst — live 24 Records / 8 proxied |
|
||||
| **Access-Policies** | **nirgends** | 🔴 nur die Apps selbst |
|
||||
| Richtung | CF → Nautobot | 🔴 **Pull-only** („CF bleibt operativer Master") |
|
||||
| `docs/cf-tunnel-mapping.md` | Repo | §1–16 eingefrorene As-Built-Historie · §0 = Live-Zeiger (altert: nennt `bf-public` „2 conns", live **12** = 3 AP-Connectoren × 4) |
|
||||
| TP-Index/Status | **Gitea** (seit TP36 — nicht Nautobot) | ❌ bleibt Gitea |
|
||||
| Live-Gerätedaten (Compliance) | Nautobot DCIM + GC | ⚠️ Facts + `--check` |
|
||||
| Soll-Config | GC-Templates (Jinja) | ✅ Templates/Inventar |
|
||||
| Ist-Config | GC-Backup | ⚠️ `netcommon` backup |
|
||||
| Doku/Inventar (133 Interfaces, 38 IPs, 54 Leases) | Nautobot DB | ❓ offen — Ansible hat keine DB |
|
||||
|
||||
**Antwort auf „full setup/recovery from scratch": heute nicht möglich.** Es fehlt (a) der Push-Weg,
|
||||
(b) Ingress-Origins, (c) DNS, (d) Access-Policies. Und ein frisch erzeugter Tunnel liefert **neue ID
|
||||
und neues Secret** → das **Token-Handoff an die Connectoren** (ap1/ap2/ap3) ist **Teil der Recovery**,
|
||||
kein Nebenschritt.
|
||||
**Offene Punkte:** (1) was leistet Nautobot *unersetzlich* (Compliance-Historie, ConfigPlan-Audit,
|
||||
GitRepository-Sync, Secret-Maskierung)? (2) Kann Ansible Inventar+Soll+Drift aus *einer* Git-Quelle
|
||||
bedienen? (3) Migrationskosten: 29 Features + 7 `config_replace`-Regeln nachbauen? (4) **Wo bleibt
|
||||
Doku**, wenn Ansible nur deployt? (5) Werden die heutigen Sonderfälle (Literal-Blöcke wegen bewusster
|
||||
Schiefstände wie `ether2` in VLAN1) mit Ansible sauberer oder chaotischer?
|
||||
|
||||
#### Umfang
|
||||
Tunnel + Ingress **inkl. Origin-Ziele** + DNS-Records + Access-Apps/Policies vollständig nach Nautobot;
|
||||
**Push-Renderer** (Dry-Run + Diff); **Recovery-Runbook** für den Aufbau von null.
|
||||
|
||||
🔴 **Vor dem ersten Schritt prüfen, nicht annehmen:** welches Nautobot-Objekt DNS-Records tragen kann
|
||||
(Kern-`ipam` vs. eigenes Modell), und ob das Ingress-Origin als `extra_config`-Feld reicht oder ein
|
||||
eigenes Modell braucht.
|
||||
|
||||
- **Abh.:** TP44 Etappe 1/2 ✅ (native `nautobot.cloud`-Modelle + Pull-Job stehen). **Abgrenzung:**
|
||||
Tunnel-**GC/Namensgebung** läuft in TP45 Schicht D; **CF-Writes** aus Nautobot sind hier *das Ziel*,
|
||||
in TP37 ausdrücklich ausgeschlossen — die Grenze verschiebt sich also bewusst mit diesem TP.
|
||||
**Abgrenzung:** nicht TP21/TP22 (bauen GC auf), nicht TP46 (CF-SSoT). Berührt die Governance-Regel
|
||||
„Config nur über Nautobot" — bei Trägerwechsel neu zu fassen. Verwandt: TP30 (der WG-Privkey ist ein
|
||||
Rotations-Kandidat). **Ergebnis = Empfehlung mit Zahlen, keine Migration.**
|
||||
|
||||
---
|
||||
|
||||
@@ -2549,7 +2342,7 @@ Kurzorientierung, wo was zu finden ist:
|
||||
| **Discovery / Media** | [TP7](teilprojekte-archiv.md#tp7) mDNS · [TP7.1](teilprojekte-archiv.md#tp7-1) SSDP-Relay · [TP7.2](teilprojekte-archiv.md#tp7-2) HA↔Sonos |
|
||||
| **Hosts / Migration** | [TP14](teilprojekte-archiv.md#tp14) (+[.1](teilprojekte-archiv.md#tp14-1)/[.2](teilprojekte-archiv.md#tp14-2)/[.2a](teilprojekte-archiv.md#tp14-2a)/[.3](teilprojekte-archiv.md#tp14-3)/[.5](teilprojekte-archiv.md#tp14-5)) VLAN20-Cutover · [TP17](teilprojekte-archiv.md#tp17) Shelly→VLAN30 · [TP19](teilprojekte-archiv.md#tp19) VLAN1-Retirement |
|
||||
| **Control-Plane** | [TP20](teilprojekte-archiv.md#tp20) Platform-Postgres · [TP21](teilprojekte-archiv.md#tp21) Nautobot GC · [TP22](teilprojekte-archiv.md#tp22) Adoption + Compliance-Push · [TP27](teilprojekte-archiv.md#tp27) RouterBOOT 7.24 · [TP-R](teilprojekte-archiv.md#tp-r) Capabilities-Sweep · [TP44](teilprojekte-archiv.md#tp44) CF-Estate Doku-SSoT |
|
||||
| **Zugang** | [TP10](teilprojekte-archiv.md#tp10) Multi-Site-VPN *(offen nur der DDNS-Updater, s. Follow-ups)* · [TP16.1](teilprojekte-archiv.md#tp16-1) natives CF-Mesh |
|
||||
| **Zugang** | [TP10](teilprojekte-archiv.md#tp10-ddns) Multi-Site-VPN *(✅ abgeschlossen 2026-10-05 — DDNS-Updater live + Mesh-Quell-NAT)* · [TP16.1](teilprojekte-archiv.md#tp16-1) natives CF-Mesh |
|
||||
| **Verworfen** | [TP9](teilprojekte-archiv.md#tp9) Gateway-HA (User-Entscheid 2026-08-08) |
|
||||
|
||||
## Reihenfolge / Abhängigkeiten
|
||||
@@ -2785,60 +2578,108 @@ public CF-Worker hinter CF Access (löst auf CF-Anycast), kein BF-Origin ⇒ daf
|
||||
Split-Horizon; Zugang via TP41-**WARP** gelöst (Geli 2026-09-10 via Android). Der Override-Bedarf für
|
||||
**interne** Namen blieb dabei übersehen — deshalb reopened, s.o.
|
||||
|
||||
<a id="tp10"></a>
|
||||
### TP10 · Gitea #49 — Multi-Site VPN (WG-Hub FM/DW) — **REOPENED 2026-10-03** (Mesh-Quell-NAT)
|
||||
<a id="tp49"></a>
|
||||
### TP49 · Gitea #76 — CF-Access: `login_method:github` im include der Terpenfinder-Policy — ✅ behoben (require statt include)
|
||||
|
||||
> **Details/Cutover/Gotchas/Eckdaten → `docs/tp10-vpn-migration.md` (Single Source of Truth).**
|
||||
> Research: `docs/research/vpn-comparison.md`. Prosa-Historie: `teilprojekte-archiv.md#tp10`.
|
||||
> **Rückgeholt aus dem Archiv am 2026-10-03** — neuer Befund an derselben Baustelle
|
||||
> (Konvention: geschlossenes Ticket, kein versiegeltes → reopen statt Zwillings-TP).
|
||||
*(Angelegt 2026-10-06. Fund vom 2026-07-10 (Memory `project_cf_tunnel_1033`).
|
||||
**Am 2026-10-06 zunächst fälschlich als Phantom geschlossen, nach einem echten Test wieder
|
||||
geöffnet und behoben.** Die Irrfahrt steht unten unter „Aktenhistorie" — sie ist lehrreich
|
||||
genug, um sie zu behalten.)*
|
||||
|
||||
**Bestand (aus dem Archiv, unverändert gültig):** beide WG-Tunnel (fm `10.101.0.0/24`, dw
|
||||
`10.95.10.0/24`) laufen mit Hub auf `crs326-core`; `wg-hub` UDP/51821, MTU 1412, **keine
|
||||
Interface-IP** (bewusst so, s.u.). Public-Edge direkt, kein Double-NAT.
|
||||
#### Befund (verifiziert, 2026-10-06)
|
||||
|
||||
#### 🔴 Neuer Befund (2026-10-03) — Mesh-Quelle außerhalb der Spoke-AllowedIPs
|
||||
WARP-Clients erreichen die Spoke-Netze über **`cf-mesh`** (TP16.1) mit Quell-IP **`100.96.0.0/12`**.
|
||||
Die Spoke-FritzBoxen führen am WG-Peer aber nur **`10.40.0.0/16`** als AllowedIPs → sie **verwerfen**
|
||||
den Verkehr. Live bewiesen am Core:
|
||||
Policy `christoph` auf App `420.teilzeitladen.xyz` (Terpenfinder v3, `e6a7991f…`, **öffentlich**)
|
||||
trug `login_method:github` im **`include`**. `include` ist ein **OR** — die Zeile „email
|
||||
christoph@emsenhuber.com" daneben war eine **Alternative**, keine Bedingung. Damit kam
|
||||
**jeder GitHub-Account** hinein, unabhängig von seiner E-Mail.
|
||||
|
||||
| Quelle | → Spoke | Ergebnis |
|
||||
**Belegt durch echten Test (Christoph, 2026-10-06):** ein **zweiter GitHub-Account** mit fremder
|
||||
Adresse kam durch. Vorher war mit `testus@emsenhuber.com` über den **Google**-IdP getestet worden
|
||||
— und **abgelehnt**; daraus war falsch geschlossen worden, GitHub verhalte sich genauso.
|
||||
|
||||
#### Fix (ausgeführt 2026-10-06)
|
||||
|
||||
`login_method:github` von `include` nach **`require`** verschoben ⇒ **`email AND github`**.
|
||||
|
||||
| | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| `src=100.96.0.6` (cf-mesh) | fm/dw | ❌ 100 % Loss — **das ist der Fehler** |
|
||||
| `src=10.40.20.1` (BF-IP) | fm/dw | ✅ 0 % Loss |
|
||||
| `include` | email · **github** · onetimepin | email · onetimepin |
|
||||
| `require` | — | **github** |
|
||||
|
||||
**Fix (live seit 2026-10-03, Commit `1fddf5d`):** eine `src-nat`-Regel auf dem `wg-hub`-**Egress**
|
||||
legt die Quelle in den AllowedIPs-Raum:
|
||||
`/ip firewall nat add action=src-nat chain=srcnat comment="TP10: NAT mesh source to WG spokes (AllowedIPs)" out-interface=wg-hub src-address=100.96.0.0/12 to-addresses=10.40.10.1`
|
||||
Soll im GC-Template (`nautobot/mikrotik_routeros.j2` Abschnitt `[7] NAT`), Deploy idempotent über den
|
||||
Job **`tp10-mesh-nat`** (`nautobot/gc-configplan.py`, manueller Plan, **nie** full-intended).
|
||||
**Zurückgelesen** über **beide** Endpunkte (App-Pfad und reusable-Pfad) — identisch, Fix sitzt.
|
||||
Fremder GitHub-Account fällt durch (hat die Adresse nicht); Christop's GitHub-Account hat sie als
|
||||
primäre Adresse und kommt durch. `onetimepin` bleibt als Include — **auf GitHub-Login-Seite ist
|
||||
der Einmalcode allein aber nicht mehr ausreichend** (`require` verlangt beides).
|
||||
|
||||
**Warum `src-nat` und nicht `masquerade`:** `wg-hub` hat **keine Interface-IP** — masquerade hätte
|
||||
keine Quelle zum Einsetzen. Die feste IP `10.40.10.1` hält zudem das Connection-Tracking: die Antwort
|
||||
an `10.40.10.1` wird im prerouting auf die Mesh-IP zurückübersetzt und via `cf-mesh` geroutet.
|
||||
**Sweep über alle Apps:** `login_method` in `include`/`require`/`exclude` — **genau zwei Treffer,
|
||||
beide in `Terpenfinder v3`** (`christoph`: github+onetimepin, `geli`: nur onetimepin=harmlos).
|
||||
Der interne Wildcard und alle anderen Apps: **kein `login_method` mehr** (TP41). Der Sweep prüfte
|
||||
**alle drei** Regel-Arrays, nicht nur `include` — sonst wäre der Fix hinterher als neuer Treffer
|
||||
aufgetaucht.
|
||||
|
||||
**Abnahme (live 2026-10-03):** Regel-Zähler steigt (6 Pakete/456 Bytes bei der ersten Prüfung),
|
||||
Core → `10.95.10.246` ✅ 0 % Loss (vorher 100 %), Mac/WARP → `10.95.10.1` ✅.
|
||||
**Scope-Warnung geprüft:** die Policy `christoph` ist `reusable: true`. Vor dem Schreiben wurde
|
||||
verifiziert, dass sie **nur an diese eine App gebunden** ist ⇒ Fix bleibt lokal, kein Kollateral.
|
||||
|
||||
#### ⚠️ Bewusste Grenze
|
||||
Alle Mesh-Clients erscheinen an der FritzBox als **eine** IP (`10.40.10.1`) — unterscheidbar nur
|
||||
über Connection-Tracking am Core. Christoph-Entscheid 2026-10-03: **bewusst so**, Erreichbarkeit war
|
||||
das Ziel. Eine Unterscheidbarkeit pro Client ist **nicht** Teil dieses TP.
|
||||
#### ⚠️ Was `login_method` NICHT kann (Christophs eigentliche Absicht)
|
||||
|
||||
#### ❌ Verworfene Diagnose — nicht wieder aufgreifen
|
||||
Ein Handoff vom 2026-09-28 behauptete, `wg-hub`s fehlende Interface-IP sei ein **Defekt**, der
|
||||
Core-eigene Traffic unerreichbar mache, und schlug `preferred-source` auf den zwei TP10-Routen vor.
|
||||
**Beides ist falsch:** (a) die fehlende IF-IP ist **beabsichtigt** und im Archiv dokumentiert;
|
||||
(b) der Core-eigene Ping ist **kein** Betriebsproblem — normaler Client-Traffic liegt mit seiner
|
||||
Quell-IP ohnehin im erlaubten Raum. Die Datei wurde gelöscht; die Erkenntnis steht hier.
|
||||
Die Absicht war, die Berechtigung **an den GitHub-Username** zu koppeln. **Das gibt Access nicht her:**
|
||||
|
||||
#### Offen / Close-Bedingung
|
||||
- [ ] **DDNS-Updater für `bf4.emsenhuber.net`** (der alte offene Punkt, unverändert): A-Record muss
|
||||
der Liwest-Public-IP folgen, aktuell manuell in Cloudflare gepflegt. Ziel: NAS/pve.
|
||||
- [ ] **Regel im täglichen GC-Lauf als konform bestätigt** — `backup → intended → compliance` grün,
|
||||
nachdem `tp10-mesh-nat` gelaufen ist.
|
||||
- [ ] **Zweiter Spoke prüfen:** der Fix ist an fm/dw gemeinsam verifiziert; ein dritter Spoke
|
||||
bekäme dieselbe AllowedIPs-Grenze und bräuchte die Regel ebenfalls (heute nicht relevant).
|
||||
- **`login_method` prüft die Methode, nicht den Nutzer.** „hat sich via GitHub angemeldet" — kein
|
||||
Username, keine Org-Zugehörigkeit.
|
||||
- **Es gibt keinen GitHub-Username-Selector** im Selector-Katalog der Doku.
|
||||
- Der **einzige** GitHub-bezogene Identitäts-Selector ist **„Identity provider group"** — der zieht
|
||||
aus **SCIM-Provisionierung**. Am hiesigen GitHub-IdP ist `scim_config.enabled = false`;
|
||||
`GET …/identity_providers/{id}/scim/groups` → **`500 (12158)`**. Der Weg ist daher **zu**.
|
||||
(Zusätzlich ist der Selector für Infrastructure-Apps laut Doku auf *GitHub **organization*** begrenzt.)
|
||||
- **Konsequenz:** `email AND github` ist keine Notlösung, sondern die **vollständige** Umsetzung
|
||||
dessen, was Access hier hergibt. Eine echte Username-/Org-Kopplung bräuchte **SCIM aktiviert**
|
||||
(Org-basiert, kostenpflichtig) — **eigener TP, nicht Teil von TP49.**
|
||||
|
||||
**→ TP10 kann geschlossen werden, sobald der DDNS-Updater steht und die Compliance die Regel
|
||||
bestätigt.** Der Mesh-NAT-Teil ist erledigt.
|
||||
#### Gotchas (live bestätigt — alle drei neu am 2026-10-06)
|
||||
|
||||
- 🔴 **Reusable Policies lassen sich NICHT über den App-Pfad ändern.**
|
||||
`PUT /accounts/{id}/access/apps/{app}/policies/{pol}` → **`12130 can not update reusable policies
|
||||
through this endpoint`**. Korrekt ist **`PUT /accounts/{id}/access/policies/{policy_id}`**
|
||||
(eigener Endpunkt für reusable Policies). Lesen geht über beide Pfade.
|
||||
⚠️ Der bisherige Merksatz „Reusable Policy nur per App-PUT detachbar" beschreibt eine andere
|
||||
Operation (Detach vom App-`policies`-Array) — **nicht** das Ändern ihres Inhalts.
|
||||
- 🔴 **Der `cloudflare`-MCP ist strikt read-only.** Write-Probe 2026-10-06: no-op-PUT
|
||||
(byte-identischer Body) → **`1010: undefined`**. Writes nur via `CF_API_TOKEN`/curl.
|
||||
- 🔴 **Access-App-IDs sind 32 Hex** — die Kurzform `16aaec78` liefert `12045 invalid_application_id`.
|
||||
Volle ID zuerst per `GET /accounts/{id}/access/apps` holen.
|
||||
|
||||
#### Aktenhistorie (dokumentierter Fehlschlag — nicht löschen)
|
||||
|
||||
Die erste Korrektur dieses Abschnitts (2026-10-06, vormittags) erklärte TP49 zum **Phantom** und
|
||||
schloss es — auf Basis eines **Google**-Tests plus eines **Schlusses** auf GitHub. Zwei Fehler:
|
||||
|
||||
1. **Lese-Schluss als Befund verkauft.** Aus „`include` ist ein OR" + „Google bindet die Adresse"
|
||||
wurde „also bindet GitHub auch" — nie gemessen. Das stand als Tatsache in Akte und Issue.
|
||||
2. **Der Gegenbeweis war keiner.** Der Google-Test zeigt nur, was der Google-IdP liefert —
|
||||
nicht, was GitHub liefert. Die IdP-Configs sind konfigurationsgleich; das Feld, in dem der
|
||||
Unterschied steckt, steht nicht in der Config.
|
||||
|
||||
**Lehre:** bei Aussagen über fremde Infrastruktur **pro IdP einzeln messen**, nicht den ersten
|
||||
Test auf den zweiten IdP übertragen. Der echte Test dauerte Minuten und hätte den Umweg erspart.
|
||||
Details in Memory `feedback_no_guessing`.
|
||||
|
||||
#### Abgrenzung
|
||||
**Nicht** TP41 (hat den internen Wildcard saniert), **nicht** TP44 (Dokumentation).
|
||||
⚠️ `420.teilzeitladen.xyz` gehört zum Projekt **blaueblüten** — Änderung erfolgte mit
|
||||
Christophs ausdrücklicher Freigabe, das Nachbar-Repo wurde **nicht** angefasst.
|
||||
|
||||
#### Abnahme (2026-10-06, durch echten Login — nicht nur API-Read)
|
||||
|
||||
Christoph hat nach dem Fix **alle Auth-Pfade neu getestet**:
|
||||
|
||||
- ✅ **Fremder GitHub-Account (2. Konto) → wird abgewiesen.** Der Fix greift real, nicht nur in der API.
|
||||
- ✅ **Eigener Zugang (GitHub, primäre Adresse `christoph@emsenhuber.com`) → funktioniert.**
|
||||
- ✅ **Geli → funktioniert** (unverändert über Einmalcode/IP; ihr `require` blieb leer).
|
||||
- ✅ **Alle übrigen Auth-Wege verhalten sich wie erwartet.**
|
||||
|
||||
Damit ist `email AND github` **verhaltensbestätigt**: es trennt das eigene Konto vom fremden,
|
||||
ohne jemanden auszusperren. Kein Regress an den Nachbar-Policies (`geli`, `terpene-svc`) und
|
||||
kein Kollateral am internen Wildcard.
|
||||
|
||||
**→ Abgeschlossen 2026-10-06: Fix gesetzt, zurückgelesen und per Login verifiziert.
|
||||
Gitea #76 `closed` / `status:done`.**
|
||||
|
||||
78
docs/tp49-cf-access-startauftrag.md
Normal file
78
docs/tp49-cf-access-startauftrag.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Startauftrag — TP49: CF-Access `login_method`-Include ✅ ERLEDIGT (2026-10-06)
|
||||
|
||||
> **Abgeschlossen.** Gitea #76, Prosa in `docs/teilprojekte.md` → `### TP49`.
|
||||
>
|
||||
> 🔴 **Diese Datei nicht mehr als Auftrag lesen — der ist ausgeführt.** Sie bleibt als Nachweis
|
||||
> stehen: Lage, Vorgehen und die Fehlschläge auf dem Weg dorthin.
|
||||
|
||||
## Der Auftrag (erledigt)
|
||||
|
||||
`login_method:github` aus dem `include` der Policy `christoph` der CF-Access-App
|
||||
`420.teilzeitladen.xyz` entfernen — nach Rücksprache, wen der Zugang treffen soll.
|
||||
|
||||
## Ausgeführt (2026-10-06)
|
||||
|
||||
`github` von `include` nach **`require`** verschoben ⇒ **`email AND github`**.
|
||||
|
||||
| | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| `include` | email · **github** · onetimepin | email · onetimepin |
|
||||
| `require` | — | **github** |
|
||||
|
||||
Gesetzt über **`PUT /accounts/{id}/access/policies/{policy_id}`**, zurückgelesen über **beide**
|
||||
Endpunkte (App-Pfad + reusable-Pfad) — identisch. Der `success:true` der API wurde **nicht**
|
||||
geglaubt, sondern die Policy neu gelesen.
|
||||
|
||||
**App-ID** `e6a7991f-466c-4d1d-bac6-4b30fafccce4` · **Policy (reusable)** `8e44f5d1-8257-443e-a797-b3de939952ee`
|
||||
**github-IdP** `72c1291b…` · **onetimepin** `9024c7a6…`
|
||||
|
||||
**Sweep über alle Apps** (`login_method` in include/require/exclude): genau zwei Treffer, beide in
|
||||
`Terpenfinder v3` — `christoph` (github+onetimepin) und `geli` (nur onetimepin, harmlos).
|
||||
Interne Apps/Wildcard: sauber (TP41).
|
||||
|
||||
## 🔴 Gotchas — live bestätigt, alle drei neu am 2026-10-06
|
||||
|
||||
1. **Reusable Policies ändern geht NICHT über den App-Pfad.**
|
||||
`PUT …/access/apps/{app}/policies/{pol}` → **`12130 can not update reusable policies through
|
||||
this endpoint`**. Korrekt: **`PUT …/access/policies/{policy_id}`**.
|
||||
⚠️ Der ältere Merksatz „Reusable Policy nur per App-PUT detachbar" meint das **Detach** vom
|
||||
App-`policies`-Array — **nicht** das Ändern des Inhalts. Zwei verschiedene Operationen.
|
||||
2. **Der `cloudflare`-MCP ist strikt read-only.** Write-Probe: no-op-PUT (byte-identischer Body)
|
||||
→ **`1010: undefined`**. Writes nur via `CF_API_TOKEN`/curl
|
||||
(`cd mcp/cloudflare && set -a; . ./.env; set +a`). Token **nie** ausgeben.
|
||||
3. **Access-App-IDs sind 32 Hex** — Kurzform `16aaec78` → `12045 invalid_application_id`.
|
||||
|
||||
## ⚠️ Was `login_method` nicht kann
|
||||
|
||||
Die ursprüngliche Absicht war eine Kopplung **an den GitHub-Username**. Das gibt Access nicht her:
|
||||
`login_method` prüft die **Methode**, nicht den Nutzer; einen Username-Selector gibt es nicht.
|
||||
Der einzige GitHub-bezogene Identitäts-Selector ist **„Identity provider group"** — der braucht
|
||||
**SCIM**, und am hiesigen GitHub-IdP ist `scim_config.enabled = false`
|
||||
(`GET …/scim/groups` → `500 (12158)`). **Weg zu.**
|
||||
⇒ `email AND github` ist die vollständige Umsetzung des hier Möglichen, keine Notlösung.
|
||||
Eine echte Org-Kopplung bräuchte SCIM aktiviert — **eigener TP**.
|
||||
|
||||
## Aktenhistorie — der Umweg (nicht löschen)
|
||||
|
||||
Zwischenzeitlich wurde TP49 als **Phantom geschlossen** — auf Basis eines **Google**-Tests plus
|
||||
eines **Schlusses** auf GitHub. Beides falsch:
|
||||
|
||||
1. **Lese-Schluss als Befund verkauft.** „`include` ist ein OR" + „Google bindet die Adresse"
|
||||
⇒ „GitHub bindet auch". Nie gemessen, aber als Tatsache in Akte und Issue geschrieben.
|
||||
2. **Der Gegenbeweis war keiner.** Der Google-Test zeigt nur Googles Verhalten. Die IdP-Configs
|
||||
sind konfigurationsgleich — das unterscheidende Feld steht **nicht** in der Config.
|
||||
|
||||
Christoph hat mit einem **zweiten GitHub-Account** getestet: er kam durch. Damit war der
|
||||
Ursprungsfund bestätigt und der Fix nötig.
|
||||
|
||||
**Lehre: pro IdP einzeln messen, nicht den ersten Test auf den zweiten übertragen.**
|
||||
|
||||
## Verifikation
|
||||
|
||||
- [x] Policy über beide Endpunkte zurückgelesen — `require` trägt github, `include` nicht mehr
|
||||
- [x] Sweep: keine weiteren `login_method` in `include` außer den bekannten
|
||||
- [x] Scope: reusable Policy nur an diese eine App gebunden (kein Kollateral)
|
||||
- [x] **Verhaltensabnahme (2026-10-06, echter Login):** fremder GitHub-Account → **abgewiesen**;
|
||||
eigener Zugang + Geli → funktionieren; alle übrigen Auth-Pfade wie erwartet
|
||||
|
||||
**Status: abgeschlossen.** Gitea #76 `closed` / `status:done`. Kein offener Punkt.
|
||||
112
routeros/tp10-ddns-cf-updater.rsc
Normal file
112
routeros/tp10-ddns-cf-updater.rsc
Normal file
@@ -0,0 +1,112 @@
|
||||
# =============================================================================
|
||||
# TP10 — Cloudflare-DDNS-Updater fuer bf4.emsenhuber.net
|
||||
# =============================================================================
|
||||
# Zweck: der WG-Hub-Endpoint der Spokes (fm/dw) ist bf4.emsenhuber.net. Der
|
||||
# A-Record muss der Liwest-Public-IP des Core folgen. Heute manuell gepflegt.
|
||||
#
|
||||
# BASIS: bajodel/mikrotik-cloudflare-dns (MIT, Copyright (c) 2025 bajodel)
|
||||
# https://github.com/bajodel/mikrotik-cloudflare-dns
|
||||
# Aenderungen ggue. dem Original (Christoph-Entscheid 2026-10-03):
|
||||
# 1. IP kommt aus der DHCP-Lease (client1/vlan100), NICHT von Echo-Diensten.
|
||||
# Liwest ist die Quelle, kein Dritter; kein Cloudflare im Pfad.
|
||||
# 2. check-certificate=yes (Original-Default war false -> Token ungeprueft).
|
||||
# 3. Nur EIN Record (bf4), Konfiguration auf das Noetige reduziert.
|
||||
#
|
||||
# Token: liegt NICHT hier, sondern als globale Script-Variable `cfDdnsToken`.
|
||||
# Einmalig setzen per SSH (siehe docs/teilprojekte.md ### TP10).
|
||||
#
|
||||
# Installation: /import file=tp10-ddns-cf-updater.rsc
|
||||
# danach /system scheduler add ... (siehe unten)
|
||||
# =============================================================================
|
||||
|
||||
# ---- Konfiguration ----------------------------------------------------------
|
||||
:local DnsRcName "bf4.emsenhuber.net"
|
||||
:local DnsZoneID "1b5bc02fdf61b6b989afde543f3f4134" # CF_ZONE_ID aus mcp/cloudflare/.env
|
||||
:local DnsRcrdID "dba9d9ec3b261fdb314c0f83e4e62f0d"
|
||||
:local RecordTTL "120" # wie der bestehende Record
|
||||
:local RecordProxied "false" # WG-Endpoint: NICHT proxied
|
||||
:local VerboseLog false
|
||||
:local WanDhcpName "client1"
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Token aus /system script environment holen (NIE im Skriptkopf, NIE per :global).
|
||||
# Grund (2026-10-03): :global cfDdnsToken liest im Skript-Kontext "nothing", obwohl
|
||||
# /system script environment cfDdnsToken gesetzt ist. CLI-Scope und Skript-Scope sind
|
||||
# auf RouterOS 7.24 unterschiedlich. `/system script environment get` ist scope-unabhaengig.
|
||||
:do {
|
||||
:local tk ""
|
||||
:local found [/system script environment find name=cfDdnsToken]
|
||||
:if ([:len $found] > 0) do={
|
||||
:set tk [/system script environment get [find name=cfDdnsToken] value]
|
||||
}
|
||||
:if ([:typeof $tk] = "") do={
|
||||
:log error "[tp10-ddns] cfDdnsToken nicht gesetzt - Abbruch"
|
||||
:error "cfDdnsToken fehlt"
|
||||
}
|
||||
:global cfDdnsToken
|
||||
:set cfDdnsToken $tk
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] cfDdnsToken nicht lesbar - Abbruch"
|
||||
:error "cfDdnsToken fehlt"
|
||||
}
|
||||
|
||||
# ---- WAN-IPv4 aus der DHCP-Lease (Quelle: Liwest, kein Dritter) ------------
|
||||
:local CFWanIP4New ""
|
||||
:do {
|
||||
:local leaseAddr [/ip dhcp-client get [find name=$WanDhcpName] address]
|
||||
:if ([:typeof $leaseAddr] != "nothing") do={
|
||||
# "90.146.33.177/24" -> "90.146.33.177"
|
||||
:local slashPos [:find $leaseAddr "/"]
|
||||
:if ($slashPos >= 0) do={
|
||||
:set CFWanIP4New [:pick $leaseAddr 0 $slashPos]
|
||||
} else={
|
||||
:set CFWanIP4New $leaseAddr
|
||||
}
|
||||
}
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] DHCP-Lease nicht lesbar ($WanDhcpName)"
|
||||
}
|
||||
|
||||
:if ($CFWanIP4New = "") do={
|
||||
:log warning "[tp10-ddns] Keine WAN-IP aus der Lease, naechster Lauf"
|
||||
} else={
|
||||
|
||||
# ---- Guard: nur oeffentliche IPv4 zulassen --------------------------------
|
||||
# Faengt den Fall ab, dass DHCP Muell liefert (privat/CGNAT) -> kein Write.
|
||||
:local isPrivate false
|
||||
:if ([:pick $CFWanIP4New 0 3] = "10.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 8] = "192.168.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 7] = "172.16.") do={ :set isPrivate true }
|
||||
:if ([:pick $CFWanIP4New 0 8] = "100.64.0") do={ :set isPrivate true } # CGNAT 100.64/10
|
||||
:if ($isPrivate) do={
|
||||
:log error "[tp10-ddns] Lease liefert nicht-oeffentliche IP ($CFWanIP4New) - ABBRUCH, kein Write"
|
||||
} else={
|
||||
|
||||
# ---- Nur bei Abweichung schreiben (Idempotenz) --------------------------
|
||||
:global CFWanIP4Cur
|
||||
:if ($CFWanIP4New != $CFWanIP4Cur) do={
|
||||
:if ($VerboseLog) do={ :log info "[tp10-ddns] WAN-IP: $CFWanIP4Cur -> $CFWanIP4New" }
|
||||
|
||||
# Kommentar am Record: Zeit + Geraet, wie im Original
|
||||
:local ds [/system clock get date]
|
||||
:local ts [/system clock get time]
|
||||
:local datetime ([:pick $ds 0 4] . "." . [:pick $ds 5 7] . "." . [:pick $ds 8 10] . "-" . [:pick $ts 0 2] . ":" . [:pick $ts 3 5])
|
||||
:local CFComment ("$datetime by crs326-core")
|
||||
|
||||
:local url "https://api.cloudflare.com/client/v4/zones/$DnsZoneID/dns_records/$DnsRcrdID/"
|
||||
:do {
|
||||
:local res [/tool fetch http-method=put mode=https url=$url check-certificate=yes output=user as-value http-header-field="Authorization: Bearer $cfDdnsToken,Content-Type: application/json" http-data="{\"type\":\"A\",\"name\":\"$DnsRcName\",\"content\":\"$CFWanIP4New\",\"ttl\":$RecordTTL,\"proxied\":$RecordProxied,\"comment\":\"$CFComment\"}"]
|
||||
:if (($res->"status") = "finished") do={
|
||||
:log warning "[tp10-ddns] $DnsRcName -> $CFWanIP4New aktualisiert"
|
||||
:set CFWanIP4Cur $CFWanIP4New
|
||||
} else={
|
||||
:log error "[tp10-ddns] Update fehlgeschlagen (status != finished)"
|
||||
}
|
||||
} on-error={
|
||||
:log error "[tp10-ddns] Exception beim Cloudflare-Update - naechster Lauf"
|
||||
}
|
||||
} else={
|
||||
:if ($VerboseLog) do={ :log info "[tp10-ddns] WAN-IP unveraendert ($CFWanIP4New)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user