TP16.3 und TP14.4 als ausfuehrbare Runbooks

Beide TPs waren "Recherche durch / Umsetzung offen" bzw. "TODO" -- also
jeweils eine Zeile, aus der eine frische Session nicht losarbeiten kann.

TP16.3: der Schnitt (nautobot aus dem bf-nas-Ingress) ausformuliert, mit
Vorbedingungen, PUT-Ablauf und Rollback. Zwei Dinge stehen jetzt explizit
drin, die den TP sonst kippen: Mesh-Hostname-Routes sind Egress und
veroeffentlichen nichts -- wer das verwechselt, baut den Public-Strang ab;
und Nautobot ist nicht nur eine Web-UI, sondern die Deploy-Pipeline, also
gehoert ein Job-Lauf zur Abnahme und nicht nur ein Browser-Aufruf.

TP14.4: Ist-Stand nachgesehen -- in mcp/cloudflare/.env stehen weder
CF_ACCESS_CLIENT_ID noch _SECRET, es ist also nichts halb erledigt. Das
Secret gibt es nur im Moment der Rotation, das steht jetzt als Warnung
davor. Abnahme braucht beide Faelle: mit Headern antwortet, ohne 403 --
sonst haette ein bypass-Hostname wie vw als Erfolg durchgehen koennen.

Kein CF-Write, kein Token rotiert. Beides bleibt Papier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 15:58:06 +02:00
parent 8b7ca8ddef
commit 914ff980b5
3 changed files with 205 additions and 0 deletions

View File

@@ -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) ### 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`. - 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 ### 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. - 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 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 häufigsten per Mesh angefasste Dienst, und ein Fehlschlag kostet nichts Produktives), eine Woche fahren, dann
entscheiden. **Rollback = eine Ingress-Zeile zurück.** 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 - **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 liefert das Entscheidungskriterium). ⚠️ **Berührt TP5:** der Mesh-Verkehr braucht bei deny-by-default eine
explizite `forward`-Zeile — s. TP5 Etappe 2. explizite `forward`-Zeile — s. TP5 Etappe 2.

View File

@@ -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: <client_id>
CF-Access-Client-Secret: <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.

View File

@@ -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.