fix(routing): apply VPN route allowlists dynamically after VPN clients load

This commit is contained in:
2026-03-31 04:15:51 +00:00
parent bb32f23d77
commit 1e4b9997f4
5 changed files with 39 additions and 70 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # 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) ## 2026-03-31 - 11.21.4 - fix(deps)
bump @push.rocks/smartvpn to 1.16.4 bump @push.rocks/smartvpn to 1.16.4

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '11.21.4', version: '11.21.5',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -813,12 +813,8 @@ export class DcRouter {
logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration'); logger.log('info', 'HTTP/3: Augmented qualifying HTTPS routes with QUIC/H3 configuration');
} }
// VPN route security injection: restrict vpn.required routes to VPN subnet // Cache constructor routes for RouteConfigManager (without VPN security baked in —
if (this.options.vpnConfig?.enabled) { // applyRoutes() injects VPN security dynamically so it stays current with client changes)
routes = this.injectVpnSecurity(routes);
}
// Cache constructor routes for RouteConfigManager
this.constructorRoutes = [...routes]; this.constructorRoutes = [...routes];
// If we have routes or need a basic SmartProxy instance, create it // If we have routes or need a basic SmartProxy instance, create it
@@ -2142,6 +2138,11 @@ export class DcRouter {
}); });
await this.vpnManager.start(); 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. */ /** Cache for DNS-resolved IPs of VPN-gated domains. TTL: 5 minutes. */
@@ -2166,48 +2167,8 @@ export class DcRouter {
} }
} }
/** // VPN security injection is now handled dynamically by RouteConfigManager.applyRoutes()
* Inject VPN security into routes that have vpn.required === true. // via the getVpnAllowList callback — no longer a separate method here.
* 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;
}
/** /**
* Set up RADIUS server for network authentication * Set up RADIUS server for network authentication

View File

@@ -252,41 +252,42 @@ export class RouteConfigManager {
const enabledRoutes: plugins.smartproxy.IRouteConfig[] = []; 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()) { for (const route of this.getHardcodedRoutes()) {
const name = route.name || ''; const name = route.name || '';
const override = this.overrides.get(name); const override = this.overrides.get(name);
if (override && !override.enabled) { if (override && !override.enabled) {
continue; // Skip disabled hardcoded route continue; // Skip disabled hardcoded route
} }
enabledRoutes.push(route); enabledRoutes.push(injectVpn(route));
} }
// Add enabled programmatic routes (with HTTP/3 and VPN augmentation) // 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()) { for (const stored of this.storedRoutes.values()) {
if (stored.enabled) { if (stored.enabled) {
let route = stored.route; let route = stored.route;
if (http3Config && http3Config.enabled !== false) { if (http3Config && http3Config.enabled !== false) {
route = augmentRouteWithHttp3(route, { enabled: true, ...http3Config }); route = augmentRouteWithHttp3(route, { enabled: true, ...http3Config });
} }
// Inject VPN security for programmatic routes with vpn.required enabledRoutes.push(injectVpn(route));
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);
} }
} }

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '11.21.4', version: '11.21.5',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }