fix
This commit is contained in:
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user