From 192960ad3e76be4ad83ef6869201eebe430cf10f Mon Sep 17 00:00:00 2001 From: lofyer Date: Sat, 4 Jul 2026 08:21:17 +0800 Subject: [PATCH] Advanced networking: per-peer site subnets (site-to-site), per-peer client routes, peer isolation option, client setup guide Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .env.example | 3 ++ app/config.py | 1 + app/db.py | 2 ++ app/main.py | 37 ++++++++++++++++++++++-- app/models.py | 2 ++ app/service.py | 16 +++++++++- app/templates/peer_detail.html | 53 ++++++++++++++++++++++++++++++++++ app/templates/peers.html | 8 +++++ app/templates/settings.html | 4 +++ app/wireguard.py | 47 ++++++++++++++++++++++++++++-- docker-compose.yml | 1 + entrypoint.sh | 8 +++++ 12 files changed, 176 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 95c5276..aa483a2 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,9 @@ WG_ALLOWED_IPS=0.0.0.0/0, ::/0 # reach the host's wg0 interface. WG_RELAY_SUBNETS= +# Set true to block peer-to-peer traffic between VPN clients (isolation). +WG_PEER_ISOLATION=false + # Admin panel login ADMIN_USERNAME=admin ADMIN_PASSWORD=password diff --git a/app/config.py b/app/config.py index 701bdc1..f935c18 100644 --- a/app/config.py +++ b/app/config.py @@ -12,6 +12,7 @@ class Settings(BaseSettings): wg_allowed_ips: str = "0.0.0.0/0, ::/0" wg_persistent_keepalive: int = 25 wg_config_dir: Path = Path("/etc/wireguard") + wg_peer_isolation: bool = False admin_username: str = "admin" admin_password: str = "changeme" diff --git a/app/db.py b/app/db.py index 896ef86..d419394 100644 --- a/app/db.py +++ b/app/db.py @@ -26,6 +26,8 @@ def get_db(): PEER_MIGRATIONS = { "note": "ALTER TABLE peers ADD COLUMN note VARCHAR(256) NOT NULL DEFAULT ''", "dns": "ALTER TABLE peers ADD COLUMN dns VARCHAR(128) NOT NULL DEFAULT ''", + "extra_allowed_ips": "ALTER TABLE peers ADD COLUMN extra_allowed_ips VARCHAR(512) NOT NULL DEFAULT ''", + "client_allowed_ips": "ALTER TABLE peers ADD COLUMN client_allowed_ips VARCHAR(512) NOT NULL DEFAULT ''", "quota_bytes": "ALTER TABLE peers ADD COLUMN quota_bytes INTEGER NOT NULL DEFAULT 0", "cum_rx": "ALTER TABLE peers ADD COLUMN cum_rx INTEGER NOT NULL DEFAULT 0", "cum_tx": "ALTER TABLE peers ADD COLUMN cum_tx INTEGER NOT NULL DEFAULT 0", diff --git a/app/main.py b/app/main.py index 9837e4e..f724e02 100644 --- a/app/main.py +++ b/app/main.py @@ -139,6 +139,8 @@ def create_peer( count: int = Form(1), address: str = Form(""), dns: str = Form(""), + extra_allowed_ips: str = Form(""), + client_allowed_ips: str = Form(""), ): expiry = datetime.fromisoformat(expires_at) if expires_at else None quota = _parse_quota_gib(quota_gib) @@ -149,7 +151,15 @@ def create_peer( ) return RedirectResponse("/peers", status_code=303) peer = service.create_peer( - db, name.strip(), expiry, note.strip(), quota, address.strip(), dns.strip() + db, + name.strip(), + expiry, + note.strip(), + quota, + address.strip(), + dns.strip(), + extra_allowed_ips.strip(), + client_allowed_ips.strip(), ) except ValueError as exc: return RedirectResponse(f"/peers?error={quote(str(exc))}", status_code=303) @@ -164,7 +174,9 @@ def _get_peer_or_404(db: Session, peer_id: int): @app.get("/peers/{peer_id}", response_class=HTMLResponse, dependencies=[logged_in]) -def peer_detail(request: Request, peer_id: int, db: Session = Depends(get_db)): +def peer_detail( + request: Request, peer_id: int, db: Session = Depends(get_db), error: str = "" +): peer = _get_peer_or_404(db, peer_id) status = wireguard.get_status() _, server_public = wireguard.ensure_server_keys() @@ -176,6 +188,8 @@ def peer_detail(request: Request, peer_id: int, db: Session = Depends(get_db)): "peer": peer, "peer_status": status.peers.get(peer.public_key), "client_config": client_config, + "error": error, + "server_tunnel_ip": wireguard.server_address().split("/")[0], }, ) @@ -187,9 +201,24 @@ def update_peer( note: str = Form(""), quota_gib: str = Form(""), dns: str = Form(""), + extra_allowed_ips: str = Form(""), + client_allowed_ips: str = Form(""), ): peer = _get_peer_or_404(db, peer_id) - service.update_peer(db, peer, note.strip(), _parse_quota_gib(quota_gib), dns.strip()) + try: + service.update_peer( + db, + peer, + note.strip(), + _parse_quota_gib(quota_gib), + dns.strip(), + extra_allowed_ips.strip(), + client_allowed_ips.strip(), + ) + except ValueError as exc: + return RedirectResponse( + f"/peers/{peer_id}?error={quote(str(exc))}", status_code=303 + ) return RedirectResponse(f"/peers/{peer_id}", status_code=303) @@ -274,6 +303,8 @@ def api_status(db: Session = Depends(get_db)): "address": peer.address, "enabled": peer.enabled, "note": peer.note, + "extra_allowed_ips": peer.extra_allowed_ips, + "client_allowed_ips": peer.client_allowed_ips, "quota_bytes": peer.quota_bytes, "cum_rx": peer.cum_rx, "cum_tx": peer.cum_tx, diff --git a/app/models.py b/app/models.py index 4933abf..93dfcdc 100644 --- a/app/models.py +++ b/app/models.py @@ -24,6 +24,8 @@ class Peer(Base): created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) note: Mapped[str] = mapped_column(String(256), default="") dns: Mapped[str] = mapped_column(String(128), default="") + extra_allowed_ips: Mapped[str] = mapped_column(String(512), default="") + client_allowed_ips: Mapped[str] = mapped_column(String(512), default="") quota_bytes: Mapped[int] = mapped_column(Integer, default=0) cum_rx: Mapped[int] = mapped_column(Integer, default=0) cum_tx: Mapped[int] = mapped_column(Integer, default=0) diff --git a/app/service.py b/app/service.py index a82a8d1..89a9a4d 100644 --- a/app/service.py +++ b/app/service.py @@ -24,6 +24,8 @@ def create_peer( quota_bytes: int = 0, address: str = "", dns: str = "", + extra_allowed_ips: str = "", + client_allowed_ips: str = "", ) -> Peer: private, public = wireguard.generate_keypair() psk = wireguard.genpsk() @@ -42,6 +44,8 @@ def create_peer( note=note, quota_bytes=quota_bytes, dns=dns, + extra_allowed_ips=wireguard.validate_cidr_list(extra_allowed_ips), + client_allowed_ips=wireguard.validate_cidr_list(client_allowed_ips), ) db.add(peer) db.commit() @@ -69,10 +73,20 @@ def create_peers_batch( return peers -def update_peer(db: Session, peer: Peer, note: str, quota_bytes: int, dns: str = "") -> Peer: +def update_peer( + db: Session, + peer: Peer, + note: str, + quota_bytes: int, + dns: str = "", + extra_allowed_ips: str = "", + client_allowed_ips: str = "", +) -> Peer: peer.note = note peer.quota_bytes = quota_bytes peer.dns = dns + peer.extra_allowed_ips = wireguard.validate_cidr_list(extra_allowed_ips) + peer.client_allowed_ips = wireguard.validate_cidr_list(client_allowed_ips) db.commit() if not peer.over_quota: apply_config(db) diff --git a/app/templates/peer_detail.html b/app/templates/peer_detail.html index 9291a2e..ddfee81 100644 --- a/app/templates/peer_detail.html +++ b/app/templates/peer_detail.html @@ -2,6 +2,9 @@ {% block title %}{{ peer.name }} - WireGuard Admin{% endblock %} {% block breadcrumbs %}/ Peers / {{ peer.name }}{% endblock %} {% block content %} +{% if error %} +

