feat: WireGuard (server + client + peers + QR) + shared UI components

WireGuard
---------
* Migration 0013: wireguard_interfaces (server|client mode, key envelope-
  encrypted) + wireguard_peers (per-server roster). Drop old empty
  0005-Schema (Option-A peer_type, kein Iface-FK), neuer Aufbau mit
  zwei Tabellen + FK.
* internal/services/secrets: Box mit AES-256-GCM, Master-Key in
  /var/lib/edgeguard/.master_key (lazy-create, 0600). Sealed/Open
  für PrivateKey + PSK.
* internal/services/wireguard: KeyGen (Curve25519 mit clamping),
  PublicFromPrivate (für Import), InterfacesRepo, PeersRepo, Importer
  (parst /etc/wireguard/*.conf, server vs. client heuristisch nach
  ListenPort + Peer-Anzahl).
* internal/wireguard: Renderer schreibt /etc/edgeguard/wireguard/<iface>.conf
  (0600), restartet wg-quick@<iface> via sudo (sudoers im postinst
  erweitert). Idempotent — re-render nur wenn content geändert.
* internal/handlers/wireguard.go: REST CRUD für interfaces+peers,
  /generate-keypair, /peers/:id/config (text/plain wg-quick conf),
  /peers/:id/qr (PNG via go-qrcode). Auto-reload nach Mutation.
* edgeguard-ctl wg-import [--path /etc/wireguard]: liest existierende
  conf-Files in die DB. Idempotent (überspringt vorhandene Iface-Namen).

Shared UI components (proxy-lb-waf design pattern)
--------------------------------------------------
* PageHeader: icon + title + subtitle + extras row, einheitlich oben
  auf jeder Page.
* ActionButtons: Edit + Delete combo mit Popconfirm + Tooltip.
* StatusDot: AntD Badge pattern statt "Yes/No" — schneller scanbar
  in dichten Tabellen.
* DataTable: pageSizeOptions [20,50,100,200] + extraActions-Alias +
  optional renderMobileCard für Card-Liste auf < md Breakpoint.
* enterprise.css: .page-header* + .datatable-toolbar Klassen.

Frontend WireGuard
------------------
* /vpn/wireguard mit zwei Tabs (Server / Client) im neuen Pattern.
* Server-Tab: Modal mit Generate-Keypair-Toggle, Peer-Roster im
  Drawer per Server. Pro Peer: QR-Code-Modal + .conf-Download.
* Client-Tab: Upstream-Card im Modal, full-tunnel-Default
  (0.0.0.0/0,::/0), Keepalive 25.
* i18n DE/EN für wg.* Block + common.* Erweiterung.

Misc
----
* Sidebar: WireGuard unter Security-Sektion.
* Nav-i18n: "Firewall (v2)" → "Firewall".
* Version 1.0.8 → 1.0.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-10 20:51:25 +02:00
parent 3545b8422b
commit 85904d0c36
33 changed files with 3046 additions and 40 deletions

View File

@@ -12,7 +12,8 @@
"ipAddresses": "IP-Adressen",
"ssl": "SSL-Zertifikate",
"vpn": "VPN",
"firewall": "Firewall (v2)",
"wireguard": "WireGuard",
"firewall": "Firewall",
"cluster": "Cluster",
"settings": "Einstellungen",
"section": {
@@ -278,6 +279,72 @@
"applying": "Update läuft …",
"started": "Update wurde gestartet — der Server wird in Kürze neu starten."
},
"wg": {
"title": "WireGuard",
"intro": "VPN-Tunnel über WireGuard. Server-Modus = wir lauschen für Peers; Client-Modus = wir verbinden zu einem festen Upstream. Privater Schlüssel liegt verschlüsselt in der DB.",
"tabs": { "servers": "Server-Tunnel", "clients": "Client-Tunnel" },
"serverIntro": "Server-Tunnel hosten ein Peer-Roster — z.B. Mitarbeiter-Geräte oder Niederlassungen. Pro Peer bekommt der Operator eine .conf zum Download (oder QR-Code für Mobile).",
"clientIntro": "Client-Tunnel verbinden EdgeGuard zu einem fremden WireGuard-Server (z.B. HQ-Datacenter). Allowed-IPs steuert, welcher Traffic durch den Tunnel geroutet wird.",
"iface": {
"name": "Name",
"namePattern": "wg gefolgt von Kleinbuchstaben/Ziffern/-, max. 15 Zeichen",
"nameExtra": "Empfehlung: wg0, wg1, wg-hq …",
"address": "Adresse (CIDR)",
"addressExtra": "Tunnel-IP der Box, z.B. 10.99.0.1/24 für /24-Pool",
"listenPort": "Listen-Port",
"publicKey": "Public-Key",
"privateKey": "Private-Key (paste)",
"privateKeyExtra": "Nur ausfüllen wenn nicht generieren — base64 32 Byte. Wird verschlüsselt gespeichert.",
"peerEndpoint": "Peer-Endpoint",
"peerPublicKey": "Peer Public-Key",
"peerPSK": "Pre-Shared-Key (PSK)",
"peerPSKExtra": "Optional, zusätzliche Schicht",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "Was durch den Tunnel geroutet wird. Default = full-tunnel.",
"keepalive": "Persistent Keepalive (sec)",
"mtu": "MTU",
"zone": "Firewall-Zone",
"description": "Beschreibung",
"addServer": "Server-Tunnel hinzufügen",
"editServer": "Server-Tunnel bearbeiten",
"addClient": "Client-Tunnel hinzufügen",
"editClient": "Client-Tunnel bearbeiten",
"upstream": "Upstream-Peer",
"deleteConfirm": "Tunnel {{name}} wirklich löschen? wg-quick wird gestoppt.",
"keys": "Schlüssel",
"generateExtra": "Wenn an: Server erzeugt ein neues Curve25519-Keypair beim Speichern.",
"generateOn": "Server generiert",
"generateOff": "Manuell paste",
"editKeyWarning": "Achtung: neue Schlüssel = bestehende Peer-Configs ungültig. Nur ändern wenn explizit gewollt."
},
"peers": {
"button": "Peers",
"drawerTitle": "Peer-Roster"
},
"peer": {
"name": "Name",
"publicKey": "Public-Key",
"publicKeyExtra": "Wird vom Peer-Gerät erzeugt; hier nur paste-bar wenn der Peer schon ein Key-Pair hat.",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "Welche Tunnel-IPs darf dieser Peer benutzen. Typisch /32 = eine IP.",
"keepalive": "Keepalive (sec)",
"keepaliveExtra": "0 = aus. Empfohlen 25 hinter NAT.",
"lastHandshake": "Letzter Handshake",
"never": "nie",
"description": "Beschreibung",
"add": "Peer hinzufügen",
"edit": "Peer bearbeiten",
"deleteConfirm": "Peer {{name}} wirklich entfernen?",
"keys": "Schlüssel",
"generateExtra": "Wenn an: Server erzeugt für diesen Peer ein Keypair und kann die Config / QR-Code ausliefern. Wenn aus: nur den Public-Key paste-en — keine Config-Download möglich.",
"pskExtra": "Wenn an: Server generiert einen 32-Byte PSK für diesen Peer.",
"pskOn": "PSK generieren",
"pskOff": "kein PSK",
"downloadConf": "wg-quick.conf herunterladen",
"qrTitle": "WireGuard-QR",
"qrHint": "Mit der WireGuard-App (iOS/Android) scannen: \"Tunnel hinzufügen\" → \"QR-Code scannen\". Endpoint im Download-Conf bitte vor Verwendung anpassen."
}
},
"common": {
"yes": "Ja",
"no": "Nein",
@@ -287,7 +354,16 @@
"error": "Fehler",
"edit": "Bearbeiten",
"delete": "Löschen",
"deleteConfirm": "Wirklich löschen?",
"search": "Suchen …",
"totalRows": "{{count}} Einträge"
"totalRows": "{{count}} Einträge",
"active": "Aktiv",
"inactive": "Inaktiv",
"noData": "Keine Einträge",
"actions": "Aktionen",
"add": "Hinzufügen",
"download": "Download",
"copy": "Kopieren",
"copied": "Kopiert"
}
}

View File

@@ -12,6 +12,7 @@
"ipAddresses": "IP addresses",
"ssl": "SSL certificates",
"vpn": "VPN",
"wireguard": "WireGuard",
"firewall": "Firewall",
"cluster": "Cluster",
"settings": "Settings",
@@ -278,6 +279,72 @@
"applying": "Update in progress …",
"started": "Update has started — the server will restart shortly."
},
"wg": {
"title": "WireGuard",
"intro": "WireGuard VPN tunnels. Server mode = we listen for peers; client mode = we dial out to a fixed upstream. Private keys are encrypted at rest.",
"tabs": { "servers": "Server tunnels", "clients": "Client tunnels" },
"serverIntro": "Server tunnels host a peer roster — typically employee devices or branch sites. Each peer can be downloaded as a wg-quick.conf or scanned as a QR code.",
"clientIntro": "Client tunnels connect EdgeGuard to a remote WireGuard server (e.g. HQ datacenter). Allowed IPs control which traffic is routed through the tunnel.",
"iface": {
"name": "Name",
"namePattern": "wg followed by lowercase letters/digits/-, max 15 chars",
"nameExtra": "Suggested: wg0, wg1, wg-hq …",
"address": "Address (CIDR)",
"addressExtra": "Box's tunnel IP, e.g. 10.99.0.1/24 for a /24 pool",
"listenPort": "Listen port",
"publicKey": "Public key",
"privateKey": "Private key (paste)",
"privateKeyExtra": "Fill in only if not auto-generating — base64 32 bytes. Stored encrypted.",
"peerEndpoint": "Peer endpoint",
"peerPublicKey": "Peer public key",
"peerPSK": "Pre-shared key (PSK)",
"peerPSKExtra": "Optional extra layer",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "What gets routed through the tunnel. Default = full tunnel.",
"keepalive": "Persistent keepalive (sec)",
"mtu": "MTU",
"zone": "Firewall zone",
"description": "Description",
"addServer": "Add server tunnel",
"editServer": "Edit server tunnel",
"addClient": "Add client tunnel",
"editClient": "Edit client tunnel",
"upstream": "Upstream peer",
"deleteConfirm": "Really delete tunnel {{name}}? wg-quick will be stopped.",
"keys": "Keys",
"generateExtra": "If on: server generates a fresh Curve25519 keypair on save.",
"generateOn": "Server-generated",
"generateOff": "Manual paste",
"editKeyWarning": "Warning: new keys invalidate all existing peer configs. Only change if intentional."
},
"peers": {
"button": "Peers",
"drawerTitle": "Peer roster"
},
"peer": {
"name": "Name",
"publicKey": "Public key",
"publicKeyExtra": "Generated by the peer device; only paste here if the peer already has a keypair.",
"allowedIPs": "Allowed IPs",
"allowedIPsExtra": "Which tunnel IPs this peer is allowed to use. Typically /32 = one IP.",
"keepalive": "Keepalive (sec)",
"keepaliveExtra": "0 = off. Recommended 25 behind NAT.",
"lastHandshake": "Last handshake",
"never": "never",
"description": "Description",
"add": "Add peer",
"edit": "Edit peer",
"deleteConfirm": "Really remove peer {{name}}?",
"keys": "Keys",
"generateExtra": "If on: server generates a keypair for this peer and can hand out the config / QR. If off: paste the peer's public key only — no config download.",
"pskExtra": "If on: server generates a 32-byte PSK for this peer.",
"pskOn": "Generate PSK",
"pskOff": "no PSK",
"downloadConf": "Download wg-quick.conf",
"qrTitle": "WireGuard QR",
"qrHint": "Scan with the WireGuard app (iOS/Android): \"Add tunnel\" → \"Scan QR code\". Replace the Endpoint placeholder in the downloaded conf before use."
}
},
"common": {
"yes": "Yes",
"no": "No",
@@ -287,7 +354,16 @@
"error": "Error",
"edit": "Edit",
"delete": "Delete",
"deleteConfirm": "Really delete?",
"search": "Search …",
"totalRows": "{{count}} rows"
"totalRows": "{{count}} rows",
"active": "Active",
"inactive": "Inactive",
"noData": "No data",
"actions": "Actions",
"add": "Add",
"download": "Download",
"copy": "Copy",
"copied": "Copied"
}
}