feat(firewall): Auto-FW-Rule-Generator + UI-Anzeige

Renderer berechnet inbound-accept-Rules aus dem laufenden
Service-State — Operator legt keine FW-Rule mehr für DNS/Squid/WG-
Listen-Sockets manuell an.

internal/firewall:
* View.AutoRules + AutoFWRule struct (proto, port, optional dst-IP,
  comment).
* loadAutoRules quert cross-service:
  - DNS: dns_settings.listen_addresses ohne 127.x/::1 → udp+tcp 53
    pro IP (mit ip daddr X-match).
  - Squid: count(active forward_proxy_acls) > 0 → tcp 3128 (any IP,
    squid bindet 0.0.0.0).
  - WireGuard: server-mode + listen_port → udp <port> pro Iface.
* nft-Template emittiert eigene "Service-Auto-Rules"-Section vor
  Operator-Rules. Comment im nft-Output zeigt source-service.
* LoadAutoRules exportiert für Handler-Endpoint.

Handler:
* GET /api/v1/firewall/auto-rules — gibt die berechnete Liste
  zurück damit die UI sie anzeigen kann.
* FirewallHandler.Pool field + ctor-arg dazugekommen.

UI:
* SystemRulesCard fetcht /firewall/auto-rules + merged sie unter
  die statischen Anti-Lockout-Rows. 30s-Polling. Operator sieht
  jetzt im /firewall/Rules-Tab oben warum z.B. udp/53 offen ist
  (auto: DNS auf 10.10.20.1).

Cleanup: alte manuelle DNS+WG-Rules per SQL gelöscht — Auto-Rules
übernehmen.

Version 1.0.38.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-11 06:47:38 +02:00
parent 8357d84c7b
commit 2556a93b34
10 changed files with 165 additions and 8 deletions

View File

@@ -11,10 +11,13 @@ import (
"github.com/gin-gonic/gin"
firewallrender "git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response"
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
"git.netcell-it.de/projekte/edgeguard-native/internal/services/audit"
"git.netcell-it.de/projekte/edgeguard-native/internal/services/firewall"
"github.com/jackc/pgx/v5/pgxpool"
)
// FirewallHandler exposes everything under /api/v1/firewall/*:
@@ -45,6 +48,11 @@ type FirewallHandler struct {
// already committed). Wire to internal/firewall.Generator.Render
// in main.go; pass nil during tests.
Reloader func(ctx context.Context) error
// Pool is needed to instantiate the renderer for the
// auto-rules endpoint (computes inbound rules derived from
// running service config — DNS/Squid/WG listen-sockets).
Pool *pgxpool.Pool
}
func NewFirewallHandler(
@@ -58,6 +66,7 @@ func NewFirewallHandler(
a *audit.Repo,
nodeID string,
reloader func(ctx context.Context) error,
pool *pgxpool.Pool,
) *FirewallHandler {
return &FirewallHandler{
Zones: zn,
@@ -66,6 +75,7 @@ func NewFirewallHandler(
Rules: rl, NATRules: nat,
Audit: a, NodeID: nodeID,
Reloader: reloader,
Pool: pool,
}
}
@@ -85,6 +95,8 @@ func (h *FirewallHandler) reload(ctx context.Context, op string) {
func (h *FirewallHandler) Register(rg *gin.RouterGroup) {
g := rg.Group("/firewall")
g.GET("/auto-rules", h.AutoRules)
zn := g.Group("/zones")
zn.GET("", h.ListZone)
zn.POST("", h.CreateZone)
@@ -135,6 +147,15 @@ func (h *FirewallHandler) Register(rg *gin.RouterGroup) {
nat.DELETE("/:id", h.DeleteNAT)
}
// AutoRules returns the inbound rules the firewall renderer derives
// from the running service config (DNS/Squid/WG listen-sockets).
// UI shows them in the SystemRulesCard so the operator understands
// why e.g. UDP/53 is open without any operator-rule.
func (h *FirewallHandler) AutoRules(c *gin.Context) {
rules := firewallrender.New(h.Pool).LoadAutoRules(c.Request.Context())
response.OK(c, gin.H{"rules": rules})
}
// ── Zones ──────────────────────────────────────────────────────────────
func (h *FirewallHandler) ListZone(c *gin.Context) {