update
This commit is contained in:
371
ts/proxies/smart-proxy/network-proxy-bridge.ts
Normal file
371
ts/proxies/smart-proxy/network-proxy-bridge.ts
Normal file
@ -0,0 +1,371 @@
|
||||
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 { CertificateData } from '../../certificate/models/certificate-types.js';
|
||||
import type { ConnectionRecord, SmartProxyOptions, DomainConfig } from './models/interfaces.js';
|
||||
|
||||
/**
|
||||
* Manages NetworkProxy integration for TLS termination
|
||||
*/
|
||||
export class NetworkProxyBridge {
|
||||
private networkProxy: NetworkProxy | null = null;
|
||||
private port80Handler: Port80Handler | null = null;
|
||||
|
||||
constructor(private settings: SmartProxyOptions) {}
|
||||
|
||||
/**
|
||||
* Set the Port80Handler to use for certificate management
|
||||
*/
|
||||
public setPort80Handler(handler: Port80Handler): void {
|
||||
this.port80Handler = handler;
|
||||
|
||||
// Subscribe to certificate events
|
||||
subscribeToPort80Handler(handler, {
|
||||
onCertificateIssued: this.handleCertificateEvent.bind(this),
|
||||
onCertificateRenewed: this.handleCertificateEvent.bind(this)
|
||||
});
|
||||
|
||||
// If NetworkProxy is already initialized, connect it with Port80Handler
|
||||
if (this.networkProxy) {
|
||||
this.networkProxy.setExternalPort80Handler(handler);
|
||||
}
|
||||
|
||||
console.log('Port80Handler connected to NetworkProxyBridge');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
useExternalPort80Handler: !!this.port80Handler // Use Port80Handler if available
|
||||
};
|
||||
|
||||
|
||||
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
||||
|
||||
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
||||
|
||||
// Connect Port80Handler if available
|
||||
if (this.port80Handler) {
|
||||
this.networkProxy.setExternalPort80Handler(this.port80Handler);
|
||||
}
|
||||
|
||||
// Convert and apply domain configurations to NetworkProxy
|
||||
await this.syncDomainConfigsToNetworkProxy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle certificate issuance or renewal events
|
||||
*/
|
||||
private handleCertificateEvent(data: CertificateData): void {
|
||||
if (!this.networkProxy) return;
|
||||
|
||||
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 an external (static) certificate into NetworkProxy
|
||||
*/
|
||||
public applyExternalCertificate(data: CertificateData): void {
|
||||
if (!this.networkProxy) {
|
||||
console.log(`NetworkProxy not initialized: cannot apply external certificate for ${data.domain}`);
|
||||
return;
|
||||
}
|
||||
this.handleCertificateEvent(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards a TLS connection to a NetworkProxy for handling
|
||||
*/
|
||||
public forwardToNetworkProxy(
|
||||
connectionId: string,
|
||||
socket: plugins.net.Socket,
|
||||
record: ConnectionRecord,
|
||||
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
|
||||
const proxyConfigs = this.networkProxy.convertSmartProxyConfigs(
|
||||
this.settings.domainConfigs,
|
||||
certPair
|
||||
);
|
||||
|
||||
// Log ACME-eligible domains
|
||||
const acmeEnabled = !!this.settings.acme?.enabled;
|
||||
if (acmeEnabled) {
|
||||
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(', ')}`);
|
||||
|
||||
// Register these domains with Port80Handler if available
|
||||
if (this.port80Handler) {
|
||||
this.registerDomainsWithPort80Handler(acmeEligibleDomains);
|
||||
}
|
||||
} 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> {
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user