275 lines
7.0 KiB
TypeScript
275 lines
7.0 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-specific security should be enforced', async () => {
|
|
// Create a simple echo server for testing
|
|
const echoServer = net.createServer((socket) => {
|
|
socket.on('data', (data) => {
|
|
socket.write(data);
|
|
});
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.listen(8877, '127.0.0.1', () => {
|
|
console.log('Echo server listening on port 8877');
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Create proxy with route-specific security
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'secure-route',
|
|
match: {
|
|
ports: 8878
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 8877
|
|
}
|
|
},
|
|
security: {
|
|
ipAllowList: ['127.0.0.1', '::1', '::ffff:127.0.0.1']
|
|
}
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: true,
|
|
routes: routes
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
// Test 1: Connection from allowed IP should work
|
|
const client1 = new net.Socket();
|
|
const connected = await new Promise<boolean>((resolve) => {
|
|
client1.connect(8878, '127.0.0.1', () => {
|
|
console.log('Client connected from allowed IP');
|
|
resolve(true);
|
|
});
|
|
|
|
client1.on('error', (err) => {
|
|
console.log('Connection error:', err.message);
|
|
resolve(false);
|
|
});
|
|
|
|
// Set timeout to prevent hanging
|
|
setTimeout(() => resolve(false), 2000);
|
|
});
|
|
|
|
if (connected) {
|
|
// Test echo
|
|
const testData = 'Hello from allowed IP';
|
|
client1.write(testData);
|
|
|
|
const response = await new Promise<string>((resolve) => {
|
|
client1.once('data', (data) => {
|
|
resolve(data.toString());
|
|
});
|
|
setTimeout(() => resolve(''), 2000);
|
|
});
|
|
|
|
expect(response).toEqual(testData);
|
|
client1.destroy();
|
|
} else {
|
|
expect(connected).toBeTrue();
|
|
}
|
|
|
|
// Clean up
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
tap.test('route-specific IP block list should be enforced', async () => {
|
|
// Create a simple echo server for testing
|
|
const echoServer = net.createServer((socket) => {
|
|
socket.on('data', (data) => {
|
|
socket.write(data);
|
|
});
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.listen(8879, '127.0.0.1', () => {
|
|
console.log('Echo server listening on port 8879');
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Create proxy with route-specific block list
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'blocked-route',
|
|
match: {
|
|
ports: 8880
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 8879
|
|
}
|
|
},
|
|
security: {
|
|
ipAllowList: ['0.0.0.0/0', '::/0'], // Allow all IPs
|
|
ipBlockList: ['127.0.0.1', '::1', '::ffff:127.0.0.1'] // But block localhost
|
|
}
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: true,
|
|
routes: routes
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
// Test: Connection from blocked IP should fail or be immediately closed
|
|
const client = new net.Socket();
|
|
let connectionSuccessful = false;
|
|
|
|
const result = await new Promise<{ connected: boolean; dataReceived: boolean }>((resolve) => {
|
|
let resolved = false;
|
|
let dataReceived = false;
|
|
|
|
const doResolve = (connected: boolean) => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
resolve({ connected, dataReceived });
|
|
}
|
|
};
|
|
|
|
client.connect(8880, '127.0.0.1', () => {
|
|
console.log('Client connect event fired');
|
|
connectionSuccessful = true;
|
|
// Try to send data to test if the connection is really established
|
|
try {
|
|
client.write('test data');
|
|
} catch (e) {
|
|
console.log('Write failed:', e.message);
|
|
}
|
|
});
|
|
|
|
client.on('data', () => {
|
|
dataReceived = true;
|
|
});
|
|
|
|
client.on('error', (err) => {
|
|
console.log('Connection error:', err.message);
|
|
doResolve(false);
|
|
});
|
|
|
|
client.on('close', () => {
|
|
console.log('Connection closed, connectionSuccessful:', connectionSuccessful, 'dataReceived:', dataReceived);
|
|
doResolve(connectionSuccessful);
|
|
});
|
|
|
|
// Set timeout
|
|
setTimeout(() => doResolve(connectionSuccessful), 1000);
|
|
});
|
|
|
|
// The connection should either fail to connect OR connect but immediately close without data exchange
|
|
if (result.connected) {
|
|
// If connected, it should have been immediately closed without data exchange
|
|
expect(result.dataReceived).toBeFalse();
|
|
console.log('Connection was established but immediately closed (acceptable behavior)');
|
|
} else {
|
|
// Connection failed entirely (also acceptable)
|
|
expect(result.connected).toBeFalse();
|
|
console.log('Connection was blocked entirely (preferred behavior)');
|
|
}
|
|
|
|
if (client.readyState !== 'closed') {
|
|
client.destroy();
|
|
}
|
|
|
|
// Clean up
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
tap.test('routes without security should allow all connections', async () => {
|
|
// Create a simple echo server for testing
|
|
const echoServer = net.createServer((socket) => {
|
|
socket.on('data', (data) => {
|
|
socket.write(data);
|
|
});
|
|
});
|
|
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.listen(8881, '127.0.0.1', () => {
|
|
console.log('Echo server listening on port 8881');
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
// Create proxy without route-specific security
|
|
const routes: IRouteConfig[] = [{
|
|
name: 'open-route',
|
|
match: {
|
|
ports: 8882
|
|
},
|
|
action: {
|
|
type: 'forward',
|
|
target: {
|
|
host: '127.0.0.1',
|
|
port: 8881
|
|
}
|
|
// No security section - should allow all
|
|
}
|
|
}];
|
|
|
|
const proxy = new smartproxy.SmartProxy({
|
|
enableDetailedLogging: true,
|
|
routes: routes
|
|
});
|
|
|
|
await proxy.start();
|
|
|
|
// Test: Connection should work without security restrictions
|
|
const client = new net.Socket();
|
|
const connected = await new Promise<boolean>((resolve) => {
|
|
client.connect(8882, '127.0.0.1', () => {
|
|
console.log('Client connected to open route');
|
|
resolve(true);
|
|
});
|
|
|
|
client.on('error', (err) => {
|
|
console.log('Connection error:', err.message);
|
|
resolve(false);
|
|
});
|
|
|
|
// Set timeout
|
|
setTimeout(() => resolve(false), 2000);
|
|
});
|
|
|
|
expect(connected).toBeTrue();
|
|
|
|
if (connected) {
|
|
// Test echo
|
|
const testData = 'Hello from open route';
|
|
client.write(testData);
|
|
|
|
const response = await new Promise<string>((resolve) => {
|
|
client.once('data', (data) => {
|
|
resolve(data.toString());
|
|
});
|
|
setTimeout(() => resolve(''), 2000);
|
|
});
|
|
|
|
expect(response).toEqual(testData);
|
|
client.destroy();
|
|
}
|
|
|
|
// Clean up
|
|
await proxy.stop();
|
|
await new Promise<void>((resolve) => {
|
|
echoServer.close(() => resolve());
|
|
});
|
|
});
|
|
|
|
export default tap.start(); |