diff --git a/ts/proxies/smart-proxy/models/interfaces.ts b/ts/proxies/smart-proxy/models/interfaces.ts index c709d8e..7bae483 100644 --- a/ts/proxies/smart-proxy/models/interfaces.ts +++ b/ts/proxies/smart-proxy/models/interfaces.ts @@ -13,64 +13,17 @@ export type TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'ht */ export type IRoutedSmartProxyOptions = ISmartProxyOptions; -/** - * Legacy domain configuration interface for backward compatibility - */ -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; - }; - 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; - }; - }; -} - /** * Helper functions for type checking configuration types */ export function isLegacyOptions(options: any): boolean { - return !!(options.domainConfigs && options.domainConfigs.length > 0 && - (!options.routes || options.routes.length === 0)); + // Legacy options are no longer supported + return false; } export function isRoutedOptions(options: any): boolean { - return !!(options.routes && options.routes.length > 0); + // All configurations are now route-based + return true; } /** @@ -80,14 +33,7 @@ export interface ISmartProxyOptions { // The unified configuration array (required) routes: IRouteConfig[]; - // Legacy options for backward compatibility - fromPort?: number; - toPort?: number; - sniEnabled?: boolean; - domainConfigs?: IDomainConfig[]; - targetIP?: string; - defaultAllowedIPs?: string[]; - defaultBlockedIPs?: string[]; + // Port range configuration globalPortRanges?: Array<{ from: number; to: number }>; forwardAllGlobalRanges?: boolean; preserveSourceIP?: boolean; @@ -99,8 +45,8 @@ export interface ISmartProxyOptions { port: number; // Default port to use when not specified in routes }; security?: { - allowedIPs?: string[]; // Default allowed IPs - blockedIPs?: string[]; // Default blocked IPs + allowedIps?: string[]; // Default allowed IPs + blockedIps?: string[]; // Default blocked IPs maxConnections?: number; // Default max connections }; preserveSourceIP?: boolean; // Default source IP preservation @@ -184,9 +130,6 @@ export interface IConnectionRecord { pendingData: Buffer[]; // Buffer to hold data during connection setup pendingDataSize: number; // Track total size of pending data - // Legacy property for backward compatibility - domainConfig?: IDomainConfig; - // Enhanced tracking fields bytesReceived: number; // Total bytes received bytesSent: number; // Total bytes sent diff --git a/ts/proxies/smart-proxy/network-proxy-bridge.ts b/ts/proxies/smart-proxy/network-proxy-bridge.ts index f96aaeb..7f6c1da 100644 --- a/ts/proxies/smart-proxy/network-proxy-bridge.ts +++ b/ts/proxies/smart-proxy/network-proxy-bridge.ts @@ -4,10 +4,19 @@ import { Port80Handler } from '../../http/port80/port80-handler.js'; import { Port80HandlerEvents } from '../../core/models/common-types.js'; import { subscribeToPort80Handler } from '../../core/utils/event-utils.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 + * + * 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 { private networkProxy: NetworkProxy | null = null; @@ -58,8 +67,8 @@ export class NetworkProxyBridge { this.networkProxy.setExternalPort80Handler(this.port80Handler); } - // Convert and apply domain configurations to NetworkProxy - await this.syncDomainConfigsToNetworkProxy(); + // Apply route configurations to NetworkProxy + 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 { + public async syncRoutesToNetworkProxy(routes: IRouteConfig[]): Promise { if (!this.networkProxy) { console.log('Cannot sync configurations - NetworkProxy not initialized'); return; @@ -282,38 +301,112 @@ export class NetworkProxyBridge { }; } - // Convert domain configs to NetworkProxy configs - const proxyConfigs = this.networkProxy.convertSmartProxyConfigs( - this.settings.domainConfigs, - certPair - ); + // Convert routes to NetworkProxy configs + const proxyConfigs = this.convertRoutesToNetworkProxyConfigs(routes, certPair); - // Log ACME-eligible domains - 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 + // Update the proxy configs 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) { - 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], + proxyConfig: { + targetIsTls: route.action.tls.mode === 'terminate-and-reencrypt', + allowHTTP1: true, + // Apply any other NetworkProxy-specific settings + ...(route.action.advanced ? { + preserveHost: true, + timeout: route.action.advanced.timeout, + headers: route.action.advanced.headers + } : {}) + } + }; + + 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 { + console.log('Method syncDomainConfigsToNetworkProxy is deprecated. Use syncRoutesToNetworkProxy instead.'); + await this.syncRoutesToNetworkProxy(this.settings.routes || []); + } /** * Request a certificate for a specific domain diff --git a/ts/proxies/smart-proxy/route-connection-handler.ts b/ts/proxies/smart-proxy/route-connection-handler.ts index 557dd9a..73898c5 100644 --- a/ts/proxies/smart-proxy/route-connection-handler.ts +++ b/ts/proxies/smart-proxy/route-connection-handler.ts @@ -1,12 +1,10 @@ import * as plugins from '../../plugins.js'; import type { IConnectionRecord, - IDomainConfig, ISmartProxyOptions } from './models/interfaces.js'; import { - isRoutedOptions, - isLegacyOptions + isRoutedOptions } from './models/interfaces.js'; import type { IRouteConfig, @@ -14,13 +12,11 @@ import type { } from './models/route-types.js'; import { ConnectionManager } from './connection-manager.js'; import { SecurityManager } from './security-manager.js'; -import { DomainConfigManager } from './domain-config-manager.js'; import { TlsManager } from './tls-manager.js'; import { NetworkProxyBridge } from './network-proxy-bridge.js'; import { TimeoutManager } from './timeout-manager.js'; import { RouteManager } from './route-manager.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 @@ -32,7 +28,6 @@ export class RouteConnectionHandler { settings: ISmartProxyOptions, private connectionManager: ConnectionManager, private securityManager: SecurityManager, - private domainConfigManager: DomainConfigManager, private tlsManager: TlsManager, private networkProxyBridge: NetworkProxyBridge, private timeoutManager: TimeoutManager, @@ -244,37 +239,20 @@ export class RouteConnectionHandler { if (!routeMatch) { console.log(`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`); - - // Fall back to legacy matching if we're using a hybrid configuration - 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 + // No matching route, use default/fallback handling console.log(`[${connectionId}] Using default route handling for connection`); - + // Check default security settings const defaultSecuritySettings = this.settings.defaults?.security; if (defaultSecuritySettings) { - if (defaultSecuritySettings.allowedIPs && defaultSecuritySettings.allowedIPs.length > 0) { + if (defaultSecuritySettings.allowedIps && defaultSecuritySettings.allowedIps.length > 0) { const isAllowed = this.securityManager.isIPAuthorized( remoteIP, - defaultSecuritySettings.allowedIPs, - defaultSecuritySettings.blockedIPs || [] + defaultSecuritySettings.allowedIps, + defaultSecuritySettings.blockedIps || [] ); - + if (!isAllowed) { console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`); socket.end(); @@ -282,34 +260,24 @@ export class RouteConnectionHandler { 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 - let targetHost: string; - let targetPort: number; + if (this.settings.defaults?.target) { + // Use defaults from configuration + const targetHost = this.settings.defaults.target.host; + const targetPort = this.settings.defaults.target.port; - if (isRoutedOptions(this.settings) && this.settings.defaults?.target) { - // Use defaults from routed configuration - targetHost = this.settings.defaults.target.host; - targetPort = this.settings.defaults.target.port; - } else if (this.settings.targetIP && this.settings.toPort) { - // Fall back to legacy settings - targetHost = this.settings.targetIP; - targetPort = this.settings.toPort; + return this.setupDirectConnection( + socket, + record, + undefined, + serverName, + initialChunk, + undefined, + targetHost, + targetPort + ); } else { // No default target available, terminate the connection console.log(`[${connectionId}] No default target configured. Closing connection.`); @@ -317,17 +285,6 @@ export class RouteConnectionHandler { this.connectionManager.cleanupConnection(record, 'no_default_target'); return; } - - return this.setupDirectConnection( - socket, - record, - undefined, - serverName, - initialChunk, - undefined, - targetHost, - targetPort - ); } // A matching route was found @@ -575,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 @@ -690,7 +541,7 @@ export class RouteConnectionHandler { private setupDirectConnection( socket: plugins.net.Socket, record: IConnectionRecord, - domainConfig?: IDomainConfig, + _unused?: any, // kept for backward compatibility serverName?: string, initialChunk?: Buffer, overridePort?: number, @@ -698,22 +549,15 @@ export class RouteConnectionHandler { targetPort?: number ): void { const connectionId = record.id; - - // Determine target host and port if not provided - const finalTargetHost = targetHost || (domainConfig - ? this.domainConfigManager.getTargetIP(domainConfig) - : 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 - const finalTargetPort = targetPort || (overridePort !== undefined - ? overridePort - : domainConfig - ? this.domainConfigManager.getTargetPort(domainConfig, this.settings.toPort) - : this.settings.defaults?.target?.port - ? this.settings.defaults.target.port - : this.settings.toPort); + // Determine target host and port if not provided + const finalTargetHost = targetHost || + (this.settings.defaults?.target?.host || 'localhost'); + + // Determine target port + const finalTargetPort = targetPort || + (overridePort !== undefined ? overridePort : + (this.settings.defaults?.target?.port || 443)); // Setup connection options const connectionOptions: plugins.net.NetConnectOpts = { diff --git a/ts/proxies/smart-proxy/smart-proxy.ts b/ts/proxies/smart-proxy/smart-proxy.ts index 6568c24..96255b2 100644 --- a/ts/proxies/smart-proxy/smart-proxy.ts +++ b/ts/proxies/smart-proxy/smart-proxy.ts @@ -3,7 +3,6 @@ import * as plugins from '../../plugins.js'; // Importing required components import { ConnectionManager } from './connection-manager.js'; import { SecurityManager } from './security-manager.js'; -import { DomainConfigManager } from './domain-config-manager.js'; import { TlsManager } from './tls-manager.js'; import { NetworkProxyBridge } from './network-proxy-bridge.js'; import { TimeoutManager } from './timeout-manager.js'; @@ -19,16 +18,25 @@ import { buildPort80Handler } from '../../certificate/acme/acme-factory.js'; import { createPort80HandlerOptions } from '../../common/port80-adapter.js'; // Import types and utilities -import type { - ISmartProxyOptions, - IRoutedSmartProxyOptions, - IDomainConfig +import type { + ISmartProxyOptions, + IRoutedSmartProxyOptions } from './models/interfaces.js'; -import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js'; +import { isRoutedOptions } from './models/interfaces.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 { private netServers: plugins.net.Server[] = []; @@ -38,7 +46,6 @@ export class SmartProxy extends plugins.EventEmitter { // Component managers private connectionManager: ConnectionManager; private securityManager: SecurityManager; - private domainConfigManager: DomainConfigManager; private tlsManager: TlsManager; private networkProxyBridge: NetworkProxyBridge; private timeoutManager: TimeoutManager; @@ -52,7 +59,35 @@ export class SmartProxy extends plugins.EventEmitter { 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) { super(); @@ -113,17 +148,10 @@ export class SmartProxy extends plugins.EventEmitter { this.timeoutManager ); - // Create the new route manager first + // Create the route manager this.routeManager = new RouteManager(this.settings); - // Create domain config manager and port range manager - this.domainConfigManager = new DomainConfigManager(this.settings); - - // Share the route manager with the domain config manager - if (typeof this.domainConfigManager.setRouteManager === 'function') { - this.domainConfigManager.setRouteManager(this.routeManager); - } - + // Create port range manager this.portRangeManager = new PortRangeManager(this.settings); // Create other required components @@ -135,7 +163,6 @@ export class SmartProxy extends plugins.EventEmitter { this.settings, this.connectionManager, this.securityManager, - this.domainConfigManager, this.tlsManager, this.networkProxyBridge, this.timeoutManager, @@ -162,7 +189,7 @@ export class SmartProxy extends plugins.EventEmitter { // Build and start the Port80Handler this.port80Handler = buildPort80Handler({ ...config, - httpsRedirectPort: config.httpsRedirectPort || (isLegacyOptions(this.settings) ? this.settings.fromPort : 443) + httpsRedirectPort: config.httpsRedirectPort || 443 }); // Share Port80Handler with NetworkProxyBridge before start @@ -184,17 +211,7 @@ export class SmartProxy extends plugins.EventEmitter { return; } - // Initialize domain config based on configuration type - if (isLegacyOptions(this.settings)) { - // Initialize domain config manager with the legacy domain configs - this.domainConfigManager.updateDomainConfigs(this.settings.domainConfigs || []); - } else if (isRoutedOptions(this.settings)) { - // For pure route-based configuration, the domain config is already initialized - // in the constructor, but we might need to regenerate it - if (typeof this.domainConfigManager.generateDomainConfigsFromRoutes === 'function') { - this.domainConfigManager.generateDomainConfigsFromRoutes(); - } - } + // Pure route-based configuration - no domain configs needed // Initialize Port80Handler if enabled await this.initializePort80Handler(); @@ -248,35 +265,18 @@ export class SmartProxy extends plugins.EventEmitter { }) || []; // Create CertProvisioner with appropriate parameters - if (isLegacyOptions(this.settings)) { - this.certProvisioner = new CertProvisioner( - this.settings.domainConfigs, - this.port80Handler, - this.networkProxyBridge, - this.settings.certProvisionFunction, - acme.renewThresholdDays!, - acme.renewCheckIntervalHours!, - acme.autoRenew!, - 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 - ); - } + // No longer need to support multiple configuration types + // Just pass the routes directly + this.certProvisioner = new CertProvisioner( + this.settings.routes, + this.port80Handler, + this.networkProxyBridge, + this.settings.certProvisionFunction, + acme.renewThresholdDays!, + acme.renewCheckIntervalHours!, + acme.autoRenew!, + domainForwards + ); // Register certificate event handler this.certProvisioner.on('certificate', (certData) => { @@ -416,60 +416,9 @@ export class SmartProxy extends plugins.EventEmitter { /** * 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 @@ -535,134 +484,74 @@ 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 { - console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`); - - // 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'); - } + public async updateDomainConfigs(): Promise { + console.warn('Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.'); + throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead'); } /** - * 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 { console.log(`Updating routes (${newRoutes.length} routes)`); - + // Update routes in RouteManager this.routeManager.updateRoutes(newRoutes); - + // If NetworkProxy is initialized, resync the configurations if (this.networkProxyBridge.getNetworkProxy()) { - // Create equivalent domain configs for NetworkProxy - const domainConfigs = this.extractDomainConfigsFromRoutes(newRoutes); - - // Update domain configs in DomainConfigManager for sync - this.domainConfigManager.updateDomainConfigs(domainConfigs); - - // Sync with NetworkProxy - await this.networkProxyBridge.syncDomainConfigsToNetworkProxy(); + await this.networkProxyBridge.syncRoutesToNetworkProxy(newRoutes); } - + // If Port80Handler is running, provision certificates based on routes if (this.port80Handler && this.settings.acme?.enabled) { for (const route of newRoutes) { // Skip routes without domains if (!route.match.domains) continue; - + // Skip non-forward routes if (route.action.type !== 'forward') continue; - + // Skip routes without TLS termination - if (!route.action.tls || - route.action.tls.mode === 'passthrough' || + if (!route.action.tls || + route.action.tls.mode === 'passthrough' || !route.action.target) continue; - + // Skip certificate provisioning if certificate is not auto if (route.action.tls.certificate !== 'auto') continue; - - const domains = Array.isArray(route.match.domains) - ? route.match.domains + + const domains = Array.isArray(route.match.domains) + ? route.match.domains : [route.match.domains]; - + for (const domain of domains) { const isWildcard = domain.includes('*'); let provision: string | plugins.tsclass.network.ICert = 'http01'; - + if (this.settings.certProvisionFunction) { try { provision = await this.settings.certProvisionFunction(domain); @@ -673,20 +562,20 @@ export class SmartProxy extends plugins.EventEmitter { console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`); continue; } - + if (provision === 'http01') { if (isWildcard) { console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`); continue; } - + // Register domain with Port80Handler this.port80Handler.addDomain({ domainName: domain, sslRedirect: true, acmeMaintenance: true }); - + console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`); } else { // Handle static certificate (e.g., DNS-01 provisioned) @@ -702,7 +591,7 @@ export class SmartProxy extends plugins.EventEmitter { } } } - + console.log('Provisioned certificates for new routes'); } }