Oktett-Klasse .150-.199 (App-Services). Nautobot gibt damit .30/.31 an AGH frei. .157 ist gitea, deshalb nimmt redis .158. - Datenmigration ueber neues nautobot/tp45-ip-move.py: Nautobot verbietet das Aendern einer IP nach dem Anlegen -> anlegen/primary_ip4 umhaengen/ Altobjekt loeschen, in dieser Reihenfolge - Stack-Redeploy (PUT /api/stacks/203), Caddy-Backend, Core-Literal *24 ueber GC deployt (Plan 51a90e25) mit Wirksamkeitspruefung am Geraet - caddy/deploy.sh: push_verified() ersetzt den push||push-Fallback. `pct push` meldet Exit 0 AUCH wenn es die Quelle nicht lesen kann, der Fallback griff deshalb nie und `caddy validate` pruefte die ALTE Datei -> gruene Abnahme bei unveraendertem Zustand (Symptom: 502 bei gesundem Backend) - 39 Referenzen in 28 Dateien mitgezogen; lebende Doku nachgezogen (CLAUDE.md, ip-plan.md, cf-tunnel-mapping §0-Kopf, TP16.3/TP31-Prosa) - GC-Compliance 140/140, 0 Drifts Neue Funde (TP45-Prosa + CLAUDE.md): - Die Core-Adressliste `nautobot` wird von GC nirgends gerendert -> ein Recovery von null erzeugt einen Core, in den Nautobot nicht mehr per SSH kommt. Gleiche Klasse wie die fehlenden DHCP-Reservierungen. - Regel *24 (Literal) und *28 (Liste) sind Dubletten; der Literal steht bei 0 Paketen. Zusammenlegung bleibt offen. - gc-manual-plan.py: /api/extras/job-log-entries/ liefert 404 (Nautobot 3.x) -> bei gescheitertem Deploy fehlt die Fehlermeldung. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
4.2 KiB
Python
106 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
r"""TP38: OOB-Port + Input-Firewall als Daten nach Nautobot schreiben.
|
|
|
|
Zwei getrennte Aenderungen, ein Skript:
|
|
|
|
1. BRANCH (crs326-branch): `firewall_filter` im local_config_context_data ANLEGEN
|
|
(8 Input-Regeln, deny-by-default). Der src-Rahmen 10.40.0.0/16 + 100.96.0.0/12
|
|
spiegelt bewusst das `available-from` der /ip service-Zeilen — kein neues
|
|
Fenster, nur stateful + deny-by-default + WOULD-DROP-Log.
|
|
2. CORE (crs326-core): EINE Regel "TP38: OOB->core mgmt" in die bestehende
|
|
firewall_filter-Liste EINFUEGEN (vor log/drop), damit der OOB-Port (untagged
|
|
ether23, VLAN99) auch den Core selbst verwalten kann.
|
|
|
|
Attributreihenfolge = alphabetisch, exakt wie RouterOS `/export` sie schreibt
|
|
(gleiche Konvention wie ingest-core-firewall.py).
|
|
|
|
python3 nautobot/ingest-tp38-oob.py # Dry-Run
|
|
python3 nautobot/ingest-tp38-oob.py --apply
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
|
|
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.154:8080")
|
|
|
|
BRANCH_RULES = [
|
|
'add action=accept chain=input comment="TP38: established" connection-state=established,related,untracked',
|
|
'add action=drop chain=input comment="TP38: invalid" connection-state=invalid',
|
|
'add action=accept chain=input comment="TP38: icmp" protocol=icmp',
|
|
'add action=accept chain=input comment="TP38: mndp" dst-port=5678 protocol=udp',
|
|
'add action=accept chain=input comment="TP38: mgmt LAN" dst-port=22,80,8291,8728 protocol=tcp src-address=10.40.0.0/16',
|
|
'add action=accept chain=input comment="TP38: mgmt mesh" dst-port=22,80,8291,8728 protocol=tcp src-address=100.96.0.0/12',
|
|
'add action=log chain=input comment="TP38: input log-only" log-prefix=TP38-BRANCH-WOULD-DROP',
|
|
'add action=drop chain=input comment="TP38: input deny-by-default"',
|
|
]
|
|
|
|
CORE_NEW_RULE = 'add action=accept chain=input comment="TP38: OOB->core mgmt" dst-port=22,80,8291,8728 in-interface=vlan99 protocol=tcp'
|
|
|
|
|
|
def nb(path, method="GET", payload=None):
|
|
tok = os.environ.get("NAUTOBOT_TOKEN") or sys.exit("NAUTOBOT_TOKEN nicht gesetzt")
|
|
req = urllib.request.Request(
|
|
f"{NB}{path}", method=method,
|
|
data=json.dumps(payload).encode() if payload is not None else None,
|
|
headers={"Authorization": f"Token {tok}", "Content-Type": "application/json",
|
|
"Accept": "application/json"})
|
|
with urllib.request.urlopen(req, timeout=60) as r:
|
|
raw = r.read()
|
|
return json.loads(raw) if raw else {}
|
|
|
|
|
|
def get_device(name):
|
|
return nb(f"/api/dcim/devices/?name={name}&depth=1")["results"][0]
|
|
|
|
|
|
def patch_device(dev, ctx, label):
|
|
dev_id = dev["id"]
|
|
nb(f"/api/dcim/devices/{dev_id}/", "PATCH", {"local_config_context_data": ctx})
|
|
print(f" ~ {label} geschrieben.")
|
|
|
|
|
|
def main():
|
|
apply_ = "--apply" in sys.argv
|
|
|
|
# --- BRANCH ---
|
|
branch = get_device("crs326-branch")
|
|
bctx = dict(branch.get("local_config_context_data") or {})
|
|
if bctx.get("firewall_filter") == BRANCH_RULES:
|
|
print("== branch: firewall_filter unveraendert ==")
|
|
else:
|
|
bctx["firewall_filter"] = BRANCH_RULES
|
|
print(f"== branch: {len(BRANCH_RULES)} Input-Regeln ==")
|
|
for ln in BRANCH_RULES:
|
|
print(" " + ln)
|
|
if apply_:
|
|
patch_device(branch, bctx, "branch firewall_filter")
|
|
|
|
# --- CORE ---
|
|
core = get_device("crs326-core")
|
|
cctx = dict(core.get("local_config_context_data") or {})
|
|
ff = list(cctx.get("firewall_filter") or [])
|
|
if CORE_NEW_RULE in ff:
|
|
print("\n== core: Regel schon vorhanden ==")
|
|
else:
|
|
# vor der Log-Regel einsortieren (die sitzt direkt vor dem finalen drop).
|
|
insert_at = len(ff)
|
|
for i, ln in enumerate(ff):
|
|
if "log-prefix=TP5-INPUT-WOULD-DROP" in ln:
|
|
insert_at = i
|
|
break
|
|
ff.insert(insert_at, CORE_NEW_RULE)
|
|
cctx["firewall_filter"] = ff
|
|
print(f"\n== core: fuege OOB-Regel an Position {insert_at} ein ==")
|
|
print(" " + CORE_NEW_RULE)
|
|
if apply_:
|
|
patch_device(core, cctx, "core firewall_filter")
|
|
|
|
if not apply_:
|
|
print("\nDRY-RUN — mit --apply schreiben. Danach: "
|
|
"push-gc-template.py + gc-configplan.py --refresh-compliance")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|