feat(portproxy): Add ACME certificate management options to PortProxy, update ACME settings handling, and bump dependency versions
This commit is contained in:
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '3.35.0',
|
||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
|
||||
version: '3.37.0',
|
||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
||||
}
|
||||
|
@ -56,9 +56,21 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
||||
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
|
||||
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
|
||||
|
||||
// New property for NetworkProxy integration
|
||||
// NetworkProxy integration
|
||||
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
||||
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
||||
|
||||
// ACME certificate management options
|
||||
acme?: {
|
||||
enabled?: boolean; // Whether to enable automatic certificate management
|
||||
port?: number; // Port to listen on for ACME challenges (default: 80)
|
||||
contactEmail?: string; // Email for Let's Encrypt account
|
||||
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
|
||||
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
|
||||
autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
|
||||
certificateStore?: string; // Directory to store certificates (default: ./certs)
|
||||
skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -418,6 +430,18 @@ export class PortProxy {
|
||||
|
||||
// NetworkProxy settings
|
||||
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
|
||||
|
||||
// ACME certificate settings with reasonable defaults
|
||||
acme: settingsArg.acme || {
|
||||
enabled: false,
|
||||
port: 80,
|
||||
contactEmail: 'admin@example.com',
|
||||
useProduction: false,
|
||||
renewThresholdDays: 30,
|
||||
autoRenew: true,
|
||||
certificateStore: './certs',
|
||||
skipConfiguredCerts: false
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize NetworkProxy if enabled
|
||||
@ -431,11 +455,19 @@ export class PortProxy {
|
||||
*/
|
||||
private async initializeNetworkProxy(): Promise<void> {
|
||||
if (!this.networkProxy) {
|
||||
this.networkProxy = new NetworkProxy({
|
||||
// Configure NetworkProxy options based on PortProxy settings
|
||||
const networkProxyOptions: any = {
|
||||
port: this.settings.networkProxyPort!,
|
||||
portProxyIntegration: true,
|
||||
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
|
||||
});
|
||||
};
|
||||
|
||||
// Add ACME settings if configured
|
||||
if (this.settings.acme) {
|
||||
networkProxyOptions.acme = { ...this.settings.acme };
|
||||
}
|
||||
|
||||
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
||||
|
||||
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
||||
|
||||
@ -458,6 +490,55 @@ export class PortProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ACME certificate settings
|
||||
* @param acmeSettings New ACME settings
|
||||
*/
|
||||
public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
|
||||
console.log('Updating ACME certificate settings');
|
||||
|
||||
// Update settings
|
||||
this.settings.acme = {
|
||||
...this.settings.acme,
|
||||
...acmeSettings
|
||||
};
|
||||
|
||||
// If NetworkProxy is initialized, update its ACME settings
|
||||
if (this.networkProxy) {
|
||||
try {
|
||||
// Recreate NetworkProxy with new settings if ACME enabled state has changed
|
||||
if (this.settings.acme.enabled !== acmeSettings.enabled) {
|
||||
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
|
||||
|
||||
// Stop the current NetworkProxy
|
||||
await this.networkProxy.stop();
|
||||
this.networkProxy = null;
|
||||
|
||||
// Reinitialize with new settings
|
||||
await this.initializeNetworkProxy();
|
||||
|
||||
// Use start() to make sure ACME gets initialized if newly enabled
|
||||
await this.networkProxy.start();
|
||||
} else {
|
||||
// Update existing NetworkProxy with new settings
|
||||
// Note: Some settings may require a restart to take effect
|
||||
console.log('Updating ACME settings in NetworkProxy');
|
||||
|
||||
// For certificate renewals, we might want to trigger checks with the new settings
|
||||
if (acmeSettings.renewThresholdDays) {
|
||||
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
|
||||
// This is implementation-dependent but gives an example
|
||||
if (this.networkProxy.options.acme) {
|
||||
this.networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error updating ACME settings: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes PortProxy domain configurations to NetworkProxy
|
||||
* This allows domains configured in PortProxy to be used by NetworkProxy
|
||||
@ -472,10 +553,24 @@ export class PortProxy {
|
||||
// Get SSL certificates from assets
|
||||
// Import fs directly since it's not in plugins
|
||||
const fs = await import('fs');
|
||||
const certPair = {
|
||||
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
|
||||
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8')
|
||||
};
|
||||
|
||||
let certPair;
|
||||
try {
|
||||
certPair = {
|
||||
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');
|
||||
|
||||
// Use empty placeholders - NetworkProxy will use its internal defaults
|
||||
// or ACME will generate proper ones if enabled
|
||||
certPair = {
|
||||
key: '',
|
||||
cert: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Convert domain configs to NetworkProxy configs
|
||||
const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
|
||||
@ -483,6 +578,19 @@ export class PortProxy {
|
||||
certPair
|
||||
);
|
||||
|
||||
// Log ACME-eligible domains if ACME is enabled
|
||||
if (this.settings.acme?.enabled) {
|
||||
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(', ')}`);
|
||||
} else {
|
||||
console.log('No domains eligible for ACME certificates found in configuration');
|
||||
}
|
||||
}
|
||||
|
||||
// Update NetworkProxy with the converted configs
|
||||
this.networkProxy.updateProxyConfigs(proxyConfigs).then(() => {
|
||||
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
||||
@ -493,6 +601,36 @@ export class PortProxy {
|
||||
console.log(`Failed to sync configurations: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a certificate for a specific domain
|
||||
* @param domain The domain to request a certificate for
|
||||
* @returns Promise that resolves to true if the request was successful, false otherwise
|
||||
*/
|
||||
public async requestCertificate(domain: string): Promise<boolean> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards a TLS connection to a NetworkProxy for handling
|
||||
@ -1340,6 +1478,18 @@ export class PortProxy {
|
||||
if (this.networkProxy) {
|
||||
await this.networkProxy.start();
|
||||
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
||||
|
||||
// Log ACME status
|
||||
if (this.settings.acme?.enabled) {
|
||||
console.log(`ACME certificate management is enabled (${this.settings.acme.useProduction ? 'Production' : 'Staging'} mode)`);
|
||||
console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`);
|
||||
|
||||
// Register domains for ACME certificates if enabled
|
||||
if (this.networkProxy.options.acme?.enabled) {
|
||||
console.log('Registering domains with ACME certificate manager...');
|
||||
// The NetworkProxy will handle this internally via registerDomainsWithAcmeManager()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define a unified connection handler for all listening ports.
|
||||
@ -2094,11 +2244,17 @@ export class PortProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop NetworkProxy if it was started
|
||||
// Stop NetworkProxy if it was started (which also stops ACME manager)
|
||||
if (this.networkProxy) {
|
||||
try {
|
||||
console.log('Stopping NetworkProxy...');
|
||||
await this.networkProxy.stop();
|
||||
console.log('NetworkProxy stopped successfully');
|
||||
|
||||
// Log ACME shutdown if it was enabled
|
||||
if (this.settings.acme?.enabled) {
|
||||
console.log('ACME certificate manager stopped');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error stopping NetworkProxy: ${err}`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user