Implements the from-scratch multi-server WireGuard management fork per CLAUDE.md spec: sqlite schema (servers/peers/audit_log/users), Curve25519 key generation, per-interface config rendering + wg-quick/systemd control, nftables hook scaffolding, session+CSRF-protected REST API with QR code and config download endpoints, a minimal vanilla-JS web UI, legacy wg0.conf migration, and both a native installer and a Proxmox LXC provisioning script (with auto-detected latest Debian template). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package firewall
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/server"
|
|
)
|
|
|
|
// HooksDir holds optional user-defined shell scripts run around lifecycle events.
|
|
var HooksDir = "/etc/wireguard-manager/hooks"
|
|
|
|
// HookEvent names the lifecycle points a hook script may exist for.
|
|
type HookEvent string
|
|
|
|
const (
|
|
HookServerStart HookEvent = "server-start"
|
|
HookServerStop HookEvent = "server-stop"
|
|
HookPeerAdd HookEvent = "peer-add"
|
|
HookPeerRemove HookEvent = "peer-remove"
|
|
)
|
|
|
|
// RunHook executes /etc/wireguard-manager/hooks/<event> if present and executable,
|
|
// passing iface (and optionally peer pubkey) as arguments. Missing hook is not an error.
|
|
func RunHook(event HookEvent, args ...string) error {
|
|
path := filepath.Join(HooksDir, string(event))
|
|
if _, err := os.Stat(path); err != nil {
|
|
return nil // hook not installed, skip silently
|
|
}
|
|
cmd := exec.Command(path, args...)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("hook %s: %w: %s", event, err, out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NFTRuleset renders a suggested nftables ruleset snippet for a server, allowing
|
|
// its UDP listen port in and forwarding traffic between the tunnel and lanIface.
|
|
func NFTRuleset(srv *server.Server, lanIface string) string {
|
|
return fmt.Sprintf(`table inet wireguard_%s {
|
|
chain input {
|
|
type filter hook input priority 0; policy accept;
|
|
udp dport %d accept
|
|
}
|
|
chain forward {
|
|
type filter hook forward priority 0; policy accept;
|
|
iifname "%s" oifname "%s" accept
|
|
iifname "%s" oifname "%s" accept
|
|
}
|
|
}
|
|
`, srv.InterfaceName, srv.ListenPort, srv.InterfaceName, lanIface, lanIface, srv.InterfaceName)
|
|
}
|
|
|
|
// ApplyRuleset writes the ruleset to a temp file and loads it with `nft -f`.
|
|
func ApplyRuleset(srv *server.Server, lanIface string) error {
|
|
tmp, err := os.CreateTemp("", "wgm-nft-*.conf")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.Remove(tmp.Name())
|
|
|
|
if _, err := tmp.WriteString(NFTRuleset(srv, lanIface)); err != nil {
|
|
tmp.Close()
|
|
return err
|
|
}
|
|
tmp.Close()
|
|
|
|
cmd := exec.Command("nft", "-f", tmp.Name())
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("nft -f: %w: %s", err, out)
|
|
}
|
|
return nil
|
|
}
|