BREAKING CHANGE(smartproxy/configuration): Migrate SmartProxy to a fully unified route‐based configuration by removing legacy domain-based settings and conversion code. CertProvisioner, NetworkProxyBridge, and RouteManager now use IRouteConfig exclusively, and related legacy interfaces and files have been removed.
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '15.1.0',
|
||||
version: '16.0.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.'
|
||||
}
|
||||
|
@@ -54,8 +54,30 @@ export function createCertificateProvisioner(
|
||||
} = acmeOptions;
|
||||
|
||||
// Create and return the certificate provisioner
|
||||
// Convert domain configs to route configs for the new CertProvisioner
|
||||
const routeConfigs = domainConfigs.map(config => {
|
||||
// Create a basic route config with the minimum required properties
|
||||
return {
|
||||
match: {
|
||||
ports: 443,
|
||||
domains: config.domains
|
||||
},
|
||||
action: {
|
||||
type: 'forward' as const,
|
||||
target: config.forwarding.target,
|
||||
tls: {
|
||||
mode: config.forwarding.type === 'https-terminate-to-https' ?
|
||||
'terminate-and-reencrypt' as const :
|
||||
'terminate' as const,
|
||||
certificate: 'auto' as 'auto'
|
||||
},
|
||||
security: config.forwarding.security
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return new CertProvisioner(
|
||||
domainConfigs,
|
||||
routeConfigs,
|
||||
port80Handler,
|
||||
networkProxyBridge,
|
||||
certProvider,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
||||
|
||||
/**
|
||||
* Certificate data structure containing all necessary information
|
||||
@@ -12,6 +13,11 @@ export interface ICertificateData {
|
||||
// Optional source and renewal information for event emissions
|
||||
source?: 'static' | 'http01' | 'dns01';
|
||||
isRenewal?: boolean;
|
||||
// Reference to the route that requested this certificate (if available)
|
||||
routeReference?: {
|
||||
routeId?: string;
|
||||
routeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,6 +35,10 @@ export interface ICertificateFailure {
|
||||
domain: string;
|
||||
error: string;
|
||||
isRenewal: boolean;
|
||||
routeReference?: {
|
||||
routeId?: string;
|
||||
routeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,35 +48,46 @@ export interface ICertificateExpiring {
|
||||
domain: string;
|
||||
expiryDate: Date;
|
||||
daysRemaining: number;
|
||||
routeReference?: {
|
||||
routeId?: string;
|
||||
routeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain forwarding configuration
|
||||
* Route-specific forwarding configuration for ACME challenges
|
||||
*/
|
||||
export interface IForwardConfig {
|
||||
ip: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain-specific forwarding configuration for ACME challenges
|
||||
*/
|
||||
export interface IDomainForwardConfig {
|
||||
export interface IRouteForwardConfig {
|
||||
domain: string;
|
||||
forwardConfig?: IForwardConfig;
|
||||
acmeForwardConfig?: IForwardConfig;
|
||||
target: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
sslRedirect?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain configuration options
|
||||
* Domain configuration options for Port80Handler
|
||||
*
|
||||
* This is used internally by the Port80Handler to manage domains
|
||||
* but will eventually be replaced with route-based options.
|
||||
*/
|
||||
export interface IDomainOptions {
|
||||
domainName: string;
|
||||
sslRedirect: boolean; // if true redirects the request to port 443
|
||||
acmeMaintenance: boolean; // tries to always have a valid cert for this domain
|
||||
forward?: IForwardConfig; // forwards all http requests to that target
|
||||
acmeForward?: IForwardConfig; // forwards letsencrypt requests to this config
|
||||
forward?: {
|
||||
ip: string;
|
||||
port: number;
|
||||
}; // forwards all http requests to that target
|
||||
acmeForward?: {
|
||||
ip: string;
|
||||
port: number;
|
||||
}; // forwards letsencrypt requests to this config
|
||||
routeReference?: {
|
||||
routeId?: string;
|
||||
routeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +104,6 @@ export interface IAcmeOptions {
|
||||
autoRenew?: boolean; // Whether to automatically renew certificates
|
||||
certificateStore?: string; // Directory to store certificates
|
||||
skipConfiguredCerts?: boolean; // Skip domains with existing certificates
|
||||
domainForwards?: IDomainForwardConfig[]; // Domain-specific forwarding configs
|
||||
routeForwards?: IRouteForwardConfig[]; // Route-specific forwarding configs
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { IDomainConfig } from '../../forwarding/config/domain-config.js';
|
||||
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
||||
import type { ICertificateData, IDomainForwardConfig, IDomainOptions } from '../models/certificate-types.js';
|
||||
import { Port80HandlerEvents, CertProvisionerEvents } from '../events/certificate-events.js';
|
||||
import { Port80Handler } from '../../http/port80/port80-handler.js';
|
||||
@@ -28,6 +29,45 @@ export class CertProvisioner extends plugins.EventEmitter {
|
||||
private port80Handler: Port80Handler;
|
||||
private networkProxyBridge: INetworkProxyBridge;
|
||||
private certProvisionFunction?: (domain: string) => Promise<TCertProvisionObject>;
|
||||
|
||||
/**
|
||||
* Extract domains from route configurations for certificate management
|
||||
* @param routes Route configurations
|
||||
*/
|
||||
private extractDomainsFromRoutes(routes: IRouteConfig[]): void {
|
||||
// Process all HTTPS routes that need certificates
|
||||
for (const route of routes) {
|
||||
// Only process routes with TLS termination that need certificates
|
||||
if (route.action.type === 'forward' &&
|
||||
route.action.tls &&
|
||||
(route.action.tls.mode === 'terminate' || route.action.tls.mode === 'terminate-and-reencrypt') &&
|
||||
route.match.domains) {
|
||||
|
||||
// Extract domains from the route
|
||||
const domains = Array.isArray(route.match.domains)
|
||||
? route.match.domains
|
||||
: [route.match.domains];
|
||||
|
||||
// Skip wildcard domains that can't use ACME
|
||||
const eligibleDomains = domains.filter(d => !d.includes('*'));
|
||||
|
||||
if (eligibleDomains.length > 0) {
|
||||
// Create a domain config object for certificate provisioning
|
||||
const domainConfig: IDomainConfig = {
|
||||
domains: eligibleDomains,
|
||||
forwarding: {
|
||||
type: route.action.tls.mode === 'terminate' ? 'https-terminate-to-http' : 'https-terminate-to-https',
|
||||
target: route.action.target || { host: 'localhost', port: 80 },
|
||||
// Add any other required properties from the legacy format
|
||||
security: route.action.security || {}
|
||||
}
|
||||
};
|
||||
|
||||
this.domainConfigs.push(domainConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private forwardConfigs: IDomainForwardConfig[];
|
||||
private renewThresholdDays: number;
|
||||
private renewCheckIntervalHours: number;
|
||||
@@ -47,7 +87,7 @@ export class CertProvisioner extends plugins.EventEmitter {
|
||||
* @param forwardConfigs Domain forwarding configurations for ACME challenges
|
||||
*/
|
||||
constructor(
|
||||
domainConfigs: IDomainConfig[],
|
||||
routeConfigs: IRouteConfig[],
|
||||
port80Handler: Port80Handler,
|
||||
networkProxyBridge: INetworkProxyBridge,
|
||||
certProvider?: (domain: string) => Promise<TCertProvisionObject>,
|
||||
@@ -57,7 +97,8 @@ export class CertProvisioner extends plugins.EventEmitter {
|
||||
forwardConfigs: IDomainForwardConfig[] = []
|
||||
) {
|
||||
super();
|
||||
this.domainConfigs = domainConfigs;
|
||||
this.domainConfigs = [];
|
||||
this.extractDomainsFromRoutes(routeConfigs);
|
||||
this.port80Handler = port80Handler;
|
||||
this.networkProxyBridge = networkProxyBridge;
|
||||
this.certProvisionFunction = certProvider;
|
||||
|
@@ -377,10 +377,7 @@ export class NetworkProxyBridge {
|
||||
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
|
||||
// Headers handling happens in the request handler level
|
||||
};
|
||||
|
||||
configs.push(config);
|
||||
|
@@ -303,135 +303,9 @@ export class RouteManager extends plugins.EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a domain config to routes
|
||||
* (For backward compatibility with code that still uses domainConfigs)
|
||||
* Domain-based configuration methods have been removed
|
||||
* as part of the migration to pure route-based configuration
|
||||
*/
|
||||
public domainConfigToRoutes(domainConfig: IDomainConfig): IRouteConfig[] {
|
||||
const routes: IRouteConfig[] = [];
|
||||
const { domains, forwarding } = domainConfig;
|
||||
|
||||
// Determine the action based on forwarding type
|
||||
let action: IRouteAction = {
|
||||
type: 'forward',
|
||||
target: {
|
||||
host: forwarding.target.host,
|
||||
port: forwarding.target.port
|
||||
}
|
||||
};
|
||||
|
||||
// Set TLS mode based on forwarding type
|
||||
switch (forwarding.type) {
|
||||
case 'http-only':
|
||||
// No TLS settings needed
|
||||
break;
|
||||
case 'https-passthrough':
|
||||
action.tls = { mode: 'passthrough' };
|
||||
break;
|
||||
case 'https-terminate-to-http':
|
||||
action.tls = {
|
||||
mode: 'terminate',
|
||||
certificate: forwarding.https?.customCert ? {
|
||||
key: forwarding.https.customCert.key,
|
||||
cert: forwarding.https.customCert.cert
|
||||
} : 'auto'
|
||||
};
|
||||
break;
|
||||
case 'https-terminate-to-https':
|
||||
action.tls = {
|
||||
mode: 'terminate-and-reencrypt',
|
||||
certificate: forwarding.https?.customCert ? {
|
||||
key: forwarding.https.customCert.key,
|
||||
cert: forwarding.https.customCert.cert
|
||||
} : 'auto'
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// Add security settings if present
|
||||
if (forwarding.security) {
|
||||
action.security = {
|
||||
allowedIps: forwarding.security.allowedIps,
|
||||
blockedIps: forwarding.security.blockedIps,
|
||||
maxConnections: forwarding.security.maxConnections
|
||||
};
|
||||
}
|
||||
|
||||
// Add advanced settings if present
|
||||
if (forwarding.advanced) {
|
||||
action.advanced = {
|
||||
timeout: forwarding.advanced.timeout,
|
||||
headers: forwarding.advanced.headers,
|
||||
keepAlive: forwarding.advanced.keepAlive
|
||||
};
|
||||
}
|
||||
|
||||
// Determine which port to use based on forwarding type
|
||||
const defaultPort = forwarding.type.startsWith('https') ? 443 : 80;
|
||||
|
||||
// Add the main route
|
||||
routes.push({
|
||||
match: {
|
||||
ports: defaultPort,
|
||||
domains
|
||||
},
|
||||
action,
|
||||
name: `Route for ${domains.join(', ')}`
|
||||
});
|
||||
|
||||
// Add HTTP redirect if needed
|
||||
if (forwarding.http?.redirectToHttps) {
|
||||
routes.push({
|
||||
match: {
|
||||
ports: 80,
|
||||
domains
|
||||
},
|
||||
action: {
|
||||
type: 'redirect',
|
||||
redirect: {
|
||||
to: 'https://{domain}{path}',
|
||||
status: 301
|
||||
}
|
||||
},
|
||||
name: `HTTP Redirect for ${domains.join(', ')}`,
|
||||
priority: 100 // Higher priority for redirects
|
||||
});
|
||||
}
|
||||
|
||||
// Add port ranges if specified
|
||||
if (forwarding.advanced?.portRanges) {
|
||||
for (const range of forwarding.advanced.portRanges) {
|
||||
routes.push({
|
||||
match: {
|
||||
ports: [{ from: range.from, to: range.to }],
|
||||
domains
|
||||
},
|
||||
action,
|
||||
name: `Port Range ${range.from}-${range.to} for ${domains.join(', ')}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routes based on domain configs
|
||||
* (For backward compatibility with code that still uses domainConfigs)
|
||||
*/
|
||||
public updateFromDomainConfigs(domainConfigs: IDomainConfig[]): void {
|
||||
const routes: IRouteConfig[] = [];
|
||||
|
||||
// Convert each domain config to routes
|
||||
for (const config of domainConfigs) {
|
||||
routes.push(...this.domainConfigToRoutes(config));
|
||||
}
|
||||
|
||||
// Merge with existing routes that aren't derived from domain configs
|
||||
const nonDomainRoutes = this.routes.filter(r =>
|
||||
!r.name || !r.name.includes('for '));
|
||||
|
||||
this.updateRoutes([...nonDomainRoutes, ...routes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the route configuration and return any warnings
|
||||
|
@@ -61,8 +61,8 @@ export class TimeoutManager {
|
||||
* Calculate effective max lifetime based on connection type
|
||||
*/
|
||||
public getEffectiveMaxLifetime(record: IConnectionRecord): number {
|
||||
// Use domain-specific timeout from forwarding.advanced if available
|
||||
const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout ||
|
||||
// Use route-specific timeout if available from the routeConfig
|
||||
const baseTimeout = record.routeConfig?.action.advanced?.timeout ||
|
||||
this.settings.maxConnectionLifetime ||
|
||||
86400000; // 24 hours default
|
||||
|
||||
|
Reference in New Issue
Block a user