Files
edgeguard-native/docs/architecture.md
Felix Netzel 0ceab4c814 chore: initial skeleton
- docs/architecture.md: native rewrite plan (5 services + control plane,
  Active-Active cluster like nmg, Floating-IP for HTTP ingress)
- cmd/edgeguard-{api,scheduler,ctl}: minimal Gin + CLI stubs
- packaging/debian/edgeguard-{api,ui,meta}: control + maintainer scripts
- deploy/systemd/edgeguard-api.service + edgeguard-scheduler.service
  with hardening defaults
- Makefile: build / cross-compile (amd64+arm64) / deb / publish targets
- scripts/install.sh + scripts/apt-repo/build-package.sh stubs
2026-05-08 18:45:41 +02:00

21 KiB
Raw Blame History

EdgeGuard — Architektur

Status: v0.1 (Entwurf) · Stand: 2026-05-08 · Ziel-Plattformen: Debian 13 (Trixie) + Ubuntu 24.04 LTS (Noble Numbat), Architekturen amd64 + arm64.

EdgeGuard ist die native Neufassung des bisherigen Docker-basierten Reverse-Proxy/Loadbalancer/Forward-Proxy/VPN-Stacks. Vorbild für Architektur, Build-System und Cluster-Modell ist mail-gateway (nmg); UI-Pattern und Bootstrap-Onliner stammen aus netcell-webpanel (enconf).


0. Leitplanken (nicht verhandelbar)

  • Kein Docker. Alle Dienste nativ unter systemd, installiert via apt. Distro-Pakete für Drittsoftware (HAProxy, Angie, Squid, WireGuard, PostgreSQL, KeyDB, certbot), eigene .deb-Pakete für EdgeGuard-Code (api, ui, ctl).
  • Plattform-Matrix: Debian 13 (Trixie) und Ubuntu 24.04 LTS (Noble Numbat), je amd64 + arm64. Alle vier Targets gleichberechtigt.
  • Auslieferung: signierte .deb-Pakete + Meta-Paket via APT. Bootstrap ist der enconf-analoge curl-Onliner curl -fsSL https://get.edgeguard.netcell-it.de | sudo bash.
  • HA nativ als Cluster: N symmetrische Peers, KeyDB Active-Active für Shared State + PostgreSQL Streaming Replication (single writer, transparenter API-Write-Proxy) + Floating-IP des Hosters für HTTP/HTTPS-Ingress (nicht VRRP, nicht DNS-RR).
  • Kein WAF, kein IDS, kein DHCP, kein RADIUS, keine Mail-Funktion in v1. Mail-Gateway ist eigenes Produkt (nmg); WAF/CrowdSec/Suricata kommen ggf. in v2.
  • Migrations: goose (SQL-Dateien), nicht GORM AutoMigrate.

Nicht-Ziele (ausdrücklich): kein WAF, kein Network-IDS (Suricata), kein IPS (CrowdSec), kein DHCP-Server (Kea), kein RADIUS, keine Mail-Verarbeitung, keine Multi-Tenant-GuardZones in v1, keine ISO-Builds (kein EdgeGuardOS-Klon — nur APT).


1. Scope — fünf Daten-Services + Control-Plane

Service Rolle Distro-Paket Config-Quelle
HAProxy Public-Ingress (80/443), interner LB (8081), TLS-PassThrough haproxy (Debian/Ubuntu) aus PG generiert, systemctl reload haproxy
Angie Reverse-Proxy, vHost-Routing, ACME-Webroot eigenes APT-Repo (angie) aus PG generiert, systemctl reload angie
Squid Forward-Proxy mit ACL/Auth squid aus PG generiert, systemctl reload squid
WireGuard Site-to-Site- + Road-Warrior-VPN wireguard-tools (Kernel-Modul ab Kernel 5.6) aus PG generiert, wg syncconf
nftables Firewall (Input + Forward + NAT) nftables aus PG generiert, nft -f /etc/nftables.conf

Control-Plane:

