fix(routing): unify route based architecture

This commit is contained in:
2025-05-13 12:48:41 +00:00
parent fe632bde67
commit fcc8cf9caa
46 changed files with 7943 additions and 1332 deletions

View File

@ -1,7 +1,6 @@
import * as plugins from '../../plugins.js';
import { NetworkProxy } from '../network-proxy/index.js';
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 } from './models/interfaces.js';
@ -11,8 +10,8 @@ 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 maps route configurations to NetworkProxy configuration format and manages
* certificate provisioning through Port80Handler when ACME is enabled.
* 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'
@ -49,7 +48,7 @@ export class NetworkProxyBridge {
*/
public async initialize(): Promise<void> {
if (!this.networkProxy && this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
// Configure NetworkProxy options based on PortProxy settings
// Configure NetworkProxy options based on SmartProxy settings
const networkProxyOptions: any = {
port: this.settings.networkProxyPort!,
portProxyIntegration: true,
@ -57,7 +56,6 @@ export class NetworkProxyBridge {
useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available
};
this.networkProxy = new NetworkProxy(networkProxyOptions);
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
@ -80,29 +78,8 @@ export class NetworkProxyBridge {
console.log(`Received certificate for ${data.domain} from Port80Handler, updating NetworkProxy`);
try {
// Find existing config for this domain
const existingConfigs = this.networkProxy.getProxyConfigs()
.filter(config => config.hostName === data.domain);
if (existingConfigs.length > 0) {
// Update existing configs with new certificate
for (const config of existingConfigs) {
config.privateKey = data.privateKey;
config.publicKey = data.certificate;
}
// Apply updated configs
this.networkProxy.updateProxyConfigs(existingConfigs)
.then(() => console.log(`Updated certificate for ${data.domain} in NetworkProxy`))
.catch(err => console.log(`Error updating certificate in NetworkProxy: ${err}`));
} else {
// Create a new config for this domain
console.log(`No existing config found for ${data.domain}, creating new config in NetworkProxy`);
}
} catch (err) {
console.log(`Error handling certificate event: ${err}`);
}
// Apply certificate directly to NetworkProxy
this.networkProxy.updateCertificate(data.domain, data.certificate, data.privateKey);
}
/**
@ -113,7 +90,9 @@ export class NetworkProxyBridge {
console.log(`NetworkProxy not initialized: cannot apply external certificate for ${data.domain}`);
return;
}
this.handleCertificateEvent(data);
// Apply certificate directly to NetworkProxy
this.networkProxy.updateCertificate(data.domain, data.certificate, data.privateKey);
}
/**
@ -155,92 +134,6 @@ export class NetworkProxyBridge {
}
}
/**
* Register domains from routes with Port80Handler for certificate management
*
* Extracts domains from routes that require TLS termination and registers them
* with the Port80Handler for certificate issuance and renewal.
*
* @param routes The route configurations to extract domains from
*/
public registerDomainsWithPort80Handler(routes: IRouteConfig[]): void {
if (!this.port80Handler) {
console.log('Cannot register domains - Port80Handler not initialized');
return;
}
// Extract domains from routes that require TLS termination
const domainsToRegister = new Set<string>();
for (const route of routes) {
// Skip routes without domains or TLS configuration
if (!route.match.domains || !route.action.tls) continue;
// Only register domains for routes that terminate TLS
if (route.action.tls.mode !== 'terminate' && route.action.tls.mode !== 'terminate-and-reencrypt') continue;
// Extract domains from route
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
// Add each domain to the set (avoiding duplicates)
for (const domain of domains) {
// Skip wildcards
if (domain.includes('*')) {
console.log(`Skipping wildcard domain for ACME: ${domain}`);
continue;
}
domainsToRegister.add(domain);
}
}
// Register each unique domain with Port80Handler
for (const domain of domainsToRegister) {
try {
this.port80Handler.addDomain({
domainName: domain,
sslRedirect: true,
acmeMaintenance: true,
// Include route reference if we can find it
routeReference: this.findRouteReferenceForDomain(domain, routes)
});
console.log(`Registered domain with Port80Handler: ${domain}`);
} catch (err) {
console.log(`Error registering domain ${domain} with Port80Handler: ${err}`);
}
}
}
/**
* Finds the route reference for a given domain
*
* @param domain The domain to find a route reference for
* @param routes The routes to search
* @returns The route reference if found, undefined otherwise
*/
private findRouteReferenceForDomain(domain: string, routes: IRouteConfig[]): { routeId?: string; routeName?: string } | undefined {
// Find the first route that matches this domain
for (const route of routes) {
if (!route.match.domains) continue;
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
if (domains.includes(domain)) {
return {
routeId: undefined, // No explicit IDs in our current routes
routeName: route.name
};
}
}
return undefined;
}
/**
* Forwards a TLS connection to a NetworkProxy for handling
*/
@ -305,7 +198,6 @@ export class NetworkProxyBridge {
socket.pipe(proxySocket);
proxySocket.pipe(socket);
// Update activity on data transfer (caller should handle this)
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
}
@ -315,13 +207,8 @@ export class NetworkProxyBridge {
/**
* Synchronizes routes to NetworkProxy
*
* This method directly maps route configurations to NetworkProxy format and updates
* the NetworkProxy with these 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
* 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
*/
@ -332,140 +219,22 @@ export class NetworkProxyBridge {
}
try {
// Get SSL certificates from assets
// Import fs directly since it's not in plugins
const fs = await import('fs');
let defaultCertPair;
try {
defaultCertPair = {
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8'),
};
} catch (certError) {
console.log(`Warning: Could not read default certificates: ${certError}`);
console.log(
'Using empty certificate placeholders - ACME will generate proper certificates if enabled'
// 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')
);
});
// Use empty placeholders - NetworkProxy will use its internal defaults
// or ACME will generate proper ones if enabled
defaultCertPair = {
key: '',
cert: '',
};
}
// Map routes directly to NetworkProxy configs
const proxyConfigs = this.mapRoutesToNetworkProxyConfigs(routes, defaultCertPair);
// Update the proxy configs
await this.networkProxy.updateProxyConfigs(proxyConfigs);
console.log(`Synced ${proxyConfigs.length} configurations to NetworkProxy`);
// Register domains with Port80Handler for certificate issuance
if (this.port80Handler) {
this.registerDomainsWithPort80Handler(routes);
}
// 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}`);
}
}
/**
* Map routes directly to NetworkProxy configuration format
*
* This method directly maps route configurations to NetworkProxy's format
* without any intermediate domain-based representation. It processes each route
* and creates appropriate NetworkProxy configs for domains that require TLS termination.
*
* @param routes Array of route configurations to map
* @param defaultCertPair Default certificate to use if no custom certificate is specified
* @returns Array of NetworkProxy configurations
*/
public mapRoutesToNetworkProxyConfigs(
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;
// Skip routes that don't require TLS termination
if (route.action.tls.mode !== 'terminate' && route.action.tls.mode !== 'terminate-and-reencrypt') 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) {
// 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 the NetworkProxy config
const config: plugins.tsclass.network.IReverseProxyConfig = {
hostName: domain,
privateKey: certKey,
publicKey: certCert,
destinationIps: targetHosts,
destinationPorts: [targetPort]
// Note: We can't include additional metadata as it's not supported in the interface
};
configs.push(config);
}
}
return configs;
}
/**
* @deprecated This method is kept for backward compatibility.
* Use mapRoutesToNetworkProxyConfigs() instead.
*/
public convertRoutesToNetworkProxyConfigs(
routes: IRouteConfig[],
defaultCertPair: { key: string; cert: string }
): plugins.tsclass.network.IReverseProxyConfig[] {
return this.mapRoutesToNetworkProxyConfigs(routes, defaultCertPair);
}
/**
* @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('DEPRECATED: Method syncDomainConfigsToNetworkProxy will be removed in a future version.');
console.log('Please use syncRoutesToNetworkProxy() instead for direct route-based configuration.');
await this.syncRoutesToNetworkProxy(this.settings.routes || []);
}
/**
* Request a certificate for a specific domain
@ -496,12 +265,6 @@ export class NetworkProxyBridge {
domainOptions.routeReference = {
routeName
};
} else {
// Try to find a route reference from the current routes
const routeReference = this.findRouteReferenceForDomain(domain, this.settings.routes || []);
if (routeReference) {
domainOptions.routeReference = routeReference;
}
}
// Register the domain for certificate issuance