import { expect, tap } from '@git.zone/tstest/tapbundle'; import { SharedSecurityManager } from '../ts/core/utils/shared-security-manager.js'; import type { IRouteConfig, IRouteContext } from '../ts/proxies/smart-proxy/models/route-types.js'; let securityManager: SharedSecurityManager; tap.test('Setup SharedSecurityManager', async () => { securityManager = new SharedSecurityManager({ maxConnectionsPerIP: 5, connectionRateLimitPerMinute: 10, cleanupIntervalMs: 1000 // 1 second for faster testing }); }); tap.test('IP connection tracking', async () => { const testIP = '192.168.1.100'; // Track multiple connections securityManager.trackConnectionByIP(testIP, 'conn1'); securityManager.trackConnectionByIP(testIP, 'conn2'); securityManager.trackConnectionByIP(testIP, 'conn3'); // Verify connection count expect(securityManager.getConnectionCountByIP(testIP)).toEqual(3); // Remove a connection securityManager.removeConnectionByIP(testIP, 'conn2'); expect(securityManager.getConnectionCountByIP(testIP)).toEqual(2); // Remove remaining connections securityManager.removeConnectionByIP(testIP, 'conn1'); securityManager.removeConnectionByIP(testIP, 'conn3'); expect(securityManager.getConnectionCountByIP(testIP)).toEqual(0); }); tap.test('Per-IP connection limits validation', async () => { const testIP = '192.168.1.101'; // Track connections up to limit for (let i = 1; i <= 5; i++) { securityManager.trackConnectionByIP(testIP, `conn${i}`); const result = securityManager.validateIP(testIP); expect(result.allowed).toBeTrue(); } // Verify we're at the limit expect(securityManager.getConnectionCountByIP(testIP)).toEqual(5); // Next connection should be rejected const result = securityManager.validateIP(testIP); expect(result.allowed).toBeFalse(); expect(result.reason).toInclude('Maximum connections per IP'); // Clean up for (let i = 1; i <= 5; i++) { securityManager.removeConnectionByIP(testIP, `conn${i}`); } }); tap.test('Connection rate limiting', async () => { const testIP = '192.168.1.102'; // Make connections at the rate limit for (let i = 0; i < 10; i++) { const result = securityManager.validateIP(testIP); expect(result.allowed).toBeTrue(); securityManager.trackConnectionByIP(testIP, `conn${i}`); } // Next connection should exceed rate limit const result = securityManager.validateIP(testIP); expect(result.allowed).toBeFalse(); expect(result.reason).toInclude('Connection rate limit'); // Clean up connections for (let i = 0; i < 10; i++) { securityManager.removeConnectionByIP(testIP, `conn${i}`); } }); tap.test('Route-level connection limits', async () => { const route: IRouteConfig = { name: 'test-route', match: { ports: 443 }, action: { type: 'forward', target: { host: 'localhost', port: 8080 } }, security: { maxConnections: 3 } }; const context: IRouteContext = { port: 443, clientIp: '192.168.1.103', serverIp: '0.0.0.0', timestamp: Date.now(), connectionId: 'test-conn' }; // Test with connection counts below limit expect(securityManager.isAllowed(route, context, 0)).toBeTrue(); expect(securityManager.isAllowed(route, context, 2)).toBeTrue(); // Test at limit expect(securityManager.isAllowed(route, context, 3)).toBeFalse(); // Test above limit expect(securityManager.isAllowed(route, context, 5)).toBeFalse(); }); tap.test('IPv4/IPv6 normalization', async () => { const ipv4 = '127.0.0.1'; const ipv4Mapped = '::ffff:127.0.0.1'; // Track connection with IPv4 securityManager.trackConnectionByIP(ipv4, 'conn1'); // Both representations should show the same connection expect(securityManager.getConnectionCountByIP(ipv4)).toEqual(1); expect(securityManager.getConnectionCountByIP(ipv4Mapped)).toEqual(1); // Track another connection with IPv6 representation securityManager.trackConnectionByIP(ipv4Mapped, 'conn2'); // Both should show 2 connections expect(securityManager.getConnectionCountByIP(ipv4)).toEqual(2); expect(securityManager.getConnectionCountByIP(ipv4Mapped)).toEqual(2); // Clean up securityManager.removeConnectionByIP(ipv4, 'conn1'); securityManager.removeConnectionByIP(ipv4Mapped, 'conn2'); }); tap.test('Automatic cleanup of expired data', async (tools) => { const testIP = '192.168.1.104'; // Track a connection and then remove it securityManager.trackConnectionByIP(testIP, 'temp-conn'); securityManager.removeConnectionByIP(testIP, 'temp-conn'); // Add some rate limit entries (they expire after 1 minute) for (let i = 0; i < 5; i++) { securityManager.validateIP(testIP); } // Wait for cleanup interval (set to 1 second in our test) await tools.delayFor(1500); // The IP should be cleaned up since it has no connections // Note: We can't directly check the internal map, but we can verify // that a new connection is allowed (fresh rate limit) const result = securityManager.validateIP(testIP); expect(result.allowed).toBeTrue(); }); tap.test('Cleanup SharedSecurityManager', async () => { securityManager.clearIPTracking(); }); tap.start();