Komponente Rolle
edgeguard-api Go/Gin REST-API, bindet 127.0.0.1:9443, Reads aus lokaler PG, Writes an Cluster-Primary
edgeguard-scheduler Cron-artige Jobs (ACME-Renewal-Hook, Backup, Health-Aggregation, License-Heartbeat)
edgeguard-ctl CLI für Setup/Wartung (initdb, migrate, cluster-join, promote, dump-config)
management-ui React 19 + AntD 6 + Vite, statisch unter /usr/share/edgeguard/ui/, Angie liefert aus
PostgreSQL 16 Single Source of Truth — Domains, Backends, Routing-Rules, ACLs, Peers, etc.
KeyDB (Redis-kompatibel) Active-Active-Replication, Cluster-State, Locks, Rate-Counter, Pub/Sub für Config-Reload

2. Package-Layout (Repo)

/var/www/edgeguard-native/
├── cmd/                           # Go-Binary-Entry-Points
│   ├── edgeguard-api/             # Management-API (HTTP, 127.0.0.1:9443)
│   ├── edgeguard-scheduler/       # Cron-artige Jobs
│   └── edgeguard-ctl/             # CLI für Setup/Wartung
├── internal/
│   ├── models/                    # GORM-Models (domain, backend, routing_rule, acl, peer, …)
│   ├── handlers/                  # HTTP-Handler (REST)
│   ├── services/                  # Business-Logik (config-render, health-check, cluster-sync)
│   ├── haproxy/                   # HAProxy-Config-Generator
│   ├── angie/                     # Angie-Config-Generator (vHosts, Upstreams, ACME)
│   ├── squid/                     # Squid-Config-Generator (squid.conf + squid.d/*)
│   ├── wireguard/                 # WireGuard-Config-Generator (wg-quick + wg syncconf)
│   ├── firewall/                  # nftables-Ruleset-Generator
│   ├── cluster/                   # Join/Promote/Peer-Discovery, KeyDB-Replication-Setup, pg_basebackup
│   ├── proxy/                     # API-Write-Proxy-Middleware (Replica → Primary), mTLS-Calls
│   ├── aggregator/                # Cluster-View-APIs (alle Backends, alle Peers, alle Health-States)
│   └── license/                   # License-Validation, License-Leader-Election (KeyDB-Lock)
├── management-ui/                 # React 19 + AntD 6 + Vite (Struktur 1:1 wie netcell-webpanel/management-ui/)
├── packaging/
│   └── debian/
│       ├── edgeguard-api/         # control, postinst, postrm, conffiles, systemd-Units
│       ├── edgeguard-ui/
│       └── edgeguard-meta/        # nur Depends, keine Dateien
├── deploy/
│   ├── systemd/                   # *.service, *.target, *.timer
│   ├── haproxy/                   # haproxy.cfg.tpl
│   ├── angie/                     # nginx.conf.tpl, sni-map.tpl
│   ├── squid/                     # squid.conf.tpl
│   └── nftables/                  # ruleset.nft.tpl
├── scripts/
│   ├── apt-repo/                  # build-package.sh, publish.sh, setup-repo.sh
│   ├── install.sh                 # Bootstrap-Onliner
│   └── release.sh                 # CI Release-Helper
├── migrations/                    # SQL-Migrations (goose-Format)
├── docs/
├── Makefile
├── go.mod                         # module git.netcell-it.de/projekte/edgeguard-native
└── go.sum

Go-Module-Name: git.netcell-it.de/projekte/edgeguard-native Build-System: Makefile (POSIX-kompatibel). Targets: build, test, lint, deb, clean, install-local, release.


3. Debian-Pakete

Drei Pakete + Meta — analog nmg, kein WAF-Paket weil kein WAF in v1.

Paket Arch Inhalt Depends
edgeguard-api amd64, arm64 /usr/bin/edgeguard-{api,scheduler,ctl}, Unit-Files, Migrations, Default-Configs postgresql-16, keydb-server, angie, haproxy, squid, wireguard-tools, nftables, certbot
edgeguard-ui all /usr/share/edgeguard/ui/ (statische Build-Artefakte), Angie-Site edgeguard-api (= ${binary:Version}), angie
edgeguard-meta all keine Dateien, nur Depends edgeguard-api, edgeguard-ui