{{ error }}

+{% endif %}

{{ peer.name }}

@@ -93,6 +96,16 @@ value="{{ '%.1f' % (peer.quota_bytes / 1073741824) if peer.quota_bytes else '' }}" class="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"> + +
+
+

Client setup guide

+
+
+

Linux (wg-quick)

+
# Install wireguard tools
+sudo apt install wireguard        # Debian/Ubuntu
+sudo dnf install wireguard-tools  # Fedora/RHEL
+
+# Save the config (downloaded or copied from above)
+sudo mv {{ peer.name }}.conf /etc/wireguard/{{ peer.name }}.conf
+sudo chmod 600 /etc/wireguard/{{ peer.name }}.conf
+
+# Bring the tunnel up / down
+sudo wg-quick up {{ peer.name }}
+sudo wg-quick down {{ peer.name }}
+
+# Start automatically at boot
+sudo systemctl enable --now wg-quick@{{ peer.name }}
+
+# Verify: check handshake and ping the server
+sudo wg show
+ping {{ server_tunnel_ip }}
+
+
+

Linux (NetworkManager)

+
nmcli connection import type wireguard file {{ peer.name }}.conf
+nmcli connection up {{ peer.name }}
+
+
+

macOS / Windows

+

Install the official WireGuard app, then Import Tunnel from File and select the downloaded {{ peer.name }}.conf, or Add Empty Tunnel and paste the config above.

+
+
+

iOS / Android

+

Install the WireGuard app, tap Add Tunnel, then Create from QR code and scan the QR code above.

+
+
+
+