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,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"
}
}