Pro Release: 1 arch-spezifisch × 2 Dists × 2 Arches = 4 .deb + 2 arch-agnostische × 2 Dists = 4 .deb8 Artefakte je Release.

KeyDB-Herkunft: KeyDB ist weder in trixie noch noble in den offiziellen Repos. Wir bauen es aus Source (amd64 + arm64), veröffentlichen es parallel im eigenen APT-Repo. edgeguard-api Depends: keydb-server löst aus unserem Repo aus.

Angie-Herkunft: Eigenes APT-Repo bei angie.softwareedgeguard-api Depends: angie. Im install.sh wird das Angie-Repo zusätzlich zu unserem hinzugefügt.

Build-Werkzeug: direkter dpkg-deb-Build analog WebPanel/EdgeGuardOS-Pattern. Nicht dh_make/debhelper, nicht fpm. Konsistenz mit existierendem Workflow.

postinst (edgeguard-api):

  1. User edgeguard anlegen (adduser --system --group --home /var/lib/edgeguard).
  2. /etc/edgeguard/, /var/lib/edgeguard/, /var/log/edgeguard/ mit 0750, chown edgeguard:edgeguard.
  3. Default-Configs nur anlegen wenn nicht vorhanden (conffiles verhindert Überschreiben).
  4. PostgreSQL: edgeguard-ctl initdb (idempotent — prüft DB/User).
  5. DB-Migration: edgeguard-ctl migrate up.
  6. systemctl daemon-reload && systemctl enable --now edgeguard-api.service edgeguard-scheduler.service.

postrm (purge): DB + User nur bei purge, niemals bei remove.


4. Verzeichnis-Layout auf Zielsystem

/etc/edgeguard/
├── edgeguard.yaml             # Hauptconfig (conffile)
├── api.env                    # API-Secrets (mode 0600, edgeguard:edgeguard)
├── haproxy/                   # haproxy.cfg-Fragmente (von edgeguard-api generiert)
├── angie/                     # vHosts + sni-map (generiert)
├── squid/                     # squid.conf-Fragmente
├── wireguard/                 # wg0.conf etc. (generiert)
├── nftables.d/                # Ruleset-Fragmente
└── tls/                       # ACME-verwaltete Zertifikate (0750, edgeguard:edgeguard)

/var/lib/edgeguard/
├── state/                     # Migrations-Marker, Cluster-Cursor
├── trial.json                 # Lizenz-Trial-File
├── .jwt_fingerprint           # JWT-Secret-Fingerprint (analog enconf — Schutz vor Rotation)
└── backups/                   # lokale PG-Dumps (vor Migration)

/var/log/edgeguard/
├── api.log
├── scheduler.log
└── audit.log

/usr/bin/
├── edgeguard-api
├── edgeguard-scheduler
└── edgeguard-ctl

/usr/share/edgeguard/
├── ui/                        # statische React-Build-Artefakte
└── templates/                 # Config-Templates für haproxy/angie/squid/wireguard/nftables

Entspricht FHS — keine Überraschungen für Admins, Lintian-clean.


5. systemd-Units

Unit Typ Depends-on User Restart
edgeguard-api.service simple postgresql.service, keydb-server.service edgeguard on-failure, RestartSec=5
edgeguard-scheduler.service simple edgeguard-api.service edgeguard on-failure
edgeguard-cert-deploy.path path
edgeguard-firewall.service oneshot, RemainAfterExit=true root
edgeguard.target target api+scheduler

Hardening-Defaults pro Unit (außer edgeguard-firewall, das braucht CAP_NET_ADMIN):

NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
PrivateTmp=true
PrivateDevices=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallFilter=@system-service
ReadWritePaths=/var/lib/edgeguard /var/log/edgeguard /etc/edgeguard

Drittsoftware (HAProxy, Angie, Squid, WireGuard via wg-quick@.service, nftables) läuft als Distro-Units. EdgeGuard generiert deren Config + signalisiert Reload, übernimmt aber die Service-Verwaltung nicht.

API bindet auf 127.0.0.1:9443 (nicht öffentlich). Angie terminiert TLS auf :443 und proxied an die API.


