Initial import from bf-network-r6.opencode — opencode migration
This commit is contained in:
122
nautobot/ingest-mangle-rule.py
Executable file
122
nautobot/ingest-mangle-rule.py
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ComplianceRule `ip-firewall-mangle` anlegen (idempotent).
|
||||
|
||||
Die Luecke, die das schliesst
|
||||
-----------------------------
|
||||
`/ip firewall mangle` stand unter **keiner** ComplianceRule. Fuer TP6.1 ist das nicht
|
||||
egal, sondern zentral: die Upload-Priorisierung besteht aus **zwei** Teilen, und der
|
||||
Queue-Tree allein ist wirkungslos. Faellt die Markierungsregel weg oder driftet sie,
|
||||
landet aller Verkehr in derselben Klasse -- und **nichts** meldet das, waehrend die
|
||||
Queue weiter zufrieden aussieht.
|
||||
|
||||
Dieselbe blinde Stelle, die TP26 fuer `/ip firewall filter` und TP6 fuer `/queue`
|
||||
geschlossen hat.
|
||||
|
||||
⚠️ `config_ordered=True`, anders als bei `queue`: Mangle ist eine Kette, die von oben
|
||||
nach unten ausgewertet wird. Die spezifische NAS-Regel MUSS vor dem Catch-all stehen --
|
||||
sonst bekaeme der Sync die Rest-Prioritaet und die ganze Konstruktion waere still
|
||||
wirkungslos. Genau wie bei `ip-firewall-filter`.
|
||||
|
||||
export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml
|
||||
python3 nautobot/ingest-mangle-rule.py # Dry-Run
|
||||
python3 nautobot/ingest-mangle-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 = "ip-firewall-mangle"
|
||||
BESCHREIBUNG = ("TP6.1: packet-marks fuer die Upload-Priorisierung. Ohne Rule waere die "
|
||||
"Markierung unsichtbarer Drift -- und ohne sie ist der Queue-Tree wirkungslos.")
|
||||
MATCH = "/ip firewall mangle"
|
||||
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,
|
||||
# Reihenfolge ist SEMANTISCH: die spezifische NAS-Regel muss vor dem
|
||||
# Catch-all stehen, sonst ist die Priorisierung still wirkungslos.
|
||||
"config_ordered": True,
|
||||
"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()
|
||||
Reference in New Issue
Block a user