sshpass -p schreibt das Passwort in argv, wo es in ps und jedem argv-Trace steht -- die Klasse, an der am 2026-08-22 das Core-Passwort verbrannt ist. Nach gc-transaktion.py (voriger Commit) jetzt auch die fuenf unter routeros/: SSHPASS-Env + sshpass -e. Nachweis, gemessen statt gelesen: - alle 13 subprocess.run mit sshpass tragen env= (per AST geprueft; ohne env waere SSHPASS leer und alles still kaputt) - Signaturen passen zu den Aufrufen (AST-Arity; py_compile findet das NICHT) - echter Lauf des gepatchten Codes gegen den lebenden Core: backup-core.py importiert, IP zur Laufzeit auf 10.40.99.1 -> "name: crs326-core", rc=0 - Passwort in argv? nein. In ps waehrend der Verbindung? nein, 40 Stichproben - Negativkontrolle: derselbe Test mit einem Dummy-Wert ueber die alte -p-Form -> 36 Treffer. Die Probe ist also nicht blind, das "nein" hat Aussagekraft Mitgefixt, weil es im umgebauten Code lag: apply-core-cutover-l3.py griff in run_silent auf ein pw zu, das dort gar nicht existierte -- jeder Fehlerfall endete in einem NameError statt in der gedachten Meldung. Fuer TP30 aendert das NICHTS: der Weg ist zu, der Wert bleibt verbrannt. Posten #1 (MikroTik-admin) bleibt faellig. Im TP so hingeschrieben, damit es niemand als erledigt liest. --- Archivierung (Christoph-Entscheid) --- Beim Fixen fiel auf, dass alle vier Cutover-Skripte auf tote Adressen zeigten: apply-core-cutover-l3.py 10.40.41.211 tot (VLAN41 seit 2026-08-27 weg) backup-core.py 10.40.41.211 tot restore-core.py 192.168.88.1 tot (Werks-Temp-Pfad) backup-ap1.py 10.40.10.1 ANTWORTET -- ist aber der CORE backup-ap1.py war der gefaehrliche: er waere nicht gescheitert, er haette den Core gesichert und die Datei ap1-pre-core-cutover-*.backup genannt. Wer das spaeter zurueckspielt, spielt Core-Config auf einen AP. Entscheid war archivieren, nicht reparieren -- der Cutover ist am 2026-08-08 durch, die Skripte haben ihre Aufgabe erfuellt. Sie liegen jetzt unter routeros/archiv-tp15-cutover/ mit README nach dem Muster von switch/. Die Pfad-Nebenwirkungen des Verschiebens sind ABSICHTLICH nicht repariert (backup-core.py schriebe jetzt ins Archivverzeichnis, apply-core-cutover-l3.py faende seine .rsc nicht mehr) -- sie zu reparieren hiesse, die Skripte wieder lauffaehig zu machen, und das ist gerade nicht die Absicht. Der sshpass-Fix wurde trotzdem VOR dem Verschieben gemacht: Archiv-Code wird abgeschrieben, also soll dort das richtige Muster stehen. import-core-rsc.py bleibt, wo es ist -- als einziges generisch nuetzlich. Sein Default-Host ist ebenfalls tot (--host mitgeben), und in --host steckt ein zweiter latenter Bug: sys.argv[1] wird als RSC-Pfad gelesen, bevor auf --host geprueft wird. NICHT gefixt, eigene Runde. Zeiger gesetzt in: CLAUDE.md (sshpass-Gotcha), teilprojekte.md (TP30), teilprojekte-archiv.md (TP15), Handoff 2026-08-31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
189 lines
10 KiB
Markdown
189 lines
10 KiB
Markdown
# Handoff 2026-08-31 — TP25 Fund E: Hebel 1 ist gezogen, die Uhr läuft
|
||
|
||
> Vorgänger: [`handoff-20260831-wlan-fund-e.md`](handoff-20260831-wlan-fund-e.md) (Befund + Messrezept).
|
||
> **Dieses Dokument ist der Stand danach.** Es gibt genau eine offene Aufgabe: **messen und entscheiden.**
|
||
|
||
## Was am 2026-08-31 um 08:09 passiert ist
|
||
|
||
**Eine** Änderung, sonst nichts:
|
||
|
||
```
|
||
/interface wifi security set [find name="sec-main"] ft-over-ds=no # vorher: yes
|
||
/interface wifi capsman remote-cap provision [find]
|
||
```
|
||
|
||
`ft=yes` bleibt — Fast Transition läuft weiter **over-the-air**, nur nicht mehr **over-the-DS**. Das
|
||
nimmt exakt die Änderung vom 2026-08-17 zurück, mehr nicht. **Kein weiterer Funk-Parameter ist
|
||
angefasst worden** (Stop-Bedingung: wer zwei Hebel zieht, weiß hinterher nichts).
|
||
|
||
## Abnahme — was tatsächlich geprüft wurde
|
||
|
||
| Prüfung | Ergebnis |
|
||
|---|---|
|
||
| Wirkt der Hebel? **am Manager, alle Master-VAPs** | `cap-wifi3/4/5/6` → `ft=true ft-over-ds=false`. `cap-wifi1/2` (= ap1) leer, weil ap1 funkstill ist. |
|
||
| CAPs zurück? | alle drei `state=Ok` |
|
||
| VAPs laufen? **am CAP gelesen**, nicht am Manager | ap2 und ap3: alle 9 Interfaces `running=yes` |
|
||
| **Frische DHCP-Lease** (die vorgeschriebene Abnahme) | iPhone `F2:62:4C:E1:AF:09` nach 71 s wieder auf `dirtyvader-main`, `10.40.10.196`, **`auth-type=ft-wpa3-psk`**, −54 dBm |
|
||
| Clients gesamt | 30 zurück |
|
||
| Roaming funktioniert noch? | ja — Log zeigt um 08:13:20 ein sauberes `roamed to` zwischen zwei APs auf `main` |
|
||
| Compliance | **134/135**, Diff **exakt** `ft-over-ds=yes → no`. **Kein Kollateral-Drift.** |
|
||
|
||
Geräte-Backup vor dem Eingriff: `pre-tp25-funde-ftoverds.backup` auf dem Core (08:07:58).
|
||
|
||
## 🔴 Warum Compliance auf 134/135 steht und das RICHTIG ist
|
||
|
||
Die Transaktion **`TP25-fund-e-ft-over-ds`** ist **offen** (Scope `wifi-security`, Ablauf
|
||
**2026-09-03**). `gc-transaktion.py status` weist den einen Drift als **erwartet** aus — genau dafür
|
||
gibt es das Werkzeug.
|
||
|
||
**Das Template wird bewusst NICHT vorher nachgezogen.** Wer es jetzt grün macht, macht den laufenden
|
||
Versuch unsichtbar, und `status` kann ihn nicht mehr von einem Versehen unterscheiden. `gc-transaktion.py`
|
||
erzwingt die richtige Reihenfolge ohnehin: es verweigert den Commit, solange der Scope driftet.
|
||
**135/135 kommt beim Commit zurück** — nach der Messung, nicht davor.
|
||
|
||
## 🔴 Die `cap-wifiN`-Karte hat sich beim Re-Provisionieren VERSCHOBEN
|
||
|
||
Wer die Logs vom 31.08. auswertet und das übersieht, ordnet Ereignisse dem falschen AP zu:
|
||
|
||
| | vor 08:09 (Logzeilen 07:xx) | ab 08:09 |
|
||
|---|---|---|
|
||
| ap1 (funkstill) | — | `cap-wifi1` / `cap-wifi2` |
|
||
| ap2 | `cap-wifi1` / `cap-wifi2` | `cap-wifi3` / `cap-wifi5` |
|
||
| ap3 | `cap-wifi5` / `cap-wifi6` | `cap-wifi4` / `cap-wifi6` |
|
||
|
||
Gelesen mit `/interface wifi radio print` am Manager. Das ist dieselbe Instabilität, die
|
||
[`tp25-walk-messung.sh`](tp25-walk-messung.sh) als `ap-map.txt` einfriert — hier von Hand.
|
||
|
||
## ⚠️ Korrektur am Befund vom Vormittag
|
||
|
||
Der Vorgänger-Handoff sagt, `main` unterscheide sich von den anderen fünf SSIDs in **genau einem**
|
||
Attribut. **Live nachgelesen sind es zwei:**
|
||
|
||
```
|
||
sec-main authentication-types=wpa3-psk ft=yes ft-over-ds=yes
|
||
sec-infra … authentication-types=wpa2-psk,wpa3-psk ft=no
|
||
```
|
||
|
||
`sec-main` ist **auch die einzige SSID ohne `wpa2-psk`**. Die Kontrollgruppe trennt FT und WPA3-only
|
||
also **nicht** voneinander. Das ändert die Hebelreihenfolge nicht — die Hypothese nennt gerade die
|
||
Kombination WPA3+FT — aber es ändert den Plan B:
|
||
|
||
⇒ **Wenn Hebel 1 und Hebel 2 (`ft=no`) beide nichts bewegen, ist der nächste Kandidat
|
||
`authentication-types=wpa2-psk,wpa3-psk` auf `sec-main` — NICHT `signal-range`.**
|
||
|
||
## Die einzige offene Aufgabe: messen
|
||
|
||
```bash
|
||
ssh root@10.40.20.100 "pct exec 400 -- sh -c 'cat /opt/tp5-syslog/log/10.40.20.1/2026-0*-*.log |
|
||
grep reauthenticating | grep -oE \"([0-9A-F]{2}:){5}[0-9A-F]{2}\" | sort | uniq -c'"
|
||
```
|
||
|
||
**Baseline: 93**, alle vom iPhone `F2:62:4C:E1:AF:09`, alle auf `dirtyvader-main`.
|
||
Davon **79 am 31.08., sämtlich in Stunde 07**, der letzte um **07:34:43** — also **vor** dem Eingriff.
|
||
Zwischen 08:00 und 08:15 kein einziges. **Das ist noch kein Ergebnis, nur der saubere Schnitt.**
|
||
|
||
Ein Gegencheck gehört dazu — er beweist, dass nicht einfach das Logging kaputt ist:
|
||
|
||
```bash
|
||
ssh root@10.40.20.100 "pct exec 400 -- sh -c '
|
||
grep wireless /opt/tp5-syslog/log/10.40.20.1/2026-09-01.log | wc -l;
|
||
grep wireless /opt/tp5-syslog/log/10.40.20.1/2026-09-01.log | grep -oE \"\\(dirtyvader-[a-z]+\\)\" | sort | uniq -c'"
|
||
```
|
||
|
||
### Die Entscheidung am 2026-09-01
|
||
|
||
| Messung | Schluss | Nächster Schritt |
|
||
|---|---|---|
|
||
| iPhone-`reauthenticating` **~0**, andere SSIDs unverändert | Hebel 1 hat gegriffen | Template `mikrotik_routeros.j2` Z. 220 auf `ft-over-ds=no` ziehen → `gc-transaktion.py commit --change-id TP25-fund-e-ft-over-ds` → Compliance zurück auf 135/135 |
|
||
| Zahl **unverändert** | Hebel 1 hat **nicht** gegriffen | Rollback ODER stehen lassen und **Hebel 2 `ft=no`** als eigene Transaktion. Nicht beides gleichzeitig. |
|
||
| Zahl runter, aber neue Ereignisse auf anderen SSIDs | Nebenwirkung | Rollback, dann neu bewerten |
|
||
|
||
Rollback-Weg (der Snapshot liegt bereit):
|
||
`python3 nautobot/gc-transaktion.py rollback --change-id TP25-fund-e-ft-over-ds`
|
||
— erzeugt einen **Vorschlag**, der gelesen werden will, und pusht bewusst nicht selbst.
|
||
|
||
## ✅ Nebenbefund erledigt: `sshpass -p` ist repo-weit weg (2026-08-31, zweiter Durchgang)
|
||
|
||
`sshpass -p <PW>` schreibt das Passwort in `argv`, wo es in `ps` und jedem argv-Trace steht — die
|
||
Klasse, an der am 2026-08-22 das Core-Passwort verbrannt ist. **Alle sechs betroffenen Skripte laufen
|
||
jetzt über `SSHPASS`-Env + `sshpass -e`:**
|
||
|
||
```
|
||
nautobot/gc-transaktion.py (im ersten Durchgang, weil es laufen musste)
|
||
routeros/import-core-rsc.py
|
||
routeros/archiv-tp15-cutover/apply-core-cutover-l3.py ┐
|
||
routeros/archiv-tp15-cutover/backup-core.py │ am selben Tag archiviert,
|
||
routeros/archiv-tp15-cutover/restore-core.py │ s. unten -- aber VOR dem
|
||
routeros/archiv-tp15-cutover/backup-ap1.py ┘ Verschieben noch gefixt
|
||
```
|
||
|
||
**Warum Archiv-Code trotzdem gefixt wurde:** Archiv wird abgeschrieben. Wer sich dort ein
|
||
sshpass-Muster holt, soll das richtige finden.
|
||
|
||
**Nachweis — gemessen, nicht gelesen:**
|
||
|
||
| Prüfung | Ergebnis |
|
||
|---|---|
|
||
| Alle 13 `subprocess.run`, die sshpass starten, tragen `env=` | ✓ per AST geprüft (ohne `env` wäre `SSHPASS` leer und alles kaputt) |
|
||
| Signaturen passen zu den Aufrufen | ✓ per AST, Arity-Abgleich — `py_compile` findet so etwas **nicht** |
|
||
| Echter Lauf des gepatchten Codes gegen den lebenden Core | ✓ `backup-core.py` importiert, IP zur Laufzeit auf `10.40.99.1`, Antwort `name: crs326-core`, rc=0 |
|
||
| Passwort in `argv`? | **nein** |
|
||
| Passwort in `ps` während der Verbindung? | **nein**, 40 Stichproben |
|
||
| **Negativkontrolle** — Dummy-Wert über die alte `-p`-Form | **36 Treffer** ⇒ die Probe ist nicht blind, das „nein" oben hat Aussagekraft |
|
||
|
||
🔴 **Für TP30 ändert das nichts.** Der Weg ist zu, der Wert bleibt verbrannt — ein Passwort, das
|
||
einmal in einem Transkript stand, wird nicht dadurch sauber, dass die Leitung dicht ist.
|
||
Rotation von Posten #1 bleibt fällig.
|
||
|
||
⚠️ **Mitgefixt, weil es in umgebautem Code lag:** `apply-core-cutover-l3.py` griff in `run_silent`
|
||
auf ein `pw` zu, das dort gar nicht existierte — **jeder Fehlerfall endete in einem `NameError`
|
||
statt in der gedachten Meldung.**
|
||
|
||
### 🔴 Und dabei aufgefallen: die IPs in diesen fünf Skripten sind Schrott
|
||
|
||
Live geprüft am 2026-08-31, **bewusst NICHT stillschweigend geändert** — nur je ein lauter Header
|
||
in die Datei:
|
||
|
||
| Skript | Ziel-IP | Realität |
|
||
|---|---|---|
|
||
| `import-core-rsc.py`, `apply-core-cutover-l3.py`, `backup-core.py` | `10.40.41.211` | **tot** — VLAN41 gibt es seit 2026-08-27 nicht mehr |
|
||
| `restore-core.py` | `192.168.88.1` | **tot** — Werks-Temp-Pfad; dazu ein hartkodierter Backup-Dateiname vom 2026-08-06 |
|
||
| `backup-ap1.py` | `10.40.10.1` | 🔴 **antwortet — ist aber der CORE** (VLAN10-SVI, tcp/22 offen). ap1 ist `10.40.99.11`. |
|
||
|
||
**`backup-ap1.py` ist der gefährliche:** er scheitert nicht, er sichert das **falsche Gerät** und legt
|
||
die Datei als `ap1-pre-core-cutover-*.backup` ab. Wer dieses Backup später zurückspielt, spielt eine
|
||
Core-Config auf einen AP.
|
||
|
||
✅ **Entschieden (Christoph, 2026-08-31): archiviert, nicht repariert.** Die vier liegen jetzt unter
|
||
[`routeros/archiv-tp15-cutover/`](../routeros/archiv-tp15-cutover/README.md) mit README nach dem
|
||
Muster von [`switch/`](../switch/README.md) — der Cutover ist am 2026-08-08 durchgezogen, die Skripte
|
||
haben ihre Aufgabe erfüllt.
|
||
|
||
⚠️ **Die Pfad-Nebenwirkungen des Verschiebens sind absichtlich NICHT repariert** (`backup-core.py`
|
||
schriebe jetzt ins Archivverzeichnis, `apply-core-cutover-l3.py` fände seine `.rsc` nicht mehr).
|
||
Sie zu reparieren hieße, die Skripte wieder lauffähig zu machen — das ist gerade nicht die Absicht.
|
||
|
||
**`import-core-rsc.py` bleibt**, wo es ist: als einziges generisch nützlich. Sein Default-Host ist
|
||
aber ebenfalls tot ⇒ **`--host` mitgeben**. (Und in `--host` steckt ein zweiter latenter Bug:
|
||
`sys.argv[1]` wird als RSC-Pfad gelesen, bevor auf `--host` geprüft wird — die Option kann so, wie
|
||
sie dokumentiert ist, nie greifen. **Nicht gefixt**, gehört zu einer eigenen Runde.)
|
||
|
||
**Entwarnung zum Snapshot:** `/export` auf ROS 7.24 gibt WLAN-Passphrasen **gar nicht** aus
|
||
(0 Vorkommen im Vollexport, live geprüft). Die Transaktionsakte im Git ist sauber.
|
||
|
||
## 🔧 Zweiter Nebenbefund: Worktrees können `gc-*.py` nicht ohne Handgriff ausführen
|
||
|
||
`docs/nas-vlan20/stacks/210-nautobot.yml` ist gitignored und existiert deshalb **nur im
|
||
Haupt-Checkout**. In einem Worktree scheitern `gc-transaktion.py` und `gc-configplan.py` am
|
||
Nautobot-Token. Behelf in dieser Session war ein Symlink auf den Haupt-Checkout — **der gehört nicht
|
||
committet**. Dauerhafte Antwort wäre `NAUTOBOT_TOKEN` aus `~/.secrets/exports.zsh` (gc-configplan
|
||
liest die Variable bereits; gc-transaktion liest nur die Stack-Datei).
|
||
|
||
## Unverändert offen (nicht Teil dieser Session)
|
||
|
||
1. TP25-Messrunde mit Rundgang (`bash docs/tp25-runde.sh`) — ⚠️ **die `ap-map` neu einfrieren**, s.o.
|
||
2. Passwort-Rotation TP30 (Admin-PW am 08-30 verbrannt; fehlende `MCP_MIKROMCP_*_PASS` in `exports.zsh`)
|
||
3. MikroMCP-Neustart (Registry inkl. ap3 wird nur beim Start gelesen)
|
||
4. ap1-Umzug ans Sideboard (`core ether3`, `5260/Ce`, Frequenz-**Liste** statt Einzelwert, `tx-power` vorher messen)
|
||
5. Versions-Angleichung 7.24 → 7.24.1
|