6. Datenbank-Setup

  • PostgreSQL 16, Distro-Paket postgresql-16.
  • Verbindung: Unix-Socket (/var/run/postgresql) für lokale Reads + Writes der API. TCP/5432 mit TLS-Client-Cert nur zwischen Cluster-Peers für Streaming Replication.
  • Topologie: ein logischer Primary zu jedem Zeitpunkt, N Read-Replicas. Lokale API liest immer aus lokaler PG; Writes routet die API-Write-Proxy-Middleware transparent an den aktuellen Primary (KeyDB-Key cluster:pg-primary-url).
  • Migrations: goose (SQL-Dateien in migrations/). Nicht GORM AutoMigrate.

GORM bleibt als ORM für Query-Komfort; nur das Schema-Management wechselt zu goose.


7. KeyDB Active-Active

KeyDB ersetzt Redis. Active-Active Replication (Multi-Master, operation-basiert, split-brain-tolerant).

Verwendung:

  • cluster:pg-primary-url — wer ist aktueller PG-Primary?
  • cluster:license-leader — Lock für License-Heartbeat (SET … NX EX 60)
  • cluster:license-status — Cache des Lizenz-Validate-Ergebnisses (TTL 24 h)
  • cluster:nodes:<node-id> — Heartbeat-Marker (TTL 2 min)
  • ratelimit:<scope>:<key> — Rate-Counter (HINCRBY-Ops mergen korrekt)
  • acme:lock:<domain> — verhindert Parallel-Issue auf zwei Nodes
  • Pub/Sub: edgeguard:config-changed — alle Nodes regenerieren Config

KeyDB hört nur auf 127.0.0.1:6379 für lokale Clients und <node-ip>:16379 (TLS) für Peer-Replication.


8. Cluster-Topologie & HA pro Service

N symmetrische Peers (1 … N Nodes, jeder vollwertig). Keine VRRP, keine Master/Backup-Rollen für Daten-Services. Public-IP: Floating-IP des Hosters (siehe §9).

Service HA-Strategie
HAProxy stateless, pro Node identisch. Floating-IP zeigt zum aktuellen aktiven Node; bei Node-Ausfall API-Call zum Hoster (oder manueller Switch) reicht.
Angie stateless, pro Node identisch. ACME-Issue nur auf License-Leader (KeyDB-Lock); Zerts werden via PG/mTLS an alle verteilt.
Squid stateless (Cache lokal, kein Sync nötig). Pro Node identische ACL-Config.
WireGuard siehe §8.1
nftables pro Node identisch, Ruleset aus PG generiert. crowdsec_blocklist/threat_intel_blocklist-Sets entfallen in v1 (kein CrowdSec).
edgeguard-api pro Node, Reads lokal, Writes via Proxy zu Primary.
edgeguard-ui statisch, pro Node identisch.
PostgreSQL Streaming Replication, manueller Promote (siehe nmg §6.2).
KeyDB Active-Active.

8.1 WireGuard im Cluster

Drei Optionen, für v1 wählen wir Option A:

  • A — Geteilte Server-Identität (gewählt): alle Peers haben denselben Server-Privatkey + dasselbe Listen-Port. Floating-IP routet UDP zum aktiven Node. Bei Failover: Floating-IP wandert, Clients schicken Pakete zum neuen Node, neuer Handshake (~12s Latenz beim ersten Paket). Replay-Protection-Counter werden nicht repliziert — beim Failover macht der Client neuen Handshake, alte Counter sind irrelevant.
  • B — Pro Node eigene Identität, Client kennt alle: Client-Configs haben mehrere [Peer]-Blöcke. Aufwendiger zu provisionieren, kein Failover-Vorteil.
  • C — Aktiv/Standby per License-Leader-Pattern: nur ein Node hat WireGuard aktiv, andere idle. Verschwendet Kapazität.

Begründung A: Privatkey ist in PG (verschlüsselt mit edgeguard.key), wird beim Cluster-Join an neue Peers verteilt. WireGuard handelt selbständig neue Sessions aus, kein State-Sync nötig. Operation-Tools (Peer hinzufügen/entfernen) wirken auf alle Nodes via edgeguard:config-changed Pub/Sub + lokales wg syncconf.

