fix(route-connection-handler): Forward non-TLS connections on HttpProxy ports to fix ACME HTTP-01 challenge handling
This commit is contained in:
168
test/test.http-fix-verification.ts
Normal file
168
test/test.http-fix-verification.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RouteConnectionHandler } from '../ts/proxies/smart-proxy/route-connection-handler.js';
|
||||
import { ISmartProxyOptions } from '../ts/proxies/smart-proxy/models/interfaces.js';
|
||||
import * as net from 'net';
|
||||
|
||||
// Direct test of the fix in RouteConnectionHandler
|
||||
tap.test('should detect and forward non-TLS connections on useHttpProxy ports', async (tapTest) => {
|
||||
// Create mock objects
|
||||
const mockSettings: ISmartProxyOptions = {
|
||||
useHttpProxy: [8080],
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: { ports: 8080 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8181 }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
let httpProxyForwardCalled = false;
|
||||
let directConnectionCalled = false;
|
||||
|
||||
// Create mocks for dependencies
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async (...args: any[]) => {
|
||||
console.log('Mock: forwardToHttpProxy called');
|
||||
httpProxyForwardCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Mock connection manager
|
||||
const mockConnectionManager = {
|
||||
createConnection: (socket: any) => ({
|
||||
id: 'test-connection',
|
||||
localPort: 8080,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: false
|
||||
}),
|
||||
initiateCleanupOnce: () => {},
|
||||
cleanupConnection: () => {}
|
||||
};
|
||||
|
||||
// Mock route manager that returns a matching route
|
||||
const mockRouteManager = {
|
||||
findMatchingRoute: (criteria: any) => ({
|
||||
route: mockSettings.routes[0]
|
||||
})
|
||||
};
|
||||
|
||||
// Create route connection handler instance
|
||||
const handler = new RouteConnectionHandler(
|
||||
mockSettings,
|
||||
mockConnectionManager as any,
|
||||
{} as any, // security manager
|
||||
{} as any, // tls manager
|
||||
mockHttpProxyBridge as any,
|
||||
{} as any, // timeout manager
|
||||
mockRouteManager as any
|
||||
);
|
||||
|
||||
// Override setupDirectConnection to track if it's called
|
||||
handler['setupDirectConnection'] = (...args: any[]) => {
|
||||
console.log('Mock: setupDirectConnection called');
|
||||
directConnectionCalled = true;
|
||||
};
|
||||
|
||||
// Test: Create a mock socket representing non-TLS connection on port 8080
|
||||
const mockSocket = new net.Socket();
|
||||
mockSocket.localPort = 8080;
|
||||
mockSocket.remoteAddress = '127.0.0.1';
|
||||
|
||||
// Simulate the handler processing the connection
|
||||
handler.handleConnection(mockSocket);
|
||||
|
||||
// Simulate receiving non-TLS data
|
||||
mockSocket.emit('data', Buffer.from('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n'));
|
||||
|
||||
// Give it a moment to process
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Verify that the connection was forwarded to HttpProxy, not direct connection
|
||||
expect(httpProxyForwardCalled).toEqual(true);
|
||||
expect(directConnectionCalled).toEqual(false);
|
||||
|
||||
mockSocket.destroy();
|
||||
});
|
||||
|
||||
// Test that verifies TLS connections still work normally
|
||||
tap.test('should handle TLS connections normally', async (tapTest) => {
|
||||
const mockSettings: ISmartProxyOptions = {
|
||||
useHttpProxy: [443],
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'tls-route',
|
||||
match: { ports: 443 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8443 },
|
||||
tls: { mode: 'terminate' }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
let httpProxyForwardCalled = false;
|
||||
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async (...args: any[]) => {
|
||||
httpProxyForwardCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
const mockConnectionManager = {
|
||||
createConnection: (socket: any) => ({
|
||||
id: 'test-tls-connection',
|
||||
localPort: 443,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: true,
|
||||
tlsHandshakeComplete: false
|
||||
}),
|
||||
initiateCleanupOnce: () => {},
|
||||
cleanupConnection: () => {}
|
||||
};
|
||||
|
||||
const mockTlsManager = {
|
||||
isTlsHandshake: (chunk: Buffer) => true,
|
||||
isClientHello: (chunk: Buffer) => true,
|
||||
extractSNI: (chunk: Buffer) => 'test.local'
|
||||
};
|
||||
|
||||
const mockRouteManager = {
|
||||
findMatchingRoute: (criteria: any) => ({
|
||||
route: mockSettings.routes[0]
|
||||
})
|
||||
};
|
||||
|
||||
const handler = new RouteConnectionHandler(
|
||||
mockSettings,
|
||||
mockConnectionManager as any,
|
||||
{} as any,
|
||||
mockTlsManager as any,
|
||||
mockHttpProxyBridge as any,
|
||||
{} as any,
|
||||
mockRouteManager as any
|
||||
);
|
||||
|
||||
const mockSocket = new net.Socket();
|
||||
mockSocket.localPort = 443;
|
||||
mockSocket.remoteAddress = '127.0.0.1';
|
||||
|
||||
handler.handleConnection(mockSocket);
|
||||
|
||||
// Simulate TLS handshake
|
||||
const tlsHandshake = Buffer.from([0x16, 0x03, 0x01, 0x00, 0x05]);
|
||||
mockSocket.emit('data', tlsHandshake);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// TLS connections with 'terminate' mode should go to HttpProxy
|
||||
expect(httpProxyForwardCalled).toEqual(true);
|
||||
|
||||
mockSocket.destroy();
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user