ComplianceRule wifi-access-list: die letzte ungepruefte Sektion schliessen
/interface wifi access-list stand unter KEINER ComplianceRule. Eintraege dort
erzeugten keinen Drift und liessen sich nicht als Config-Transaktion fuehren --
gc-transaktion.py verlangt eine ComplianceRule als --scope. Seit dem 22.8. ist
das nicht mehr theoretisch: docs/tp25-pin-s22.sh schreibt bei jedem Messrundgang
Regeln dorthin, und TP25 Fund E (signal-range-Boden) wuerde dauerhaft genau in
diesem blinden Fleck landen.
- nautobot/ingest-wifi-access-list-rule.py (idempotent, Dry-Run default)
- Template-Abschnitt [6c], bewusst NUR Kommentare: der Soll-Zustand ist LEER,
und das ist die Aussage. Die intended config gewinnt keine wirksame Zeile.
Nach bf-golden-config gepusht (51c11f0), Repo-Sync SUCCESS.
ZWEI KORREKTUREN AN DER AUFGABENSTELLUNG:
1. Der Zaehler geht auf 96, nicht auf 94. Die Regeln gelten PLATTFORMWEIT, nicht
geraetespezifisch -- 92 Checks waren exakt 23 Rules x 4 Geraete, jetzt sind es
24 x 4. Die Annahme "2 geraete-relevante Checks" war falsch.
2. Vor Beginn stand bereits ein Drift in der Anzeige (bridge-vlan auf
crs326-core, ether3 als Trunk), obwohl der Sideboard-Rollback am 08-22
gelandet war. Das Geraet war live korrekt -- die Compliance vergleicht gegen
das letzte BACKUP, und das war aelter als der Rollback. Mit dem Refresh weg.
WIRKSAMKEIT BEWIESEN, nicht behauptet (TP22-Regel "Job-Status ist kein
Wirksamkeitsnachweis"):
96/96 compliant, 0 Drifts
-> Test-Eintrag mit nicht existierender MAC (kein echter Client betroffen)
-> 95/96, extra = exakt diese Zeile bei crs326-core | wifi-access-list
-> Eintrag entfernt -> 96/96, 0 Drifts
Zusaetzlich: gc-transaktion.py akzeptiert --scope wifi-access-list.
Ausserdem geprueft: 4 Compliance-Datensaetze (einer je Geraet), und der
[6c]-Block steht wirklich in der intended config von crs326-core.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,8 @@ ab jetzt bei jeder Änderung mitgezogen, nicht nur die TP-Abschnitte.)*
|
|||||||
08-18) — TP19 ist geschlossen; `cudy1` hängt stromlos an core `ether11`, physischer Ausbau ist hands-on.
|
08-18) — TP19 ist geschlossen; `cudy1` hängt stromlos an core `ether11`, physischer Ausbau ist hands-on.
|
||||||
- **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 4 Geräte in der Registry),
|
- **Kontrolle / Governance:** MikroMCP für Reads/Debug (alle 4 Geräte in der Registry),
|
||||||
**Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf,
|
**Config-Änderungen ausschließlich über Nautobot Golden Config** (TP21/TP22 — Deploy-Pipeline scharf,
|
||||||
**92/92 Compliance-Checks grün, 0 Drifts** bei **23 Rules**, Stand 2026-08-18).
|
**96/96 Compliance-Checks grün, 0 Drifts** bei **24 Rules × 4 Geräten**, Stand 2026-08-23 —
|
||||||
|
neu dazu: **`wifi-access-list`**, s. TP22-Nachtrag).
|
||||||
|
|
||||||
<details><summary>📜 Historische Fassung (Stand bis 2026-08-08, Topologie <b>vor</b> dem Core-Cutover — bewusst erhalten, nicht als Ist lesen)</summary>
|
<details><summary>📜 Historische Fassung (Stand bis 2026-08-08, Topologie <b>vor</b> dem Core-Cutover — bewusst erhalten, nicht als Ist lesen)</summary>
|
||||||
|
|
||||||
@@ -1214,6 +1215,26 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
|||||||
- **Nutzen:** Drift-Erkennung + reproduzierbarer Rebuild jedes Geräts aus dem Modell.
|
- **Nutzen:** Drift-Erkennung + reproduzierbarer Rebuild jedes Geräts aus dem Modell.
|
||||||
- **Abh.:** TP21 ✅ (Plattform/Modell/Templates), TP20 ✅, einheitliches Admin-PW ✅. **Danach ist der kritische Pfad frei für TP5 (Deny-by-default) → TP6 (CAKE).**
|
- **Abh.:** TP21 ✅ (Plattform/Modell/Templates), TP20 ✅, einheitliches Admin-PW ✅. **Danach ist der kritische Pfad frei für TP5 (Deny-by-default) → TP6 (CAKE).**
|
||||||
|
|
||||||
|
- ✅ **Nachtrag 2026-08-23 — Governance-Lücke geschlossen: ComplianceRule `wifi-access-list`.**
|
||||||
|
`/interface wifi access-list` stand unter **keiner** Regel: Einträge dort erzeugten keinen Drift
|
||||||
|
und ließen sich nicht als Transaktion führen (`gc-transaktion.py` verlangt eine ComplianceRule
|
||||||
|
als `--scope`). Seit `docs/tp25-pin-s22.sh` schreibt jeder Messrundgang dort Regeln, und **TP25
|
||||||
|
Fund E** (`signal-range`-Boden) würde dauerhaft genau dort landen.
|
||||||
|
Angelegt per `nautobot/ingest-wifi-access-list-rule.py` (idempotent), Template um den
|
||||||
|
dokumentierten Abschnitt **[6c]** ergänzt (**nur Kommentare** — der Soll-Zustand ist *leer*,
|
||||||
|
und das ist die Aussage).
|
||||||
|
- 🔢 **Zählerkorrektur: 92 → 96, nicht 94.** Die Regeln gelten **plattformweit**, nicht
|
||||||
|
gerätespezifisch: 24 Rules × 4 Geräte. Die Annahme „nur 2 Geräte betroffen" war falsch.
|
||||||
|
- ✅ **Wirksamkeit bewiesen, nicht behauptet** (die TP22-Regel „Job-Status ist kein
|
||||||
|
Wirksamkeitsnachweis"): Test-Eintrag mit einer nicht existierenden MAC gesetzt →
|
||||||
|
**95/96 mit exakt dieser Zeile als `extra`** → Eintrag entfernt → **96/96, 0 Drifts**.
|
||||||
|
Zusätzlich geprüft: `gc-transaktion.py` akzeptiert `--scope wifi-access-list` jetzt.
|
||||||
|
- 🔎 **Nebenbefund beim Start:** ein `bridge-vlan`-Drift auf `crs326-core` stand noch in der
|
||||||
|
Anzeige (ether3 als Trunk), obwohl der Sideboard-Rollback am 08-22 gelandet war. Das Gerät
|
||||||
|
war live korrekt; die **Compliance vergleicht gegen das letzte Backup**, und das war älter
|
||||||
|
als der Rollback. Mit dem Refresh verschwunden. Merkregel bleibt: bei einem Drift zuerst
|
||||||
|
fragen, wie alt das Backup ist.
|
||||||
|
|
||||||
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 1 DURCH, STUFE 2 VORBEREITET (2026-08-18)**
|
### TP23 — DNS-Policy & Self-Service-Ausnahmen ▸ ⏳ **STUFE 1 DURCH, STUFE 2 VORBEREITET (2026-08-18)**
|
||||||
- ✅ **Stufe 1 — DoH auf dem Core scharf** (`routeros/tp23-stufe1-doh-core.rsc`), schliesst zugleich
|
- ✅ **Stufe 1 — DoH auf dem Core scharf** (`routeros/tp23-stufe1-doh-core.rsc`), schliesst zugleich
|
||||||
**TP21.1 TODO 1**. `use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query`,
|
**TP21.1 TODO 1**. `use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query`,
|
||||||
|
|||||||
@@ -24,3 +24,20 @@ python3 nautobot/ingest-baseline.py --apply # schreiben
|
|||||||
einen `Controller` (bei uns logisch, `controller_device` leer — kein CAPsMAN).
|
einen `Controller` (bei uns logisch, `controller_device` leer — kein CAPsMAN).
|
||||||
- `RadioProfile.channel_width` will Integer (`[20,40,80]`), nicht `"20mhz"`.
|
- `RadioProfile.channel_width` will Integer (`[20,40,80]`), nicht `"20mhz"`.
|
||||||
- IP↔Interface läuft über `/ipam/ip-address-to-interface/`.
|
- IP↔Interface läuft über `/ipam/ip-address-to-interface/`.
|
||||||
|
|
||||||
|
## `ingest-wifi-access-list-rule.py`
|
||||||
|
Legt ComplianceFeature + ComplianceRule **`wifi-access-list`** an (Plattform RouterOS,
|
||||||
|
`match_config=/interface wifi access-list`). Idempotent, Dry-Run ist Standard.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NAUTOBOT_TOKEN=...
|
||||||
|
python3 nautobot/ingest-wifi-access-list-rule.py --apply
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warum es die Rule braucht:** ohne sie erzeugen Eintraege in der access-list **keinen Drift**
|
||||||
|
und lassen sich nicht als Config-Transaktion fuehren -- `gc-transaktion.py` verlangt eine
|
||||||
|
ComplianceRule als `--scope`. `docs/tp25-pin-s22.sh` schreibt dort bei jedem Messrundgang, und
|
||||||
|
TP25 Fund E (`signal-range`-Boden) landet dauerhaft dort.
|
||||||
|
|
||||||
|
**Der Soll-Zustand ist leer** und steht als Kommentarblock `[6c]` im Template. Waehrend eines
|
||||||
|
Messlaufs ist der Drift **erwartet** und gehoert als offene Transaktion gefuehrt.
|
||||||
|
|||||||
121
nautobot/ingest-wifi-access-list-rule.py
Normal file
121
nautobot/ingest-wifi-access-list-rule.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
ComplianceRule `wifi-access-list` anlegen (idempotent).
|
||||||
|
|
||||||
|
Die Luecke, die das schliesst
|
||||||
|
-----------------------------
|
||||||
|
Es gibt ComplianceRules fuer wifi-configuration/-datapath/-security/-steering/
|
||||||
|
-provisioning/-channel/-cap/-capsman -- aber **keine** fuer
|
||||||
|
`/interface wifi access-list`. Eintraege dort erzeugen deshalb KEINEN Drift und
|
||||||
|
lassen sich auch nicht als Config-Transaktion fuehren: `gc-transaktion.py`
|
||||||
|
verlangt eine ComplianceRule als `--scope`.
|
||||||
|
|
||||||
|
Seit 2026-08-22 ist das nicht mehr theoretisch. `docs/tp25-pin-s22.sh` schreibt
|
||||||
|
bei jedem Messrundgang access-list-Regeln auf den Manager, und TP25 **Fund E**
|
||||||
|
(`signal-range`-Boden gegen klebende Clients) wuerde dauerhaft genau dort landen
|
||||||
|
-- in einem Abschnitt, den niemand prueft.
|
||||||
|
|
||||||
|
Der Soll-Zustand ist heute **leer**: am Geraet steht keine access-list, im
|
||||||
|
Template steht keine. Beide Seiten leer = compliant. Genau das ist der Sinn --
|
||||||
|
ab jetzt faellt jeder Eintrag dort auf, statt unsichtbar zu bleiben.
|
||||||
|
|
||||||
|
export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml
|
||||||
|
python3 nautobot/ingest-wifi-access-list-rule.py # Dry-Run
|
||||||
|
python3 nautobot/ingest-wifi-access-list-rule.py --apply
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
BASE = "http://10.40.20.30:8080/api"
|
||||||
|
TOKEN = os.environ.get("NAUTOBOT_TOKEN", "")
|
||||||
|
APPLY = "--apply" in sys.argv
|
||||||
|
|
||||||
|
FEATURE = "wifi-access-list"
|
||||||
|
BESCHREIBUNG = ("TP25 Fund E / tp25-pin-s22.sh: signal-range-Boden und Mess-Pins "
|
||||||
|
"landen hier. Soll ist leer, solange Fund E nicht entschieden ist.")
|
||||||
|
MATCH = "/interface wifi access-list"
|
||||||
|
PLATTFORM = "RouterOS"
|
||||||
|
|
||||||
|
|
||||||
|
def req(method, path, data=None):
|
||||||
|
url = path if path.startswith("http") else BASE + path
|
||||||
|
body = json.dumps(data).encode() if data is not None else None
|
||||||
|
r = urllib.request.Request(url, data=body, method=method)
|
||||||
|
r.add_header("Authorization", "Token " + TOKEN)
|
||||||
|
r.add_header("Content-Type", "application/json")
|
||||||
|
r.add_header("Accept", "application/json")
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(r, timeout=60) as resp:
|
||||||
|
raw = resp.read()
|
||||||
|
return json.loads(raw) if raw else {}
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
# 🔴 Fehlerkoerper ausgeben: Nautobot begruendet 400er praezise, und ohne den
|
||||||
|
# Text raet man an Feldnamen herum (TP22-Erfahrung).
|
||||||
|
sys.exit("!! %s %s -> %s\n %s" % (method, url, e.code, e.read().decode()[:600]))
|
||||||
|
|
||||||
|
|
||||||
|
def eins(pfad, **filter):
|
||||||
|
q = urllib.parse.urlencode(filter)
|
||||||
|
res = req("GET", "%s?%s" % (pfad, q)).get("results", [])
|
||||||
|
if len(res) > 1:
|
||||||
|
sys.exit("FEHLER: %s?%s liefert %d Treffer, erwartet 0 oder 1" % (pfad, q, len(res)))
|
||||||
|
return res[0] if res else None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not TOKEN:
|
||||||
|
sys.exit("NAUTOBOT_TOKEN fehlt.")
|
||||||
|
print("Modus:", "APPLY" if APPLY else "DRY-RUN (nichts wird geschrieben)")
|
||||||
|
|
||||||
|
plat = eins("/dcim/platforms/", name=PLATTFORM)
|
||||||
|
if not plat:
|
||||||
|
sys.exit("Plattform %r nicht gefunden." % PLATTFORM)
|
||||||
|
print("Plattform : %s (%s)" % (plat["display"], plat["id"]))
|
||||||
|
|
||||||
|
feat = eins("/plugins/golden-config/compliance-feature/", name=FEATURE)
|
||||||
|
if feat:
|
||||||
|
print("Feature : existiert bereits (%s)" % feat["id"])
|
||||||
|
else:
|
||||||
|
print("Feature : anlegen -> %s" % FEATURE)
|
||||||
|
if APPLY:
|
||||||
|
feat = req("POST", "/plugins/golden-config/compliance-feature/",
|
||||||
|
{"name": FEATURE, "slug": FEATURE, "description": BESCHREIBUNG})
|
||||||
|
print(" angelegt (%s)" % feat["id"])
|
||||||
|
|
||||||
|
if not feat:
|
||||||
|
print("\nDry-Run: ohne Feature kann die Rule nicht geprueft werden. Mit --apply erneut.")
|
||||||
|
return
|
||||||
|
|
||||||
|
rule = eins("/plugins/golden-config/compliance-rule/",
|
||||||
|
feature=feat["id"], platform=plat["id"])
|
||||||
|
if rule:
|
||||||
|
print("Rule : existiert bereits (%s), match_config=%r"
|
||||||
|
% (rule["id"], rule.get("match_config")))
|
||||||
|
if rule.get("match_config") != MATCH:
|
||||||
|
print(" ⚠️ match_config weicht ab -- erwartet %r" % MATCH)
|
||||||
|
return
|
||||||
|
|
||||||
|
nutzlast = {
|
||||||
|
"feature": feat["id"],
|
||||||
|
"platform": plat["id"],
|
||||||
|
"description": BESCHREIBUNG,
|
||||||
|
"config_ordered": False, # Reihenfolge der access-list-Eintraege ist hier egal
|
||||||
|
"config_type": "cli",
|
||||||
|
"match_config": MATCH,
|
||||||
|
"custom_compliance": False,
|
||||||
|
"config_remediation": False,
|
||||||
|
}
|
||||||
|
print("Rule : anlegen -> %s auf %s, match_config=%r" % (FEATURE, PLATTFORM, MATCH))
|
||||||
|
if APPLY:
|
||||||
|
r = req("POST", "/plugins/golden-config/compliance-rule/", nutzlast)
|
||||||
|
print(" angelegt (%s)" % r["id"])
|
||||||
|
else:
|
||||||
|
print(" Nutzlast: %s" % json.dumps(nutzlast))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -212,6 +212,28 @@
|
|||||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 5GHz" master-configuration=cfg-main-5g-ap2 radio-mac=D0:EA:11:4D:40:3E slave-configurations=cfg-media,cfg-untrusted,cfg-guest
|
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 5GHz" master-configuration=cfg-main-5g-ap2 radio-mac=D0:EA:11:4D:40:3E slave-configurations=cfg-media,cfg-untrusted,cfg-guest
|
||||||
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 2.4GHz" master-configuration=cfg-main-2g-ap2 radio-mac=D0:EA:11:4D:40:3F slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
|
/interface wifi provisioning add action=create-dynamic-enabled comment="ap2 2.4GHz" master-configuration=cfg-main-2g-ap2 radio-mac=D0:EA:11:4D:40:3F slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
|
||||||
|
|
||||||
|
# [6c] WLAN ACCESS-LIST — der Soll-Zustand ist LEER, und das ist eine Aussage.
|
||||||
|
#
|
||||||
|
# Bis 2026-08-22 stand dieser Abschnitt unter KEINER ComplianceRule. Aenderungen
|
||||||
|
# dort erzeugten keinen Drift und liessen sich nicht als Transaktion fuehren --
|
||||||
|
# `gc-transaktion.py` verlangt eine ComplianceRule als --scope. Das war nicht
|
||||||
|
# theoretisch: `docs/tp25-pin-s22.sh` schreibt bei jedem Messrundgang
|
||||||
|
# access-list-Regeln (Kommentar TP25-PIN-S22) auf den Manager, um ein Telefon auf
|
||||||
|
# einen AP festzunageln.
|
||||||
|
#
|
||||||
|
# Seit der ComplianceRule `wifi-access-list` faellt jeder Eintrag hier auf.
|
||||||
|
# Waehrend eines Messlaufs ist der gemeldete Drift ERWARTET -- dann gehoert er als
|
||||||
|
# offene Transaktion gefuehrt:
|
||||||
|
# python3 nautobot/gc-transaktion.py begin --device crs326-core \
|
||||||
|
# --scope wifi-access-list --change-id TEST-... --expires JJJJ-MM-TT
|
||||||
|
#
|
||||||
|
# 🔴 Hier landet TP25 **Fund E** (`signal-range`-Boden gegen klebende Clients),
|
||||||
|
# sobald er entschieden ist. Stand 2026-08-22 ist er es NICHT: der Sticky-Client
|
||||||
|
# vom 08-20 reproduzierte sich beim Rundgang am 08-22 nicht, und in den schwachen
|
||||||
|
# Raeumen gibt es keinen besseren AP, auf den ein Boden steuern koennte. Ein
|
||||||
|
# voreilig gesetzter Boden verschlechtert die Terrasse, statt zu helfen.
|
||||||
|
# Vor dem Eintragen also: Sticky-Fall erneut messen. Siehe teilprojekte.md TP25.
|
||||||
|
|
||||||
# Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich):
|
# Kanonischer Mgmt-Nummernplan (User-Bestaetigung 2026-08-13, verbindlich):
|
||||||
# core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12
|
# core .99.1 | branch .99.2 | ap1 .99.11 | ap2 .99.12
|
||||||
/ip dns static add address=10.40.99.1 comment="core = GW/Router" name=router.lan type=A
|
/ip dns static add address=10.40.99.1 comment="core = GW/Router" name=router.lan type=A
|
||||||
|
|||||||
Reference in New Issue
Block a user