8.2 Manual Promote (PG-Primary-Failover)

1:1 nmg-Pattern (siehe mail-gateway/docs/architecture.md §6.2). Bei Ausfall des Primary antworten Config-Writes mit 503 + actionable Error. Admin promotet via UI/CLI. Datenebene (HAProxy/Angie/Squid/WireGuard) läuft unbeeinträchtigt weiter, weil jeder Node eine lokale PG-Replica hat.

8.3 License-Leader-Election

Ein einziger Node kontaktiert license.netcell-it.com (KeyDB-Lock, 60-s-TTL). Ergebnis cluster-weit in cluster:license-status (TTL 24 h). active_servers-Verbrauchswert = Count der Peers mit Heartbeat < 2 min.


9. Public-Ingress — Floating-IP statt VRRP

Problem: HTTP-Clients machen kein automatisches Failover bei DNS-RR (anders als MTAs). Ein toter A-Record = 50% Fehler bis DNS-TTL.

Entscheidung: Floating-IP des Hosters. Der Hoster bietet eine API zum Umroute der IP zwischen Servern (z. B. via REST oder DNS-Update bei dynamischer Anycast-Lösung). Failover dauert Sekunden, kein VRRP-Drama, kein "VIP verschwindet"-Problem aus dem alten Setup.

Optionen pro Hoster:

  1. Provider-Floating-IP (gewünscht): API-Call schaltet IP um. EdgeGuard exponiert POST /api/v1/cluster/promote-this-node, das die Hoster-API aufruft.
  2. DNS-RR mit kurzer TTL (60s) als Notlösung wenn keine Floating-IP verfügbar.
  3. Anycast/BGP als Premium-Variante (für Enterprise).

v1-Default: Single-Node mit fest zugewiesener Floating-IP. Cluster-Erweiterung kommt mit Phase 2.

OFFEN: Welcher Hoster ist Standard? API-Spec dokumentieren sobald geklärt.


10. Erst-Einrichtung — curl-Onliner

curl -fsSL https://get.edgeguard.netcell-it.de | sudo bash

Schritte (idempotent, analog netcell-webpanel/install.sh):

  1. OS-Detection (/etc/os-release): nur Trixie oder Noble, sonst Abbruch.
  2. Arch-Detection: nur amd64 oder arm64.
  3. Base-Deps: curl gnupg ca-certificates apt-transport-https.
  4. APT-Keyrings:
    • https://apt.netcell-it.de/edgeguard/repository.key/etc/apt/keyrings/netcell-edgeguard.gpg
    • Angie-Repo-Key (für angie-Paket)
  5. APT-Sources: /etc/apt/sources.list.d/netcell-edgeguard.list + Angie-List.
  6. Install: apt-get install -y edgeguard (Meta-Paket).
  7. Auto-Security-Updates: unattended-upgrades + apt-listchanges (nach enconf-Muster).
  8. Setup-Modus: edgeguard-api läuft im Setup-Modus bis Admin-User existiert. UI leitet alle Anfragen auf /setup um. Wizard: Admin-Account, FQDN, ACME-Email, Lizenz oder Trial.

Cluster-Join (zusätzlicher Peer):

curl -fsSL https://get.edgeguard.netcell-it.de | sudo bash -s -- \
  --join https://<existing-node-fqdn> \
  --token <cluster-join-token>

edgeguard-ctl cluster-join führt aus: PG-Basebackup vom Primary, KeyDB-Replication-Setup, Node-Registrierung in ha_nodes, TLS-Cert-Pull via mTLS, Config-Regeneration, Service-Start.


