TP5 E5: MAIN->IOT aufgemacht, Drucker und Shellys wieder erreichbar

Der Deny-by-default arbeitete korrekt — es gab nie eine vlan10->vlan30-Zeile.
Offen waren nur MAIN->MEDIA, MAIN->INFRA, MAIN->MGMT; IOT fehlte als einziges
Paar. Neue Regel voll einbahnig ohne Portfilter (User-Entscheid): MAIN steht zu
INFRA/MEDIA/MGMT ohnehin ungefiltert offen, und dort liegen NAS/Proxmox/Nautobot.
IOT->MAIN bleibt bewusst zu — dort sitzt der Wert der Segmentierung.

Merkregel aus dem Drop-Log: der Drucker faellt mit udp/161 auf, nicht mit 631.
macOS fragt zuerst den SNMP-Status ab und bricht ab, bevor es zu IPP kommt —
eine Whitelist auf 631 haette nichts repariert.

Abnahme von 10.40.10.82 (der Quelle aus dem Log): .30.5 ICMP 0% Loss,
snmpget sysName.0 -> P750W, tcp/631+9100+80 offen; Shellys .30.202/.203/.221
HTTP 200. Compliance 92/92, 0 Drifts. Seit dem Deploy keine neue
vlan10->vlan30-Zeile im Kollektor.

Nebenbei: gc-manual-plan.py --assert URL-encodet die Query jetzt. Ein Kommentar
mit Leerzeichen warf InvalidURL erst NACH dem erfolgreichen Deploy — man steht
mit einem Traceback da und weiss nicht, ob der Change gewirkt hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 16:02:29 +02:00
parent 777ac95c3f
commit 1eabd0434b
3 changed files with 84 additions and 10 deletions

View File

@@ -58,6 +58,7 @@ import re
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.30:8080")
@@ -203,7 +204,20 @@ def rest_call(name, path, query, post=False):
k: v for k, _, v in (p.partition("=") for p in query.split("&") if p)
}).encode()
elif query:
url += f"?{query}"
# Werte URL-encoden. Ein Kommentar wie "TP5-E5: MAIN->IOT" traegt ein
# Leerzeichen; roh angehaengt wirft http.client InvalidURL ("URL can't
# contain control characters") — der Deploy ist dann laengst durch und
# nur die Gegenpruefung crasht, was schlimmer ist als kein Assert.
# (2026-08-19 bei TP5-E5 aufgeschlagen.) Trennzeichen & und = bleiben.
parts = []
for p_ in query.split("&"):
if not p_:
continue
k, sep, v = p_.partition("=")
parts.append(f"{urllib.parse.quote(k, safe='')}{sep}"
f"{urllib.parse.quote(v, safe='')}" if sep
else urllib.parse.quote(k, safe=""))
url += "?" + "&".join(parts)
auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
req = urllib.request.Request(
url, data=body, method="POST" if post else "GET",