fix(firewall+wg): Operator-Rule landete als Comment + wg-quick las falsche Conf
Zwei show-stopper beim Cutover .101 → .6 entdeckt + behoben:
1. nft-Template-Bug: {{- if ...}}-Whitespace-Trimmer nach der
'# rule N' Kommentarzeile schluckte den Newline → die ganze
Operator-Rule landete als Teil des # Kommentars. nft akzeptierte
die Datei (legaler Comment) und der Operator sah keine Wirkung.
Fix: Body auf eigener Zeile via {{""}}-Padding, Trimmer raus.
2. wg-Renderer schrieb /etc/edgeguard/wireguard/<iface>.conf, aber
wg-quick@<iface>.service liest /etc/wireguard/<iface>.conf
(Distro-Default). Die zwei Files driftet auseinander — beim
Restart sah wg-quick die alte AllowedIPs. Fix: Renderer legt
einen Symlink /etc/wireguard/<iface>.conf → /etc/edgeguard/...
beim Render an (idempotent, ersetzt vorhandene Real-Files).
Beide Fixes waren voraussetzung für den .101 → .6 Cutover, der
jetzt sauber läuft: VIP .100 lebt auf .6, Unify Home dial't durch
zu wg7 (handshake), 10.0.10.x via wg7-Tunnel reachable.
Version 1.0.18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,7 @@ import (
|
||||
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
||||
)
|
||||
|
||||
var version = "1.0.16"
|
||||
var version = "1.0.18"
|
||||
|
||||
func main() {
|
||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var version = "1.0.16"
|
||||
var version = "1.0.18"
|
||||
|
||||
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||
)
|
||||
|
||||
var version = "1.0.16"
|
||||
var version = "1.0.18"
|
||||
|
||||
const (
|
||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||
|
||||
@@ -48,20 +48,14 @@ table inet edgeguard {
|
||||
tcp dport 8443 ip6 saddr @peer_ipv6 accept
|
||||
|
||||
# ── Operator-defined rules ──
|
||||
{{- range .Legs}}
|
||||
{{range .Legs}}
|
||||
# rule {{.RuleID}}{{if .Name}} ({{.Name}}){{end}}{{if .Comment}} — {{.Comment}}{{end}}
|
||||
{{- if .SrcIfaces}} iifname { {{join .SrcIfaces ", "}} }{{end -}}
|
||||
{{- if .DstIfaces}} oifname { {{join .DstIfaces ", "}} }{{end -}}
|
||||
{{- if .SrcAddrs}} ip saddr { {{join .SrcAddrs ", "}} }{{end -}}
|
||||
{{- if .DstAddrs}} ip daddr { {{join .DstAddrs ", "}} }{{end -}}
|
||||
{{- with .Service -}}
|
||||
{{- if and (or (eq .Proto "tcp") (eq .Proto "udp")) .PortStart}} {{.Proto}} dport {{.PortStart}}{{if and .PortEnd (ne .PortEnd .PortStart)}}-{{.PortEnd}}{{end}}
|
||||
{{- else if eq .Proto "icmp"}} ip protocol icmp
|
||||
{{- else if eq .Proto "icmpv6"}} ip6 nexthdr icmpv6
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if .Log}} log prefix "edgeguard:{{.RuleID}} "{{end}} {{.Action}}
|
||||
{{- end}}
|
||||
{{- /* Body MUSS auf EIGENER Zeile starten (nicht via {{- }} an
|
||||
die Comment-Zeile angehängt — sonst frisst nft die rule
|
||||
als Teil des # Kommentars). */ -}}
|
||||
{{""}}
|
||||
{{if .SrcIfaces}}iifname { {{join .SrcIfaces ", "}} } {{end}}{{if .DstIfaces}}oifname { {{join .DstIfaces ", "}} } {{end}}{{if .SrcAddrs}}ip saddr { {{join .SrcAddrs ", "}} } {{end}}{{if .DstAddrs}}ip daddr { {{join .DstAddrs ", "}} } {{end}}{{with .Service}}{{if and (or (eq .Proto "tcp") (eq .Proto "udp")) .PortStart}}{{.Proto}} dport {{.PortStart}}{{if and .PortEnd (ne .PortEnd .PortStart)}}-{{.PortEnd}}{{end}} {{else if eq .Proto "icmp"}}ip protocol icmp {{else if eq .Proto "icmpv6"}}ip6 nexthdr icmpv6 {{end}}{{end}}{{if .Log}}log prefix "edgeguard:{{.RuleID}} " {{end}}{{.Action}}
|
||||
{{end}}
|
||||
}
|
||||
|
||||
chain forward {
|
||||
|
||||
@@ -155,6 +155,15 @@ func (g *Generator) renderIface(ctx context.Context, ifc models.WireguardInterfa
|
||||
}
|
||||
|
||||
path := filepath.Join(ConfDir, ifc.Name+".conf")
|
||||
// wg-quick@<iface>.service liest /etc/wireguard/<iface>.conf (Distro-
|
||||
// Default), nicht unseren ConfDir. Wir lassen die Quelle of truth in
|
||||
// /etc/edgeguard/wireguard/ und symlinken einmalig — sonst lesen
|
||||
// wg-quick und unser Renderer aus zwei verschiedenen Files und
|
||||
// driften auseinander (gefangen 2026-05-10 als wg-quick beim restart
|
||||
// noch alte AllowedIPs aus /etc/wireguard/wg7.conf gelesen hat).
|
||||
if err := ensureWGQuickSymlink(ifc.Name, path); err != nil {
|
||||
return fmt.Errorf("symlink: %w", err)
|
||||
}
|
||||
if existing, err := os.ReadFile(path); err == nil && bytes.Equal(existing, body.Bytes()) {
|
||||
return startWGQuick(ifc.Name)
|
||||
}
|
||||
@@ -163,3 +172,20 @@ func (g *Generator) renderIface(ctx context.Context, ifc models.WireguardInterfa
|
||||
}
|
||||
return restartWGQuick(ifc.Name)
|
||||
}
|
||||
|
||||
// ensureWGQuickSymlink puts /etc/wireguard/<iface>.conf as a symlink
|
||||
// pointing at our managed file in /etc/edgeguard/wireguard/. Idempotent
|
||||
// — if the symlink already targets the right path we no-op; if the
|
||||
// distro path holds a real (legacy) file we replace it.
|
||||
func ensureWGQuickSymlink(iface, target string) error {
|
||||
wgDir := "/etc/wireguard"
|
||||
if err := os.MkdirAll(wgDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
link := filepath.Join(wgDir, iface+".conf")
|
||||
if cur, err := os.Readlink(link); err == nil && cur == target {
|
||||
return nil
|
||||
}
|
||||
_ = os.Remove(link)
|
||||
return os.Symlink(target, link)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "edgeguard-management-ui",
|
||||
"private": true,
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.18",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -70,7 +70,7 @@ const NAV: NavSection[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const VERSION = '1.0.16'
|
||||
const VERSION = '1.0.18'
|
||||
|
||||
export default function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
Reference in New Issue
Block a user