From 1e4b9997f469288211aa181b2007b28ce27884cc Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 31 Mar 2026 04:15:51 +0000 Subject: [PATCH] fix(routing): apply VPN route allowlists dynamically after VPN clients load --- changelog.md | 7 +++ ts/00_commitinfo_data.ts | 2 +- ts/classes.dcrouter.ts | 57 ++++------------------- ts/config/classes.route-config-manager.ts | 41 ++++++++-------- ts_web/00_commitinfo_data.ts | 2 +- 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/changelog.md b/changelog.md index f542c5e..2ca4387 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-31 - 11.21.5 - fix(routing) +apply VPN route allowlists dynamically after VPN clients load + +- Moves VPN security injection for hardcoded and programmatic routes into RouteConfigManager.applyRoutes() so allowlists are generated from current VPN client state. +- Re-applies routes after starting the VPN manager to ensure tag-based ipAllowLists are available once VPN clients are loaded. +- Avoids caching constructor routes with stale VPN security baked in while preserving HTTP/3 route augmentation. + ## 2026-03-31 - 11.21.4 - fix(deps) bump @push.rocks/smartvpn to 1.16.4 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 5545e8c..f6f9f41 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.21.4', + version: '11.21.5', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/classes.dcrouter.ts b/ts/classes.dcrouter.ts index 6a7c043..aa645f2 100644 --- a/ts/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -813,12 +813,8 @@ export class DcRouter { logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration'); } - // VPN route security injection: restrict vpn.required routes to VPN subnet - if (this.options.vpnConfig?.enabled) { - routes = this.injectVpnSecurity(routes); - } - - // Cache constructor routes for RouteConfigManager + // Cache constructor routes for RouteConfigManager (without VPN security baked in — + // applyRoutes() injects VPN security dynamically so it stays current with client changes) this.constructorRoutes = [...routes]; // If we have routes or need a basic SmartProxy instance, create it @@ -2142,6 +2138,11 @@ export class DcRouter { }); await this.vpnManager.start(); + + // Re-apply routes now that VPN clients are loaded — ensures hardcoded routes + // get correct tag-based ipAllowLists (not possible during setupSmartProxy since + // VPN server wasn't ready yet) + this.routeConfigManager?.applyRoutes(); } /** Cache for DNS-resolved IPs of VPN-gated domains. TTL: 5 minutes. */ @@ -2166,48 +2167,8 @@ export class DcRouter { } } - /** - * Inject VPN security into routes that have vpn.required === true. - * Adds the VPN subnet to security.ipAllowList so only VPN clients can access them. - */ - private injectVpnSecurity(routes: plugins.smartproxy.IRouteConfig[]): plugins.smartproxy.IRouteConfig[] { - const vpnSubnet = this.options.vpnConfig?.subnet || '10.8.0.0/24'; - let injectedCount = 0; - - const result = routes.map((route) => { - const dcrouterRoute = route as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig; - if (dcrouterRoute.vpn?.required) { - injectedCount++; - const existing = route.security?.ipAllowList || []; - - let vpnAllowList: string[]; - if (dcrouterRoute.vpn.allowedServerDefinedClientTags?.length && this.vpnManager) { - // Tag-based: only specific client IPs - vpnAllowList = this.vpnManager.getClientIpsForServerDefinedTags( - dcrouterRoute.vpn.allowedServerDefinedClientTags, - ); - } else { - // No tags specified: entire VPN subnet - vpnAllowList = [vpnSubnet]; - } - - return { - ...route, - security: { - ...route.security, - ipAllowList: [...existing, ...vpnAllowList], - }, - }; - } - return route; - }); - - if (injectedCount > 0) { - logger.log('info', `VPN: Injected ipAllowList into ${injectedCount} VPN-protected route(s)`); - } - - return result; - } + // VPN security injection is now handled dynamically by RouteConfigManager.applyRoutes() + // via the getVpnAllowList callback — no longer a separate method here. /** * Set up RADIUS server for network authentication diff --git a/ts/config/classes.route-config-manager.ts b/ts/config/classes.route-config-manager.ts index 676b56a..53381e6 100644 --- a/ts/config/classes.route-config-manager.ts +++ b/ts/config/classes.route-config-manager.ts @@ -252,41 +252,42 @@ export class RouteConfigManager { const enabledRoutes: plugins.smartproxy.IRouteConfig[] = []; - // Add enabled hardcoded routes (respecting overrides) + const http3Config = this.getHttp3Config?.(); + const vpnAllowList = this.getVpnAllowList; + + // Helper: inject VPN security into a route if vpn.required is set + const injectVpn = (route: plugins.smartproxy.IRouteConfig): plugins.smartproxy.IRouteConfig => { + if (!vpnAllowList) return route; + const dcRoute = route as IDcRouterRouteConfig; + if (!dcRoute.vpn?.required) return route; + const allowList = vpnAllowList(dcRoute.vpn.allowedServerDefinedClientTags); + return { + ...route, + security: { + ...route.security, + ipAllowList: [...(route.security?.ipAllowList || []), ...allowList], + }, + }; + }; + + // Add enabled hardcoded routes (respecting overrides, with fresh VPN injection) for (const route of this.getHardcodedRoutes()) { const name = route.name || ''; const override = this.overrides.get(name); if (override && !override.enabled) { continue; // Skip disabled hardcoded route } - enabledRoutes.push(route); + enabledRoutes.push(injectVpn(route)); } // Add enabled programmatic routes (with HTTP/3 and VPN augmentation) - const http3Config = this.getHttp3Config?.(); - const vpnAllowList = this.getVpnAllowList; for (const stored of this.storedRoutes.values()) { if (stored.enabled) { let route = stored.route; if (http3Config && http3Config.enabled !== false) { route = augmentRouteWithHttp3(route, { enabled: true, ...http3Config }); } - // Inject VPN security for programmatic routes with vpn.required - if (vpnAllowList) { - const dcRoute = route as IDcRouterRouteConfig; - if (dcRoute.vpn?.required) { - const existing = route.security?.ipAllowList || []; - const allowList = vpnAllowList(dcRoute.vpn.allowedServerDefinedClientTags); - route = { - ...route, - security: { - ...route.security, - ipAllowList: [...existing, ...allowList], - }, - }; - } - } - enabledRoutes.push(route); + enabledRoutes.push(injectVpn(route)); } } diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 5545e8c..f6f9f41 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.21.4', + version: '11.21.5', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }