2025-05-09 21:21:28 +00:00
|
|
|
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';
|
2025-05-09 22:46:53 +00:00
|
|
|
import type { ICertificateData } from '../../certificate/models/certificate-types.js';
|
|
|
|
import type { IConnectionRecord, ISmartProxyOptions, IDomainConfig } from './models/interfaces.js';
|
2025-03-14 09:53:25 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages NetworkProxy integration for TLS termination
|
|
|
|
*/
|
|
|
|
export class NetworkProxyBridge {
|
|
|
|
private networkProxy: NetworkProxy | null = null;
|
2025-03-25 22:30:57 +00:00
|
|
|
private port80Handler: Port80Handler | null = null;
|
2025-05-09 21:21:28 +00:00
|
|
|
|
2025-05-09 22:46:53 +00:00
|
|
|
constructor(private settings: ISmartProxyOptions) {}
|
2025-03-14 09:53:25 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
/**
|
|
|
|
* Set the Port80Handler to use for certificate management
|
|
|
|
*/
|
|
|
|
public setPort80Handler(handler: Port80Handler): void {
|
|
|
|
this.port80Handler = handler;
|
|
|
|
|
2025-05-02 14:58:33 +00:00
|
|
|
// Subscribe to certificate events
|
|
|
|
subscribeToPort80Handler(handler, {
|
|
|
|
onCertificateIssued: this.handleCertificateEvent.bind(this),
|
|
|
|
onCertificateRenewed: this.handleCertificateEvent.bind(this)
|
|
|
|
});
|
2025-03-25 22:30:57 +00:00
|
|
|
|
|
|
|
// If NetworkProxy is already initialized, connect it with Port80Handler
|
|
|
|
if (this.networkProxy) {
|
|
|
|
this.networkProxy.setExternalPort80Handler(handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Port80Handler connected to NetworkProxyBridge');
|
|
|
|
}
|
|
|
|
|
2025-03-14 09:53:25 +00:00
|
|
|
/**
|
|
|
|
* Initialize NetworkProxy instance
|
|
|
|
*/
|
|
|
|
public async initialize(): Promise<void> {
|
|
|
|
if (!this.networkProxy && this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
|
|
|
|
// Configure NetworkProxy options based on PortProxy settings
|
|
|
|
const networkProxyOptions: any = {
|
|
|
|
port: this.settings.networkProxyPort!,
|
|
|
|
portProxyIntegration: true,
|
|
|
|
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info',
|
2025-03-25 22:30:57 +00:00
|
|
|
useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available
|
2025-03-14 09:53:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
|
|
|
|
|
|
|
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
2025-03-25 22:30:57 +00:00
|
|
|
|
|
|
|
// Connect Port80Handler if available
|
|
|
|
if (this.port80Handler) {
|
|
|
|
this.networkProxy.setExternalPort80Handler(this.port80Handler);
|
|
|
|
}
|
2025-03-14 09:53:25 +00:00
|
|
|
|
|
|
|
// Convert and apply domain configurations to NetworkProxy
|
|
|
|
await this.syncDomainConfigsToNetworkProxy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
/**
|
|
|
|
* Handle certificate issuance or renewal events
|
|
|
|
*/
|
2025-05-09 22:46:53 +00:00
|
|
|
private handleCertificateEvent(data: ICertificateData): void {
|
2025-03-25 22:30:57 +00:00
|
|
|
if (!this.networkProxy) return;
|
2025-05-09 22:46:53 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
console.log(`Received certificate for ${data.domain} from Port80Handler, updating NetworkProxy`);
|
2025-05-09 22:46:53 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
try {
|
|
|
|
// Find existing config for this domain
|
|
|
|
const existingConfigs = this.networkProxy.getProxyConfigs()
|
|
|
|
.filter(config => config.hostName === data.domain);
|
2025-05-09 22:46:53 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
if (existingConfigs.length > 0) {
|
|
|
|
// Update existing configs with new certificate
|
|
|
|
for (const config of existingConfigs) {
|
|
|
|
config.privateKey = data.privateKey;
|
|
|
|
config.publicKey = data.certificate;
|
|
|
|
}
|
2025-05-09 22:46:53 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
// 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}`);
|
|
|
|
}
|
|
|
|
}
|
2025-05-09 22:46:53 +00:00
|
|
|
|
2025-05-01 12:13:18 +00:00
|
|
|
/**
|
|
|
|
* Apply an external (static) certificate into NetworkProxy
|
|
|
|
*/
|
2025-05-09 22:46:53 +00:00
|
|
|
public applyExternalCertificate(data: ICertificateData): void {
|
2025-05-01 12:13:18 +00:00
|
|
|
if (!this.networkProxy) {
|
|
|
|
console.log(`NetworkProxy not initialized: cannot apply external certificate for ${data.domain}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.handleCertificateEvent(data);
|
|
|
|
}
|
|
|
|
|
2025-03-14 09:53:25 +00:00
|
|
|
/**
|
|
|
|
* 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<void> {
|
|
|
|
if (this.networkProxy) {
|
|
|
|
await this.networkProxy.start();
|
|
|
|
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop NetworkProxy
|
|
|
|
*/
|
|
|
|
public async stop(): Promise<void> {
|
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
/**
|
|
|
|
* Register domains with Port80Handler
|
|
|
|
*/
|
|
|
|
public registerDomainsWithPort80Handler(domains: string[]): void {
|
|
|
|
if (!this.port80Handler) {
|
|
|
|
console.log('Cannot register domains - Port80Handler not initialized');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const domain of domains) {
|
|
|
|
// Skip wildcards
|
|
|
|
if (domain.includes('*')) {
|
|
|
|
console.log(`Skipping wildcard domain for ACME: ${domain}`);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register the domain
|
|
|
|
try {
|
|
|
|
this.port80Handler.addDomain({
|
|
|
|
domainName: domain,
|
|
|
|
sslRedirect: true,
|
|
|
|
acmeMaintenance: true
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log(`Registered domain with Port80Handler: ${domain}`);
|
|
|
|
} catch (err) {
|
|
|
|
console.log(`Error registering domain ${domain} with Port80Handler: ${err}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-14 09:53:25 +00:00
|
|
|
/**
|
|
|
|
* Forwards a TLS connection to a NetworkProxy for handling
|
|
|
|
*/
|
|
|
|
public forwardToNetworkProxy(
|
|
|
|
connectionId: string,
|
|
|
|
socket: plugins.net.Socket,
|
2025-05-09 22:46:53 +00:00
|
|
|
record: IConnectionRecord,
|
2025-03-14 09:53:25 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
// Update activity on data transfer (caller should handle this)
|
|
|
|
if (this.settings.enableDetailedLogging) {
|
|
|
|
console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Synchronizes domain configurations to NetworkProxy
|
|
|
|
*/
|
|
|
|
public async syncDomainConfigsToNetworkProxy(): Promise<void> {
|
|
|
|
if (!this.networkProxy) {
|
|
|
|
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Get SSL certificates from assets
|
|
|
|
// Import fs directly since it's not in plugins
|
|
|
|
const fs = await import('fs');
|
|
|
|
|
|
|
|
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
|
2025-05-09 21:21:28 +00:00
|
|
|
const proxyConfigs = this.networkProxy.convertSmartProxyConfigs(
|
2025-03-14 09:53:25 +00:00
|
|
|
this.settings.domainConfigs,
|
|
|
|
certPair
|
|
|
|
);
|
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
// Log ACME-eligible domains
|
2025-05-02 14:58:33 +00:00
|
|
|
const acmeEnabled = !!this.settings.acme?.enabled;
|
2025-03-25 22:30:57 +00:00
|
|
|
if (acmeEnabled) {
|
2025-03-14 09:53:25 +00:00
|
|
|
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(', ')}`);
|
2025-03-25 22:30:57 +00:00
|
|
|
|
|
|
|
// Register these domains with Port80Handler if available
|
|
|
|
if (this.port80Handler) {
|
|
|
|
this.registerDomainsWithPort80Handler(acmeEligibleDomains);
|
|
|
|
}
|
2025-03-14 09:53:25 +00:00
|
|
|
} else {
|
|
|
|
console.log('No domains eligible for ACME certificates found in configuration');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update NetworkProxy with the converted configs
|
|
|
|
await this.networkProxy.updateProxyConfigs(proxyConfigs);
|
|
|
|
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
|
|
|
} catch (err) {
|
|
|
|
console.log(`Failed to sync configurations: ${err}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request a certificate for a specific domain
|
|
|
|
*/
|
|
|
|
public async requestCertificate(domain: string): Promise<boolean> {
|
2025-03-25 22:30:57 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register the domain for certificate issuance
|
|
|
|
this.port80Handler.addDomain({
|
|
|
|
domainName: domain,
|
|
|
|
sslRedirect: true,
|
|
|
|
acmeMaintenance: true
|
|
|
|
});
|
|
|
|
|
|
|
|
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
|
2025-03-14 09:53:25 +00:00
|
|
|
if (!this.networkProxy) {
|
|
|
|
console.log('Cannot request certificate - NetworkProxy not initialized');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-05-02 14:58:33 +00:00
|
|
|
if (!this.settings.acme?.enabled) {
|
2025-03-14 09:53:25 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|