diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index b501b0c..6f792df 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -575,6 +575,11 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". ### TP14.4 — Session D: CF-Access-Profil für Claude ▸ 🔜 TODO (nicht cutoff-kritisch) - CF-Token „claude code mcp" hat jetzt `Access: Service Tokens: Write`+`Read` → `claude-mcp` rotieren (`POST …/access/service_tokens/{id}/rotate`) → `CF_ACCESS_CLIENT_ID`/`_SECRET` in `mcp/cloudflare/.env` (chmod600). Memory `ref-cf-access-claude-profile`. +- 📋 **Runbook: `docs/tp14.4-cf-access-token-runbook.md`** (2026-08-19). **Ist-Stand verifiziert:** + `mcp/cloudflare/.env` enthält bisher weder `CF_ACCESS_CLIENT_ID` noch `_SECRET` — es ist nichts halb + erledigt. 🔴 **Das Secret wird nur bei der Rotation ausgegeben**, wer es nicht sofort schreibt, muss + erneut rotieren. Abnahme = ein Aufruf **mit** den Headern (antwortet) **und** einer **ohne** (403) — + der Erfolgsfall allein beweist nichts, der Hostname könnte auf `bypass` stehen. ### TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ ✅ ERFÜLLT (2026-08-08) — kein eigener Change - Gate „TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20)" ist erfüllt → **TP5 (Deny-by-default) ist entblockt.** Reines Reihenfolge-Gate, kein eigener Change. @@ -1429,6 +1434,15 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung". aus dem CF-Ingress nehmen (Kandidat: `nautobot` — nur Christoph nutzt es, im Mesh-Log mit 62 Treffern der am häufigsten per Mesh angefasste Dienst, und ein Fehlschlag kostet nichts Produktives), eine Woche fahren, dann entscheiden. **Rollback = eine Ingress-Zeile zurück.** +- 📋 **Runbook: `docs/tp16.3-nautobot-mesh-only-runbook.md`** (2026-08-19) — der Schnitt oben, ausführbar + ausformuliert: Vorbedingungen (Mesh-Route, Zugriff über `10.40.20.30:8080` **mit WARP von außerhalb**), + Ingress-`PUT` über `CF_API_TOKEN` (**nicht** über den CF-MCP, der ist read-only), Rollback aus der + gesicherten Config. + - 🔴 **Der Abnahmepunkt, den man übersieht:** Nautobot ist nicht nur Web-UI, sondern die + **Deploy-Pipeline**. Nach dem Schnitt gehört ein Job-Lauf zur Abnahme, nicht nur ein Browser-Aufruf + — der Netmiko-Deploy kommt von `10.40.20.31` (`nautobot-worker`), nicht von `.20.30`. + - ⚠️ Beim Kürzen des `ingress`-Arrays muss die **Catch-all-Regel die letzte bleiben**, sonst greift + der Ingress für alle übrigen Hostnames nicht mehr. - **Abh.:** TP16.1 ✅ (Mesh live), TP16 (Public-Strang + dessen HA-Degradierung), **TP16.2** (der `vw`-Constraint liefert das Entscheidungskriterium). ⚠️ **Berührt TP5:** der Mesh-Verkehr braucht bei deny-by-default eine explizite `forward`-Zeile — s. TP5 Etappe 2. diff --git a/docs/tp14.4-cf-access-token-runbook.md b/docs/tp14.4-cf-access-token-runbook.md new file mode 100644 index 0000000..eb38e0e --- /dev/null +++ b/docs/tp14.4-cf-access-token-runbook.md @@ -0,0 +1,70 @@ +# TP14.4 — CF-Access-Service-Token für Claude rotieren + +> **Stand 2026-08-19 · vorbereitet, nicht ausgeführt.** Kurzes Runbook für einen Einmal-Schuss: +> das Secret ist **genau einmal** sichtbar, danach nie wieder. + +--- + +## 1. Ist-Stand (verifiziert 2026-08-19) + +`mcp/cloudflare/.env` enthält **kein** `CF_ACCESS_CLIENT_ID` und **kein** `CF_ACCESS_CLIENT_SECRET`. +Der TP steht also zu Recht offen — es ist nichts halb erledigt, was man erst zurückbauen müsste. + +Vorhanden und nutzbar: `CF_API_TOKEN`, `CF_ACCOUNT_ID`, `CF_ZONE_ID` (chmod 600, gitignored). +Das Token trägt laut TP14.4 bereits die nötigen Scopes **`Access: Service Tokens: Read + Write`**. + +**Wozu das Ganze:** damit Claude Dienste hinter CF-Access erreichen kann, ohne den interaktiven +Login-Flow — das ist das „komm schwer durch Tunnel"-Symptom aus Memory `ref_cf_access_claude_profile`. + +--- + +## 2. Ablauf + +1. **Token-ID ermitteln:** + `GET /accounts/{account_id}/access/service_tokens` → den Eintrag `claude-mcp` heraussuchen, `id` notieren. +2. **Rotieren:** + `POST /accounts/{account_id}/access/service_tokens/{id}/rotate` +3. 🔴 **Sofort schreiben.** Die Antwort enthält `client_id` **und** `client_secret` — das Secret wird + **nur bei der Rotation** ausgegeben. Wer es nicht in diesem Moment wegschreibt, muss erneut rotieren + (was den alten Wert ungültig macht, also nicht folgenlos ist). +4. **Ablegen** in `mcp/cloudflare/.env`: + ``` + CF_ACCESS_CLIENT_ID=… + CF_ACCESS_CLIENT_SECRET=… + ``` + Datei bleibt `chmod 600`; sie ist über `mcp/**/.env` bereits gitignored — **nicht** committen. +5. **Konsolidieren** nach globaler Konvention: Werte zusätzlich in `~/.secrets/credentials.env`, + Index-Zeile (Schlüssel → Herkunftspfad) in `~/.secrets/README.md`. Single Source of Truth bleibt + der Originalort, `~/.secrets/` ist die Nachschlage-Kopie. + +⚠️ **Beim Ausgeben von Fehlern/Tracebacks maskieren** — Projektregel aus +`feedback_redact_router_passwords_in_tracebacks`, gilt hier genauso. + +--- + +## 3. Abnahme + +Ein Aufruf gegen einen **Access-geschützten** Hostname mit den beiden Headern: + +``` +CF-Access-Client-Id: +CF-Access-Client-Secret: +``` + +- **mit** Headern → der Dienst antwortet +- **ohne** Header → `403` + +Beides prüfen. Nur der Erfolgsfall allein beweist nichts (der Hostname könnte auf `bypass` stehen — +`vw` tut das bewusst, s. TP16.2). + +Danach Memory `ref_cf_access_claude_profile` nachziehen: dort steht bisher „Service-Token-Secret fehlt +lokal" — das ist nach dieser Rotation nicht mehr wahr. + +--- + +## 4. Fußnote mit Verfallsdatum + +⏰ **Der `CF_API_TOKEN` selbst läuft am 2027-01-09 ab** (Memory `ref_cf_mcp_readonly_token_writes`). +Er ist **account-owned**, verifiziert also über einen anderen Endpoint als ein User-Token — wer das +verwechselt, hält ein funktionierendes Token für kaputt. Ohne ihn ist auch diese Rotation nicht mehr +möglich, ebensowenig der TP16.3-Schnitt. diff --git a/docs/tp16.3-nautobot-mesh-only-runbook.md b/docs/tp16.3-nautobot-mesh-only-runbook.md new file mode 100644 index 0000000..421027a --- /dev/null +++ b/docs/tp16.3-nautobot-mesh-only-runbook.md @@ -0,0 +1,121 @@ +# TP16.3 — Probe: `nautobot` aus dem CF-Ingress nehmen (mesh-only) + +> **Stand 2026-08-19 · vorbereitet, nicht ausgeführt.** Die Recherche zu TP16.3 ist seit 2026-08-17 +> durch; offen war nur die Umsetzung. Dieses Runbook macht daraus einen ausführbaren, in einer Minute +> zurücknehmbaren Schnitt. + +--- + +## 1. Worum es geht (in drei Sätzen) + +Der private Datenpfad läuft seit TP16.1 über **natives WireGuard auf dem Core** (`cf-mesh`), nicht über +Container — belegt, nicht vermutet: im TP5-Log-Lauf standen **137 Zeilen `in:cf-mesh`**. Für Dienste, +die **niemand ohne WARP im Browser** aufrufen muss, ist `cloudflared` damit überflüssiger Aufwand: +eine Ingress-Zeile, ein Connector-Paar, eine HA-Frage. Diese Probe nimmt **einen** solchen Dienst +heraus und schaut eine Woche zu. + +### Warum ausgerechnet `nautobot` + +| Kriterium | `nautobot` | +|---|---| +| Wer nutzt es | nur Christoph — kein Fremdgerät, kein Gast | +| Braucht es einen Client ohne Access-Login? | **nein** (das ist das TP16.2-Kriterium, an dem `vw` hängt) | +| Wie oft schon per Mesh angefasst | **62×** im TP5-Log — der Spitzenreiter | +| Was kostet ein Fehlschlag | nichts Produktives; im Zweifel greift man per IP zu | + +--- + +## 2. 🔴 Die Verwechslungsfalle, die den ganzen TP kippen kann + +**Mesh-Hostname-Routes sind Egress, nicht Publishing.** Cloudflare kennt seit 2026-07-02 auch +Hostname-Routes auf dem Mesh — die klingen so, als könnten sie den Ingress ersetzen. Sie können es +nicht: sie leiten *ausgehenden* Verkehr zu diesem Hostname über den Node und egressen mit dessen +öffentlicher IP. **Sie veröffentlichen nichts.** + +Wer das verwechselt, baut den öffentlichen Strang ab und wundert sich, warum von außen nichts mehr geht. +→ **Diese Probe ersetzt nichts. Sie entfernt einen Dienst, der öffentlich gar nicht gebraucht wird.** + +--- + +## 3. Vorbedingung — VOR dem Schnitt prüfen (read-only) + +Alle drei Punkte müssen erfüllt sein, sonst wird nicht geschnitten: + +1. **Die Mesh-Route steht.** Auf dem Core: die vnet-Route `10.40.0.0/16` zeigt auf `bf-core-mesh` + (TP16.1 §4.4, „Weg A"). Ohne sie ist Mesh-Zugriff auf `10.40.20.30` gar nicht möglich. +2. **Der Dienst antwortet über die private IP, mit aktivem WARP** — von einem Client, der *nicht* im + BF-LAN steht (das ist der einzige Test, der etwas beweist; im LAN geht es ohnehin): + ``` + http://10.40.20.30:8080 + ``` +3. ⚠️ **ICMP nicht als Beweis nehmen, aber auch nicht als Gegenbeweis.** Der Mesh kann ICMP (das war + der Gewinn von TP16.1). Wenn Ping scheitert, TCP aber geht, ist zuerst zu prüfen, ob lokal noch ein + WARP-Client läuft — s. Memory `ref_warp_local_icmp_trap`. Umgekehrt ist ein erfolgreicher Ping kein + Beleg, dass der HTTP-Dienst erreichbar ist. + +--- + +## 4. Der Schnitt + +**Was:** die `nautobot`-Zeile aus der Ingress-Config des Tunnels **`bf-nas`** entfernen. +Die DNS-CNAME-Zeile bleibt zunächst stehen (Rollback bleibt so eine reine Ingress-Operation). + +**Womit:** die Cloudflare-API mit `CF_API_TOKEN`. + +> 🔴 **Nicht über den Cloudflare-MCP.** Der ist **strikt read-only** — Writes gehen ausschließlich über +> die API mit dem Token (Memory `ref_cf_mcp_readonly_token_writes`). Das Token ist **account-owned**, +> nutzt also einen anderen verify-Endpoint als ein User-Token, und **läuft am 2027-01-09 ab**. + +**Wo die Werte stehen:** `mcp/cloudflare/.env` (chmod 600, gitignored) — +`CF_API_TOKEN`, `CF_ACCOUNT_ID`, `CF_BF_TUNNEL_ID`. + +**Ablauf:** + +1. **Ist-Config sichern** (das ist der Rollback): + `GET /accounts/{account_id}/cfd_tunnel/{tunnel_id}/configurations` → JSON wegschreiben, mit Datum. +2. Aus dem `ingress`-Array die `nautobot`-Zeile entfernen. ⚠️ Die abschließende Catch-all-Regel + (`service: http_status:404` ohne `hostname`) **muss die letzte bleiben** — sonst greift der Ingress + für alle übrigen Hostnames nicht mehr. +3. **Zurückschreiben:** `PUT` derselben Route mit dem gekürzten Array. +4. **Gegenprobe am Gerät, nicht am Statuscode:** ein `GET` der Config liest zurück, was wirklich steht. + *(Dieselbe Regel wie in TP22/TP23: ein „success" der API ist kein Wirksamkeitsnachweis.)* + +--- + +## 5. Abnahme + +| Prüfung | Erwartung | +|---|---| +| Öffentlicher FQDN `nautobot.…` ohne WARP | **nicht mehr erreichbar** (das ist der Zweck) | +| `http://10.40.20.30:8080` mit WARP, von außerhalb des LAN | erreichbar | +| Die übrigen Hostnames auf `bf-nas` (gitea, grafana, pve-mcp, vw, ds-bf) | **unverändert erreichbar** | +| Golden-Config-Deploys (Netmiko aus Nautobot) | laufen weiter | + +🔴 **Der letzte Punkt ist der, den man übersieht:** Nautobot ist nicht nur eine Web-UI, sondern die +Deploy-Pipeline. Der Netmiko-Deploy kommt von **`10.40.20.31`** (`nautobot-worker`), nicht von `.20.30` +— das war einer der vier TP5-Funde. Der Schnitt betrifft nur den *eingehenden* öffentlichen Weg, aber +nach dem Schnitt gehört ein Job-Lauf zur Abnahme, nicht nur ein Browser-Aufruf. + +--- + +## 6. Betriebsfenster und Rollback + +- **Eine Woche fahren**, dann entscheiden: bleibt es so, oder kommt es zurück. +- **Rollback = die gesicherte Ingress-Config aus Schritt 1 zurückschreiben.** Eine Zeile, eine Minute. +- Danach ist zu entscheiden, ob weitere Dienste der rechten Spalte folgen (`pve`, `portainer`, + `grafana`, `pve-mcp`, `ha-mcp`, `gitea-mcp`, `ds-bf`, `ccu`, `iobroker`, `z2m25`). + +### Was in dieser Probe ausdrücklich NICHT entschieden wird + +- **`vw`, `hass`, `n8n` bleiben öffentlich** — harte Kriterien: Bitwarden-Clients (TP16.2-Constraint), + die HA-Companion-App (das 400-Muster), eingehende Webhooks von Dritten. +- **`gitea` ist offen** und wird hier nicht mitentschieden: Git-Clients/CI von außen können keinen + interaktiven Access-Login. Braucht eine eigene Prüfung, ob es solche Zugriffe real gibt. + +--- + +## 7. Nebeneffekt, der den Fall stärkt + +Die **TP16-HA-Degradierung** (`bf-nas` und `bf-pve` haben je nur **einen** Connector, Befund 2026-08-16) +verliert an Gewicht, je weniger Dienste an cloudflared hängen. Das ersetzt die Reparatur nicht — +aber es verkleinert, was der SPOF trifft.