feat: HA-Cluster v1.2.x — Split-Brain, TOTP, Enterprise-FW, Drift-Fix, VIP-Recovery
- keepalived: pg_role='standby' hat Vorrang vor role für BACKUP-Bestimmung - keepalived-master.sh: gecrasht Dienste beim MASTER-Übergang starten (nicht nur reload) - confighash: ip_addresses per Interface-Name hashen statt per FK (Cross-Node-Drift-Fix) - TOTP/2FA: RFC 6238 — Setup-Flow, QR-Code, Admin-Disable; two-step Login - Firewall-UI: Enterprise-Design — auto-Beschreibung, icon-only Actions, zero-hit Indikator - fe80-Filter: Link-local IPv6 aus NTP/DNS Listen-Dropdowns entfernen - VIP-Dashboard, Dual-Path VRRP, GW-Tracking (Migrations 0033/0034) - Forward Proxy + DNS erweiterte Einstellungen (Migrations 0031/0032) - unbound-control: edgeguard in unbound-Gruppe via postinst Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ Description: EdgeGuard — native Reverse-Proxy / LB / Forward-Proxy / VPN / Fir
|
||||
PG Streaming Replication + provider Floating-IP for HTTP ingress).
|
||||
.
|
||||
This package ships the management API, scheduler and CLI.
|
||||
Depends: postgresql-16 | postgresql-17, haproxy (>= 2.8), squid, wireguard-tools, unbound, chrony, nftables, certbot, openssl, sudo, adduser, systemd, ca-certificates, ulogd2, ulogd2-json, iputils-ping, traceroute, dnsutils, curl, netcat-openbsd
|
||||
Depends: postgresql-16 | postgresql-17, haproxy (>= 2.8), squid, wireguard-tools, unbound, chrony, nftables, keepalived, certbot, openssl, sudo, adduser, systemd, ca-certificates, ulogd2, ulogd2-json, iputils-ping, traceroute, dnsutils, curl, netcat-openbsd
|
||||
Recommends: edgeguard-keydb (>= 6.3.4-edgeguard1), apparmor, fail2ban
|
||||
Section: admin
|
||||
Priority: optional
|
||||
|
||||
@@ -25,6 +25,12 @@ case "$1" in
|
||||
if getent group haproxy >/dev/null; then
|
||||
usermod -a -G haproxy "$EG_USER" || true
|
||||
fi
|
||||
# unbound-Gruppe: unbound-control-Socket ist root:unbound srw-rw----
|
||||
# → edgeguard muss in der Gruppe sein um stats_noreset + flush aufrufen
|
||||
# zu können (kein sudo nötig, Gruppe reicht).
|
||||
if getent group unbound >/dev/null; then
|
||||
usermod -a -G unbound "$EG_USER" || true
|
||||
fi
|
||||
# systemd-journal + adm: damit edgeguard-api `journalctl -u …`
|
||||
# ohne sudo lesen kann — wird für /api/v1/logs gebraucht
|
||||
# (zentrale Log-Übersicht über alle Services).
|
||||
@@ -90,9 +96,13 @@ edgeguard ALL=(root) NOPASSWD: /usr/sbin/nft list table inet edgeguard
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl start wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl start wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl stop wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl enable wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl disable wg-quick@*.service
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/wg show all dump
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/wg show *
|
||||
# WireGuard symlink: /etc/wireguard/ ist root:root 700; edgeguard-api
|
||||
@@ -139,6 +149,8 @@ edgeguard ALL=(root) NOPASSWD: /usr/bin/systemd-run --unit=edgeguard-restore.ser
|
||||
# Keepalived reload: VIP-Settings-Änderung triggert keepalived-Reload.
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload-or-restart keepalived.service
|
||||
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload-or-restart keepalived.service
|
||||
# VIP-Schwenk-Test: dediziertes Script mit interner Input-Validierung.
|
||||
edgeguard ALL=(root) NOPASSWD: /usr/lib/edgeguard/vip-cmd.sh
|
||||
SUDOERS
|
||||
|
||||
# ── Distro-Conf-Includes für die per-Service Renderer ─────────
|
||||
@@ -422,6 +434,7 @@ create_iface() {
|
||||
local typ="$1" name="$2" parent="$3" vlan_id="$4" mtu="$5" members="$6"
|
||||
case "$typ" in
|
||||
vlan)
|
||||
ip link set "$parent" up 2>/dev/null || true
|
||||
ip link add link "$parent" name "$name" type vlan id "$vlan_id" || return 1
|
||||
;;
|
||||
bridge)
|
||||
@@ -464,13 +477,31 @@ fi
|
||||
while IFS='|' read -r typ name parent vlan_id mtu members; do
|
||||
[ -z "$typ" ] && continue
|
||||
case "$typ" in '#'*) continue;; esac
|
||||
if ! ip link show "$name" >/dev/null 2>&1; then
|
||||
if ip link show "$name" >/dev/null 2>&1; then
|
||||
# Typ-Änderung: altes Interface löschen und neu erstellen
|
||||
existing_type=$(ip -d link show "$name" 2>/dev/null | awk '/^[[:space:]]/{print $1; exit}')
|
||||
case "$typ" in
|
||||
vlan) expected="vlan" ;;
|
||||
bridge) expected="bridge_slave" ;; # bridge selbst hat keinen eigenen type-String
|
||||
bond) expected="bond_slave" ;;
|
||||
*) expected="" ;;
|
||||
esac
|
||||
current_type=$(ip -d link show "$name" 2>/dev/null | grep -oE 'vlan |bridge |bond ' | head -1 | tr -d ' ')
|
||||
if [ -n "$current_type" ] && [ "$current_type" != "$typ" ]; then
|
||||
ip link set "$name" down 2>/dev/null || true
|
||||
ip link del "$name" 2>/dev/null || true
|
||||
if ! create_iface "$typ" "$name" "$parent" "$vlan_id" "$mtu" "$members"; then
|
||||
echo "edgeguard-interfaces: failed to recreate $typ $name" >&2
|
||||
fi
|
||||
else
|
||||
[ -n "$mtu" ] && ip link set "$name" mtu "$mtu" 2>/dev/null || true
|
||||
[ "$typ" = "vlan" ] && [ -n "$parent" ] && ip link set "$parent" up 2>/dev/null || true
|
||||
ip link set "$name" up 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
if ! create_iface "$typ" "$name" "$parent" "$vlan_id" "$mtu" "$members"; then
|
||||
echo "edgeguard-interfaces: failed to create $typ $name" >&2
|
||||
fi
|
||||
else
|
||||
[ -n "$mtu" ] && ip link set "$name" mtu "$mtu" 2>/dev/null || true
|
||||
ip link set "$name" up 2>/dev/null || true
|
||||
fi
|
||||
done < "$CONF"
|
||||
|
||||
@@ -574,12 +605,40 @@ IPADDRUNIT
|
||||
# werden von Keepalived als notify_master / notify_backup / check
|
||||
# aufgerufen. Kein Auto-Promote — keepalived-master.sh loggt nur.
|
||||
install -d -m 0755 /usr/lib/edgeguard
|
||||
for script in keepalived-check.sh keepalived-master.sh keepalived-backup.sh; do
|
||||
for script in keepalived-check.sh keepalived-master.sh keepalived-backup.sh keepalived-gw-check.sh; do
|
||||
if [ -f "/usr/lib/edgeguard/${script}" ]; then
|
||||
chmod 0755 "/usr/lib/edgeguard/${script}"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── VIP-Schwenk-Script ───────────────────────────────────────
|
||||
# Führt `ip addr add/del` für VIP-Failover-Tests aus. Läuft via
|
||||
# sudo (Whitelist in sudoers). Interne Input-Validierung verhindert
|
||||
# Command-Injection trotz breiter sudoers-Regel.
|
||||
cat > /usr/lib/edgeguard/vip-cmd.sh <<'VIPCMD'
|
||||
#!/bin/bash
|
||||
# vip-cmd.sh {add|del} {address/prefix} {device}
|
||||
# Wird von edgeguard-api via sudo für VIP-Schwenk-Tests aufgerufen.
|
||||
# Nach `ip addr add` werden squid/unbound/haproxy reloaded damit sie
|
||||
# die neu aktive VIP sofort binden.
|
||||
set -e
|
||||
action="$1" addrpfx="$2" dev="$3"
|
||||
case "$action" in
|
||||
add|del) ;;
|
||||
*) echo "vip-cmd: ungültige action '$action'" >&2; exit 1 ;;
|
||||
esac
|
||||
[[ -z "$addrpfx" || -z "$dev" ]] && { echo "vip-cmd: fehlende Parameter" >&2; exit 1; }
|
||||
[[ "$addrpfx" =~ ^[0-9a-fA-F.:\/]+$ ]] || { echo "vip-cmd: ungültige Adresse '$addrpfx'" >&2; exit 1; }
|
||||
[[ "$dev" =~ ^[a-zA-Z0-9._-]+$ ]] || { echo "vip-cmd: ungültiges Device '$dev'" >&2; exit 1; }
|
||||
ip addr "$action" "$addrpfx" dev "$dev"
|
||||
if [ "$action" = "add" ]; then
|
||||
for svc in squid.service unbound.service haproxy.service; do
|
||||
systemctl is-active --quiet "$svc" && systemctl reload "$svc" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
VIPCMD
|
||||
chmod 0755 /usr/lib/edgeguard/vip-cmd.sh
|
||||
|
||||
# ── 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
|
||||
@@ -625,6 +684,25 @@ IPADDRUNIT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ALTER PUBLICATION erfordert den PG-Superuser (edgeguard ist nicht
|
||||
# Owner der Publication). Idempotent — No-Op auf Secondary-Nodes
|
||||
# (wo die Publication nicht existiert) und wenn die Tabellen schon
|
||||
# drin sind.
|
||||
sudo -u postgres psql edgeguard <<'EOSQL' 2>/dev/null || true
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'edgeguard_shared') THEN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_publication_tables
|
||||
WHERE pubname = 'edgeguard_shared' AND tablename = 'network_interfaces'
|
||||
) THEN
|
||||
ALTER PUBLICATION edgeguard_shared ADD TABLE network_interfaces, ip_addresses;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
EOSQL
|
||||
|
||||
# ── Render initial service configs ───────────────────────────
|
||||
# Writes /etc/edgeguard/haproxy/haproxy.cfg + nftables.d/
|
||||
# ruleset.nft from the (just-migrated, empty) PG state.
|
||||
@@ -641,6 +719,19 @@ IPADDRUNIT
|
||||
echo "postinst: edgeguard-ctl render-config (nftables) failed — aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Keepalived-Config rendern (optional — nur wenn VIPs konfiguriert)
|
||||
# /etc/keepalived/ wird vom keepalived-Paket als root angelegt;
|
||||
# edgeguard braucht Schreibrecht damit der Config-Generator den
|
||||
# atomic-write (tempfile → rename) durchführen kann.
|
||||
install -d -m 0755 /etc/keepalived
|
||||
chown "$EG_USER":"$EG_USER" /etc/keepalived
|
||||
sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl render-config --only=keepalived || true
|
||||
if [ -f /etc/keepalived/keepalived.conf ]; then
|
||||
systemctl enable keepalived >/dev/null 2>&1 || true
|
||||
systemctl is-active --quiet keepalived \
|
||||
&& systemctl reload keepalived \
|
||||
|| systemctl start keepalived || true
|
||||
fi
|
||||
|
||||
# ── HAProxy systemd drop-in: read EdgeGuard config ───────────
|
||||
# Keeps the distro /etc/haproxy/haproxy.cfg untouched (it's a
|
||||
@@ -665,6 +756,15 @@ IPADDRUNIT
|
||||
# restart. Bei Erst-Install ist nichts running, dann ist das
|
||||
# ein normaler Start.
|
||||
systemctl restart edgeguard-api.service edgeguard-scheduler.service || true
|
||||
|
||||
# WireGuard-Sicherung: enabled-aber-inaktive wg-quick@-Interfaces
|
||||
# nach dem API-Restart starten. Der API-Renderer kann wg-quick@
|
||||
# kurz flippen; falls er dabei fehlschlägt, stellt dieser Block
|
||||
# das Interface wieder her ohne manuellen Eingriff.
|
||||
while IFS= read -r wg_unit; do
|
||||
[ -n "$wg_unit" ] || continue
|
||||
systemctl is-active --quiet "$wg_unit" || systemctl start "$wg_unit" 2>/dev/null || true
|
||||
done < <(systemctl list-unit-files 'wg-quick@*.service' --state=enabled --no-legend 2>/dev/null | awk '{print $1}')
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
|
||||
Reference in New Issue
Block a user