fix(wg+fw): Peer-Sync-Bug via sudo-Symlink + Site-to-Site-Masquerade
- WireGuard-Peer-Änderungen landeten nicht im laufenden Interface: /etc/wireguard/ ist root:root 700, os.Readlink schlug fehl → ensureWGQuickSymlink fiel immer in den Error-Pfad. Fix: Symlink via sudo /bin/ln -sf (sudoers-Entry in postinst ergänzt). - Site-to-Site-Masquerade: Roadwarrior-Clients (z. B. 192.168.99.3) konnten LANs hinter anderen Peers nicht erreichen, weil das remote Gateway die VPN-Client-IP nicht als Tunnel-Route kannte. Fix: auto masquerade in nftables postrouting_nat pro WireGuard-Server-Interface (oifname "wg7" ip saddr 192.168.99.0/24 masquerade). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,3 +33,16 @@ func stopWGQuick(iface string) error {
|
||||
_ = cmd.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
// symlinkWGQuickConf creates (or atomically replaces) the symlink
|
||||
// /etc/wireguard/<iface>.conf → target via sudo. /etc/wireguard/ is
|
||||
// owned root:root 700 so the edgeguard user cannot write to it directly;
|
||||
// the sudoers entry in postinst whitelists exactly this ln command.
|
||||
func symlinkWGQuickConf(iface, target string) error {
|
||||
link := "/etc/wireguard/" + iface + ".conf"
|
||||
cmd := exec.Command("sudo", "-n", "/bin/ln", "-sf", target, link)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("ln -sf %s %s: %w: %s", target, link, err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,11 +157,10 @@ 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 {
|
||||
// /etc/edgeguard/wireguard/ und symlinken via sudo — /etc/wireguard/
|
||||
// ist root:root 700, daher braucht es sudo /bin/ln. Das sudoers-Entry
|
||||
// wird von postinst angelegt.
|
||||
if err := symlinkWGQuickConf(ifc.Name, path); err != nil {
|
||||
return fmt.Errorf("symlink: %w", err)
|
||||
}
|
||||
if existing, err := os.ReadFile(path); err == nil && bytes.Equal(existing, body.Bytes()) {
|
||||
@@ -173,19 +172,3 @@ 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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user