feat(routes): Static-Routes-Management + Live-View (Networks-Tab)

Migration 0019: static_routes (id, destination, gateway, dev, metric,
table_name, active, comment).

internal/services/staticroutes/:
  - CRUD-Repo
  - Generator schreibt /etc/edgeguard/routes.conf (pipe-format) und
    triggert `sudo systemctl restart edgeguard-routes.service`
  - LiveAll() ruft `ip -j route show table all` und parsed JSON

internal/handlers/routes.go:
  GET /api/v1/routes           — managed (DB)
  POST/PUT/DELETE              — CRUD (re-render + apply on mutate)
  GET /api/v1/routes/live      — kernel-state via ip(8)

postinst:
  - /usr/sbin/edgeguard-apply-routes (root-owned shell-script). Liest
    routes.conf, flusht `proto 250` (= edgeguard), setzt neue Routen
    mit proto 250. Andere Quellen (kernel/dhcp/manuell) bleiben
    unangetastet.
  - /etc/systemd/system/edgeguard-routes.service (Type=oneshot,
    After=network-online.target). Beim Boot automatisch via
    multi-user.target.
  - /etc/iproute2/rt_protos.d/edgeguard.conf — Symbol "edgeguard" =
    250 damit `ip route show proto edgeguard` funktioniert.
    (Debian 13 hat kein /etc/iproute2 default → .d-Pattern statt
    rt_protos-Anhängen.)
  - sudoers: edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl
    restart edgeguard-routes.service

UI: Networks-Page jetzt mit Tabs (Interfaces + Routen). Routes-Tab
hat zwei Cards:
  - Live-Routen (read-only, 30s refresh, `proto edgeguard` farblich
    hervorgehoben)
  - Verwaltete Routen (CRUD-Tabelle, Add/Edit-Modal mit destination/
    gateway/dev/metric/table/active/comment)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-12 23:50:26 +02:00
parent dbc14a24a4
commit b031725dfe
14 changed files with 1162 additions and 275 deletions

View File

@@ -93,6 +93,9 @@ edgeguard ALL=(root) NOPASSWD: /usr/bin/apt-get update
# Backup-Pfad: pg_dump als postgres-User. Whitelist exakt mit
# --clean --if-exists --no-owner --no-acl + dem festen DB-Namen.
edgeguard ALL=(postgres) NOPASSWD: /usr/bin/pg_dump --clean --if-exists --no-owner --no-acl edgeguard
# Static-Routes: API ruft `sudo systemctl restart edgeguard-routes.service`
# nach jedem Mutate, damit das apply-Skript die neue routes.conf anwendet.
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-routes.service
# Self-Upgrade-Pfad (handlers/system.go → /system/upgrade). Whitelist
# nur die exakte Unit-Form, damit edgeguard NICHT beliebige systemd-
# Units anlegen darf.
@@ -297,6 +300,78 @@ LOGROTATE
echo "postinst: ulogd2.service not installed — install ulogd2 to enable firewall log" >&2
fi
# ── Static-routes apply-script + systemd unit ────────────────
# Verwaltet aus /etc/edgeguard/routes.conf. `proto edgeguard`
# markiert die Routen damit das flush keine fremden Routen
# tötet (kernel/dhcp/static manuell gesetzt).
cat > /usr/sbin/edgeguard-apply-routes <<'APPLYROUTES'
#!/bin/bash
# Managed by edgeguard — DO NOT EDIT.
# Reads /etc/edgeguard/routes.conf (pipe-format) and applies via ip(8).
set -e
CONF=/etc/edgeguard/routes.conf
# Existierende edgeguard-Routen weg, bevor wir neue setzen. Andere
# Quellen (kernel/dhcp/manuell ohne proto) bleiben intakt.
ip route flush proto 250 2>/dev/null || true
[ -f "$CONF" ] || exit 0
while IFS='|' read -r dest gw dev metric table; do
[ -z "$dest" ] && continue
case "$dest" in '#'*) continue;; esac
args=("$dest")
[ -n "$gw" ] && args+=("via" "$gw")
[ -n "$dev" ] && args+=("dev" "$dev")
[ -n "$metric" ] && args+=("metric" "$metric")
if [ -n "$table" ] && [ "$table" != "main" ]; then
args+=("table" "$table")
fi
args+=("proto" "250")
if ! ip route add "${args[@]}"; then
echo "edgeguard-routes: failed to add: ip route add ${args[*]}" >&2
# weitermachen — eine fehlende Route soll nicht alle anderen
# blockieren.
fi
done < "$CONF"
APPLYROUTES
chmod 0755 /usr/sbin/edgeguard-apply-routes
chown root:root /usr/sbin/edgeguard-apply-routes
# rt_protos-Eintrag für `proto edgeguard` (Symbolname statt
# numerisch). Debian 13 hat /etc/iproute2 nicht als Default,
# also conf.d-Pattern: /etc/iproute2/rt_protos.d/edgeguard.conf
# überlagert die /usr/share/iproute2/rt_protos.
install -d /etc/iproute2/rt_protos.d
echo "250 edgeguard" > /etc/iproute2/rt_protos.d/edgeguard.conf
chmod 0644 /etc/iproute2/rt_protos.d/edgeguard.conf
cat > /etc/systemd/system/edgeguard-routes.service <<'ROUTESUNIT'
[Unit]
Description=EdgeGuard static-routes apply
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/edgeguard-apply-routes
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
ROUTESUNIT
systemctl daemon-reload
systemctl enable edgeguard-routes.service >/dev/null 2>&1 || true
# Initialer Apply — leere /etc/edgeguard/routes.conf ist ok
# (Skript exitet einfach ohne irgendwas zu tun).
systemctl start edgeguard-routes.service 2>/dev/null || true
# Initialer Stub damit `cat` im Skript nicht klagt
if [ ! -f /etc/edgeguard/routes.conf ]; then
: > /etc/edgeguard/routes.conf
chown "$EG_USER":"$EG_USER" /etc/edgeguard/routes.conf
fi
# ── 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