import * as plugins from '../../plugins.js'; import { NetworkProxy } from '../network-proxy/index.js'; import { Port80Handler } from '../../http/port80/port80-handler.js'; import { subscribeToPort80Handler } from '../../core/utils/event-utils.js'; import type { ICertificateData } from '../../certificate/models/certificate-types.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 directly passes route configurations to NetworkProxy and manages the physical * connection piping between SmartProxy and NetworkProxy for TLS termination. * * 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; private port80Handler: Port80Handler | null = null; constructor(private settings: ISmartProxyOptions) {} /** * Set the Port80Handler to use for certificate management */ public setPort80Handler(handler: Port80Handler): void { this.port80Handler = handler; // Subscribe to certificate events subscribeToPort80Handler(handler, { onCertificateIssued: this.handleCertificateEvent.bind(this), onCertificateRenewed: this.handleCertificateEvent.bind(this) }); // If NetworkProxy is already initialized, connect it with Port80Handler if (this.networkProxy) { this.networkProxy.setExternalPort80Handler(handler); } console.log('Port80Handler connected to NetworkProxyBridge'); } /** * Initialize NetworkProxy instance */ public async initialize(): Promise { if (!this.networkProxy && this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) { // Configure NetworkProxy options based on SmartProxy settings const networkProxyOptions: any = { port: this.settings.networkProxyPort!, portProxyIntegration: true, logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info', useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available }; this.networkProxy = new NetworkProxy(networkProxyOptions); console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`); // Connect Port80Handler if available if (this.port80Handler) { this.networkProxy.setExternalPort80Handler(this.port80Handler); } // Apply route configurations to NetworkProxy await this.syncRoutesToNetworkProxy(this.settings.routes || []); } } /** * Handle certificate issuance or renewal events */ private handleCertificateEvent(data: ICertificateData): void { if (!this.networkProxy) return; console.log(`Received certificate for ${data.domain} from Port80Handler, updating NetworkProxy`); // Apply certificate directly to NetworkProxy this.networkProxy.updateCertificate(data.domain, data.certificate, data.privateKey); } /** * Apply an external (static) certificate into NetworkProxy */ public applyExternalCertificate(data: ICertificateData): void { if (!this.networkProxy) { console.log(`NetworkProxy not initialized: cannot apply external certificate for ${data.domain}`); return; } // Apply certificate directly to NetworkProxy this.networkProxy.updateCertificate(data.domain, data.certificate, data.privateKey); } /** * Get the NetworkProxy instance */ public getNetworkProxy(): NetworkProxy | null { return this.networkProxy; } /** * Get the NetworkProxy port */ public getNetworkProxyPort(): number { return this.networkProxy ? this.networkProxy.getListeningPort() : this.settings.networkProxyPort || 8443; } /** * Start NetworkProxy */ public async start(): Promise { if (this.networkProxy) { await this.networkProxy.start(); console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`); } } /** * Stop NetworkProxy */ public async stop(): Promise { if (this.networkProxy) { try { console.log('Stopping NetworkProxy...'); await this.networkProxy.stop(); console.log('NetworkProxy stopped successfully'); } catch (err) { console.log(`Error stopping NetworkProxy: ${err}`); } } } /** * Forwards a TLS connection to a NetworkProxy for handling */ public forwardToNetworkProxy( connectionId: string, socket: plugins.net.Socket, record: IConnectionRecord, initialData: Buffer, customProxyPort?: number, onError?: (reason: string) => void ): void { // Ensure NetworkProxy is initialized if (!this.networkProxy) { console.log( `[${connectionId}] NetworkProxy not initialized. Cannot forward connection.` ); if (onError) { onError('network_proxy_not_initialized'); } return; } // Use the custom port if provided, otherwise use the default NetworkProxy port const proxyPort = customProxyPort || this.networkProxy.getListeningPort(); const proxyHost = 'localhost'; // Assuming NetworkProxy runs locally if (this.settings.enableDetailedLogging) { console.log( `[${connectionId}] Forwarding TLS connection to NetworkProxy at ${proxyHost}:${proxyPort}` ); } // Create a connection to the NetworkProxy const proxySocket = plugins.net.connect({ host: proxyHost, port: proxyPort, }); // Store the outgoing socket in the record record.outgoing = proxySocket; record.outgoingStartTime = Date.now(); record.usingNetworkProxy = true; // Set up error handlers proxySocket.on('error', (err) => { console.log(`[${connectionId}] Error connecting to NetworkProxy: ${err.message}`); if (onError) { onError('network_proxy_connect_error'); } }); // Handle connection to NetworkProxy proxySocket.on('connect', () => { if (this.settings.enableDetailedLogging) { console.log(`[${connectionId}] Connected to NetworkProxy at ${proxyHost}:${proxyPort}`); } // First send the initial data that contains the TLS ClientHello proxySocket.write(initialData); // Now set up bidirectional piping between client and NetworkProxy socket.pipe(proxySocket); proxySocket.pipe(socket); if (this.settings.enableDetailedLogging) { console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`); } }); } /** * Synchronizes routes to NetworkProxy * * This method directly passes route configurations to NetworkProxy without any * intermediate conversion. NetworkProxy natively understands route configurations. * * @param routes The route configurations to sync to NetworkProxy */ public async syncRoutesToNetworkProxy(routes: IRouteConfig[]): Promise { if (!this.networkProxy) { console.log('Cannot sync configurations - NetworkProxy not initialized'); return; } try { // Filter only routes that are applicable to NetworkProxy (TLS termination) const networkProxyRoutes = routes.filter(route => { return ( route.action.type === 'forward' && route.action.tls && (route.action.tls.mode === 'terminate' || route.action.tls.mode === 'terminate-and-reencrypt') ); }); // Pass routes directly to NetworkProxy await this.networkProxy.updateRouteConfigs(networkProxyRoutes); console.log(`Synced ${networkProxyRoutes.length} routes directly to NetworkProxy`); } catch (err) { console.log(`Error syncing routes to NetworkProxy: ${err}`); } } /** * Request a certificate for a specific domain * * @param domain The domain to request a certificate for * @param routeName Optional route name to associate with this certificate */ public async requestCertificate(domain: string, routeName?: string): Promise { // Delegate to Port80Handler if available if (this.port80Handler) { try { // Check if the domain is already registered const cert = this.port80Handler.getCertificate(domain); if (cert) { console.log(`Certificate already exists for ${domain}`); return true; } // Build the domain options const domainOptions: any = { domainName: domain, sslRedirect: true, acmeMaintenance: true, }; // Add route reference if available if (routeName) { domainOptions.routeReference = { routeName }; } // Register the domain for certificate issuance this.port80Handler.addDomain(domainOptions); console.log(`Domain ${domain} registered for certificate issuance`); return true; } catch (err) { console.log(`Error requesting certificate: ${err}`); return false; } } // Fall back to NetworkProxy if Port80Handler is not available if (!this.networkProxy) { console.log('Cannot request certificate - NetworkProxy not initialized'); return false; } if (!this.settings.acme?.enabled) { console.log('Cannot request certificate - ACME is not enabled'); return false; } try { const result = await this.networkProxy.requestCertificate(domain); if (result) { console.log(`Certificate request for ${domain} submitted successfully`); } else { console.log(`Certificate request for ${domain} failed`); } return result; } catch (err) { console.log(`Error requesting certificate: ${err}`); return false; } } }