* Migration 0012: firewall_zones (id, name UNIQUE, description, builtin),
Seed wan/lan/dmz/mgmt/cluster als builtin. CHECK-Constraints auf
network_interfaces.role + firewall_rules.{src,dst}_zone +
firewall_nat_rules.{in,out}_zone gedroppt — Validation lebt jetzt
app-side (Handler prüft Existenz in firewall_zones).
* Backend: firewall.ZonesRepo (CRUD + Exists + References-Lookup),
/api/v1/firewall/zones, builtin geschützt (Name nicht änderbar,
Delete blockiert), Rename eines Custom-Zone aktuell ohne Cascade
(Handler-Sorge bei Rules/NAT/Networks).
* Handler-Validation in CreateRule/UpdateRule/CreateNAT/UpdateNAT +
NetworksHandler: Zone-Existence-Check pro Mutation, 400 bei Tippfehler.
* Frontend: Firewall-Tab "Zonen" (CRUD mit builtin-Schutz). Networks-
Form lädt Rollen aus /firewall/zones (statt hardcoded Liste); Rules-
und NAT-Forms ziehen die Zone-Auswahl ebenfalls aus der API.
* Domain-Form bekommt Primary-Backend-Picker (Field war im Modell,
fehlte im UI). Backends-Tabelle zeigt umgekehrt welche Domains
darauf zeigen — bidirektionale Sicht ohne Schemaänderung.
* HAProxy-Renderer: safeID-FuncMap escaped Server-Namen mit Whitespace
("Control Master 1" → "Control_Master_1"). Vorher ist haproxy beim
Reload an Spaces im Backend-Namen kaputt gegangen.
* Version 1.0.3 → 1.0.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/firewall/firewall.go komplett neu: joint zone-iface-mapping
(network_interfaces.role), address objects + groups (members
expandiert), services + groups, rules, nat-rules. Output: einheitliche
View mit Legs (rule × service cross-product) damit das Template kein
sub-template/dict braucht.
Template:
* Anti-Lockout-Block am input-chain-Top (SSH+443 immer erlaubt,
KANN nicht von Custom-Rules overruled werden — User-Wunsch).
* Rules: pro Leg eine nft-Zeile mit iif/oif sets, ip saddr/daddr,
proto+dport, optional log-prefix.
* prerouting_nat: iteriert dnat-Rules.
* postrouting_nat: snat + masquerade.
Auto-apply: FirewallHandler bekommt einen Reloader-Hook der nach
jedem POST/PUT/DELETE aufgerufen wird. main.go injected
firewall.New(pool).Render — schreibt + sudo nft -f.
Sudoers (/etc/sudoers.d/edgeguard): NOPASSWD für 'nft -f
/etc/edgeguard/nftables.d/ruleset.nft'. configgen.ReloadService
nutzt jetzt sudo (haproxy reload klappte vorher nicht aus dem
edgeguard-User).
Frontend (Sweep): style={{ marginBottom: 16 }} → className="mb-16"
in allen 7 Firewall-Tabs — User-Feedback "globales CSS statt inline".
Live auf 89.163.205.6: nft list table inet edgeguard zeigt
Anti-Lockout + Baseline + Cluster-Peer-Set + (jetzt noch leere)
Custom-Rules-Sektion. render-config postinst-mäßig sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/handlers/firewall.go: ein FirewallHandler-Struct hält alle
6 Repos + Audit-Ref. Register(authed) mountet 30 Endpoints unter
/api/v1/firewall/{address-objects,address-groups,services,
service-groups,rules,nat-rules}.
Validation:
* Address-Objects: kind=host → ParseIP, network → ParseCIDR,
range → "IP-IP", fqdn → looksLikeFQDN.
* Rules: src/dst max one of (object_id|group_id|cidr); 0 = "any".
service max one of (object|group). CIDR-Werte werden geparsed.
* NAT: kind-spezifische Pflichtfelder. dnat braucht target_addr
+ match_dport_start. snat braucht target_addr. masquerade
verbietet target_addr (Iface-IP gewinnt).
* Services: builtin-Rows können nicht editiert/gelöscht werden
(Repo-Layer enforced).
Audit-Log pro Mutation. NoContent für DELETE.
Wiring in cmd/edgeguard-api/main.go: 6 Repos + ein
NewFirewallHandler(...).Register(authed).
Renderer (nft aus allen Joins) + Frontend folgen in den nächsten
Commits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>