diff --git a/VERSION b/VERSION index c2320f5..4a4127c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.20 +1.0.25 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index 2a35a41..a2e9483 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -39,7 +39,7 @@ import ( wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard" ) -var version = "1.0.20" +var version = "1.0.25" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index 9288a23..947b120 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -9,7 +9,7 @@ import ( "os" ) -var version = "1.0.20" +var version = "1.0.25" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 764fd95..f374f64 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -21,7 +21,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.0.20" +var version = "1.0.25" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/internal/firewall/ruleset.nft.tpl b/internal/firewall/ruleset.nft.tpl index 9435984..67c7f80 100644 --- a/internal/firewall/ruleset.nft.tpl +++ b/internal/firewall/ruleset.nft.tpl @@ -63,6 +63,22 @@ table inet edgeguard { ct state established,related accept ct state invalid drop + + # DNAT-rewrites aus prerouting_nat haben den ct.status DNAT-Bit + # gesetzt — die müssen forward-passieren dürfen, sonst kommen + # Port-Forwards (z.B. :2030 → 10.10.20.12:22) zwar durch das + # NAT-Rewrite, scheitern aber an policy=drop. Equivalent zu + # iptables -m conntrack --ctstate DNAT. + ct status dnat accept + + # Auto-Forward für SNAT/Masquerade-Origin-Pakete. Forward-chain + # sieht das Paket VOR der postrouting-Translation; ct.status ist + # also noch nicht "snat". Wir ziehen pro NAT-Rule das SrcCIDR + # nach und erlauben new-state-Pakete von dort. Return-Pakete + # gehen via ct state established schon durch. +{{range .NATRules}}{{if or (eq .Kind "snat") (eq .Kind "masquerade")}}{{if .SrcCIDR}} + ip saddr {{.SrcCIDR}} ct state new accept comment "auto-forward for NAT rule {{.ID}}" +{{end}}{{end}}{{end}} } chain output { @@ -71,29 +87,36 @@ table inet edgeguard { chain prerouting_nat { type nat hook prerouting priority -100; -{{- range .NATRules}}{{if eq .Kind "dnat"}} +{{range .NATRules}}{{if eq .Kind "dnat"}} # NAT {{.ID}} (dnat{{if .Comment}} — {{.Comment}}{{end}}) - {{- if .InIfaces}} iifname { {{join .InIfaces ", "}} }{{end}} - {{- if and .Proto (ne .Proto "any")}} {{.Proto}}{{else}} meta l4proto { tcp, udp }{{end}} - {{- if .SrcCIDR}} ip saddr {{.SrcCIDR}}{{end}} - {{- if .DstCIDR}} ip daddr {{.DstCIDR}}{{end}} - {{- if .DPortStart}} dport {{.DPortStart}}{{if and .DPortEnd (ne .DPortEnd .DPortStart)}}-{{.DPortEnd}}{{end}}{{end}} - {{- if .TargetAddr}} dnat to {{.TargetAddr}}{{if .TargetPortStart}}:{{.TargetPortStart}}{{if and .TargetPortEnd (ne .TargetPortEnd .TargetPortStart)}}-{{.TargetPortEnd}}{{end}}{{end}}{{end}} -{{- end}}{{end}} + {{""}} + {{/* nft-Syntax: erst L3-match (ip saddr/daddr), DANN L4 (tcp/udp dport). + Sonst quittiert der parser '... unexpected ip' an dieser Stelle. */}} + {{if .InIfaces}}iifname { {{join .InIfaces ", "}} } {{end}}{{if .SrcCIDR}}ip saddr {{.SrcCIDR}} {{end}}{{if .DstCIDR}}ip daddr {{.DstCIDR}} {{end}}{{if and .Proto (ne .Proto "any")}}{{.Proto}} {{else}}meta l4proto { tcp, udp } {{end}}{{if .DPortStart}}dport {{.DPortStart}}{{if and .DPortEnd (ne .DPortEnd .DPortStart)}}-{{.DPortEnd}}{{end}} {{end}}{{if .TargetAddr}}dnat to {{.TargetAddr}}{{if .TargetPortStart}}:{{.TargetPortStart}}{{if and .TargetPortEnd (ne .TargetPortEnd .TargetPortStart)}}-{{.TargetPortEnd}}{{end}}{{end}}{{end}} +{{end}}{{end}} } chain postrouting_nat { type nat hook postrouting priority 100; -{{- range .NATRules}}{{if eq .Kind "snat"}} + + # Auto-Hairpin für DNAT-Pakete: alle in prerouting_nat + # umgeschriebenen Pakete bekommen zusätzlich SNAT auf die + # Box-IP des ausgehenden Iface (masquerade). Sonst antwortet + # das DNAT-Ziel via seinem eigenen default-Gateway, das oft + # nicht zur EdgeGuard-Box zeigt → SYN_SENT + UNREPLIED. + # Trade-off: Backend sieht die Box-IP statt der echten + # client-IP (für Logging / Geo-Block: später optional via + # NAT-Rule-Flag preserve_client_ip). + ct status dnat masquerade +{{range .NATRules}}{{if eq .Kind "snat"}} # NAT {{.ID}} (snat{{if .Comment}} — {{.Comment}}{{end}}) - {{- if .OutIfaces}} oifname { {{join .OutIfaces ", "}} }{{end}} - {{- if .SrcCIDR}} ip saddr {{.SrcCIDR}}{{end}} - {{- if .TargetAddr}} snat to {{.TargetAddr}}{{end}} -{{- end}}{{if eq .Kind "masquerade"}} + {{""}} + {{if .OutIfaces}}oifname { {{join .OutIfaces ", "}} } {{end}}{{if .SrcCIDR}}ip saddr {{.SrcCIDR}} {{end}}{{if .TargetAddr}}snat to {{.TargetAddr}}{{end}} +{{end}}{{if eq .Kind "masquerade"}} # NAT {{.ID}} (masquerade{{if .Comment}} — {{.Comment}}{{end}}) - {{- if .OutIfaces}} oifname { {{join .OutIfaces ", "}} }{{end}} - {{- if .SrcCIDR}} ip saddr {{.SrcCIDR}}{{end}} masquerade -{{- end}}{{end}} + {{""}} + {{if .OutIfaces}}oifname { {{join .OutIfaces ", "}} } {{end}}{{if .SrcCIDR}}ip saddr {{.SrcCIDR}} {{end}}masquerade +{{end}}{{end}} } } diff --git a/management-ui/package.json b/management-ui/package.json index 16d187c..5ecb669 100644 --- a/management-ui/package.json +++ b/management-ui/package.json @@ -1,7 +1,7 @@ { "name": "edgeguard-management-ui", "private": true, - "version": "1.0.20", + "version": "1.0.25", "type": "module", "scripts": { "dev": "vite", diff --git a/management-ui/src/components/Layout/Sidebar.tsx b/management-ui/src/components/Layout/Sidebar.tsx index acc1578..41c4c39 100644 --- a/management-ui/src/components/Layout/Sidebar.tsx +++ b/management-ui/src/components/Layout/Sidebar.tsx @@ -70,7 +70,7 @@ const NAV: NavSection[] = [ }, ] -const VERSION = '1.0.20' +const VERSION = '1.0.25' export default function Sidebar({ isOpen, onClose }: SidebarProps) { const { t } = useTranslation() diff --git a/packaging/debian/edgeguard-api/DEBIAN/postinst b/packaging/debian/edgeguard-api/DEBIAN/postinst index 7ae88bb..c159bad 100755 --- a/packaging/debian/edgeguard-api/DEBIAN/postinst +++ b/packaging/debian/edgeguard-api/DEBIAN/postinst @@ -50,6 +50,80 @@ edgeguard ALL=(root) NOPASSWD: /usr/bin/wg show * SUDOERS chmod 0440 /etc/sudoers.d/edgeguard + # ── Sysctl-Profil für Edge-Gateway (NAT + HAProxy + Forwarding) ── + # Voraussetzung für NAT/DNAT/Masquerade + sinnvolle Defaults + # für eine high-throughput Forwarding-Box. Edit nicht von Hand + # — Re-install vom Package überschreibt die Datei. Eigene + # Tweaks gehören in eine Datei mit höherer Nummer als 99. + rm -f /etc/sysctl.d/99-edgeguard-forward.conf # Vorgänger + cat > /etc/sysctl.d/99-edgeguard.conf <<'SYSCTL' +# ── Managed by edgeguard ──────────────────────────────────────────── +# Lade-Reihenfolge: 99-* überschreibt distro-Defaults. Eigene +# Operator-Tweaks: /etc/sysctl.d/99-zzz-local.conf (lexikografisch +# später) — nicht in DIESE Datei! + +# ─── Forwarding (NAT/DNAT/Masquerade) ─────────────────────────────── +net.ipv4.ip_forward = 1 +net.ipv6.conf.all.forwarding = 1 +net.ipv4.conf.all.send_redirects = 0 +net.ipv4.conf.default.send_redirects = 0 +net.ipv4.conf.all.accept_redirects = 0 +net.ipv6.conf.all.accept_redirects = 0 +net.ipv4.conf.all.accept_source_route = 0 +net.ipv6.conf.all.accept_source_route = 0 + +# ─── Reverse-Path-Filter (anti-spoof, loose-Modus für asymmetrisches +# Routing wie Multi-WAN / WireGuard split) ───────────────────── +net.ipv4.conf.all.rp_filter = 2 +net.ipv4.conf.default.rp_filter = 2 + +# ─── Conntrack — Edge-Box trackt viele parallele Sessions ───────── +net.netfilter.nf_conntrack_max = 524288 +net.netfilter.nf_conntrack_tcp_timeout_established = 86400 +net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30 +net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30 +net.netfilter.nf_conntrack_buckets = 131072 + +# ─── TCP/IP-Stack-Tuning für HAProxy + viele Backends ───────────── +net.core.somaxconn = 65535 +net.core.netdev_max_backlog = 16384 +net.core.rmem_max = 16777216 +net.core.wmem_max = 16777216 +net.core.rmem_default = 262144 +net.core.wmem_default = 262144 +net.ipv4.tcp_rmem = 4096 87380 16777216 +net.ipv4.tcp_wmem = 4096 65536 16777216 +net.ipv4.tcp_max_syn_backlog = 65535 +net.ipv4.tcp_fin_timeout = 15 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.tcp_keepalive_time = 300 +net.ipv4.tcp_keepalive_probes = 5 +net.ipv4.tcp_keepalive_intvl = 30 +net.ipv4.tcp_mtu_probing = 1 +net.ipv4.tcp_slow_start_after_idle = 0 +net.ipv4.tcp_no_metrics_save = 1 +net.ipv4.ip_local_port_range = 10240 65535 + +# ─── Modern congestion control + queueing (BBR + fq) ────────────── +# Wenn der Kernel BBR nicht hat, fällt Linux still auf cubic zurück. +net.ipv4.tcp_congestion_control = bbr +net.core.default_qdisc = fq + +# ─── Anti-DoS / Hardening ───────────────────────────────────────── +net.ipv4.tcp_syncookies = 1 +net.ipv4.icmp_echo_ignore_broadcasts = 1 +net.ipv4.icmp_ignore_bogus_error_responses = 1 +net.ipv4.conf.all.log_martians = 1 +kernel.kptr_restrict = 2 +kernel.dmesg_restrict = 1 + +# ─── Memory ─────────────────────────────────────────────────────── +vm.swappiness = 10 +vm.dirty_ratio = 20 +vm.dirty_background_ratio = 5 +SYSCTL + sysctl --system >/dev/null 2>&1 || true + # ── Self-signed default cert so HAProxy starts cleanly ─────── # HAProxy `bind :443 ssl crt /etc/edgeguard/tls/` needs at least # one PEM in the directory to come up. Operator runs certbot