- Renamed port proxy and SNI handler source files to classes.pp.portproxy.js and classes.pp.snihandler.js respectively - Updated import paths in index.ts and test files (e.g. in test.ts and test.router.ts) to reference the new file names - This refactor improves code organization but breaks direct imports from the old paths
258 lines
8.1 KiB
TypeScript
258 lines
8.1 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import { NetworkProxy } from './classes.networkproxy.js';
|
|
import type { IConnectionRecord, IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
|
|
|
|
/**
|
|
* Manages NetworkProxy integration for TLS termination
|
|
*/
|
|
export class NetworkProxyBridge {
|
|
private networkProxy: NetworkProxy | null = null;
|
|
|
|
constructor(private settings: IPortProxySettings) {}
|
|
|
|
/**
|
|
* 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',
|
|
};
|
|
|
|
// 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}`);
|
|
|
|
// Convert and apply domain configurations to NetworkProxy
|
|
await this.syncDomainConfigsToNetworkProxy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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}`);
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop NetworkProxy
|
|
*/
|
|
public async stop(): Promise<void> {
|
|
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}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forwards a TLS connection to a NetworkProxy for handling
|
|
*/
|
|
public forwardToNetworkProxy(
|
|
connectionId: string,
|
|
socket: plugins.net.Socket,
|
|
record: IConnectionRecord,
|
|
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.convertPortProxyConfigs(
|
|
this.settings.domainConfigs,
|
|
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
|
|
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> {
|
|
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;
|
|
}
|
|
}
|
|
} |