Zwei Bugs, die WireGuard-Verbindungen verhinderten/abrissen: 1) Client-Config-Endpoint war hartkodierter Platzhalter REPLACE_WITH_PUBLIC_HOST → neue Clients bauten nie einen Tunnel auf (Host löst nicht auf). Jetzt: WireguardHandler.PublicHost (aus setup.json FQDN, main.go) → Endpoint = <fqdn>:<port>. Platzhalter nur noch als Fallback wenn FQDN unbekannt. 2) Renderer machte bei JEDER Config-Änderung 'systemctl restart wg-quick@<iface>' → voller Link-Flap, alle Peers droppen (verstößt gegen 'wireguard darf nie abbrechen'). Jetzt: laufendes Interface → 'wg-quick strip | wg syncconf' (Peers/Listen-Port live, KEIN Abbruch); nur erstmaliges Hochfahren via systemctl start; restart nur noch als Fallback mit WARN. interfaceExists() via 'ip link show'. Neue sudoers: wg syncconf *, wg-quick strip *. Ein edgeguard-api-Restart (Deploy) fasst wg-quick@<iface> nicht an → Tunnel bleibt während Deploy bestehen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
930 lines
46 KiB
Bash
Executable File
930 lines
46 KiB
Bash
Executable File
#!/bin/bash
|
|
# postinst for edgeguard-api — creates system user, filesystem layout,
|
|
# initialises PostgreSQL (role + db + migrations), enables systemd
|
|
# units. Each step idempotent; safe to re-run on every upgrade.
|
|
set -e
|
|
|
|
export LC_ALL=C
|
|
export LANG=C
|
|
|
|
EG_USER="edgeguard"
|
|
EG_HOME="/var/lib/edgeguard"
|
|
|
|
case "$1" in
|
|
configure)
|
|
# ── System user ──────────────────────────────────────────────
|
|
if ! getent passwd "$EG_USER" >/dev/null; then
|
|
adduser --system --group --home "$EG_HOME" \
|
|
--shell /usr/sbin/nologin --no-create-home \
|
|
--gecos "EdgeGuard daemon" "$EG_USER"
|
|
fi
|
|
# haproxy admin.sock liegt mit mode 0660 root:haproxy. Damit
|
|
# edgeguard-api das Backend-Stat-CSV lesen kann (Dashboard),
|
|
# in die haproxy-group adden — best-effort, nicht failen wenn
|
|
# das Paket noch nicht installiert ist.
|
|
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
|
|
# freeradius: edgeguard muss die gerenderten clients.conf/authorize
|
|
# ins freeradius-lesbare Verzeichnis schreiben (setgid-Dir unten).
|
|
if getent group freeradius >/dev/null; then
|
|
usermod -a -G freeradius "$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).
|
|
if getent group systemd-journal >/dev/null; then
|
|
usermod -a -G systemd-journal "$EG_USER" || true
|
|
fi
|
|
if getent group adm >/dev/null; then
|
|
usermod -a -G adm "$EG_USER" || true
|
|
fi
|
|
|
|
# ── Directories ──────────────────────────────────────────────
|
|
# /etc/edgeguard und Service-Subdirs müssen für die Service-User
|
|
# (squid, unbound, haproxy laufen NICHT als edgeguard) traversier-
|
|
# bzw lesbar sein. 0755 statt 0750 — kein Geheimnis ist hier
|
|
# gespeichert, alles sind Renderer-Outputs.
|
|
for d in /etc/edgeguard /etc/edgeguard/haproxy /etc/edgeguard/squid \
|
|
/etc/edgeguard/wireguard /etc/edgeguard/unbound \
|
|
/etc/edgeguard/kea \
|
|
/etc/edgeguard/nftables.d; do
|
|
install -d -m 0755 -o "$EG_USER" -g "$EG_USER" "$d"
|
|
done
|
|
# Sensitive Verzeichnisse bleiben 0750 (TLS-Keys, ACME-State).
|
|
for d in /etc/edgeguard/tls /var/lib/edgeguard /var/log/edgeguard \
|
|
/var/lib/edgeguard/acme; do
|
|
install -d -m 0750 -o "$EG_USER" -g "$EG_USER" "$d"
|
|
done
|
|
# ACME-Account-Dir 0700 — hält den lego-Account-Schlüssel,
|
|
# gehört nur edgeguard.
|
|
install -d -m 0700 -o "$EG_USER" -g "$EG_USER" /var/lib/edgeguard/acme-account
|
|
|
|
# Cluster-TLS-Dir 0700 — Cluster-CA-Key + peer.key sind sehr
|
|
# sensitiv (CA-Compromise = vollständiger Cluster-Bruch). Wird
|
|
# beim API-Boot mit Material gefüllt; postinst legt nur den
|
|
# Dir an damit der API-User schreiben kann.
|
|
install -d -m 0700 -o "$EG_USER" -g "$EG_USER" /var/lib/edgeguard/cluster-tls
|
|
|
|
# Backup-Verzeichnis: edgeguard-owned damit der scheduler /
|
|
# API-Prozess schreiben kann. Mode 0750 — Backups enthalten
|
|
# einen pg_dump der edgeguard-DB + license_key, also nicht
|
|
# world-readable.
|
|
install -d -m 0750 -o "$EG_USER" -g "$EG_USER" /var/backups/edgeguard
|
|
|
|
# node.conf — node-lokale Cluster-Identität (NIEMALS in Sync
|
|
# zwischen Peers). API auto-befüllt das beim ersten Boot mit
|
|
# NODE_ID + Hostname + erster non-loopback-IPv4. Operator
|
|
# darf danach editieren (z.B. MGMT_IP korrigieren wenn Box
|
|
# mehrere Interfaces hat).
|
|
if [ ! -f /etc/edgeguard/node.conf ]; then
|
|
: > /etc/edgeguard/node.conf
|
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/node.conf
|
|
chmod 0644 /etc/edgeguard/node.conf
|
|
fi
|
|
|
|
# ── sudoers: HAProxy reload + (later) systemd-networkd reload
|
|
# Damit edgeguard-api nach einer SSL- oder Netzwerk-Mutation
|
|
# selbst reloaden kann ohne root zu sein. NOPASSWD ist auf
|
|
# genau dieses Kommando beschränkt.
|
|
cat > /etc/sudoers.d/edgeguard <<'SUDOERS'
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload haproxy.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload haproxy.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/sbin/nft -f /etc/edgeguard/nftables.d/ruleset.nft
|
|
edgeguard ALL=(root) NOPASSWD: /usr/sbin/nft list tables
|
|
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 Live-Reload ohne Tunnel-Abbruch: wg syncconf + wg-quick strip
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/wg syncconf *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/wg-quick strip *
|
|
# WireGuard symlink: /etc/wireguard/ ist root:root 700; edgeguard-api
|
|
# legt Symlinks an damit wg-quick@<iface> die Configs findet.
|
|
edgeguard ALL=(root) NOPASSWD: /bin/ln -sf /etc/edgeguard/wireguard/* /etc/wireguard/*
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload squid.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload squid.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart unbound.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reload chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl reload chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart chrony.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart chrony.service
|
|
# DHCP (Kea): Service-Lifecycle an dhcp_settings.enabled gekoppelt.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl stop kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl enable kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable kea-dhcp4-server.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl disable kea-dhcp4-server.service
|
|
# RADIUS (FreeRADIUS): Service-Lifecycle an radius_settings.enabled gekoppelt.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl restart freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl stop freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl enable freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /bin/systemctl disable freeradius.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/apt-get update -qq
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/apt-get update
|
|
# Auto-Update-Toggle (Settings → Automatische Updates) schreibt/entfernt
|
|
# exakt diese Conf-Datei. Restrict auf den genauen Pfad damit edgeguard
|
|
# NICHT beliebig in /etc/apt/apt.conf.d schreiben darf.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/tee /etc/apt/apt.conf.d/52edgeguard-auto-updates
|
|
edgeguard ALL=(root) NOPASSWD: /bin/rm -f /etc/apt/apt.conf.d/52edgeguard-auto-updates
|
|
# 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
|
|
# Network-Interfaces: API ruft `sudo systemctl restart edgeguard-interfaces.service`
|
|
# nach jedem Mutate (Create/Update/Delete) von VLAN/bridge/bond-Interfaces.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-interfaces.service
|
|
# IP-Addresses: API ruft `sudo systemctl restart edgeguard-ipaddresses.service`
|
|
# nach jedem Mutate (Create/Update/Delete).
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-ipaddresses.service
|
|
# Self-Upgrade-Pfad (handlers/system.go → /system/upgrade). Whitelist
|
|
# nur die exakte Unit-Form, damit edgeguard NICHT beliebige systemd-
|
|
# Units anlegen darf.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reset-failed edgeguard-upgrade.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemd-run --unit=edgeguard-upgrade.service --description=EdgeGuard self-upgrade --collect bash /var/lib/edgeguard/upgrade.sh
|
|
# Backup-Restore: gleiche Pattern wie Upgrade — Skript landet immer
|
|
# unter /var/lib/edgeguard/restore.sh, Unit-Form ist fix.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reset-failed edgeguard-restore.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemd-run --unit=edgeguard-restore.service --description=EdgeGuard self-restore --collect bash /var/lib/edgeguard/restore.sh
|
|
# Replication-Repair: Resync der Logical-Replication-Subscription vom
|
|
# Primary (Config-Drift-Banner → "Resync erzwingen"). Skript landet immer
|
|
# unter /var/lib/edgeguard/repair-replication.sh, Unit-Form ist fix.
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl reset-failed edgeguard-repair-replication.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemd-run --unit=edgeguard-repair-replication.service --description=EdgeGuard replication repair --collect bash /var/lib/edgeguard/repair-replication.sh
|
|
# 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
|
|
# CrowdSec management
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli decisions list -o json
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli decisions add *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli decisions delete *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli alerts list *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli alerts delete *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli bouncers list -o json
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli bouncers delete *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli machines list -o json
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli machines delete *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli collections list -o json
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli collections install *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli collections remove *
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/cscli version
|
|
# WAF agent service control
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl start edgeguard-waf.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop edgeguard-waf.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable edgeguard-waf.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable edgeguard-waf.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-waf.service
|
|
# CrowdSec service toggle (start/stop/enable/disable)
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl start crowdsec.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop crowdsec.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable crowdsec.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable crowdsec.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl start crowdsec-firewall-bouncer.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl stop crowdsec-firewall-bouncer.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl enable crowdsec-firewall-bouncer.service
|
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl disable crowdsec-firewall-bouncer.service
|
|
SUDOERS
|
|
|
|
# ── Distro-Conf-Includes für die per-Service Renderer ─────────
|
|
# Squid + Unbound lesen ihre Distro-Default-Conf, die wir per
|
|
# Symlink/Drop-in auf unsere managed conf zeigen lassen müssen.
|
|
# Renderer können das nicht selbst (kein Schreibrecht in /etc/squid
|
|
# bzw. /etc/unbound/unbound.conf.d/), daher hier einmalig.
|
|
# Plus: Squid + Unbound laufen als eigene User (squid/unbound),
|
|
# nicht edgeguard. Damit sie unsere Conf lesen können, müssen
|
|
# die Conf-Dirs world-readable sein (Configs ohne Secrets).
|
|
install -d -m 0755 -o "$EG_USER" -g "$EG_USER" /etc/edgeguard/squid /etc/edgeguard/unbound
|
|
# Squid: ersetzt die Distro-Datei durch Symlink (Backup .distro-bak)
|
|
if [ -f /etc/squid/squid.conf ] && [ ! -L /etc/squid/squid.conf ]; then
|
|
mv /etc/squid/squid.conf /etc/squid/squid.conf.distro-bak
|
|
fi
|
|
ln -sfn /etc/edgeguard/squid/squid.conf /etc/squid/squid.conf
|
|
# Unbound: Drop-in im conf.d-Verzeichnis. Wir schreiben direkt
|
|
# rein (statt /etc/edgeguard/unbound/...) weil das AppArmor-
|
|
# Profil unbound nur /etc/unbound erlaubt. Datei gehört dem
|
|
# edgeguard-User damit der Renderer sie überschreiben kann.
|
|
install -d /etc/unbound/unbound.conf.d
|
|
# Vorgänger-Symlink (aus früheren Versionen) wegräumen.
|
|
if [ -L /etc/unbound/unbound.conf.d/edgeguard.conf ]; then
|
|
rm /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
fi
|
|
if [ ! -f /etc/unbound/unbound.conf.d/edgeguard.conf ]; then
|
|
: > /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
fi
|
|
chown "$EG_USER":"$EG_USER" /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
chmod 0644 /etc/unbound/unbound.conf.d/edgeguard.conf
|
|
|
|
# Chrony: gleicher Pattern wie Unbound — Drop-in im conf.d, der
|
|
# vom distro-default chrony.conf included wird. Datei gehört
|
|
# edgeguard damit der Renderer sie überschreiben kann.
|
|
install -d /etc/chrony/conf.d
|
|
if [ -L /etc/chrony/conf.d/edgeguard.conf ]; then
|
|
rm /etc/chrony/conf.d/edgeguard.conf
|
|
fi
|
|
if [ ! -f /etc/chrony/conf.d/edgeguard.conf ]; then
|
|
: > /etc/chrony/conf.d/edgeguard.conf
|
|
fi
|
|
chown "$EG_USER":"$EG_USER" /etc/chrony/conf.d/edgeguard.conf
|
|
chmod 0644 /etc/chrony/conf.d/edgeguard.conf
|
|
|
|
# Kea DHCPv4: managed config unter /etc/edgeguard/kea, vom Distro-
|
|
# Pfad symlinked (Distro-Original wird gesichert). Der Dienst läuft
|
|
# NUR, wenn der Operator DHCP aktiviert (Default AUS) — kein
|
|
# rogue DHCP-Server nach Install. Der Renderer enabled+restartet
|
|
# kea-dhcp4-server, sobald dhcp_settings.enabled=true gesetzt wird.
|
|
install -d /etc/kea
|
|
if [ -f /etc/kea/kea-dhcp4.conf ] && [ ! -L /etc/kea/kea-dhcp4.conf ]; then
|
|
mv /etc/kea/kea-dhcp4.conf /etc/kea/kea-dhcp4.conf.distro-bak
|
|
fi
|
|
ln -sfn /etc/edgeguard/kea/kea-dhcp4.conf /etc/kea/kea-dhcp4.conf
|
|
systemctl disable --now kea-dhcp4-server.service 2>/dev/null || true
|
|
|
|
# FreeRADIUS: managed clients.conf + authorize unter /etc/edgeguard/
|
|
# freeradius (setgid → Dateien erben Gruppe freeradius, 0640 → der
|
|
# freeradius-Dienst kann lesen). Symlinks von den Distro-Pfaden.
|
|
# Dienst default AUS (kein RADIUS bis Operator aktiviert).
|
|
if getent group freeradius >/dev/null; then
|
|
install -d -m 2750 -o "$EG_USER" -g freeradius /etc/edgeguard/freeradius
|
|
else
|
|
install -d -m 0750 -o "$EG_USER" -g "$EG_USER" /etc/edgeguard/freeradius
|
|
fi
|
|
if [ -d /etc/freeradius/3.0 ]; then
|
|
if [ -e /etc/freeradius/3.0/clients.conf ] && [ ! -L /etc/freeradius/3.0/clients.conf ]; then
|
|
mv /etc/freeradius/3.0/clients.conf /etc/freeradius/3.0/clients.conf.distro-bak
|
|
fi
|
|
ln -sfn /etc/edgeguard/freeradius/clients.conf /etc/freeradius/3.0/clients.conf
|
|
if [ -e /etc/freeradius/3.0/mods-config/files/authorize ] && [ ! -L /etc/freeradius/3.0/mods-config/files/authorize ]; then
|
|
mv /etc/freeradius/3.0/mods-config/files/authorize /etc/freeradius/3.0/mods-config/files/authorize.distro-bak
|
|
fi
|
|
install -d /etc/freeradius/3.0/mods-config/files
|
|
ln -sfn /etc/edgeguard/freeradius/authorize /etc/freeradius/3.0/mods-config/files/authorize
|
|
fi
|
|
systemctl disable --now freeradius.service 2>/dev/null || true
|
|
|
|
chmod 0440 /etc/sudoers.d/edgeguard
|
|
|
|
# ── Sysctl-Profil für Edge-Gateway (NAT + HAProxy + Forwarding) ──
|
|
# Voraussetzung für NAT/DNAT/Masquerade + sinnvolle Defaults
|
|
# für eine high-throughput Forwarding-Box. Edit nicht von Hand
|
|
# — Re-install vom Package überschreibt die Datei. Eigene
|
|
# Tweaks gehören in eine Datei mit höherer Nummer als 99.
|
|
rm -f /etc/sysctl.d/99-edgeguard-forward.conf # Vorgänger
|
|
cat > /etc/sysctl.d/99-edgeguard.conf <<'SYSCTL'
|
|
# ── Managed by edgeguard ────────────────────────────────────────────
|
|
# Lade-Reihenfolge: 99-* überschreibt distro-Defaults. Eigene
|
|
# Operator-Tweaks: /etc/sysctl.d/99-zzz-local.conf (lexikografisch
|
|
# später) — nicht in DIESE Datei!
|
|
|
|
# ─── Forwarding (NAT/DNAT/Masquerade) ───────────────────────────────
|
|
net.ipv4.ip_forward = 1
|
|
net.ipv6.conf.all.forwarding = 1
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.default.send_redirects = 0
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv6.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.all.accept_source_route = 0
|
|
net.ipv6.conf.all.accept_source_route = 0
|
|
|
|
# ─── Reverse-Path-Filter (anti-spoof, loose-Modus für asymmetrisches
|
|
# Routing wie Multi-WAN / WireGuard split) ─────────────────────
|
|
net.ipv4.conf.all.rp_filter = 2
|
|
net.ipv4.conf.default.rp_filter = 2
|
|
|
|
# ─── Conntrack — Edge-Box trackt viele parallele Sessions ─────────
|
|
net.netfilter.nf_conntrack_max = 524288
|
|
net.netfilter.nf_conntrack_tcp_timeout_established = 86400
|
|
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
|
|
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
|
|
net.netfilter.nf_conntrack_buckets = 131072
|
|
|
|
# ─── TCP/IP-Stack-Tuning für HAProxy + viele Backends ─────────────
|
|
net.core.somaxconn = 65535
|
|
net.core.netdev_max_backlog = 16384
|
|
net.core.rmem_max = 16777216
|
|
net.core.wmem_max = 16777216
|
|
net.core.rmem_default = 262144
|
|
net.core.wmem_default = 262144
|
|
net.ipv4.tcp_rmem = 4096 87380 16777216
|
|
net.ipv4.tcp_wmem = 4096 65536 16777216
|
|
net.ipv4.tcp_max_syn_backlog = 65535
|
|
net.ipv4.tcp_fin_timeout = 15
|
|
net.ipv4.tcp_tw_reuse = 1
|
|
net.ipv4.tcp_keepalive_time = 300
|
|
net.ipv4.tcp_keepalive_probes = 5
|
|
net.ipv4.tcp_keepalive_intvl = 30
|
|
net.ipv4.tcp_mtu_probing = 1
|
|
net.ipv4.tcp_slow_start_after_idle = 0
|
|
net.ipv4.tcp_no_metrics_save = 1
|
|
net.ipv4.ip_local_port_range = 10240 65535
|
|
|
|
# ─── Modern congestion control + queueing (BBR + fq) ──────────────
|
|
# Wenn der Kernel BBR nicht hat, fällt Linux still auf cubic zurück.
|
|
net.ipv4.tcp_congestion_control = bbr
|
|
net.core.default_qdisc = fq
|
|
|
|
# ─── Anti-DoS / Hardening ─────────────────────────────────────────
|
|
net.ipv4.tcp_syncookies = 1
|
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
|
net.ipv4.conf.all.log_martians = 1
|
|
kernel.kptr_restrict = 2
|
|
kernel.dmesg_restrict = 1
|
|
|
|
# ─── Memory ───────────────────────────────────────────────────────
|
|
vm.swappiness = 10
|
|
vm.dirty_ratio = 20
|
|
vm.dirty_background_ratio = 5
|
|
SYSCTL
|
|
sysctl --system >/dev/null 2>&1 || true
|
|
|
|
# ── Firewall-Logging via ulogd2 (NFLOG group 0) ──────────────
|
|
# nft-Renderer emittiert `log prefix "edgeguard:<rule-id>" group 0`
|
|
# für jede Rule mit log=true. ulogd2 subscribed auf netlink-group
|
|
# 0 und schreibt JSON-Lines nach /var/log/edgeguard/firewall.jsonl.
|
|
# Das UI tailt das File für Live-Log + Historie.
|
|
install -d -m 0755 /var/log/edgeguard
|
|
install -d -m 0755 -o "$EG_USER" -g "$EG_USER" /var/log/edgeguard
|
|
if [ ! -f /var/log/edgeguard/firewall.jsonl ]; then
|
|
: > /var/log/edgeguard/firewall.jsonl
|
|
fi
|
|
# ulogd2 läuft als root (eigener Daemon); File muss von ihm
|
|
# schreibbar UND von edgeguard-API lesbar sein.
|
|
chown root:"$EG_USER" /var/log/edgeguard/firewall.jsonl
|
|
chmod 0640 /var/log/edgeguard/firewall.jsonl
|
|
|
|
cat > /etc/ulogd.conf <<'ULOGD'
|
|
# Managed by edgeguard — re-installation overwrites this file.
|
|
# NFLOG group 0 → JSON-Lines pro Paket nach /var/log/edgeguard/firewall.jsonl
|
|
# Format-Felder: oob.time.sec, oob.prefix (rule-id), src_ip, dst_ip,
|
|
# src_port, dst_port, ip.protocol, raw.pktlen, oob.in/oob.out (iface).
|
|
[global]
|
|
logfile="/var/log/ulogd.log"
|
|
loglevel=5
|
|
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_inppkt_NFLOG.so"
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_IFINDEX.so"
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_IP2STR.so"
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_filter_HWHDR.so"
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_raw2packet_BASE.so"
|
|
plugin="/usr/lib/x86_64-linux-gnu/ulogd/ulogd_output_JSON.so"
|
|
|
|
stack=fw1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,mac2str1:HWHDR,json1:JSON
|
|
|
|
[fw1]
|
|
group=0
|
|
# qthreshold=1 + qtimeout=1: Kernel batched sonst Pakete bis 1024 oder
|
|
# bis 1s. Mit 1/1 → jedes Paket SOFORT raus, kein Sammeln. Wichtig
|
|
# damit die Live-Log-UI in real-time fließt statt in Bursts.
|
|
qthreshold=1
|
|
qtimeout=1
|
|
|
|
[json1]
|
|
sync=1
|
|
file="/var/log/edgeguard/firewall.jsonl"
|
|
timestamp=1
|
|
boolean_label=1
|
|
ULOGD
|
|
chmod 0644 /etc/ulogd.conf
|
|
|
|
# Logrotate-Profil für firewall.jsonl — Default: daily, 14 days
|
|
# comprimiert, copytruncate damit ulogd kein Reopen-Signal braucht.
|
|
cat > /etc/logrotate.d/edgeguard-firewall <<'LOGROTATE'
|
|
/var/log/edgeguard/firewall.jsonl {
|
|
daily
|
|
rotate 14
|
|
missingok
|
|
notifempty
|
|
compress
|
|
delaycompress
|
|
copytruncate
|
|
create 0640 root edgeguard
|
|
}
|
|
LOGROTATE
|
|
chmod 0644 /etc/logrotate.d/edgeguard-firewall
|
|
|
|
# ulogd2 enablen + restarten (idempotent). Wenn das Paket nicht
|
|
# da ist (Dependency-Konflikt o.ä.), nur warnen — die Firewall
|
|
# läuft auch ohne Logger.
|
|
if systemctl list-unit-files ulogd2.service >/dev/null 2>&1; then
|
|
systemctl enable ulogd2.service >/dev/null 2>&1 || true
|
|
systemctl restart ulogd2.service || \
|
|
echo "postinst: ulogd2.service restart failed (firewall logs disabled until fixed)" >&2
|
|
else
|
|
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
|
|
|
|
# ── Network-Interfaces apply-script + systemd unit ───────────
|
|
# Verwaltet aus /etc/edgeguard/interfaces.conf. VLAN/bridge/bond-
|
|
# Interfaces die der Operator anlegt werden sofort via
|
|
# `systemctl restart edgeguard-interfaces.service` erstellt.
|
|
# Ethernet + WireGuard werden vom OS bzw. wg-quick verwaltet,
|
|
# nicht von diesem Skript. Diff-Ansatz analog ip-addresses.
|
|
cat > /usr/sbin/edgeguard-apply-interfaces <<'APPLYIFACES'
|
|
#!/bin/bash
|
|
# Managed by edgeguard — DO NOT EDIT.
|
|
CONF=/etc/edgeguard/interfaces.conf
|
|
APPLIED=/var/lib/edgeguard/interfaces-applied.conf
|
|
|
|
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)
|
|
ip link add "$name" type bridge || return 1
|
|
if [ -n "$members" ]; then
|
|
echo "$members" | tr ',' '\n' | while read -r m; do
|
|
[ -n "$m" ] && ip link set "$m" master "$name" 2>/dev/null || true
|
|
done
|
|
fi
|
|
;;
|
|
bond)
|
|
ip link add "$name" type bond || return 1
|
|
if [ -n "$members" ]; then
|
|
echo "$members" | tr ',' '\n' | while read -r m; do
|
|
[ -n "$m" ] && { ip link set "$m" down 2>/dev/null; ip link set "$m" master "$name" 2>/dev/null || true; }
|
|
done
|
|
fi
|
|
;;
|
|
*) return 0 ;;
|
|
esac
|
|
[ -n "$mtu" ] && ip link set "$name" mtu "$mtu" 2>/dev/null || true
|
|
ip link set "$name" up 2>/dev/null || true
|
|
}
|
|
|
|
# Entferne Interfaces die im letzten Apply waren, aber jetzt nicht mehr
|
|
if [ -f "$APPLIED" ]; then
|
|
while IFS='|' read -r typ name rest; do
|
|
[ -z "$typ" ] && continue
|
|
case "$typ" in '#'*) continue;; esac
|
|
if ! awk -F'|' -v n="$name" 'NR>2 && $2==n {found=1} END{exit !found}' "$CONF" 2>/dev/null; then
|
|
ip link set "$name" down 2>/dev/null || true
|
|
ip link del "$name" 2>/dev/null || true
|
|
fi
|
|
done < "$APPLIED"
|
|
fi
|
|
|
|
[ -f "$CONF" ] || { : > "$APPLIED"; exit 0; }
|
|
|
|
# Erstelle/aktualisiere Interfaces aus Config
|
|
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
|
|
# 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
|
|
fi
|
|
done < "$CONF"
|
|
|
|
cp "$CONF" "$APPLIED"
|
|
APPLYIFACES
|
|
chmod 0755 /usr/sbin/edgeguard-apply-interfaces
|
|
chown root:root /usr/sbin/edgeguard-apply-interfaces
|
|
|
|
cat > /etc/systemd/system/edgeguard-interfaces.service <<'IFACESUNIT'
|
|
[Unit]
|
|
Description=EdgeGuard managed network interfaces apply
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/sbin/edgeguard-apply-interfaces
|
|
RemainAfterExit=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
IFACESUNIT
|
|
systemctl daemon-reload
|
|
systemctl enable edgeguard-interfaces.service >/dev/null 2>&1 || true
|
|
# Initialer Apply — leere /etc/edgeguard/interfaces.conf ist ok
|
|
systemctl start edgeguard-interfaces.service 2>/dev/null || true
|
|
|
|
if [ ! -f /etc/edgeguard/interfaces.conf ]; then
|
|
: > /etc/edgeguard/interfaces.conf
|
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/interfaces.conf
|
|
fi
|
|
|
|
# ── IP-Addresses apply-script + systemd unit ─────────────────
|
|
# Verwaltet aus /etc/edgeguard/ip-addresses.conf. Adressen die
|
|
# der Operator über die GUI hinzufügt/entfernt werden sofort via
|
|
# `systemctl restart edgeguard-ipaddresses.service` angewendet.
|
|
# Diff-Ansatz: /var/lib/edgeguard/ip-addresses-applied.conf hält
|
|
# den zuletzt angewendeten Stand; alte Adressen werden entfernt,
|
|
# neue hinzugefügt — manuell gebundene Adressen bleiben intakt.
|
|
cat > /usr/sbin/edgeguard-apply-ipaddresses <<'APPLYIP'
|
|
#!/bin/bash
|
|
# Managed by edgeguard — DO NOT EDIT.
|
|
set -e
|
|
CONF=/etc/edgeguard/ip-addresses.conf
|
|
APPLIED=/var/lib/edgeguard/ip-addresses-applied.conf
|
|
|
|
# Entferne Adressen die im letzten Apply-Lauf waren, aber jetzt nicht mehr
|
|
if [ -f "$APPLIED" ]; then
|
|
while IFS='|' read -r dev addrpfx; do
|
|
[ -z "$dev" ] && continue
|
|
case "$dev" in '#'*) continue;; esac
|
|
# Nur entfernen wenn nicht mehr in der neuen Config
|
|
if ! grep -qxF "${dev}|${addrpfx}" "$CONF" 2>/dev/null; then
|
|
ip addr del "$addrpfx" dev "$dev" 2>/dev/null || true
|
|
fi
|
|
done < "$APPLIED"
|
|
fi
|
|
|
|
[ -f "$CONF" ] || { : > "$APPLIED"; exit 0; }
|
|
|
|
# Füge neue Adressen hinzu
|
|
while IFS='|' read -r dev addrpfx; do
|
|
[ -z "$dev" ] && continue
|
|
case "$dev" in '#'*) continue;; esac
|
|
ip addr add "$addrpfx" dev "$dev" 2>/dev/null || true
|
|
done < "$CONF"
|
|
|
|
# Aktuelle Config als "applied" speichern
|
|
cp "$CONF" "$APPLIED"
|
|
APPLYIP
|
|
chmod 0755 /usr/sbin/edgeguard-apply-ipaddresses
|
|
chown root:root /usr/sbin/edgeguard-apply-ipaddresses
|
|
|
|
cat > /etc/systemd/system/edgeguard-ipaddresses.service <<'IPADDRUNIT'
|
|
[Unit]
|
|
Description=EdgeGuard managed IP addresses apply
|
|
After=network-online.target edgeguard-interfaces.service
|
|
Wants=network-online.target
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/sbin/edgeguard-apply-ipaddresses
|
|
RemainAfterExit=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
IPADDRUNIT
|
|
systemctl daemon-reload
|
|
systemctl enable edgeguard-ipaddresses.service >/dev/null 2>&1 || true
|
|
systemctl start edgeguard-ipaddresses.service 2>/dev/null || true
|
|
|
|
if [ ! -f /etc/edgeguard/ip-addresses.conf ]; then
|
|
: > /etc/edgeguard/ip-addresses.conf
|
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/ip-addresses.conf
|
|
fi
|
|
|
|
# ── Keepalived notify-scripts installieren ───────────────────
|
|
# Die Skripte liegen im Package unter /usr/lib/edgeguard/ und
|
|
# 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 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
|
|
# later; until then, browsers see an unverified cert which is
|
|
# the expected first-boot UX.
|
|
DEFAULT_PEM="/etc/edgeguard/tls/_default.pem"
|
|
if [ ! -f "$DEFAULT_PEM" ]; then
|
|
HOSTNAME_FQDN="$(hostname -f 2>/dev/null || hostname)"
|
|
TMP_KEY="$(mktemp)"
|
|
TMP_CRT="$(mktemp)"
|
|
openssl req -x509 -nodes -newkey rsa:2048 \
|
|
-keyout "$TMP_KEY" -out "$TMP_CRT" \
|
|
-days 3650 \
|
|
-subj "/CN=$HOSTNAME_FQDN" \
|
|
-addext "subjectAltName = DNS:$HOSTNAME_FQDN,DNS:localhost" \
|
|
>/dev/null 2>&1
|
|
cat "$TMP_CRT" "$TMP_KEY" > "$DEFAULT_PEM"
|
|
chown "$EG_USER:$EG_USER" "$DEFAULT_PEM"
|
|
chmod 0640 "$DEFAULT_PEM"
|
|
rm -f "$TMP_KEY" "$TMP_CRT"
|
|
fi
|
|
|
|
# ── Pre-flight: validate embedded migration set ──────────────
|
|
# Catches duplicate version prefixes BEFORE we touch the DB,
|
|
# so a broken upgrade can't half-apply migrations and leave
|
|
# the cluster wedged (mail-gateway 2026-05-08 incident).
|
|
if ! /usr/bin/edgeguard-ctl migrate check; then
|
|
echo "postinst: embedded migrations failed validation — aborting" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── PostgreSQL: ensure role + database exist ─────────────────
|
|
# Requires postgresql-16 (or -17) running locally — guaranteed
|
|
# by Depends. Idempotent — re-runs on upgrade are no-ops.
|
|
if ! /usr/bin/edgeguard-ctl initdb; then
|
|
echo "postinst: edgeguard-ctl initdb failed — aborting" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── Apply pending schema migrations ──────────────────────────
|
|
if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl migrate up; then
|
|
echo "postinst: edgeguard-ctl migrate up failed — aborting" >&2
|
|
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
|
|
|
|
# ── CrowdSec IDS installation ─────────────────────────────────────────
|
|
# Install CrowdSec if not present. We use the official CrowdSec APT repo.
|
|
# Only done on initial install (not on upgrade) to avoid overwriting
|
|
# user configuration.
|
|
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
|
if ! command -v cscli >/dev/null 2>&1; then
|
|
echo "postinst: installing CrowdSec..."
|
|
# Add CrowdSec repo key
|
|
install -d -m 0755 /etc/apt/keyrings
|
|
curl -s https://packagecloud.io/crowdsec/crowdsec/gpgkey | \
|
|
gpg --dearmor -o /etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg 2>/dev/null || true
|
|
# Add repo (bookworm compat for Trixie)
|
|
echo "deb [signed-by=/etc/apt/keyrings/crowdsec_crowdsec-archive-keyring.gpg] https://packagecloud.io/crowdsec/crowdsec/debian/ bookworm main" \
|
|
> /etc/apt/sources.list.d/crowdsec.list
|
|
apt-get update -qq 2>/dev/null || true
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq crowdsec crowdsec-firewall-bouncer-nftables 2>/dev/null || true
|
|
|
|
# Install default collections
|
|
if command -v cscli >/dev/null 2>&1; then
|
|
cscli collections install crowdsecurity/linux crowdsecurity/haproxy 2>/dev/null || true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ── Render initial service configs ───────────────────────────
|
|
# Writes /etc/edgeguard/haproxy/haproxy.cfg + nftables.d/
|
|
# ruleset.nft from the (just-migrated, empty) PG state.
|
|
#
|
|
# haproxy bekommt --no-reload (drop-in unten zeigt erst danach
|
|
# auf unsere cfg; wir restarten explizit); nftables muss aber
|
|
# aktiv reloadet werden, sonst läuft das Kernel-Set bei Template-
|
|
# Änderungen (z.B. neue anti-lockout-Ports) hinterher.
|
|
if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl render-config --only=haproxy --no-reload; then
|
|
echo "postinst: edgeguard-ctl render-config (haproxy) failed — aborting" >&2
|
|
exit 1
|
|
fi
|
|
if ! sudo -n -u "$EG_USER" /usr/bin/edgeguard-ctl render-config --only=nftables; then
|
|
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
|
|
# conffile of the haproxy package). Drop-in is reversible by
|
|
# removing the file + daemon-reload.
|
|
install -d /etc/systemd/system/haproxy.service.d
|
|
if [ -f /etc/edgeguard/systemd/haproxy-edgeguard.conf ]; then
|
|
install -m 0644 /etc/edgeguard/systemd/haproxy-edgeguard.conf \
|
|
/etc/systemd/system/haproxy.service.d/edgeguard.conf
|
|
fi
|
|
|
|
# ── OWASP Core Rule Set (CRS) für edgeguard-waf ─────────────────────
|
|
# Nur beim Erstinstall — kein Überschreiben bei Upgrades damit
|
|
# admins angepasste crs-setup.conf behalten können.
|
|
WAF_CRS_DIR="/usr/share/edgeguard/waf/crs"
|
|
if [ "$1" = "configure" ] && [ -z "$2" ] && [ ! -d "$WAF_CRS_DIR/rules" ]; then
|
|
CRS_VERSION="4.7.0"
|
|
echo "postinst: installing OWASP CRS v${CRS_VERSION}..."
|
|
install -d -m 0755 "$WAF_CRS_DIR/rules"
|
|
CRS_TMP="$(mktemp -d)"
|
|
if curl -sL --max-time 60 \
|
|
"https://github.com/coreruleset/coreruleset/archive/refs/tags/v${CRS_VERSION}.tar.gz" \
|
|
-o "${CRS_TMP}/crs.tar.gz" 2>/dev/null; then
|
|
tar xzf "${CRS_TMP}/crs.tar.gz" -C "${CRS_TMP}" 2>/dev/null || true
|
|
CRS_SRC="${CRS_TMP}/coreruleset-${CRS_VERSION}"
|
|
if [ -f "${CRS_SRC}/crs-setup.conf.example" ]; then
|
|
install -m 0644 "${CRS_SRC}/crs-setup.conf.example" "${WAF_CRS_DIR}/crs-setup.conf"
|
|
install -m 0644 "${CRS_SRC}/rules/"* "${WAF_CRS_DIR}/rules/" 2>/dev/null || true
|
|
chown -R "$EG_USER":"$EG_USER" "$WAF_CRS_DIR"
|
|
echo "postinst: OWASP CRS v${CRS_VERSION} installed to ${WAF_CRS_DIR}"
|
|
else
|
|
echo "postinst: CRS download succeeded but layout unexpected — skipping" >&2
|
|
fi
|
|
else
|
|
echo "postinst: CRS download failed (no internet?) — WAF usable without CRS" >&2
|
|
fi
|
|
rm -rf "${CRS_TMP}"
|
|
fi
|
|
|
|
# ── systemd: pick up new units + restart haproxy with our cfg
|
|
systemctl daemon-reload
|
|
systemctl restart haproxy.service || true
|
|
systemctl enable edgeguard-api.service edgeguard-scheduler.service edgeguard-waf.service || true
|
|
# restart (NICHT enable --now): das neue Unit-File könnte ein
|
|
# geänderter ReadWritePaths-Block sein (Befund 1.0.87:
|
|
# /var/lib/apt fehlte → apt-get update aus dem Update-Banner-
|
|
# Handler schrieb in read-only Filesystem und failte still).
|
|
# daemon-reload allein lädt die neue Unit nur für NÄCHSTE Starts;
|
|
# der laufende Daemon behält das alte Sandbox-Snapshot bis zum
|
|
# restart. Bei Erst-Install ist nichts running, dann ist das
|
|
# ein normaler Start.
|
|
systemctl restart edgeguard-api.service edgeguard-scheduler.service edgeguard-waf.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)
|
|
;;
|
|
|
|
*)
|
|
echo "postinst called with unknown argument \`$1'" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
#DEBHELPER#
|
|
|
|
exit 0
|