11. Update-Pfad (apt-Repo)

  • Primärquelle: Gitea Package Registry (https://git.netcell-it.de/api/packages/projekte/debian).
  • Kunden-Mirror: https://apt.netcell-it.de/edgeguard/ (rsync von Gitea).
  • Suiten: stable · testing · security — pro Codename (trixie, noble).
  • Signatur: GPG-Key netcell-edgeguard-signing, ausgeliefert in /etc/apt/keyrings/.
  • Update-Check-API: GET /api/v1/system/package-versions → pro edgeguard-*-Paket {name, installed, available, reboot_required}.
  • Upgrade-Trigger: POST /api/v1/system/upgrade startet systemd-run --unit=edgeguard-upgrade.service --collect … (HTTP-Response geht VOR dem Upgrade raus, weil API beim Self-Update stirbt — Pattern aus netcell-webpanel/management-agent/internal/handlers/update.go:105).

Build-/Release-Scripts identisch zu mail-gateway/scripts/apt-repo/.


12. Lizenz & ACME

12.1 Lizenz

1:1 nach netcell-webpanel/docs/licensing-integration.md. Verbrauchswert: active_domains (Anzahl konfigurierter EdgeGuard-Domains).

  • Lizenzserver: https://license.netcell-it.com (öffentlich, kein API-Key).
  • Verify-Endpoint: GET /api/v1/licenses/{key}/verify?system_id={fp}&system_name={host}&active_domains={n}.
  • Fingerprint: SHA256(/etc/machine-id + erste-aktive-MAC + hostname).
  • Caching: Live → KeyDB cluster:license-status (TTL 24h) → /var/lib/edgeguard/trial.json (30 Tage) → expired.
  • Leader-Election wie nmg §6.3.

12.2 ACME

  • certbot (Distro-Paket) mit --webroot-Plugin für Angie-vHosts.
  • Lock vor Issue: acme:lock:<domain> in KeyDB verhindert Parallel-Issue auf zwei Nodes.
  • Deploy-Hook: schreibt Sentinel-Datei → edgeguard-cert-deploy.path-Unit triggert Angie-/HAProxy-Reload.
  • Cert-Verteilung im Cluster: Issuing-Node pushed via mTLS-API an alle Peers, Zerts landen in /etc/edgeguard/tls/.

13. UI — 100% enconf-WebPanel-Pattern

Komponentenbibliothek, Theme, Layouts, Navigations-Struktur, Form-Patterns, i18n-Setup vollständig 1:1 aus netcell-webpanel/management-ui/. Keine eigenen Design-Entscheidungen.

Pflichtlektüre:

  • netcell-webpanel/docs/design-system.md
  • netcell-webpanel/docs/design.md
  • netcell-webpanel/docs/frontend-reference.md

Stack: React 19 + TypeScript strict, Vite, Ant Design 6.x, TanStack Query 5, axios, i18next (de/en), @uiw/react-codemirror, recharts, ESLint flat config.

Seiten v1: Dashboard · Domains · Backends · Routing-Rules · SSL · ForwardProxy (Squid) · VPN (WireGuard) · Firewall · Cluster · Logs · Settings · Setup-Wizard.


14. Plattform-Matrix

Distribution Codename Arch Status v1
Debian 13 trixie amd64 Tier 1
Debian 13 trixie arm64 Tier 1
Ubuntu 24.04 LTS noble amd64 Tier 1
Ubuntu 24.04 LTS noble arm64 Tier 1

Andere Distributionen (Debian 12, Ubuntu 22.04, RHEL/Rocky) sind nicht unterstützt. Installer bricht hart ab.


15. Migration vom Docker-Stand

EdgeGuard-Native ist eigenes Repo (git.netcell-it.de/projekte/edgeguard-native), parallel zum bestehenden proxy-lb-waf. Migration:

  1. Frische Installation auf Test-VM via install.sh.
  2. Config-Export aus altem Stack (edgeguard-ctl export --from-docker) — liest aus alter PG, schreibt in neues Format.
  3. Validierung Side-by-Side (alter Stack auf einem Server, neuer Stack auf anderem, Traffic vergleichen).
  4. Cutover via Floating-IP-Switch.

Der alte proxy-lb-waf-Code bleibt für Bestandskunden im Wartungsmodus, keine neuen Features.


Offene Punkte

  • Hoster + Floating-IP-API (§9): Spec dokumentieren.
  • Angie-Repo-Verfügbarkeit für arm64 prüfen.
  • WireGuard-State-Replication in der Praxis testen (Handshake-Latenz nach Floating-IP-Switch messen).
  • get.edgeguard.netcell-it.de anlegen oder Übergangs-URL auf apt.netcell-it.de/edgeguard/install.sh nutzen.