Compare commits
13 Commits
75bd43d1f7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c2d9c0ea7 | |||
| 5c8824850f | |||
| 359ad666c4 | |||
| 4d4779e400 | |||
| 4b307315f0 | |||
| 81cd7bfb7e | |||
| c5086db223 | |||
| a3f2ade4e0 | |||
| 52a8ac462c | |||
| 9638a03e4b | |||
| fb415ae52d | |||
| f63149a769 | |||
| 3601a2b6cb |
@@ -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) |
|
||||
@@ -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/`
|
||||
@@ -2709,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).
|
||||
@@ -2268,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.**
|
||||
|
||||
---
|
||||
|
||||
@@ -2338,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
|
||||
@@ -2574,64 +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
|
||||
- [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).
|
||||
- **`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 (2026-10-03).** DDNS-Updater steht & funktioniert live,
|
||||
Mesh-NAT-Regel im Template, Compliance wird beim nächsten GC-Lauf grün melden.
|
||||
#### 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.
|
||||
Reference in New Issue
Block a user