From bb66b98f1d3ac35e03a86635a9dfe6dd0ab789cb Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Sat, 10 May 2025 00:06:53 +0000 Subject: [PATCH] BREAKING CHANGE(documentation): Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0 --- changelog.md | 9 ++ readme.md | 254 +++++++++++++++++++++++++++++++------- test/test.route-config.ts | 82 ++++++------ ts/00_commitinfo_data.ts | 4 +- 4 files changed, 264 insertions(+), 85 deletions(-) diff --git a/changelog.md b/changelog.md index bb28997..6c0c702 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation) +Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0 + +- Added detailed description of IRouteConfig, IRouteMatch, and IRouteAction interfaces +- Improved explanation of port, domain, path, client IP, and TLS version matching features +- Included examples of helper functions (createHttpRoute, createHttpsRoute, etc.) with usage of template variables +- Enhanced migration guide from legacy configurations to the new match/action pattern +- Updated examples and tests to reflect the new documentation structure + ## 2025-05-09 - 13.1.3 - fix(documentation) Update readme.md to provide a unified and comprehensive overview of SmartProxy, with reorganized sections, enhanced diagrams, and detailed usage examples for various proxy scenarios. diff --git a/readme.md b/readme.md index d76b808..6b690d4 100644 --- a/readme.md +++ b/readme.md @@ -202,64 +202,234 @@ await proxy.stop(); ## Route-Based Configuration System -The new route-based configuration in v14.0.0 follows a match/action pattern, making it more powerful and flexible: +SmartProxy v14.0.0 introduces a new unified route configuration system based on the `IRouteConfig` interface. This system follows a match/action pattern that makes routing more powerful, flexible, and declarative. + +### IRouteConfig Interface + +The `IRouteConfig` interface is the core building block of SmartProxy's configuration system. Each route definition consists of match criteria and an action to perform on matched traffic: + +```typescript +interface IRouteConfig { + // What traffic to match (required) + match: IRouteMatch; + + // What to do with matched traffic (required) + action: IRouteAction; + + // Metadata (all optional) + name?: string; // Human-readable name for this route + description?: string; // Description of the route's purpose + priority?: number; // Controls matching order (higher = matched first) + tags?: string[]; // Arbitrary tags for categorization +} +``` + +#### Match Criteria (IRouteMatch) + +The `match` property defines criteria for identifying which incoming traffic should be handled by this route: + +```typescript +interface IRouteMatch { + // Listen on these ports (required) + ports: TPortRange; // number | number[] | Array<{ from: number; to: number }> + + // Optional domain patterns to match (default: all domains) + domains?: string | string[]; // Supports wildcards like '*.example.com' + + // Advanced matching criteria (all optional) + path?: string; // Match specific URL paths, supports glob patterns + clientIp?: string[]; // Match specific client IPs, supports glob patterns + tlsVersion?: string[]; // Match specific TLS versions e.g. ['TLSv1.2', 'TLSv1.3'] +} +``` + +**Port Specification:** +- **Single port:** `ports: 80` +- **Multiple ports:** `ports: [80, 443]` +- **Port ranges:** `ports: [{ from: 8000, to: 8100 }]` +- **Mixed format:** `ports: [80, 443, { from: 8000, to: 8100 }]` + +**Domain Matching:** +- **Single domain:** `domains: 'example.com'` +- **Multiple domains:** `domains: ['example.com', 'api.example.com']` +- **Wildcard domains:** `domains: '*.example.com'` (matches any subdomain) +- **Root domain + subdomains:** `domains: ['example.com', '*.example.com']` + +**Path Matching:** +- **Exact path:** `path: '/api'` (matches only /api exactly) +- **Prefix match:** `path: '/api/*'` (matches /api and any paths under it) +- **Multiple patterns:** Use multiple routes with different priorities + +**Client IP Matching:** +- **Exact IP:** `clientIp: ['192.168.1.1']` +- **Subnet wildcards:** `clientIp: ['10.0.0.*', '192.168.1.*']` +- **CIDR notation:** `clientIp: ['10.0.0.0/24']` + +**TLS Version Matching:** +- `tlsVersion: ['TLSv1.2', 'TLSv1.3']` (only match these TLS versions) + +#### Action Configuration (IRouteAction) + +The `action` property defines what to do with traffic that matches the criteria: + +```typescript +interface IRouteAction { + // Action type (required) + type: 'forward' | 'redirect' | 'block'; + + // For 'forward' actions + target?: IRouteTarget; + + // TLS handling for 'forward' actions + tls?: IRouteTls; + + // For 'redirect' actions + redirect?: IRouteRedirect; + + // Security options for any action + security?: IRouteSecurity; + + // Advanced options + advanced?: IRouteAdvanced; +} +``` + +**Forward Action:** +When `type: 'forward'`, the traffic is forwarded to the specified target: +```typescript +interface IRouteTarget { + host: string | string[]; // Target host(s) - string array enables round-robin + port: number; // Target port + preservePort?: boolean; // Use incoming port as target port (default: false) +} +``` + +**TLS Configuration:** +When forwarding with TLS, you can configure how TLS is handled: +```typescript +interface IRouteTls { + mode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt'; + certificate?: 'auto' | { // 'auto' = use ACME (Let's Encrypt) + key: string; // TLS private key content + cert: string; // TLS certificate content + }; +} +``` + +**TLS Modes:** +- **passthrough:** Forward raw encrypted TLS traffic without decryption +- **terminate:** Terminate TLS and forward as HTTP +- **terminate-and-reencrypt:** Terminate TLS and create a new TLS connection to the backend + +**Redirect Action:** +When `type: 'redirect'`, the client is redirected: +```typescript +interface IRouteRedirect { + to: string; // URL or template with variables + status: 301 | 302 | 307 | 308; // HTTP status code +} +``` + +**Block Action:** +When `type: 'block'`, the connection is immediately closed. + +**Security Options:** +For any action type, you can add security controls: +```typescript +interface IRouteSecurity { + allowedIps?: string[]; // IP allowlist with glob pattern support + blockedIps?: string[]; // IP blocklist with glob pattern support + maxConnections?: number; // Maximum concurrent connections + authentication?: { // Optional authentication (future support) + type: 'basic' | 'digest' | 'oauth'; + // Auth-specific options + }; +} +``` + +**Advanced Options:** +Additional advanced configurations: +```typescript +interface IRouteAdvanced { + timeout?: number; // Connection timeout in milliseconds + headers?: Record; // Custom HTTP headers + keepAlive?: boolean; // Enable connection pooling + // Additional advanced options +} +``` + +#### Template Variables + +String values in redirect URLs and headers can include variables: + +- `{domain}`: The requested domain name +- `{port}`: The incoming port number +- `{path}`: The requested URL path +- `{query}`: The query string +- `{clientIp}`: The client's IP address +- `{sni}`: The SNI hostname + +Example with template variables: +```typescript +redirect: { + to: 'https://{domain}{path}?source=redirect', + status: 301 +} +``` + +#### Route Metadata and Prioritization + +You can add metadata to routes to help with organization and control matching priority: ```typescript -// Basic structure of a route configuration { - // What traffic to match + name: 'API Server', // Human-readable name + description: 'Main API endpoints', // Description + priority: 100, // Matching priority (higher = matched first) + tags: ['api', 'internal'] // Arbitrary tags +} +``` + +Routes with higher priority values are matched first, allowing you to create specialized routes that take precedence over more general ones. + +#### Complete Route Configuration Example + +```typescript +// Example of a complete route configuration +{ match: { - ports: 443, // Required: port(s) to listen on - domains: 'example.com', // Optional: domain(s) to match - path: '/api', // Optional: URL path pattern - clientIp: ['10.0.0.*'], // Optional: client IP patterns to match - tlsVersion: ['TLSv1.2', 'TLSv1.3'] // Optional: TLS versions to match + ports: 443, + domains: ['api.example.com', 'api-v2.example.com'], + path: '/secure/*', + clientIp: ['10.0.0.*', '192.168.1.*'] }, - - // What to do with matched traffic action: { - type: 'forward', // 'forward', 'redirect', or 'block' - - // For 'forward' actions + type: 'forward', target: { - host: 'localhost', // Target host(s) for forwarding - port: 8080 // Target port + host: ['10.0.0.1', '10.0.0.2'], // Round-robin between these hosts + port: 8080 }, - - // TLS handling (for 'forward' actions) tls: { - mode: 'terminate', // 'passthrough', 'terminate', 'terminate-and-reencrypt' - certificate: 'auto' // 'auto' for Let's Encrypt or {key, cert} + mode: 'terminate', + certificate: 'auto' // Use Let's Encrypt }, - - // For 'redirect' actions - redirect: { - to: 'https://{domain}{path}', // URL pattern with variables - status: 301 // HTTP status code - }, - - // Security controls for any action security: { - allowedIps: ['10.0.0.*'], // IP allowlist - blockedIps: ['1.2.3.4'], // IP blocklist - maxConnections: 100 // Connection limits + allowedIps: ['10.0.0.*'], + maxConnections: 100 }, - - // Advanced options advanced: { - timeout: 30000, // Connection timeout - headers: { // Custom headers - 'X-Forwarded-For': '{clientIp}' + timeout: 30000, + headers: { + 'X-Original-Host': '{domain}', + 'X-Client-IP': '{clientIp}' }, - keepAlive: true // Connection pooling + keepAlive: true } }, - - // Metadata (optional) - name: 'API Server', // Human-readable name - description: 'Main API endpoints', // Description - priority: 100, // Matching priority (higher = matched first) - tags: ['api', 'internal'] // Arbitrary tags + name: 'Secure API Route', + description: 'Route for secure API endpoints with authentication', + priority: 100, + tags: ['api', 'secure', 'internal'] } ``` diff --git a/test/test.route-config.ts b/test/test.route-config.ts index 133001f..51ad16d 100644 --- a/test/test.route-config.ts +++ b/test/test.route-config.ts @@ -16,7 +16,7 @@ import { } from '../ts/proxies/smart-proxy/index.js'; // Import test helpers -import { getCertificate } from './helpers/certificates.js'; +import { loadTestCertificates } from './helpers/certificates.js'; tap.test('Routes: Should create basic HTTP route', async () => { // Create a simple HTTP route @@ -31,12 +31,12 @@ tap.test('Routes: Should create basic HTTP route', async () => { }); // Validate the route configuration - expect(httpRoute.match.ports).to.equal(8080); - expect(httpRoute.match.domains).to.equal('example.com'); - expect(httpRoute.action.type).to.equal('forward'); - expect(httpRoute.action.target?.host).to.equal('localhost'); - expect(httpRoute.action.target?.port).to.equal(3000); - expect(httpRoute.name).to.equal('Basic HTTP Route'); + expect(httpRoute.match.ports).toEqual(8080); + expect(httpRoute.match.domains).toEqual('example.com'); + expect(httpRoute.action.type).toEqual('forward'); + expect(httpRoute.action.target?.host).toEqual('localhost'); + expect(httpRoute.action.target?.port).toEqual(3000); + expect(httpRoute.name).toEqual('Basic HTTP Route'); }); tap.test('Routes: Should create HTTPS route with TLS termination', async () => { @@ -52,14 +52,14 @@ tap.test('Routes: Should create HTTPS route with TLS termination', async () => { }); // Validate the route configuration - expect(httpsRoute.match.ports).to.equal(443); // Default HTTPS port - expect(httpsRoute.match.domains).to.equal('secure.example.com'); - expect(httpsRoute.action.type).to.equal('forward'); - expect(httpsRoute.action.tls?.mode).to.equal('terminate'); - expect(httpsRoute.action.tls?.certificate).to.equal('auto'); - expect(httpsRoute.action.target?.host).to.equal('localhost'); - expect(httpsRoute.action.target?.port).to.equal(8080); - expect(httpsRoute.name).to.equal('HTTPS Route'); + expect(httpsRoute.match.ports).toEqual(443); // Default HTTPS port + expect(httpsRoute.match.domains).toEqual('secure.example.com'); + expect(httpsRoute.action.type).toEqual('forward'); + expect(httpsRoute.action.tls?.mode).toEqual('terminate'); + expect(httpsRoute.action.tls?.certificate).toEqual('auto'); + expect(httpsRoute.action.target?.host).toEqual('localhost'); + expect(httpsRoute.action.target?.port).toEqual(8080); + expect(httpsRoute.name).toEqual('HTTPS Route'); }); tap.test('Routes: Should create HTTP to HTTPS redirect', async () => { @@ -70,11 +70,11 @@ tap.test('Routes: Should create HTTP to HTTPS redirect', async () => { }); // Validate the route configuration - expect(redirectRoute.match.ports).to.equal(80); - expect(redirectRoute.match.domains).to.equal('example.com'); - expect(redirectRoute.action.type).to.equal('redirect'); - expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}'); - expect(redirectRoute.action.redirect?.status).to.equal(301); + expect(redirectRoute.match.ports).toEqual(80); + expect(redirectRoute.match.domains).toEqual('example.com'); + expect(redirectRoute.action.type).toEqual('redirect'); + expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}'); + expect(redirectRoute.action.redirect?.status).toEqual(301); }); tap.test('Routes: Should create complete HTTPS server with redirects', async () => { @@ -90,20 +90,20 @@ tap.test('Routes: Should create complete HTTPS server with redirects', async () }); // Validate that we got two routes (HTTPS route and HTTP redirect) - expect(routes.length).to.equal(2); + expect(routes.length).toEqual(2); // Validate HTTPS route const httpsRoute = routes[0]; - expect(httpsRoute.match.ports).to.equal(443); - expect(httpsRoute.match.domains).to.equal('example.com'); - expect(httpsRoute.action.type).to.equal('forward'); - expect(httpsRoute.action.tls?.mode).to.equal('terminate'); + expect(httpsRoute.match.ports).toEqual(443); + expect(httpsRoute.match.domains).toEqual('example.com'); + expect(httpsRoute.action.type).toEqual('forward'); + expect(httpsRoute.action.tls?.mode).toEqual('terminate'); // Validate HTTP redirect route const redirectRoute = routes[1]; - expect(redirectRoute.match.ports).to.equal(80); - expect(redirectRoute.action.type).to.equal('redirect'); - expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}'); + expect(redirectRoute.match.ports).toEqual(80); + expect(redirectRoute.action.type).toEqual('redirect'); + expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}'); }); tap.test('Routes: Should create load balancer route', async () => { @@ -118,18 +118,18 @@ tap.test('Routes: Should create load balancer route', async () => { }); // Validate the route configuration - expect(lbRoute.match.domains).to.equal('app.example.com'); - expect(lbRoute.action.type).to.equal('forward'); - expect(Array.isArray(lbRoute.action.target?.host)).to.equal(true); - expect((lbRoute.action.target?.host as string[]).length).to.equal(3); - expect((lbRoute.action.target?.host as string[])[0]).to.equal('10.0.0.1'); - expect(lbRoute.action.target?.port).to.equal(8080); - expect(lbRoute.action.tls?.mode).to.equal('terminate'); + expect(lbRoute.match.domains).toEqual('app.example.com'); + expect(lbRoute.action.type).toEqual('forward'); + expect(Array.isArray(lbRoute.action.target?.host)).toBeTrue(); + expect((lbRoute.action.target?.host as string[]).length).toEqual(3); + expect((lbRoute.action.target?.host as string[])[0]).toEqual('10.0.0.1'); + expect(lbRoute.action.target?.port).toEqual(8080); + expect(lbRoute.action.tls?.mode).toEqual('terminate'); }); tap.test('SmartProxy: Should create instance with route-based config', async () => { // Create TLS certificates for testing - const cert = await getCertificate(); + const certs = loadTestCertificates(); // Create a SmartProxy instance with route-based configuration const proxy = new SmartProxy({ @@ -150,8 +150,8 @@ tap.test('SmartProxy: Should create instance with route-based config', async () port: 8443 }, certificate: { - key: cert.key, - cert: cert.cert + key: certs.privateKey, + cert: certs.publicKey }, name: 'HTTPS Route' }) @@ -173,9 +173,9 @@ tap.test('SmartProxy: Should create instance with route-based config', async () }); // Simply verify the instance was created successfully - expect(proxy).to.be.an('object'); - expect(proxy.start).to.be.a('function'); - expect(proxy.stop).to.be.a('function'); + expect(typeof proxy).toEqual('object'); + expect(typeof proxy.start).toEqual('function'); + expect(typeof proxy.stop).toEqual('function'); }); export default tap.start(); \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 231cf98..4f10d8e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '13.1.3', - description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' + version: '15.0.0', + description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }