279 lines
6.9 KiB
TypeScript
279 lines
6.9 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as smartproxy from '../ts/index.js';
|
|
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
|
|
import * as net from 'net';
|
|
|
|
tap.test('route security should block connections from unauthorized IPs', async () => {
|
|
// Create a target server that should never receive connections
|
|
let targetServerConnections = 0;
|
|
const targetServer = net.createServer((socket) => {
|
|
targetServerConnections++;
|
|
console.log('Target server received connection - this should not happen!');
|
|
socket.write('ERROR: This connection should have been blocked');
|
|
socket.end();
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
targetServer.listen(9990, '127.0.0.1', () => {
|
|
console.log('Target server listening on port 9990');
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Create proxy with restrictive security at route level
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'secure-route',
|
|
match: {
|
|
ports: 9991
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 9990
|
|
}
|
|
},
|
|
security: {
|
|
// Only allow a non-existent IP
|
|
ipAllowList: ['192.168.99.99']
|
|
}
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: true,
|
|
routes: routes
|
|
});
|
|
|
|
|
|
await proxy.start();
|
|
console.log('Proxy started on port 9991');
|
|
|
|
// Wait a moment to ensure server is fully ready
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
// Try to connect from localhost (should be blocked)
|
|
const client = new net.Socket();
|
|
const events: string[] = [];
|
|
|
|
const result = await new Promise<string>((resolve) => {
|
|
let resolved = false;
|
|
|
|
client.on('connect', () => {
|
|
console.log('Client connected (TCP handshake succeeded)');
|
|
events.push('connected');
|
|
// Send initial data to trigger routing
|
|
client.write('test');
|
|
});
|
|
|
|
client.on('data', (data) => {
|
|
console.log('Client received data:', data.toString());
|
|
events.push('data');
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve('data');
|
|
}
|
|
});
|
|
|
|
client.on('error', (err: any) => {
|
|
console.log('Client error:', err.code);
|
|
events.push('error');
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve('error');
|
|
}
|
|
});
|
|
|
|
client.on('close', () => {
|
|
console.log('Client connection closed by server');
|
|
events.push('closed');
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve('closed');
|
|
}
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve('timeout');
|
|
}
|
|
}, 2000);
|
|
|
|
console.log('Attempting connection from 127.0.0.1...');
|
|
client.connect(9991, '127.0.0.1');
|
|
});
|
|
|
|
console.log('Connection result:', result);
|
|
console.log('Events:', events);
|
|
|
|
// The connection might be closed before or after TCP handshake
|
|
// What matters is that the target server never receives a connection
|
|
console.log('Test passed: Connection was properly blocked by security');
|
|
|
|
// Target server should not have received any connections
|
|
expect(targetServerConnections).toEqual(0);
|
|
|
|
// Clean up
|
|
client.destroy();
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
targetServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
tap.test('route security with block list should work', async () => {
|
|
// Create a target server
|
|
let targetServerConnections = 0;
|
|
const targetServer = net.createServer((socket) => {
|
|
targetServerConnections++;
|
|
socket.write('Hello from target');
|
|
socket.end();
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
targetServer.listen(9992, '127.0.0.1', () => resolve());
|
|
});
|
|
|
|
// Create proxy with security at route level (not action level)
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'secure-route-level',
|
|
match: {
|
|
ports: 9993
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 9992
|
|
}
|
|
},
|
|
security: { // Security at route level, not action level
|
|
ipBlockList: ['127.0.0.1', '::1', '::ffff:127.0.0.1']
|
|
}
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: true,
|
|
routes: routes
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
// Try to connect (should be blocked)
|
|
const client = new net.Socket();
|
|
const events: string[] = [];
|
|
|
|
const result = await new Promise<string>((resolve) => {
|
|
let resolved = false;
|
|
const timeout = setTimeout(() => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve('timeout');
|
|
}
|
|
}, 2000);
|
|
|
|
client.on('connect', () => {
|
|
console.log('Client connected to block list test');
|
|
events.push('connected');
|
|
// Send initial data to trigger routing
|
|
client.write('test');
|
|
});
|
|
|
|
client.on('error', () => {
|
|
events.push('error');
|
|
if (!resolved) {
|
|
resolved = true;
|
|
clearTimeout(timeout);
|
|
resolve('error');
|
|
}
|
|
});
|
|
|
|
client.on('close', () => {
|
|
events.push('closed');
|
|
if (!resolved) {
|
|
resolved = true;
|
|
clearTimeout(timeout);
|
|
resolve('closed');
|
|
}
|
|
});
|
|
|
|
client.connect(9993, '127.0.0.1');
|
|
});
|
|
|
|
// Should connect then be immediately closed by security
|
|
expect(events).toContain('connected');
|
|
expect(events).toContain('closed');
|
|
expect(result).toEqual('closed');
|
|
expect(targetServerConnections).toEqual(0);
|
|
|
|
// Clean up
|
|
client.destroy();
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
targetServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
tap.test('route without security should allow all connections', async () => {
|
|
// Create echo server
|
|
const echoServer = net.createServer((socket) => {
|
|
socket.on('data', (data) => {
|
|
socket.write(data);
|
|
});
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.listen(9994, '127.0.0.1', () => resolve());
|
|
});
|
|
|
|
// Create proxy without security
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'open-route',
|
|
match: {
|
|
ports: 9995
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 9994
|
|
}
|
|
}
|
|
// No security defined
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: false,
|
|
routes: routes
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
// Connect and test echo
|
|
const client = new net.Socket();
|
|
await new Promise<void>((resolve) => {
|
|
client.connect(9995, '127.0.0.1', () => resolve());
|
|
});
|
|
|
|
// Send data and verify echo
|
|
const testData = 'Hello World';
|
|
client.write(testData);
|
|
|
|
const response = await new Promise<string>((resolve) => {
|
|
client.once('data', (data) => {
|
|
resolve(data.toString());
|
|
});
|
|
setTimeout(() => resolve(''), 2000);
|
|
});
|
|
|
|
expect(response).toEqual(testData);
|
|
|
|
// Clean up
|
|
client.destroy();
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
export default tap.start(); |