diff --git a/changelog.md b/changelog.md index ad7f7d5..b246107 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-03-30 - 11.19.0 - feat(vpn) +document tag-based VPN access control, declarative clients, and destination policy options + +- Adds documentation for restricting VPN-protected routes with allowedServerDefinedClientTags. +- Documents pre-defined VPN clients in configuration via vpnConfig.clients. +- Describes destinationPolicy behavior for forceTarget, allow, and block traffic handling. +- Updates interface docs to reflect serverDefinedClientTags and revised VPN server status fields. + ## 2026-03-30 - 11.18.0 - feat(vpn-ui) add format selection for VPN client config exports diff --git a/readme.md b/readme.md index 127a2e3..b97b9e1 100644 --- a/readme.md +++ b/readme.md @@ -77,10 +77,13 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community ### 🔐 VPN Access Control (powered by [smartvpn](https://code.foss.global/push.rocks/smartvpn)) - **WireGuard + native transports** — standard WireGuard clients (iOS, Android, macOS, Windows, Linux) plus custom WebSocket/QUIC tunnels - **Route-level VPN gating** — mark any route with `vpn: { required: true }` to restrict access to VPN clients only -- **Rootless operation** — auto-detects privileges: kernel TUN when running as root, userspace NAT (smoltcp) when not -- **Client management** — create, enable, disable, rotate keys, export WireGuard `.conf` files via OpsServer API +- **Tag-based access control** — assign `serverDefinedClientTags` to clients and restrict routes with `allowedServerDefinedClientTags` +- **Constructor-defined clients** — pre-define VPN clients with tags in config for declarative, code-driven setup +- **Rootless operation** — uses userspace NAT (smoltcp) with no root required +- **Destination policy** — configurable `forceTarget`, `block`, or `allow` with allowList/blockList for granular traffic control +- **Client management** — create, enable, disable, rotate keys, export WireGuard/SmartVPN configs via OpsServer API and dashboard - **IP-based enforcement** — VPN clients get IPs from a configurable subnet; SmartProxy enforces `ipAllowList` per route -- **PROXY protocol v2** — in socket mode, the NAT engine sends PP v2 on outbound connections to preserve VPN client identity +- **PROXY protocol v2** — the NAT engine sends PP v2 on outbound connections to preserve VPN client identity ### ⚡ High Performance - **Rust-powered proxy engine** via SmartProxy for maximum throughput @@ -261,7 +264,9 @@ const router = new DcRouter({ vpnConfig: { enabled: true, serverEndpoint: 'vpn.example.com', - wgListenPort: 51820, + clients: [ + { clientId: 'dev-laptop', serverDefinedClientTags: ['engineering'] }, + ], }, // Persistent storage @@ -456,7 +461,17 @@ interface IDcRouterOptions { wgListenPort?: number; // default: 51820 dns?: string[]; // DNS servers pushed to VPN clients serverEndpoint?: string; // Hostname in generated client configs - forwardingMode?: 'tun' | 'socket'; // default: auto-detect (root → tun, else socket) + clients?: Array<{ // Pre-defined VPN clients + clientId: string; + serverDefinedClientTags?: string[]; + description?: string; + }>; + destinationPolicy?: { // Traffic routing policy + default: 'forceTarget' | 'block' | 'allow'; + target?: string; // IP for forceTarget (default: '127.0.0.1') + allowList?: string[]; // Pass through directly + blockList?: string[]; // Always block (overrides allowList) + }; }; // ── HTTP/3 (QUIC) ──────────────────────────────────────────── @@ -1014,17 +1029,33 @@ DcRouter integrates [`@push.rocks/smartvpn`](https://code.foss.global/push.rocks 1. **SmartVPN daemon** runs inside dcrouter with a Rust data plane (WireGuard via `boringtun`, custom protocol via Noise IK) 2. Clients connect and get assigned an IP from the VPN subnet (e.g. `10.8.0.0/24`) -3. Routes with `vpn: { required: true }` get `security.ipAllowList` automatically injected with the VPN subnet -4. SmartProxy enforces the allowlist — only VPN-sourced traffic is accepted on those routes +3. Routes with `vpn: { required: true }` get `security.ipAllowList` automatically injected +4. When `allowedServerDefinedClientTags` is set, only matching client IPs are injected (not the whole subnet) +5. SmartProxy enforces the allowlist — only authorized VPN clients can access protected routes +6. All VPN traffic is forced through SmartProxy via userspace NAT with PROXY protocol v2 — no root required -### Two Operating Modes +### Destination Policy -| Mode | Root Required? | How It Works | -|------|---------------|-------------| -| **TUN** (`forwardingMode: 'tun'`) | Yes | Kernel TUN device — VPN traffic enters the network stack with real VPN IPs | -| **Socket** (`forwardingMode: 'socket'`) | No | Userspace NAT via smoltcp — outbound connections send PROXY protocol v2 to preserve VPN client IPs | +By default, VPN client traffic is redirected to localhost (SmartProxy) via `forceTarget`. You can customize this with a destination policy: -DcRouter auto-detects: if running as root, it uses TUN mode; otherwise, it falls back to socket mode. You can override this with the `forwardingMode` option. +```typescript +// Default: all traffic → SmartProxy +destinationPolicy: { default: 'forceTarget', target: '127.0.0.1' } + +// Allow direct access to a backend subnet +destinationPolicy: { + default: 'forceTarget', + target: '127.0.0.1', + allowList: ['192.168.190.*'], // direct access to this subnet + blockList: ['192.168.190.1'], // except the gateway +} + +// Block everything except specific IPs +destinationPolicy: { + default: 'block', + allowList: ['10.0.0.*', '192.168.1.*'], +} +``` ### Configuration @@ -1032,26 +1063,47 @@ DcRouter auto-detects: if running as root, it uses TUN mode; otherwise, it falls const router = new DcRouter({ vpnConfig: { enabled: true, - subnet: '10.8.0.0/24', // VPN client IP pool (default) - wgListenPort: 51820, // WireGuard UDP port (default) + subnet: '10.8.0.0/24', // VPN client IP pool (default) + wgListenPort: 51820, // WireGuard UDP port (default) serverEndpoint: 'vpn.example.com', // Hostname in generated client configs - dns: ['1.1.1.1', '8.8.8.8'], // DNS servers pushed to clients - // forwardingMode: 'socket', // Override auto-detection + dns: ['1.1.1.1', '8.8.8.8'], // DNS servers pushed to clients + + // Pre-define VPN clients with server-defined tags + clients: [ + { clientId: 'alice-laptop', serverDefinedClientTags: ['engineering'], description: 'Dev laptop' }, + { clientId: 'bob-phone', serverDefinedClientTags: ['engineering', 'mobile'] }, + { clientId: 'carol-desktop', serverDefinedClientTags: ['finance'] }, + ], + + // Optional: customize destination policy (default: forceTarget → localhost) + // destinationPolicy: { default: 'forceTarget', target: '127.0.0.1', allowList: ['192.168.1.*'] }, }, smartProxyConfig: { routes: [ - // This route is VPN-only — non-VPN clients are blocked + // 🔐 VPN-only: any VPN client can access { - name: 'admin-panel', - match: { domains: ['admin.example.com'], ports: [443] }, + name: 'internal-app', + match: { domains: ['internal.example.com'], ports: [443] }, action: { type: 'forward', targets: [{ host: '192.168.1.50', port: 8080 }], tls: { mode: 'terminate', certificate: 'auto' }, }, - vpn: { required: true }, // 🔐 Only VPN clients can access this + vpn: { required: true }, }, - // This route is public — anyone can access it + // 🔐 VPN + tag-restricted: only 'engineering' tagged clients + { + name: 'eng-dashboard', + match: { domains: ['eng.example.com'], ports: [443] }, + action: { + type: 'forward', + targets: [{ host: '192.168.1.51', port: 8080 }], + tls: { mode: 'terminate', certificate: 'auto' }, + }, + vpn: { required: true, allowedServerDefinedClientTags: ['engineering'] }, + // → alice + bob can access, carol cannot + }, + // 🌐 Public: no VPN required { name: 'public-site', match: { domains: ['example.com'], ports: [443] }, @@ -1066,17 +1118,29 @@ const router = new DcRouter({ }); ``` -### Client Management via OpsServer API +### Client Tags -Once the VPN server is running, you can manage clients through the OpsServer dashboard or API: +SmartVPN distinguishes between two types of client tags: + +| Tag Type | Set By | Purpose | +|----------|--------|---------| +| `serverDefinedClientTags` | Admin (via config or API) | **Trusted** — used for route access control | +| `clientDefinedClientTags` | Connecting client | **Informational** — displayed in dashboard, never used for security | + +Routes with `allowedServerDefinedClientTags` only permit VPN clients whose admin-assigned tags match. Clients cannot influence their own server-defined tags. + +### Client Management via OpsServer + +The OpsServer dashboard and API provide full VPN client lifecycle management: - **Create client** — generates WireGuard keypairs, assigns IP, returns a ready-to-use `.conf` file - **Enable / Disable** — toggle client access without deleting - **Rotate keys** — generate fresh keypairs (invalidates old ones) -- **Export config** — re-export in WireGuard or SmartVPN format +- **Export config** — download in WireGuard (`.conf`) or SmartVPN (`.json`) format - **Telemetry** — per-client bytes sent/received, keepalives, rate limiting +- **Delete** — remove a client and revoke access -Standard WireGuard clients on any platform (iOS, Android, macOS, Windows, Linux) can connect using the generated `.conf` file or QR code — no custom VPN software needed. +Standard WireGuard clients on any platform (iOS, Android, macOS, Windows, Linux) can connect using the generated `.conf` file — no custom VPN software needed. ## Certificate Management diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index efd1e99..c0f0a9e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.18.0', + version: '11.19.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_interfaces/readme.md b/ts_interfaces/readme.md index e0b2b64..32ab3e6 100644 --- a/ts_interfaces/readme.md +++ b/ts_interfaces/readme.md @@ -97,13 +97,13 @@ interface IIdentity { | `IRemoteIngressStatus` | Runtime status: connected, publicIp, activeTunnels, lastHeartbeat | | `IRouteRemoteIngress` | Route-level config: enabled flag and optional edgeFilter | | `IDcRouterRouteConfig` | Extended SmartProxy route config with optional `remoteIngress` and `vpn` properties | -| `IRouteVpn` | Route-level VPN config: `required` flag to restrict access to VPN clients | +| `IRouteVpn` | Route-level VPN config: `required` flag and optional `allowedServerDefinedClientTags` | #### VPN Interfaces | Interface | Description | |-----------|-------------| -| `IVpnClient` | Client registration: clientId, enabled, tags, description, assignedIp, timestamps | -| `IVpnServerStatus` | Server status: running, forwardingMode, subnet, wgListenPort, publicKeys, client counts | +| `IVpnClient` | Client registration: clientId, enabled, serverDefinedClientTags, description, assignedIp, timestamps | +| `IVpnServerStatus` | Server status: running, subnet, wgListenPort, publicKeys, client counts | | `IVpnClientTelemetry` | Per-client metrics: bytes sent/received, packets dropped, keepalives, rate limits | ### Request Interfaces (`requests`) diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index efd1e99..c0f0a9e 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.18.0', + version: '11.19.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }