Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
455858af0d | |||
b4a0e4be6b | |||
36bea96ac7 | |||
529857220d | |||
3596d35f45 | |||
8dd222443d | |||
18f03c1acf | |||
200635e4bd |
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-05-10 - 15.1.0 - feat(smartproxy)
|
||||||
|
Update documentation and route helper functions; add createPortRange, createSecurityConfig, createStaticFileRoute, and createTestRoute helpers to the readme and tests. Refactor test examples to use the new helper API and remove legacy connection handling files (including the old connection handler and PortRangeManager) to fully embrace the unified route‐based configuration.
|
||||||
|
|
||||||
|
- Added new helper functions (createPortRange, createSecurityConfig, createStaticFileRoute, createTestRoute) in readme and route helpers.
|
||||||
|
- Refactored tests (test.forwarding.examples.ts, test.forwarding.unit.ts, etc.) to update references to the new API.
|
||||||
|
- Removed legacy connection handler and PortRangeManager files to simplify code and align with route‐based configuration.
|
||||||
|
|
||||||
## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation)
|
## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation)
|
||||||
Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
|
Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "15.0.0",
|
"version": "15.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
|
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
@ -463,6 +463,10 @@ Available helper functions:
|
|||||||
- `createBlockRoute()` - Create a route to block specific traffic
|
- `createBlockRoute()` - Create a route to block specific traffic
|
||||||
- `createLoadBalancerRoute()` - Create a route for load balancing
|
- `createLoadBalancerRoute()` - Create a route for load balancing
|
||||||
- `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect
|
- `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect
|
||||||
|
- `createPortRange()` - Helper to create port range configurations from various formats
|
||||||
|
- `createSecurityConfig()` - Helper to create security configuration objects
|
||||||
|
- `createStaticFileRoute()` - Create a route for serving static files
|
||||||
|
- `createTestRoute()` - Create a test route for debugging and testing purposes
|
||||||
|
|
||||||
## What You Can Do with SmartProxy
|
## What You Can Do with SmartProxy
|
||||||
|
|
||||||
|
@ -1,112 +1,197 @@
|
|||||||
import * as plugins from '../ts/plugins.js';
|
import * as path from 'path';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
|
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
|
||||||
import type { TForwardingType } from '../ts/forwarding/config/forwarding-types.js';
|
|
||||||
import type { IDomainConfig } from '../ts/forwarding/config/domain-config.js';
|
|
||||||
import {
|
import {
|
||||||
httpOnly,
|
createHttpRoute,
|
||||||
httpsPassthrough,
|
createHttpsRoute,
|
||||||
tlsTerminateToHttp,
|
createPassthroughRoute,
|
||||||
tlsTerminateToHttps
|
createRedirectRoute,
|
||||||
} from '../ts/forwarding/config/forwarding-types.js';
|
createHttpToHttpsRedirect,
|
||||||
|
createBlockRoute,
|
||||||
|
createLoadBalancerRoute,
|
||||||
|
createHttpsServer,
|
||||||
|
createPortRange,
|
||||||
|
createSecurityConfig,
|
||||||
|
createStaticFileRoute,
|
||||||
|
createTestRoute
|
||||||
|
} from '../ts/proxies/smart-proxy/route-helpers/index.js';
|
||||||
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
||||||
|
|
||||||
// Test to demonstrate various forwarding configurations
|
// Test to demonstrate various route configurations using the new helpers
|
||||||
tap.test('Forwarding configuration examples', async (tools) => {
|
tap.test('Route-based configuration examples', async (tools) => {
|
||||||
// Example 1: HTTP-only configuration
|
// Example 1: HTTP-only configuration
|
||||||
const httpOnlyConfig: IDomainConfig = {
|
const httpOnlyRoute = createHttpRoute({
|
||||||
domains: ['http.example.com'],
|
domains: 'http.example.com',
|
||||||
forwarding: httpOnly({
|
|
||||||
target: {
|
target: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 3000
|
port: 3000
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
allowedIps: ['*'] // Allow all
|
allowedIps: ['*'] // Allow all
|
||||||
}
|
},
|
||||||
})
|
name: 'Basic HTTP Route'
|
||||||
};
|
});
|
||||||
console.log(httpOnlyConfig.forwarding, 'HTTP-only configuration created successfully');
|
|
||||||
expect(httpOnlyConfig.forwarding.type).toEqual('http-only');
|
|
||||||
|
|
||||||
// Example 2: HTTPS Passthrough (SNI)
|
console.log('HTTP-only route created successfully:', httpOnlyRoute.name);
|
||||||
const httpsPassthroughConfig: IDomainConfig = {
|
expect(httpOnlyRoute.action.type).toEqual('forward');
|
||||||
domains: ['pass.example.com'],
|
expect(httpOnlyRoute.match.domains).toEqual('http.example.com');
|
||||||
forwarding: httpsPassthrough({
|
|
||||||
|
// Example 2: HTTPS Passthrough (SNI) configuration
|
||||||
|
const httpsPassthroughRoute = createPassthroughRoute({
|
||||||
|
domains: 'pass.example.com',
|
||||||
target: {
|
target: {
|
||||||
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
|
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
|
||||||
port: 443
|
port: 443
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
allowedIps: ['*'] // Allow all
|
allowedIps: ['*'] // Allow all
|
||||||
}
|
},
|
||||||
})
|
name: 'HTTPS Passthrough Route'
|
||||||
};
|
});
|
||||||
expect(httpsPassthroughConfig.forwarding).toBeTruthy();
|
|
||||||
expect(httpsPassthroughConfig.forwarding.type).toEqual('https-passthrough');
|
expect(httpsPassthroughRoute).toBeTruthy();
|
||||||
expect(Array.isArray(httpsPassthroughConfig.forwarding.target.host)).toBeTrue();
|
expect(httpsPassthroughRoute.action.tls?.mode).toEqual('passthrough');
|
||||||
|
expect(Array.isArray(httpsPassthroughRoute.action.target?.host)).toBeTrue();
|
||||||
|
|
||||||
// Example 3: HTTPS Termination to HTTP Backend
|
// Example 3: HTTPS Termination to HTTP Backend
|
||||||
const terminateToHttpConfig: IDomainConfig = {
|
const terminateToHttpRoute = createHttpsRoute({
|
||||||
domains: ['secure.example.com'],
|
domains: 'secure.example.com',
|
||||||
forwarding: tlsTerminateToHttp({
|
|
||||||
target: {
|
target: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 8080
|
port: 8080
|
||||||
},
|
},
|
||||||
http: {
|
tlsMode: 'terminate',
|
||||||
redirectToHttps: true, // Redirect HTTP requests to HTTPS
|
certificate: 'auto',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Forwarded-Proto': 'https'
|
'X-Forwarded-Proto': 'https'
|
||||||
}
|
|
||||||
},
|
|
||||||
acme: {
|
|
||||||
enabled: true,
|
|
||||||
maintenance: true,
|
|
||||||
production: false // Use staging ACME server for testing
|
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
allowedIps: ['*'] // Allow all
|
allowedIps: ['*'] // Allow all
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
expect(terminateToHttpConfig.forwarding).toBeTruthy();
|
|
||||||
expect(terminateToHttpConfig.forwarding.type).toEqual('https-terminate-to-http');
|
|
||||||
expect(terminateToHttpConfig.forwarding.http?.redirectToHttps).toBeTrue();
|
|
||||||
|
|
||||||
// Example 4: HTTPS Termination to HTTPS Backend
|
|
||||||
const terminateToHttpsConfig: IDomainConfig = {
|
|
||||||
domains: ['proxy.example.com'],
|
|
||||||
forwarding: tlsTerminateToHttps({
|
|
||||||
target: {
|
|
||||||
host: 'internal-api.local',
|
|
||||||
port: 8443
|
|
||||||
},
|
},
|
||||||
https: {
|
name: 'HTTPS Termination to HTTP Backend'
|
||||||
forwardSni: true // Forward original SNI info
|
});
|
||||||
|
|
||||||
|
// Create the HTTP to HTTPS redirect for this domain
|
||||||
|
const httpToHttpsRedirect = createHttpToHttpsRedirect({
|
||||||
|
domains: 'secure.example.com',
|
||||||
|
name: 'HTTP to HTTPS Redirect for secure.example.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(terminateToHttpRoute).toBeTruthy();
|
||||||
|
expect(terminateToHttpRoute.action.tls?.mode).toEqual('terminate');
|
||||||
|
expect(terminateToHttpRoute.action.advanced?.headers?.['X-Forwarded-Proto']).toEqual('https');
|
||||||
|
expect(httpToHttpsRedirect.action.type).toEqual('redirect');
|
||||||
|
|
||||||
|
// Example 4: Load Balancer with HTTPS
|
||||||
|
const loadBalancerRoute = createLoadBalancerRoute({
|
||||||
|
domains: 'proxy.example.com',
|
||||||
|
targets: ['internal-api-1.local', 'internal-api-2.local'],
|
||||||
|
targetPort: 8443,
|
||||||
|
tlsMode: 'terminate-and-reencrypt',
|
||||||
|
certificate: 'auto',
|
||||||
|
headers: {
|
||||||
|
'X-Original-Host': '{domain}'
|
||||||
},
|
},
|
||||||
security: {
|
security: {
|
||||||
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
|
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
|
||||||
maxConnections: 1000
|
maxConnections: 1000
|
||||||
},
|
},
|
||||||
advanced: {
|
name: 'Load Balanced HTTPS Route'
|
||||||
timeout: 3600000, // 1 hour in ms
|
});
|
||||||
|
|
||||||
|
expect(loadBalancerRoute).toBeTruthy();
|
||||||
|
expect(loadBalancerRoute.action.tls?.mode).toEqual('terminate-and-reencrypt');
|
||||||
|
expect(Array.isArray(loadBalancerRoute.action.target?.host)).toBeTrue();
|
||||||
|
expect(loadBalancerRoute.action.security?.allowedIps?.length).toEqual(2);
|
||||||
|
|
||||||
|
// Example 5: Block specific IPs
|
||||||
|
const blockRoute = createBlockRoute({
|
||||||
|
ports: [80, 443],
|
||||||
|
clientIp: ['192.168.5.0/24'],
|
||||||
|
name: 'Block Suspicious IPs',
|
||||||
|
priority: 1000 // High priority to ensure it's evaluated first
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(blockRoute.action.type).toEqual('block');
|
||||||
|
expect(blockRoute.match.clientIp?.length).toEqual(1);
|
||||||
|
expect(blockRoute.priority).toEqual(1000);
|
||||||
|
|
||||||
|
// Example 6: Complete HTTPS Server with HTTP Redirect
|
||||||
|
const httpsServerRoutes = createHttpsServer({
|
||||||
|
domains: 'complete.example.com',
|
||||||
|
target: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
certificate: 'auto',
|
||||||
|
name: 'Complete HTTPS Server'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(Array.isArray(httpsServerRoutes)).toBeTrue();
|
||||||
|
expect(httpsServerRoutes.length).toEqual(2); // HTTPS route and HTTP redirect
|
||||||
|
expect(httpsServerRoutes[0].action.tls?.mode).toEqual('terminate');
|
||||||
|
expect(httpsServerRoutes[1].action.type).toEqual('redirect');
|
||||||
|
|
||||||
|
// Example 7: Static File Server
|
||||||
|
const staticFileRoute = createStaticFileRoute({
|
||||||
|
domains: 'static.example.com',
|
||||||
|
targetDirectory: '/var/www/static',
|
||||||
|
tlsMode: 'terminate',
|
||||||
|
certificate: 'auto',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Original-Host': '{sni}'
|
'Cache-Control': 'public, max-age=86400'
|
||||||
}
|
},
|
||||||
}
|
name: 'Static File Server'
|
||||||
})
|
});
|
||||||
};
|
|
||||||
expect(terminateToHttpsConfig.forwarding).toBeTruthy();
|
|
||||||
expect(terminateToHttpsConfig.forwarding.type).toEqual('https-terminate-to-https');
|
|
||||||
expect(terminateToHttpsConfig.forwarding.https?.forwardSni).toBeTrue();
|
|
||||||
expect(terminateToHttpsConfig.forwarding.security?.allowedIps?.length).toEqual(2);
|
|
||||||
|
|
||||||
// Skip the SmartProxy integration test for now and just verify our configuration objects work
|
expect(staticFileRoute.action.advanced?.staticFiles?.directory).toEqual('/var/www/static');
|
||||||
console.log('All forwarding configurations were created successfully');
|
expect(staticFileRoute.action.advanced?.headers?.['Cache-Control']).toEqual('public, max-age=86400');
|
||||||
|
|
||||||
// This is just to verify that our test passes
|
// Example 8: Test Route for Debugging
|
||||||
expect(true).toBeTrue();
|
const testRoute = createTestRoute({
|
||||||
|
ports: 8000,
|
||||||
|
domains: 'test.example.com',
|
||||||
|
response: {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ status: 'ok', message: 'API is working!' })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(testRoute.match.ports).toEqual(8000);
|
||||||
|
expect(testRoute.action.advanced?.testResponse?.status).toEqual(200);
|
||||||
|
|
||||||
|
// Create a SmartProxy instance with all routes
|
||||||
|
const allRoutes: IRouteConfig[] = [
|
||||||
|
httpOnlyRoute,
|
||||||
|
httpsPassthroughRoute,
|
||||||
|
terminateToHttpRoute,
|
||||||
|
httpToHttpsRedirect,
|
||||||
|
loadBalancerRoute,
|
||||||
|
blockRoute,
|
||||||
|
...httpsServerRoutes,
|
||||||
|
staticFileRoute,
|
||||||
|
testRoute
|
||||||
|
];
|
||||||
|
|
||||||
|
// We're not actually starting the SmartProxy in this test,
|
||||||
|
// just verifying that the configuration is valid
|
||||||
|
const smartProxy = new SmartProxy({
|
||||||
|
routes: allRoutes,
|
||||||
|
acme: {
|
||||||
|
email: 'admin@example.com',
|
||||||
|
termsOfServiceAgreed: true,
|
||||||
|
directoryUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Smart Proxy configured with ${allRoutes.length} routes`);
|
||||||
|
|
||||||
|
// Verify our example proxy was created correctly
|
||||||
|
expect(smartProxy).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '15.0.0',
|
version: '15.1.0',
|
||||||
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import type { IDomainConfig, ISmartProxyOptions } from './models/interfaces.js';
|
|||||||
import type { TForwardingType, IForwardConfig } from '../../forwarding/config/forwarding-types.js';
|
import type { TForwardingType, IForwardConfig } from '../../forwarding/config/forwarding-types.js';
|
||||||
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
|
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
|
||||||
import { ForwardingHandlerFactory } from '../../forwarding/factory/forwarding-factory.js';
|
import { ForwardingHandlerFactory } from '../../forwarding/factory/forwarding-factory.js';
|
||||||
|
import type { IRouteConfig } from './models/route-types.js';
|
||||||
|
import { RouteManager } from './route-manager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages domain configurations and target selection
|
* Manages domain configurations and target selection
|
||||||
@ -14,13 +16,112 @@ export class DomainConfigManager {
|
|||||||
// Cache forwarding handlers for each domain config
|
// Cache forwarding handlers for each domain config
|
||||||
private forwardingHandlers: Map<IDomainConfig, ForwardingHandler> = new Map();
|
private forwardingHandlers: Map<IDomainConfig, ForwardingHandler> = new Map();
|
||||||
|
|
||||||
constructor(private settings: ISmartProxyOptions) {}
|
// Store derived domain configs from routes
|
||||||
|
private derivedDomainConfigs: IDomainConfig[] = [];
|
||||||
|
|
||||||
|
// Reference to RouteManager for route-based configuration
|
||||||
|
private routeManager?: RouteManager;
|
||||||
|
|
||||||
|
constructor(private settings: ISmartProxyOptions) {
|
||||||
|
// Initialize with derived domain configs if using route-based configuration
|
||||||
|
if (settings.routes && !settings.domainConfigs) {
|
||||||
|
this.generateDomainConfigsFromRoutes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the route manager reference for route-based queries
|
||||||
|
*/
|
||||||
|
public setRouteManager(routeManager: RouteManager): void {
|
||||||
|
this.routeManager = routeManager;
|
||||||
|
|
||||||
|
// Regenerate domain configs from routes if needed
|
||||||
|
if (this.settings.routes && (!this.settings.domainConfigs || this.settings.domainConfigs.length === 0)) {
|
||||||
|
this.generateDomainConfigsFromRoutes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate domain configs from routes
|
||||||
|
*/
|
||||||
|
public generateDomainConfigsFromRoutes(): void {
|
||||||
|
this.derivedDomainConfigs = [];
|
||||||
|
|
||||||
|
if (!this.settings.routes) return;
|
||||||
|
|
||||||
|
for (const route of this.settings.routes) {
|
||||||
|
if (route.action.type !== 'forward' || !route.match.domains) continue;
|
||||||
|
|
||||||
|
// Convert route to domain config
|
||||||
|
const domainConfig = this.routeToDomainConfig(route);
|
||||||
|
if (domainConfig) {
|
||||||
|
this.derivedDomainConfigs.push(domainConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a route to a domain config
|
||||||
|
*/
|
||||||
|
private routeToDomainConfig(route: IRouteConfig): IDomainConfig | null {
|
||||||
|
if (route.action.type !== 'forward' || !route.action.target) return null;
|
||||||
|
|
||||||
|
// Get domains from route
|
||||||
|
const domains = Array.isArray(route.match.domains) ?
|
||||||
|
route.match.domains :
|
||||||
|
(route.match.domains ? [route.match.domains] : []);
|
||||||
|
|
||||||
|
if (domains.length === 0) return null;
|
||||||
|
|
||||||
|
// Determine forwarding type based on TLS mode
|
||||||
|
let forwardingType: TForwardingType = 'http-only';
|
||||||
|
if (route.action.tls) {
|
||||||
|
switch (route.action.tls.mode) {
|
||||||
|
case 'passthrough':
|
||||||
|
forwardingType = 'https-passthrough';
|
||||||
|
break;
|
||||||
|
case 'terminate':
|
||||||
|
forwardingType = 'https-terminate-to-http';
|
||||||
|
break;
|
||||||
|
case 'terminate-and-reencrypt':
|
||||||
|
forwardingType = 'https-terminate-to-https';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create domain config
|
||||||
|
return {
|
||||||
|
domains,
|
||||||
|
forwarding: {
|
||||||
|
type: forwardingType,
|
||||||
|
target: {
|
||||||
|
host: route.action.target.host,
|
||||||
|
port: route.action.target.port
|
||||||
|
},
|
||||||
|
security: route.action.security ? {
|
||||||
|
allowedIps: route.action.security.allowedIps,
|
||||||
|
blockedIps: route.action.security.blockedIps,
|
||||||
|
maxConnections: route.action.security.maxConnections
|
||||||
|
} : undefined,
|
||||||
|
https: route.action.tls && route.action.tls.certificate !== 'auto' ? {
|
||||||
|
customCert: route.action.tls.certificate
|
||||||
|
} : undefined,
|
||||||
|
advanced: route.action.advanced
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the domain configurations
|
* Updates the domain configurations
|
||||||
*/
|
*/
|
||||||
public updateDomainConfigs(newDomainConfigs: IDomainConfig[]): void {
|
public updateDomainConfigs(newDomainConfigs: IDomainConfig[]): void {
|
||||||
|
// If we're using domainConfigs property, update it
|
||||||
|
if (this.settings.domainConfigs) {
|
||||||
this.settings.domainConfigs = newDomainConfigs;
|
this.settings.domainConfigs = newDomainConfigs;
|
||||||
|
} else {
|
||||||
|
// Otherwise update our derived configs
|
||||||
|
this.derivedDomainConfigs = newDomainConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset target indices for removed configs
|
// Reset target indices for removed configs
|
||||||
const currentConfigSet = new Set(newDomainConfigs);
|
const currentConfigSet = new Set(newDomainConfigs);
|
||||||
@ -60,7 +161,8 @@ export class DomainConfigManager {
|
|||||||
* Get all domain configurations
|
* Get all domain configurations
|
||||||
*/
|
*/
|
||||||
public getDomainConfigs(): IDomainConfig[] {
|
public getDomainConfigs(): IDomainConfig[] {
|
||||||
return this.settings.domainConfigs;
|
// Use domainConfigs from settings if available, otherwise use derived configs
|
||||||
|
return this.settings.domainConfigs || this.derivedDomainConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,23 +171,64 @@ export class DomainConfigManager {
|
|||||||
public findDomainConfig(serverName: string): IDomainConfig | undefined {
|
public findDomainConfig(serverName: string): IDomainConfig | undefined {
|
||||||
if (!serverName) return undefined;
|
if (!serverName) return undefined;
|
||||||
|
|
||||||
return this.settings.domainConfigs.find((config) =>
|
// Get domain configs from the appropriate source
|
||||||
config.domains.some((d) => plugins.minimatch(serverName, d))
|
const domainConfigs = this.getDomainConfigs();
|
||||||
);
|
|
||||||
|
// Check for direct match
|
||||||
|
for (const config of domainConfigs) {
|
||||||
|
if (config.domains.some(d => plugins.minimatch(serverName, d))) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match found
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find domain config for a specific port
|
* Find domain config for a specific port
|
||||||
*/
|
*/
|
||||||
public findDomainConfigForPort(port: number): IDomainConfig | undefined {
|
public findDomainConfigForPort(port: number): IDomainConfig | undefined {
|
||||||
return this.settings.domainConfigs.find(
|
// Get domain configs from the appropriate source
|
||||||
(domain) => {
|
const domainConfigs = this.getDomainConfigs();
|
||||||
|
|
||||||
|
// Check if any domain config has a matching port range
|
||||||
|
for (const domain of domainConfigs) {
|
||||||
const portRanges = domain.forwarding?.advanced?.portRanges;
|
const portRanges = domain.forwarding?.advanced?.portRanges;
|
||||||
return portRanges &&
|
if (portRanges && portRanges.length > 0 && this.isPortInRanges(port, portRanges)) {
|
||||||
portRanges.length > 0 &&
|
return domain;
|
||||||
this.isPortInRanges(port, portRanges);
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// If we're in route-based mode, also check routes for this port
|
||||||
|
if (this.settings.routes && (!this.settings.domainConfigs || this.settings.domainConfigs.length === 0)) {
|
||||||
|
const routesForPort = this.settings.routes.filter(route => {
|
||||||
|
// Check if this port is in the route's ports
|
||||||
|
if (typeof route.match.ports === 'number') {
|
||||||
|
return route.match.ports === port;
|
||||||
|
} else if (Array.isArray(route.match.ports)) {
|
||||||
|
return route.match.ports.some(p => {
|
||||||
|
if (typeof p === 'number') {
|
||||||
|
return p === port;
|
||||||
|
} else if (p.from && p.to) {
|
||||||
|
return port >= p.from && port <= p.to;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we found any routes for this port, convert the first one to a domain config
|
||||||
|
if (routesForPort.length > 0 && routesForPort[0].action.type === 'forward') {
|
||||||
|
const domainConfig = this.routeToDomainConfig(routesForPort[0]);
|
||||||
|
if (domainConfig) {
|
||||||
|
return domainConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -14,62 +14,16 @@ export type TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'ht
|
|||||||
export type IRoutedSmartProxyOptions = ISmartProxyOptions;
|
export type IRoutedSmartProxyOptions = ISmartProxyOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy domain configuration interface for backward compatibility
|
* Helper functions for type checking configuration types
|
||||||
*/
|
|
||||||
export interface IDomainConfig {
|
|
||||||
domains: string[];
|
|
||||||
forwarding: {
|
|
||||||
type: TForwardingType;
|
|
||||||
target: {
|
|
||||||
host: string | string[];
|
|
||||||
port: number;
|
|
||||||
};
|
|
||||||
acme?: {
|
|
||||||
enabled?: boolean;
|
|
||||||
maintenance?: boolean;
|
|
||||||
production?: boolean;
|
|
||||||
forwardChallenges?: {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
useTls?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
http?: {
|
|
||||||
enabled?: boolean;
|
|
||||||
redirectToHttps?: boolean;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
https?: {
|
|
||||||
customCert?: {
|
|
||||||
key: string;
|
|
||||||
cert: string;
|
|
||||||
};
|
|
||||||
forwardSni?: boolean;
|
|
||||||
};
|
|
||||||
security?: {
|
|
||||||
allowedIps?: string[];
|
|
||||||
blockedIps?: string[];
|
|
||||||
maxConnections?: number;
|
|
||||||
};
|
|
||||||
advanced?: {
|
|
||||||
portRanges?: Array<{ from: number; to: number }>;
|
|
||||||
networkProxyPort?: number;
|
|
||||||
keepAlive?: boolean;
|
|
||||||
timeout?: number;
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper functions for type checking - now always assume route-based config
|
|
||||||
*/
|
*/
|
||||||
export function isLegacyOptions(options: any): boolean {
|
export function isLegacyOptions(options: any): boolean {
|
||||||
return false; // No longer supporting legacy options
|
// Legacy options are no longer supported
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRoutedOptions(options: any): boolean {
|
export function isRoutedOptions(options: any): boolean {
|
||||||
return true; // Always assume routed options
|
// All configurations are now route-based
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,14 +33,7 @@ export interface ISmartProxyOptions {
|
|||||||
// The unified configuration array (required)
|
// The unified configuration array (required)
|
||||||
routes: IRouteConfig[];
|
routes: IRouteConfig[];
|
||||||
|
|
||||||
// Legacy options for backward compatibility
|
// Port range configuration
|
||||||
fromPort?: number;
|
|
||||||
toPort?: number;
|
|
||||||
sniEnabled?: boolean;
|
|
||||||
domainConfigs?: IDomainConfig[];
|
|
||||||
targetIP?: string;
|
|
||||||
defaultAllowedIPs?: string[];
|
|
||||||
defaultBlockedIPs?: string[];
|
|
||||||
globalPortRanges?: Array<{ from: number; to: number }>;
|
globalPortRanges?: Array<{ from: number; to: number }>;
|
||||||
forwardAllGlobalRanges?: boolean;
|
forwardAllGlobalRanges?: boolean;
|
||||||
preserveSourceIP?: boolean;
|
preserveSourceIP?: boolean;
|
||||||
@ -98,8 +45,8 @@ export interface ISmartProxyOptions {
|
|||||||
port: number; // Default port to use when not specified in routes
|
port: number; // Default port to use when not specified in routes
|
||||||
};
|
};
|
||||||
security?: {
|
security?: {
|
||||||
allowedIPs?: string[]; // Default allowed IPs
|
allowedIps?: string[]; // Default allowed IPs
|
||||||
blockedIPs?: string[]; // Default blocked IPs
|
blockedIps?: string[]; // Default blocked IPs
|
||||||
maxConnections?: number; // Default max connections
|
maxConnections?: number; // Default max connections
|
||||||
};
|
};
|
||||||
preserveSourceIP?: boolean; // Default source IP preservation
|
preserveSourceIP?: boolean; // Default source IP preservation
|
||||||
@ -183,9 +130,6 @@ export interface IConnectionRecord {
|
|||||||
pendingData: Buffer[]; // Buffer to hold data during connection setup
|
pendingData: Buffer[]; // Buffer to hold data during connection setup
|
||||||
pendingDataSize: number; // Track total size of pending data
|
pendingDataSize: number; // Track total size of pending data
|
||||||
|
|
||||||
// Legacy property for backward compatibility
|
|
||||||
domainConfig?: IDomainConfig;
|
|
||||||
|
|
||||||
// Enhanced tracking fields
|
// Enhanced tracking fields
|
||||||
bytesReceived: number; // Total bytes received
|
bytesReceived: number; // Total bytes received
|
||||||
bytesSent: number; // Total bytes sent
|
bytesSent: number; // Total bytes sent
|
||||||
|
@ -61,6 +61,25 @@ export interface IRouteRedirect {
|
|||||||
status: 301 | 302 | 307 | 308;
|
status: 301 | 302 | 307 | 308;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication options
|
||||||
|
*/
|
||||||
|
export interface IRouteAuthentication {
|
||||||
|
type: 'basic' | 'digest' | 'oauth' | 'jwt';
|
||||||
|
credentials?: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}[];
|
||||||
|
realm?: string;
|
||||||
|
jwtSecret?: string;
|
||||||
|
jwtIssuer?: string;
|
||||||
|
oauthProvider?: string;
|
||||||
|
oauthClientId?: string;
|
||||||
|
oauthClientSecret?: string;
|
||||||
|
oauthRedirectUri?: string;
|
||||||
|
[key: string]: any; // Allow additional auth-specific options
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security options for route actions
|
* Security options for route actions
|
||||||
*/
|
*/
|
||||||
@ -68,10 +87,28 @@ export interface IRouteSecurity {
|
|||||||
allowedIps?: string[];
|
allowedIps?: string[];
|
||||||
blockedIps?: string[];
|
blockedIps?: string[];
|
||||||
maxConnections?: number;
|
maxConnections?: number;
|
||||||
authentication?: {
|
authentication?: IRouteAuthentication;
|
||||||
type: 'basic' | 'digest' | 'oauth';
|
}
|
||||||
// Auth-specific options would go here
|
|
||||||
};
|
/**
|
||||||
|
* Static file server configuration
|
||||||
|
*/
|
||||||
|
export interface IRouteStaticFiles {
|
||||||
|
directory: string;
|
||||||
|
indexFiles?: string[];
|
||||||
|
cacheControl?: string;
|
||||||
|
expires?: number;
|
||||||
|
followSymlinks?: boolean;
|
||||||
|
disableDirectoryListing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test route response configuration
|
||||||
|
*/
|
||||||
|
export interface IRouteTestResponse {
|
||||||
|
status: number;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,6 +118,8 @@ export interface IRouteAdvanced {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
|
staticFiles?: IRouteStaticFiles;
|
||||||
|
testResponse?: IRouteTestResponse;
|
||||||
// Additional advanced options would go here
|
// Additional advanced options would go here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,19 @@ import { Port80Handler } from '../../http/port80/port80-handler.js';
|
|||||||
import { Port80HandlerEvents } from '../../core/models/common-types.js';
|
import { Port80HandlerEvents } from '../../core/models/common-types.js';
|
||||||
import { subscribeToPort80Handler } from '../../core/utils/event-utils.js';
|
import { subscribeToPort80Handler } from '../../core/utils/event-utils.js';
|
||||||
import type { ICertificateData } from '../../certificate/models/certificate-types.js';
|
import type { ICertificateData } from '../../certificate/models/certificate-types.js';
|
||||||
import type { IConnectionRecord, ISmartProxyOptions, IDomainConfig } from './models/interfaces.js';
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
||||||
|
import type { IRouteConfig } from './models/route-types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages NetworkProxy integration for TLS termination
|
* Manages NetworkProxy integration for TLS termination
|
||||||
|
*
|
||||||
|
* NetworkProxyBridge connects SmartProxy with NetworkProxy to handle TLS termination.
|
||||||
|
* It converts route configurations to NetworkProxy configuration format and manages
|
||||||
|
* certificate provisioning through Port80Handler when ACME is enabled.
|
||||||
|
*
|
||||||
|
* It is used by SmartProxy for routes that have:
|
||||||
|
* - TLS mode of 'terminate' or 'terminate-and-reencrypt'
|
||||||
|
* - Certificate set to 'auto' or custom certificate
|
||||||
*/
|
*/
|
||||||
export class NetworkProxyBridge {
|
export class NetworkProxyBridge {
|
||||||
private networkProxy: NetworkProxy | null = null;
|
private networkProxy: NetworkProxy | null = null;
|
||||||
@ -58,8 +67,8 @@ export class NetworkProxyBridge {
|
|||||||
this.networkProxy.setExternalPort80Handler(this.port80Handler);
|
this.networkProxy.setExternalPort80Handler(this.port80Handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert and apply domain configurations to NetworkProxy
|
// Apply route configurations to NetworkProxy
|
||||||
await this.syncDomainConfigsToNetworkProxy();
|
await this.syncRoutesToNetworkProxy(this.settings.routes || []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,9 +258,19 @@ export class NetworkProxyBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes domain configurations to NetworkProxy
|
* Synchronizes routes to NetworkProxy
|
||||||
|
*
|
||||||
|
* This method converts route configurations to NetworkProxy format and updates
|
||||||
|
* the NetworkProxy with the converted configurations. It handles:
|
||||||
|
*
|
||||||
|
* - Extracting domain, target, and certificate information from routes
|
||||||
|
* - Converting TLS mode settings to NetworkProxy configuration
|
||||||
|
* - Applying security and advanced settings
|
||||||
|
* - Registering domains for ACME certificate provisioning when needed
|
||||||
|
*
|
||||||
|
* @param routes The route configurations to sync to NetworkProxy
|
||||||
*/
|
*/
|
||||||
public async syncDomainConfigsToNetworkProxy(): Promise<void> {
|
public async syncRoutesToNetworkProxy(routes: IRouteConfig[]): Promise<void> {
|
||||||
if (!this.networkProxy) {
|
if (!this.networkProxy) {
|
||||||
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
||||||
return;
|
return;
|
||||||
@ -282,39 +301,107 @@ export class NetworkProxyBridge {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert domain configs to NetworkProxy configs
|
// Convert routes to NetworkProxy configs
|
||||||
const proxyConfigs = this.networkProxy.convertSmartProxyConfigs(
|
const proxyConfigs = this.convertRoutesToNetworkProxyConfigs(routes, certPair);
|
||||||
this.settings.domainConfigs,
|
|
||||||
certPair
|
|
||||||
);
|
|
||||||
|
|
||||||
// Log ACME-eligible domains
|
// Update the proxy configs
|
||||||
const acmeEnabled = !!this.settings.acme?.enabled;
|
|
||||||
if (acmeEnabled) {
|
|
||||||
const acmeEligibleDomains = proxyConfigs
|
|
||||||
.filter((config) => !config.hostName.includes('*')) // Exclude wildcards
|
|
||||||
.map((config) => config.hostName);
|
|
||||||
|
|
||||||
if (acmeEligibleDomains.length > 0) {
|
|
||||||
console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
|
|
||||||
|
|
||||||
// Register these domains with Port80Handler if available
|
|
||||||
if (this.port80Handler) {
|
|
||||||
this.registerDomainsWithPort80Handler(acmeEligibleDomains);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No domains eligible for ACME certificates found in configuration');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update NetworkProxy with the converted configs
|
|
||||||
await this.networkProxy.updateProxyConfigs(proxyConfigs);
|
await this.networkProxy.updateProxyConfigs(proxyConfigs);
|
||||||
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
console.log(`Synced ${proxyConfigs.length} configurations to NetworkProxy`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Failed to sync configurations: ${err}`);
|
console.log(`Error syncing routes to NetworkProxy: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert routes to NetworkProxy configuration format
|
||||||
|
*
|
||||||
|
* This method transforms route-based configuration to NetworkProxy's configuration format.
|
||||||
|
* It processes each route and creates appropriate NetworkProxy configs for domains
|
||||||
|
* that require TLS termination.
|
||||||
|
*
|
||||||
|
* @param routes Array of route configurations to convert
|
||||||
|
* @param defaultCertPair Default certificate to use if no custom certificate is specified
|
||||||
|
* @returns Array of NetworkProxy configurations
|
||||||
|
*/
|
||||||
|
public convertRoutesToNetworkProxyConfigs(
|
||||||
|
routes: IRouteConfig[],
|
||||||
|
defaultCertPair: { key: string; cert: string }
|
||||||
|
): plugins.tsclass.network.IReverseProxyConfig[] {
|
||||||
|
const configs: plugins.tsclass.network.IReverseProxyConfig[] = [];
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
// Skip routes without domains
|
||||||
|
if (!route.match.domains) continue;
|
||||||
|
|
||||||
|
// Skip non-forward routes
|
||||||
|
if (route.action.type !== 'forward') continue;
|
||||||
|
|
||||||
|
// Skip routes without TLS configuration
|
||||||
|
if (!route.action.tls || !route.action.target) continue;
|
||||||
|
|
||||||
|
// Get domains from route
|
||||||
|
const domains = Array.isArray(route.match.domains)
|
||||||
|
? route.match.domains
|
||||||
|
: [route.match.domains];
|
||||||
|
|
||||||
|
// Create a config for each domain
|
||||||
|
for (const domain of domains) {
|
||||||
|
// Determine if this route requires TLS termination
|
||||||
|
const needsTermination = route.action.tls.mode === 'terminate' ||
|
||||||
|
route.action.tls.mode === 'terminate-and-reencrypt';
|
||||||
|
|
||||||
|
// Skip passthrough domains for NetworkProxy
|
||||||
|
if (route.action.tls.mode === 'passthrough') continue;
|
||||||
|
|
||||||
|
// Get certificate
|
||||||
|
let certKey = defaultCertPair.key;
|
||||||
|
let certCert = defaultCertPair.cert;
|
||||||
|
|
||||||
|
// Use custom certificate if specified
|
||||||
|
if (route.action.tls.certificate !== 'auto' && typeof route.action.tls.certificate === 'object') {
|
||||||
|
certKey = route.action.tls.certificate.key;
|
||||||
|
certCert = route.action.tls.certificate.cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine target hosts and ports
|
||||||
|
const targetHosts = Array.isArray(route.action.target.host)
|
||||||
|
? route.action.target.host
|
||||||
|
: [route.action.target.host];
|
||||||
|
|
||||||
|
const targetPort = route.action.target.port;
|
||||||
|
|
||||||
|
// Create NetworkProxy config
|
||||||
|
const config: plugins.tsclass.network.IReverseProxyConfig = {
|
||||||
|
hostName: domain,
|
||||||
|
privateKey: certKey,
|
||||||
|
publicKey: certCert,
|
||||||
|
destinationIps: targetHosts,
|
||||||
|
destinationPorts: [targetPort],
|
||||||
|
// Use backendProtocol for TLS re-encryption:
|
||||||
|
backendProtocol: route.action.tls.mode === 'terminate-and-reencrypt' ? 'http2' : 'http1',
|
||||||
|
// Add rewriteHostHeader for host header handling:
|
||||||
|
rewriteHostHeader: route.action.advanced?.headers ? true : false
|
||||||
|
};
|
||||||
|
|
||||||
|
configs.push(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This method is deprecated and will be removed in a future version.
|
||||||
|
* Use syncRoutesToNetworkProxy() instead.
|
||||||
|
*
|
||||||
|
* This legacy method exists only for backward compatibility and
|
||||||
|
* simply forwards to syncRoutesToNetworkProxy().
|
||||||
|
*/
|
||||||
|
public async syncDomainConfigsToNetworkProxy(): Promise<void> {
|
||||||
|
console.log('Method syncDomainConfigsToNetworkProxy is deprecated. Use syncRoutesToNetworkProxy instead.');
|
||||||
|
await this.syncRoutesToNetworkProxy(this.settings.routes || []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request a certificate for a specific domain
|
* Request a certificate for a specific domain
|
||||||
*/
|
*/
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import * as plugins from '../../plugins.js';
|
import * as plugins from '../../plugins.js';
|
||||||
import type {
|
import type {
|
||||||
IConnectionRecord,
|
IConnectionRecord,
|
||||||
IDomainConfig,
|
|
||||||
ISmartProxyOptions
|
ISmartProxyOptions
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import {
|
import {
|
||||||
isRoutedOptions,
|
isRoutedOptions
|
||||||
isLegacyOptions
|
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import type {
|
import type {
|
||||||
IRouteConfig,
|
IRouteConfig,
|
||||||
@ -14,13 +12,11 @@ import type {
|
|||||||
} from './models/route-types.js';
|
} from './models/route-types.js';
|
||||||
import { ConnectionManager } from './connection-manager.js';
|
import { ConnectionManager } from './connection-manager.js';
|
||||||
import { SecurityManager } from './security-manager.js';
|
import { SecurityManager } from './security-manager.js';
|
||||||
import { DomainConfigManager } from './domain-config-manager.js';
|
|
||||||
import { TlsManager } from './tls-manager.js';
|
import { TlsManager } from './tls-manager.js';
|
||||||
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
||||||
import { TimeoutManager } from './timeout-manager.js';
|
import { TimeoutManager } from './timeout-manager.js';
|
||||||
import { RouteManager } from './route-manager.js';
|
import { RouteManager } from './route-manager.js';
|
||||||
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
|
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
|
||||||
import type { TForwardingType } from '../../forwarding/config/forwarding-types.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles new connection processing and setup logic with support for route-based configuration
|
* Handles new connection processing and setup logic with support for route-based configuration
|
||||||
@ -32,7 +28,6 @@ export class RouteConnectionHandler {
|
|||||||
settings: ISmartProxyOptions,
|
settings: ISmartProxyOptions,
|
||||||
private connectionManager: ConnectionManager,
|
private connectionManager: ConnectionManager,
|
||||||
private securityManager: SecurityManager,
|
private securityManager: SecurityManager,
|
||||||
private domainConfigManager: DomainConfigManager,
|
|
||||||
private tlsManager: TlsManager,
|
private tlsManager: TlsManager,
|
||||||
private networkProxyBridge: NetworkProxyBridge,
|
private networkProxyBridge: NetworkProxyBridge,
|
||||||
private timeoutManager: TimeoutManager,
|
private timeoutManager: TimeoutManager,
|
||||||
@ -245,34 +240,17 @@ export class RouteConnectionHandler {
|
|||||||
if (!routeMatch) {
|
if (!routeMatch) {
|
||||||
console.log(`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`);
|
console.log(`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`);
|
||||||
|
|
||||||
// Fall back to legacy matching if we're using a hybrid configuration
|
// No matching route, use default/fallback handling
|
||||||
const domainConfig = serverName
|
|
||||||
? this.domainConfigManager.findDomainConfig(serverName)
|
|
||||||
: this.domainConfigManager.findDomainConfigForPort(localPort);
|
|
||||||
|
|
||||||
if (domainConfig) {
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
|
||||||
console.log(`[${connectionId}] Using legacy domain configuration for ${serverName || 'port ' + localPort}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate this domain config with the connection
|
|
||||||
record.domainConfig = domainConfig;
|
|
||||||
|
|
||||||
// Handle the connection using the legacy setup
|
|
||||||
return this.handleLegacyConnection(socket, record, serverName, domainConfig, initialChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No matching route or domain config, use default/fallback handling
|
|
||||||
console.log(`[${connectionId}] Using default route handling for connection`);
|
console.log(`[${connectionId}] Using default route handling for connection`);
|
||||||
|
|
||||||
// Check default security settings
|
// Check default security settings
|
||||||
const defaultSecuritySettings = this.settings.defaults?.security;
|
const defaultSecuritySettings = this.settings.defaults?.security;
|
||||||
if (defaultSecuritySettings) {
|
if (defaultSecuritySettings) {
|
||||||
if (defaultSecuritySettings.allowedIPs && defaultSecuritySettings.allowedIPs.length > 0) {
|
if (defaultSecuritySettings.allowedIps && defaultSecuritySettings.allowedIps.length > 0) {
|
||||||
const isAllowed = this.securityManager.isIPAuthorized(
|
const isAllowed = this.securityManager.isIPAuthorized(
|
||||||
remoteIP,
|
remoteIP,
|
||||||
defaultSecuritySettings.allowedIPs,
|
defaultSecuritySettings.allowedIps,
|
||||||
defaultSecuritySettings.blockedIPs || []
|
defaultSecuritySettings.blockedIps || []
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isAllowed) {
|
if (!isAllowed) {
|
||||||
@ -282,35 +260,13 @@ export class RouteConnectionHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
|
|
||||||
// Legacy default IP restrictions
|
|
||||||
const isAllowed = this.securityManager.isIPAuthorized(
|
|
||||||
remoteIP,
|
|
||||||
this.settings.defaultAllowedIPs,
|
|
||||||
this.settings.defaultBlockedIPs || []
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isAllowed) {
|
|
||||||
console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`);
|
|
||||||
socket.end();
|
|
||||||
this.connectionManager.cleanupConnection(record, 'ip_blocked');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup direct connection with default settings
|
// Setup direct connection with default settings
|
||||||
let targetHost: string;
|
if (this.settings.defaults?.target) {
|
||||||
let targetPort: number;
|
// Use defaults from configuration
|
||||||
|
const targetHost = this.settings.defaults.target.host;
|
||||||
if (isRoutedOptions(this.settings) && this.settings.defaults?.target) {
|
const targetPort = this.settings.defaults.target.port;
|
||||||
// Use defaults from routed configuration
|
|
||||||
targetHost = this.settings.defaults.target.host;
|
|
||||||
targetPort = this.settings.defaults.target.port;
|
|
||||||
} else {
|
|
||||||
// Fall back to legacy settings
|
|
||||||
targetHost = this.settings.targetIP || 'localhost';
|
|
||||||
targetPort = this.settings.toPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.setupDirectConnection(
|
return this.setupDirectConnection(
|
||||||
socket,
|
socket,
|
||||||
@ -322,6 +278,13 @@ export class RouteConnectionHandler {
|
|||||||
targetHost,
|
targetHost,
|
||||||
targetPort
|
targetPort
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// No default target available, terminate the connection
|
||||||
|
console.log(`[${connectionId}] No default target configured. Closing connection.`);
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, 'no_default_target');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A matching route was found
|
// A matching route was found
|
||||||
@ -569,114 +532,8 @@ export class RouteConnectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a connection using legacy domain configuration
|
* Legacy connection handling has been removed in favor of pure route-based approach
|
||||||
*/
|
*/
|
||||||
private handleLegacyConnection(
|
|
||||||
socket: plugins.net.Socket,
|
|
||||||
record: IConnectionRecord,
|
|
||||||
serverName: string,
|
|
||||||
domainConfig: IDomainConfig,
|
|
||||||
initialChunk?: Buffer
|
|
||||||
): void {
|
|
||||||
const connectionId = record.id;
|
|
||||||
|
|
||||||
// Get the forwarding type for this domain
|
|
||||||
const forwardingType = this.domainConfigManager.getForwardingType(domainConfig);
|
|
||||||
|
|
||||||
// IP validation
|
|
||||||
const ipRules = this.domainConfigManager.getEffectiveIPRules(domainConfig);
|
|
||||||
|
|
||||||
if (!this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)) {
|
|
||||||
console.log(
|
|
||||||
`[${connectionId}] Connection rejected: IP ${record.remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`
|
|
||||||
);
|
|
||||||
socket.end();
|
|
||||||
this.connectionManager.initiateCleanupOnce(record, 'ip_blocked');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle based on forwarding type
|
|
||||||
switch (forwardingType) {
|
|
||||||
case 'http-only':
|
|
||||||
// For HTTP-only configs with TLS traffic
|
|
||||||
if (record.isTLS) {
|
|
||||||
console.log(`[${connectionId}] Received TLS connection for HTTP-only domain ${serverName}`);
|
|
||||||
socket.end();
|
|
||||||
this.connectionManager.initiateCleanupOnce(record, 'wrong_protocol');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'https-passthrough':
|
|
||||||
// For TLS passthrough with TLS traffic
|
|
||||||
if (record.isTLS) {
|
|
||||||
try {
|
|
||||||
const handler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
|
||||||
console.log(`[${connectionId}] Using forwarding handler for SNI passthrough to ${serverName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the connection using the handler
|
|
||||||
return handler.handleConnection(socket);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'https-terminate-to-http':
|
|
||||||
case 'https-terminate-to-https':
|
|
||||||
// For TLS termination with TLS traffic
|
|
||||||
if (record.isTLS) {
|
|
||||||
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
|
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
|
||||||
console.log(`[${connectionId}] Using TLS termination (${forwardingType}) for ${serverName} on port ${networkProxyPort}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward to NetworkProxy with domain-specific port
|
|
||||||
return this.networkProxyBridge.forwardToNetworkProxy(
|
|
||||||
connectionId,
|
|
||||||
socket,
|
|
||||||
record,
|
|
||||||
initialChunk!,
|
|
||||||
networkProxyPort,
|
|
||||||
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're still here, use the forwarding handler if available
|
|
||||||
try {
|
|
||||||
const handler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
|
||||||
console.log(`[${connectionId}] Using general forwarding handler for domain ${serverName || 'unknown'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the connection using the handler
|
|
||||||
return handler.handleConnection(socket);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: set up direct connection
|
|
||||||
const targetIp = this.domainConfigManager.getTargetIP(domainConfig);
|
|
||||||
const targetPort = this.domainConfigManager.getTargetPort(domainConfig, this.settings.toPort);
|
|
||||||
|
|
||||||
return this.setupDirectConnection(
|
|
||||||
socket,
|
|
||||||
record,
|
|
||||||
domainConfig,
|
|
||||||
serverName,
|
|
||||||
initialChunk,
|
|
||||||
undefined,
|
|
||||||
targetIp,
|
|
||||||
targetPort
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a direct connection to the target
|
* Sets up a direct connection to the target
|
||||||
@ -684,7 +541,7 @@ export class RouteConnectionHandler {
|
|||||||
private setupDirectConnection(
|
private setupDirectConnection(
|
||||||
socket: plugins.net.Socket,
|
socket: plugins.net.Socket,
|
||||||
record: IConnectionRecord,
|
record: IConnectionRecord,
|
||||||
domainConfig?: IDomainConfig,
|
_unused?: any, // kept for backward compatibility
|
||||||
serverName?: string,
|
serverName?: string,
|
||||||
initialChunk?: Buffer,
|
initialChunk?: Buffer,
|
||||||
overridePort?: number,
|
overridePort?: number,
|
||||||
@ -694,20 +551,13 @@ export class RouteConnectionHandler {
|
|||||||
const connectionId = record.id;
|
const connectionId = record.id;
|
||||||
|
|
||||||
// Determine target host and port if not provided
|
// Determine target host and port if not provided
|
||||||
const finalTargetHost = targetHost || (domainConfig
|
const finalTargetHost = targetHost ||
|
||||||
? this.domainConfigManager.getTargetIP(domainConfig)
|
(this.settings.defaults?.target?.host || 'localhost');
|
||||||
: this.settings.defaults?.target?.host
|
|
||||||
? this.settings.defaults.target.host
|
|
||||||
: this.settings.targetIP!);
|
|
||||||
|
|
||||||
// Determine target port - first try explicit port, then forwarding config, then fallback
|
// Determine target port
|
||||||
const finalTargetPort = targetPort || (overridePort !== undefined
|
const finalTargetPort = targetPort ||
|
||||||
? overridePort
|
(overridePort !== undefined ? overridePort :
|
||||||
: domainConfig
|
(this.settings.defaults?.target?.port || 443));
|
||||||
? this.domainConfigManager.getTargetPort(domainConfig, this.settings.toPort)
|
|
||||||
: this.settings.defaults?.target?.port
|
|
||||||
? this.settings.defaults.target.port
|
|
||||||
: this.settings.toPort);
|
|
||||||
|
|
||||||
// Setup connection options
|
// Setup connection options
|
||||||
const connectionOptions: plugins.net.NetConnectOpts = {
|
const connectionOptions: plugins.net.NetConnectOpts = {
|
||||||
@ -891,20 +741,7 @@ export class RouteConnectionHandler {
|
|||||||
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a forwarding handler for this domain, let it handle the error
|
// Route-based configuration doesn't use domain handlers
|
||||||
if (domainConfig) {
|
|
||||||
try {
|
|
||||||
const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
||||||
forwardingHandler.emit('connection_error', {
|
|
||||||
socket,
|
|
||||||
error: err,
|
|
||||||
connectionId
|
|
||||||
});
|
|
||||||
} catch (handlerErr) {
|
|
||||||
// If getting the handler fails, just log and continue with normal cleanup
|
|
||||||
console.log(`Error getting forwarding handler for error handling: ${handlerErr}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up the connection
|
// Clean up the connection
|
||||||
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
||||||
@ -1037,8 +874,8 @@ export class RouteConnectionHandler {
|
|||||||
`${
|
`${
|
||||||
serverName
|
serverName
|
||||||
? ` (SNI: ${serverName})`
|
? ` (SNI: ${serverName})`
|
||||||
: domainConfig
|
: record.lockedDomain
|
||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Domain: ${record.lockedDomain})`
|
||||||
: ''
|
: ''
|
||||||
}` +
|
}` +
|
||||||
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
||||||
@ -1051,8 +888,8 @@ export class RouteConnectionHandler {
|
|||||||
`${
|
`${
|
||||||
serverName
|
serverName
|
||||||
? ` (SNI: ${serverName})`
|
? ` (SNI: ${serverName})`
|
||||||
: domainConfig
|
: record.lockedDomain
|
||||||
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
? ` (Domain: ${record.lockedDomain})`
|
||||||
: ''
|
: ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,8 @@ import type {
|
|||||||
IRouteTls,
|
IRouteTls,
|
||||||
IRouteRedirect,
|
IRouteRedirect,
|
||||||
IRouteSecurity,
|
IRouteSecurity,
|
||||||
IRouteAdvanced
|
IRouteAdvanced,
|
||||||
|
TPortRange
|
||||||
} from './models/route-types.js';
|
} from './models/route-types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -342,3 +343,155 @@ export function createHttpsServer(
|
|||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a port range configuration from various input formats
|
||||||
|
*/
|
||||||
|
export function createPortRange(
|
||||||
|
ports: number | number[] | string | Array<{ from: number; to: number }>
|
||||||
|
): TPortRange {
|
||||||
|
// If it's a string like "80,443" or "8000-9000", parse it
|
||||||
|
if (typeof ports === 'string') {
|
||||||
|
if (ports.includes('-')) {
|
||||||
|
// Handle range like "8000-9000"
|
||||||
|
const [start, end] = ports.split('-').map(p => parseInt(p.trim(), 10));
|
||||||
|
return [{ from: start, to: end }];
|
||||||
|
} else if (ports.includes(',')) {
|
||||||
|
// Handle comma-separated list like "80,443,8080"
|
||||||
|
return ports.split(',').map(p => parseInt(p.trim(), 10));
|
||||||
|
} else {
|
||||||
|
// Handle single port as string
|
||||||
|
return parseInt(ports.trim(), 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise return as is
|
||||||
|
return ports;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a security configuration object
|
||||||
|
*/
|
||||||
|
export function createSecurityConfig(
|
||||||
|
options: {
|
||||||
|
allowedIps?: string[];
|
||||||
|
blockedIps?: string[];
|
||||||
|
maxConnections?: number;
|
||||||
|
authentication?: {
|
||||||
|
type: 'basic' | 'digest' | 'oauth';
|
||||||
|
// Auth-specific options
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
): IRouteSecurity {
|
||||||
|
return {
|
||||||
|
...(options.allowedIps ? { allowedIps: options.allowedIps } : {}),
|
||||||
|
...(options.blockedIps ? { blockedIps: options.blockedIps } : {}),
|
||||||
|
...(options.maxConnections ? { maxConnections: options.maxConnections } : {}),
|
||||||
|
...(options.authentication ? { authentication: options.authentication } : {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a static file server route
|
||||||
|
*/
|
||||||
|
export function createStaticFileRoute(
|
||||||
|
options: {
|
||||||
|
ports?: number | number[]; // Default: 80
|
||||||
|
domains: string | string[];
|
||||||
|
path?: string;
|
||||||
|
targetDirectory: string;
|
||||||
|
tlsMode?: 'terminate' | 'terminate-and-reencrypt';
|
||||||
|
certificate?: 'auto' | { key: string; cert: string };
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
security?: IRouteSecurity;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
priority?: number;
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
): IRouteConfig {
|
||||||
|
const useTls = options.tlsMode !== undefined;
|
||||||
|
const defaultPort = useTls ? 443 : 80;
|
||||||
|
|
||||||
|
return createRoute(
|
||||||
|
{
|
||||||
|
ports: options.ports || defaultPort,
|
||||||
|
domains: options.domains,
|
||||||
|
...(options.path ? { path: options.path } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: 'localhost', // Static file serving is typically handled locally
|
||||||
|
port: 0, // Special value indicating a static file server
|
||||||
|
preservePort: false
|
||||||
|
},
|
||||||
|
...(useTls ? {
|
||||||
|
tls: {
|
||||||
|
mode: options.tlsMode!,
|
||||||
|
certificate: options.certificate || 'auto'
|
||||||
|
}
|
||||||
|
} : {}),
|
||||||
|
advanced: {
|
||||||
|
...(options.headers ? { headers: options.headers } : {}),
|
||||||
|
staticFiles: {
|
||||||
|
directory: options.targetDirectory,
|
||||||
|
indexFiles: ['index.html', 'index.htm']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...(options.security ? { security: options.security } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: options.name || 'Static File Server',
|
||||||
|
description: options.description || `Serving static files from ${options.targetDirectory}`,
|
||||||
|
priority: options.priority,
|
||||||
|
tags: options.tags
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a test route for debugging purposes
|
||||||
|
*/
|
||||||
|
export function createTestRoute(
|
||||||
|
options: {
|
||||||
|
ports?: number | number[]; // Default: 8000
|
||||||
|
domains?: string | string[];
|
||||||
|
path?: string;
|
||||||
|
response?: {
|
||||||
|
status?: number;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body?: string;
|
||||||
|
};
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
): IRouteConfig {
|
||||||
|
return createRoute(
|
||||||
|
{
|
||||||
|
ports: options.ports || 8000,
|
||||||
|
...(options.domains ? { domains: options.domains } : {}),
|
||||||
|
...(options.path ? { path: options.path } : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: 'test', // Special value indicating a test route
|
||||||
|
port: 0
|
||||||
|
},
|
||||||
|
advanced: {
|
||||||
|
testResponse: {
|
||||||
|
status: options.response?.status || 200,
|
||||||
|
headers: options.response?.headers || { 'Content-Type': 'text/plain' },
|
||||||
|
body: options.response?.body || 'Test route is working!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: options.name || 'Test Route',
|
||||||
|
description: 'Route for testing and debugging',
|
||||||
|
priority: 500,
|
||||||
|
tags: ['test', 'debug']
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
9
ts/proxies/smart-proxy/route-helpers/index.ts
Normal file
9
ts/proxies/smart-proxy/route-helpers/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Route helpers for SmartProxy
|
||||||
|
*
|
||||||
|
* This module provides helper functions for creating various types of route configurations
|
||||||
|
* to be used with the SmartProxy system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Re-export all functions from the route-helpers.ts file
|
||||||
|
export * from '../route-helpers.js';
|
@ -7,8 +7,7 @@ import type {
|
|||||||
} from './models/route-types.js';
|
} from './models/route-types.js';
|
||||||
import type {
|
import type {
|
||||||
ISmartProxyOptions,
|
ISmartProxyOptions,
|
||||||
IRoutedSmartProxyOptions,
|
IRoutedSmartProxyOptions
|
||||||
IDomainConfig
|
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import {
|
import {
|
||||||
isRoutedOptions,
|
isRoutedOptions,
|
||||||
|
@ -3,11 +3,10 @@ import * as plugins from '../../plugins.js';
|
|||||||
// Importing required components
|
// Importing required components
|
||||||
import { ConnectionManager } from './connection-manager.js';
|
import { ConnectionManager } from './connection-manager.js';
|
||||||
import { SecurityManager } from './security-manager.js';
|
import { SecurityManager } from './security-manager.js';
|
||||||
import { DomainConfigManager } from './domain-config-manager.js';
|
|
||||||
import { TlsManager } from './tls-manager.js';
|
import { TlsManager } from './tls-manager.js';
|
||||||
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
||||||
import { TimeoutManager } from './timeout-manager.js';
|
import { TimeoutManager } from './timeout-manager.js';
|
||||||
import { PortRangeManager } from './port-range-manager.js';
|
// import { PortRangeManager } from './port-range-manager.js';
|
||||||
import { RouteManager } from './route-manager.js';
|
import { RouteManager } from './route-manager.js';
|
||||||
import { RouteConnectionHandler } from './route-connection-handler.js';
|
import { RouteConnectionHandler } from './route-connection-handler.js';
|
||||||
|
|
||||||
@ -21,14 +20,23 @@ import { createPort80HandlerOptions } from '../../common/port80-adapter.js';
|
|||||||
// Import types and utilities
|
// Import types and utilities
|
||||||
import type {
|
import type {
|
||||||
ISmartProxyOptions,
|
ISmartProxyOptions,
|
||||||
IRoutedSmartProxyOptions,
|
IRoutedSmartProxyOptions
|
||||||
IDomainConfig
|
|
||||||
} from './models/interfaces.js';
|
} from './models/interfaces.js';
|
||||||
import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
|
import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
|
||||||
import type { IRouteConfig } from './models/route-types.js';
|
import type { IRouteConfig } from './models/route-types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SmartProxy - Unified route-based API
|
* SmartProxy - Pure route-based API
|
||||||
|
*
|
||||||
|
* SmartProxy is a unified proxy system that works with routes to define connection handling behavior.
|
||||||
|
* Each route contains matching criteria (ports, domains, etc.) and an action to take (forward, redirect, block).
|
||||||
|
*
|
||||||
|
* Configuration is provided through a set of routes, with each route defining:
|
||||||
|
* - What to match (ports, domains, paths, client IPs)
|
||||||
|
* - What to do with matching traffic (forward, redirect, block)
|
||||||
|
* - How to handle TLS (passthrough, terminate, terminate-and-reencrypt)
|
||||||
|
* - Security settings (IP restrictions, connection limits)
|
||||||
|
* - Advanced options (timeout, headers, etc.)
|
||||||
*/
|
*/
|
||||||
export class SmartProxy extends plugins.EventEmitter {
|
export class SmartProxy extends plugins.EventEmitter {
|
||||||
private netServers: plugins.net.Server[] = [];
|
private netServers: plugins.net.Server[] = [];
|
||||||
@ -38,11 +46,10 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
// Component managers
|
// Component managers
|
||||||
private connectionManager: ConnectionManager;
|
private connectionManager: ConnectionManager;
|
||||||
private securityManager: SecurityManager;
|
private securityManager: SecurityManager;
|
||||||
private domainConfigManager: DomainConfigManager;
|
|
||||||
private tlsManager: TlsManager;
|
private tlsManager: TlsManager;
|
||||||
private networkProxyBridge: NetworkProxyBridge;
|
private networkProxyBridge: NetworkProxyBridge;
|
||||||
private timeoutManager: TimeoutManager;
|
private timeoutManager: TimeoutManager;
|
||||||
private portRangeManager: PortRangeManager;
|
// private portRangeManager: PortRangeManager;
|
||||||
private routeManager: RouteManager;
|
private routeManager: RouteManager;
|
||||||
private routeConnectionHandler: RouteConnectionHandler;
|
private routeConnectionHandler: RouteConnectionHandler;
|
||||||
|
|
||||||
@ -52,7 +59,35 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
private certProvisioner?: CertProvisioner;
|
private certProvisioner?: CertProvisioner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor that supports both legacy and route-based configuration
|
* Constructor for SmartProxy
|
||||||
|
*
|
||||||
|
* @param settingsArg Configuration options containing routes and other settings
|
||||||
|
* Routes define how traffic is matched and handled, with each route having:
|
||||||
|
* - match: criteria for matching traffic (ports, domains, paths, IPs)
|
||||||
|
* - action: what to do with matched traffic (forward, redirect, block)
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```ts
|
||||||
|
* const proxy = new SmartProxy({
|
||||||
|
* routes: [
|
||||||
|
* {
|
||||||
|
* match: {
|
||||||
|
* ports: 443,
|
||||||
|
* domains: ['example.com', '*.example.com']
|
||||||
|
* },
|
||||||
|
* action: {
|
||||||
|
* type: 'forward',
|
||||||
|
* target: { host: '10.0.0.1', port: 8443 },
|
||||||
|
* tls: { mode: 'passthrough' }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* defaults: {
|
||||||
|
* target: { host: 'localhost', port: 8080 },
|
||||||
|
* security: { allowedIps: ['*'] }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
constructor(settingsArg: ISmartProxyOptions) {
|
constructor(settingsArg: ISmartProxyOptions) {
|
||||||
super();
|
super();
|
||||||
@ -98,7 +133,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
autoRenew: true,
|
autoRenew: true,
|
||||||
certificateStore: './certs',
|
certificateStore: './certs',
|
||||||
skipConfiguredCerts: false,
|
skipConfiguredCerts: false,
|
||||||
httpsRedirectPort: this.settings.fromPort || 443,
|
httpsRedirectPort: 443,
|
||||||
renewCheckIntervalHours: 24,
|
renewCheckIntervalHours: 24,
|
||||||
domainForwards: []
|
domainForwards: []
|
||||||
};
|
};
|
||||||
@ -113,13 +148,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
this.timeoutManager
|
this.timeoutManager
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create domain config manager and port range manager (for backward compatibility)
|
// Create the route manager
|
||||||
this.domainConfigManager = new DomainConfigManager(this.settings);
|
|
||||||
this.portRangeManager = new PortRangeManager(this.settings);
|
|
||||||
|
|
||||||
// Create the new route manager
|
|
||||||
this.routeManager = new RouteManager(this.settings);
|
this.routeManager = new RouteManager(this.settings);
|
||||||
|
|
||||||
|
// Create port range manager
|
||||||
|
// this.portRangeManager = new PortRangeManager(this.settings);
|
||||||
|
|
||||||
// Create other required components
|
// Create other required components
|
||||||
this.tlsManager = new TlsManager(this.settings);
|
this.tlsManager = new TlsManager(this.settings);
|
||||||
this.networkProxyBridge = new NetworkProxyBridge(this.settings);
|
this.networkProxyBridge = new NetworkProxyBridge(this.settings);
|
||||||
@ -129,7 +163,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
this.settings,
|
this.settings,
|
||||||
this.connectionManager,
|
this.connectionManager,
|
||||||
this.securityManager,
|
this.securityManager,
|
||||||
this.domainConfigManager,
|
|
||||||
this.tlsManager,
|
this.tlsManager,
|
||||||
this.networkProxyBridge,
|
this.networkProxyBridge,
|
||||||
this.timeoutManager,
|
this.timeoutManager,
|
||||||
@ -156,7 +189,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
// Build and start the Port80Handler
|
// Build and start the Port80Handler
|
||||||
this.port80Handler = buildPort80Handler({
|
this.port80Handler = buildPort80Handler({
|
||||||
...config,
|
...config,
|
||||||
httpsRedirectPort: config.httpsRedirectPort || (isLegacyOptions(this.settings) ? this.settings.fromPort : 443)
|
httpsRedirectPort: config.httpsRedirectPort || 443
|
||||||
});
|
});
|
||||||
|
|
||||||
// Share Port80Handler with NetworkProxyBridge before start
|
// Share Port80Handler with NetworkProxyBridge before start
|
||||||
@ -178,11 +211,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If using legacy format, make sure domainConfigs are initialized
|
// Pure route-based configuration - no domain configs needed
|
||||||
if (isLegacyOptions(this.settings)) {
|
|
||||||
// Initialize domain config manager with the processed configs
|
|
||||||
this.domainConfigManager.updateDomainConfigs(this.settings.domainConfigs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Port80Handler if enabled
|
// Initialize Port80Handler if enabled
|
||||||
await this.initializePort80Handler();
|
await this.initializePort80Handler();
|
||||||
@ -191,22 +220,22 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
if (this.port80Handler) {
|
if (this.port80Handler) {
|
||||||
const acme = this.settings.acme!;
|
const acme = this.settings.acme!;
|
||||||
|
|
||||||
// Setup domain forwards based on configuration type
|
// Setup domain forwards
|
||||||
const domainForwards = acme.domainForwards?.map(f => {
|
const domainForwards = acme.domainForwards?.map(f => {
|
||||||
if (isLegacyOptions(this.settings)) {
|
// Check if a matching route exists
|
||||||
// If using legacy mode, check if domain config exists
|
const matchingRoute = this.settings.routes.find(
|
||||||
const domainConfig = this.settings.domainConfigs.find(
|
route => Array.isArray(route.match.domains)
|
||||||
dc => dc.domains.some(d => d === f.domain)
|
? route.match.domains.some(d => d === f.domain)
|
||||||
|
: route.match.domains === f.domain
|
||||||
);
|
);
|
||||||
|
|
||||||
if (domainConfig?.forwarding) {
|
if (matchingRoute) {
|
||||||
return {
|
return {
|
||||||
domain: f.domain,
|
domain: f.domain,
|
||||||
forwardConfig: f.forwardConfig,
|
forwardConfig: f.forwardConfig,
|
||||||
acmeForwardConfig: f.acmeForwardConfig,
|
acmeForwardConfig: f.acmeForwardConfig,
|
||||||
sslRedirect: f.sslRedirect || domainConfig.forwarding.http?.redirectToHttps || false
|
sslRedirect: f.sslRedirect || false
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// In route mode, look for matching route
|
// In route mode, look for matching route
|
||||||
const route = this.routeManager.findMatchingRoute({
|
const route = this.routeManager.findMatchingRoute({
|
||||||
@ -236,9 +265,10 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
// Create CertProvisioner with appropriate parameters
|
// Create CertProvisioner with appropriate parameters
|
||||||
if (isLegacyOptions(this.settings)) {
|
// No longer need to support multiple configuration types
|
||||||
|
// Just pass the routes directly
|
||||||
this.certProvisioner = new CertProvisioner(
|
this.certProvisioner = new CertProvisioner(
|
||||||
this.settings.domainConfigs,
|
this.settings.routes,
|
||||||
this.port80Handler,
|
this.port80Handler,
|
||||||
this.networkProxyBridge,
|
this.networkProxyBridge,
|
||||||
this.settings.certProvisionFunction,
|
this.settings.certProvisionFunction,
|
||||||
@ -247,24 +277,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
acme.autoRenew!,
|
acme.autoRenew!,
|
||||||
domainForwards
|
domainForwards
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// For route-based configuration, we need to adapt the interface
|
|
||||||
// Convert routes to domain configs for CertProvisioner
|
|
||||||
const domainConfigs: IDomainConfig[] = this.extractDomainConfigsFromRoutes(
|
|
||||||
(this.settings as IRoutedSmartProxyOptions).routes
|
|
||||||
);
|
|
||||||
|
|
||||||
this.certProvisioner = new CertProvisioner(
|
|
||||||
domainConfigs,
|
|
||||||
this.port80Handler,
|
|
||||||
this.networkProxyBridge,
|
|
||||||
this.settings.certProvisionFunction,
|
|
||||||
acme.renewThresholdDays!,
|
|
||||||
acme.renewCheckIntervalHours!,
|
|
||||||
acme.autoRenew!,
|
|
||||||
domainForwards
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register certificate event handler
|
// Register certificate event handler
|
||||||
this.certProvisioner.on('certificate', (certData) => {
|
this.certProvisioner.on('certificate', (certData) => {
|
||||||
@ -320,10 +332,8 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
||||||
console.log(
|
console.log(
|
||||||
`SmartProxy -> OK: Now listening on port ${port}${
|
`SmartProxy -> OK: Now listening on port ${port}${
|
||||||
isLegacyOptions(this.settings) && this.settings.sniEnabled && !isNetworkProxyPort ?
|
isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''
|
||||||
' (SNI passthrough enabled)' :
|
}`
|
||||||
''
|
|
||||||
}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -404,60 +414,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract domain configurations from routes for certificate provisioning
|
* Extract domain configurations from routes for certificate provisioning
|
||||||
|
*
|
||||||
|
* Note: This method has been removed as we now work directly with routes
|
||||||
*/
|
*/
|
||||||
private extractDomainConfigsFromRoutes(routes: IRouteConfig[]): IDomainConfig[] {
|
|
||||||
const domainConfigs: IDomainConfig[] = [];
|
|
||||||
|
|
||||||
for (const route of routes) {
|
|
||||||
// Skip routes without domain specs
|
|
||||||
if (!route.match.domains) continue;
|
|
||||||
|
|
||||||
// Skip non-forward routes
|
|
||||||
if (route.action.type !== 'forward') continue;
|
|
||||||
|
|
||||||
// Only process routes that need TLS termination (those with certificates)
|
|
||||||
if (!route.action.tls ||
|
|
||||||
route.action.tls.mode === 'passthrough' ||
|
|
||||||
!route.action.target) continue;
|
|
||||||
|
|
||||||
const domains = Array.isArray(route.match.domains)
|
|
||||||
? route.match.domains
|
|
||||||
: [route.match.domains];
|
|
||||||
|
|
||||||
// Determine forwarding type based on TLS mode
|
|
||||||
const forwardingType = route.action.tls.mode === 'terminate'
|
|
||||||
? 'https-terminate-to-http'
|
|
||||||
: 'https-terminate-to-https';
|
|
||||||
|
|
||||||
// Create a forwarding config
|
|
||||||
const forwarding = {
|
|
||||||
type: forwardingType as any,
|
|
||||||
target: {
|
|
||||||
host: Array.isArray(route.action.target.host)
|
|
||||||
? route.action.target.host[0]
|
|
||||||
: route.action.target.host,
|
|
||||||
port: route.action.target.port
|
|
||||||
},
|
|
||||||
// Add TLS settings
|
|
||||||
https: {
|
|
||||||
customCert: route.action.tls.certificate !== 'auto'
|
|
||||||
? route.action.tls.certificate
|
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
// Add security settings if present
|
|
||||||
security: route.action.security,
|
|
||||||
// Add advanced settings if present
|
|
||||||
advanced: route.action.advanced
|
|
||||||
};
|
|
||||||
|
|
||||||
domainConfigs.push({
|
|
||||||
domains,
|
|
||||||
forwarding
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop the proxy server
|
* Stop the proxy server
|
||||||
@ -523,90 +482,37 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the domain configurations for the proxy (legacy support)
|
* Updates the domain configurations for the proxy
|
||||||
|
*
|
||||||
|
* Note: This legacy method has been removed. Use updateRoutes instead.
|
||||||
*/
|
*/
|
||||||
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
|
public async updateDomainConfigs(): Promise<void> {
|
||||||
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
console.warn('Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
|
||||||
|
throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
|
||||||
// Update domain configs in DomainConfigManager (legacy)
|
|
||||||
this.domainConfigManager.updateDomainConfigs(newDomainConfigs);
|
|
||||||
|
|
||||||
// Also update the RouteManager with these domain configs
|
|
||||||
this.routeManager.updateFromDomainConfigs(newDomainConfigs);
|
|
||||||
|
|
||||||
// If NetworkProxy is initialized, resync the configurations
|
|
||||||
if (this.networkProxyBridge.getNetworkProxy()) {
|
|
||||||
await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If Port80Handler is running, provision certificates based on forwarding type
|
|
||||||
if (this.port80Handler && this.settings.acme?.enabled) {
|
|
||||||
for (const domainConfig of newDomainConfigs) {
|
|
||||||
// Skip certificate provisioning for http-only or passthrough configs that don't need certs
|
|
||||||
const forwardingType = this.domainConfigManager.getForwardingType(domainConfig);
|
|
||||||
const needsCertificate =
|
|
||||||
forwardingType === 'https-terminate-to-http' ||
|
|
||||||
forwardingType === 'https-terminate-to-https';
|
|
||||||
|
|
||||||
// Skip certificate provisioning if ACME is explicitly disabled for this domain
|
|
||||||
const acmeDisabled = domainConfig.forwarding.acme?.enabled === false;
|
|
||||||
|
|
||||||
if (!needsCertificate || acmeDisabled) {
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
|
||||||
console.log(`Skipping certificate provisioning for ${domainConfig.domains.join(', ')} (${forwardingType})`);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const domain of domainConfig.domains) {
|
|
||||||
const isWildcard = domain.includes('*');
|
|
||||||
let provision: string | plugins.tsclass.network.ICert = 'http01';
|
|
||||||
|
|
||||||
// Check for ACME forwarding configuration in the domain
|
|
||||||
const forwardAcmeChallenges = domainConfig.forwarding.acme?.forwardChallenges;
|
|
||||||
|
|
||||||
if (this.settings.certProvisionFunction) {
|
|
||||||
try {
|
|
||||||
provision = await this.settings.certProvisionFunction(domain);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`certProvider error for ${domain}: ${err}`);
|
|
||||||
}
|
|
||||||
} else if (isWildcard) {
|
|
||||||
console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provision === 'http01') {
|
|
||||||
if (isWildcard) {
|
|
||||||
console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Port80Handler options from the forwarding configuration
|
|
||||||
const port80Config = createPort80HandlerOptions(domain, domainConfig.forwarding);
|
|
||||||
|
|
||||||
this.port80Handler.addDomain(port80Config);
|
|
||||||
console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`);
|
|
||||||
} else {
|
|
||||||
// Static certificate (e.g., DNS-01 provisioned) supports wildcards
|
|
||||||
const certObj = provision as plugins.tsclass.network.ICert;
|
|
||||||
const certData: ICertificateData = {
|
|
||||||
domain: certObj.domainName,
|
|
||||||
certificate: certObj.publicKey,
|
|
||||||
privateKey: certObj.privateKey,
|
|
||||||
expiryDate: new Date(certObj.validUntil)
|
|
||||||
};
|
|
||||||
this.networkProxyBridge.applyExternalCertificate(certData);
|
|
||||||
console.log(`Applied static certificate for ${domain} from certProvider`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('Provisioned certificates for new domains');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update routes with new configuration (new API)
|
* Update routes with new configuration
|
||||||
|
*
|
||||||
|
* This method replaces the current route configuration with the provided routes.
|
||||||
|
* It also provisions certificates for routes that require TLS termination and have
|
||||||
|
* `certificate: 'auto'` set in their TLS configuration.
|
||||||
|
*
|
||||||
|
* @param newRoutes Array of route configurations to use
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```ts
|
||||||
|
* proxy.updateRoutes([
|
||||||
|
* {
|
||||||
|
* match: { ports: 443, domains: 'secure.example.com' },
|
||||||
|
* action: {
|
||||||
|
* type: 'forward',
|
||||||
|
* target: { host: '10.0.0.1', port: 8443 },
|
||||||
|
* tls: { mode: 'terminate', certificate: 'auto' }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
||||||
console.log(`Updating routes (${newRoutes.length} routes)`);
|
console.log(`Updating routes (${newRoutes.length} routes)`);
|
||||||
@ -616,14 +522,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
|
|
||||||
// If NetworkProxy is initialized, resync the configurations
|
// If NetworkProxy is initialized, resync the configurations
|
||||||
if (this.networkProxyBridge.getNetworkProxy()) {
|
if (this.networkProxyBridge.getNetworkProxy()) {
|
||||||
// Create equivalent domain configs for NetworkProxy
|
await this.networkProxyBridge.syncRoutesToNetworkProxy(newRoutes);
|
||||||
const domainConfigs = this.extractDomainConfigsFromRoutes(newRoutes);
|
|
||||||
|
|
||||||
// Update domain configs in DomainConfigManager for sync
|
|
||||||
this.domainConfigManager.updateDomainConfigs(domainConfigs);
|
|
||||||
|
|
||||||
// Sync with NetworkProxy
|
|
||||||
await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Port80Handler is running, provision certificates based on routes
|
// If Port80Handler is running, provision certificates based on routes
|
||||||
@ -822,17 +721,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
domains.push(...eligibleDomains);
|
domains.push(...eligibleDomains);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For legacy mode, also get domains from domain configs
|
// Legacy mode is no longer supported
|
||||||
if (isLegacyOptions(this.settings)) {
|
|
||||||
for (const config of this.settings.domainConfigs) {
|
|
||||||
// Skip domains that can't be used with ACME
|
|
||||||
const eligibleDomains = config.domains.filter(domain =>
|
|
||||||
!domain.includes('*') && this.isValidDomain(domain)
|
|
||||||
);
|
|
||||||
|
|
||||||
domains.push(...eligibleDomains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains;
|
return domains;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user