diff --git a/readme.plan.md b/readme.plan.md index 0e4b3fc..8158964 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -6,6 +6,21 @@ ## Overview Simplify the ACME/Certificate system by consolidating components, removing unnecessary abstraction layers, and integrating directly into SmartProxy's route-based architecture. +## Core Principles +1. **No backward compatibility** - Clean break from legacy implementations +2. **No migration helpers** - Users must update to new configuration format +3. **Remove all legacy code** - Delete deprecated methods and interfaces +4. **Forward-only approach** - Focus on simplicity over compatibility +5. **No complexity for edge cases** - Only support the clean, new way + +## Key Discoveries from Implementation Analysis + +1. **SmartProxy already supports static routes** - The 'static' type exists in TRouteActionType +2. **Path-based routing works perfectly** - The route matching system handles paths with glob patterns +3. **Dynamic route updates are safe** - SmartProxy's updateRoutes() method handles changes gracefully +4. **Priority-based routing exists** - Routes are sorted by priority, ensuring ACME routes match first +5. **No separate HTTP server needed** - ACME challenges can be regular SmartProxy routes + ## Current State Analysis ### Files to be Removed/Replaced @@ -353,10 +368,12 @@ export class SmartCertManager { /** * Create ACME challenge route + * NOTE: SmartProxy already handles path-based routing and priority */ private createChallengeRoute(): IRouteConfig { return { name: 'acme-challenge', + priority: 1000, // High priority to ensure it's checked first match: { ports: 80, path: '/.well-known/acme-challenge/*' @@ -656,7 +673,7 @@ export class CertStore { ``` -### Phase 2: Update Route Types +### Phase 2: Update Route Types and Handler #### 2.1 Update route-types.ts ```typescript @@ -683,6 +700,7 @@ export interface IStaticResponse { /** * Update IRouteAction to support static handlers + * NOTE: The 'static' type already exists in TRouteActionType */ export interface IRouteAction { type: TRouteActionType; @@ -694,6 +712,16 @@ export interface IRouteAction { handler?: (context: IRouteContext) => Promise; // For static routes } +/** + * Extend IRouteConfig to ensure challenge routes have higher priority + */ +export interface IRouteConfig { + name?: string; + match: IRouteMatch; + action: IRouteAction; + priority?: number; // Already exists - ACME routes should use high priority +} + /** * Extended TLS configuration for route actions */ @@ -714,6 +742,101 @@ export interface IRouteTls { } ``` +#### 2.2 Add Static Route Handler +```typescript +// Add to ts/proxies/smart-proxy/route-connection-handler.ts + +/** + * Handle the route based on its action type + */ +switch (route.action.type) { + case 'forward': + return this.handleForwardAction(socket, record, route, initialChunk); + + case 'redirect': + return this.handleRedirectAction(socket, record, route); + + case 'block': + return this.handleBlockAction(socket, record, route); + + case 'static': + return this.handleStaticAction(socket, record, route); + + default: + console.log(`[${connectionId}] Unknown action type: ${(route.action as any).type}`); + socket.end(); + this.connectionManager.cleanupConnection(record, 'unknown_action'); +} + +/** + * Handle a static action for a route + */ +private async handleStaticAction( + socket: plugins.net.Socket, + record: IConnectionRecord, + route: IRouteConfig +): Promise { + const connectionId = record.id; + + if (!route.action.handler) { + console.error(`[${connectionId}] Static route '${route.name}' has no handler`); + socket.end(); + this.connectionManager.cleanupConnection(record, 'no_handler'); + return; + } + + try { + // Build route context + const context: IRouteContext = { + port: record.localPort, + domain: record.lockedDomain, + clientIp: record.remoteIP, + serverIp: socket.localAddress!, + path: record.path, // Will need to be extracted from HTTP request + isTls: record.isTLS, + tlsVersion: record.tlsVersion, + routeName: route.name, + routeId: route.name, + timestamp: Date.now(), + connectionId + }; + + // Call the handler + const response = await route.action.handler(context); + + // Send HTTP response + const headers = response.headers || {}; + headers['Content-Length'] = Buffer.byteLength(response.body).toString(); + + let httpResponse = `HTTP/1.1 ${response.status} ${getStatusText(response.status)}\r\n`; + for (const [key, value] of Object.entries(headers)) { + httpResponse += `${key}: ${value}\r\n`; + } + httpResponse += '\r\n'; + + socket.write(httpResponse); + socket.write(response.body); + socket.end(); + + this.connectionManager.cleanupConnection(record, 'completed'); + } catch (error) { + console.error(`[${connectionId}] Error in static handler: ${error}`); + socket.end(); + this.connectionManager.cleanupConnection(record, 'handler_error'); + } +} + +// Helper function for status text +function getStatusText(status: number): string { + const statusTexts: Record = { + 200: 'OK', + 404: 'Not Found', + 500: 'Internal Server Error' + }; + return statusTexts[status] || 'Unknown'; +} +``` + ### Phase 3: SmartProxy Integration #### 3.1 Update SmartProxy class @@ -1033,49 +1156,9 @@ export class NetworkProxyBridge { } ``` -### Phase 4: Migration Guide +### Phase 4: Configuration Examples (No Migration) -#### 4.1 Configuration Migration -```typescript -// Old configuration style -const proxy = new SmartProxy({ - acme: { - enabled: true, - accountEmail: 'admin@example.com', - useProduction: true, - certificateStore: './certs' - }, - routes: [{ - match: { ports: 443, domains: 'example.com' }, - action: { - type: 'forward', - target: { host: 'backend', port: 8080 }, - tls: { mode: 'terminate', certificate: 'auto' } - } - }] -}); - -// New configuration style -const proxy = new SmartProxy({ - routes: [{ - match: { ports: 443, domains: 'example.com' }, - action: { - type: 'forward', - target: { host: 'backend', port: 8080 }, - tls: { - mode: 'terminate', - certificate: 'auto', - acme: { - email: 'admin@example.com', - useProduction: true - } - } - } - }] -}); -``` - -#### 4.2 Test Migration +#### 4.1 New Configuration Format ONLY ```typescript // Update test files to use new structure // test/test.certificate-provisioning.ts @@ -1282,6 +1365,17 @@ sed -i '/port80\//d' ts/http/index.ts # sed -i '/smartexpress/d' ts/plugins.ts ``` +#### 6.2 Key Simplifications Achieved + +1. **No custom ACME wrapper** - Direct use of @push.rocks/smartacme +2. **No separate HTTP server** - ACME challenges are regular routes +3. **Built-in path routing** - SmartProxy already handles path-based matching +4. **Built-in priorities** - Routes are already sorted by priority +5. **Safe updates** - Route updates are already thread-safe +6. **Minimal new code** - Mostly configuration and integration + +The simplification leverages SmartProxy's existing capabilities rather than reinventing them. + #### 6.2 Update Package.json ```json { @@ -1304,10 +1398,10 @@ sed -i '/port80\//d' ts/http/index.ts - Simplify NetworkProxyBridge - Remove old certificate system -3. **Day 3: Testing & Migration** - - Migrate existing tests - - Create new integration tests - - Test migration scenarios +3. **Day 3: Testing** + - Create new tests using new format only + - No migration testing needed + - Test all new functionality 4. **Day 4: Documentation & Cleanup** - Update all documentation @@ -1316,17 +1410,33 @@ sed -i '/port80\//d' ts/http/index.ts ## Risk Mitigation -1. **Backward Compatibility** - - Create migration helper to convert old configs - - Deprecation warnings for old methods - - Phased rollout with feature flags +1. **Static Route Handler** + - Already exists in the type system + - Just needs implementation in route-connection-handler.ts + - Low risk as it follows existing patterns -2. **Testing Strategy** - - Unit tests for each new component - - Integration tests for full workflow - - Migration tests for existing deployments +2. **Route Updates During Operation** + - SmartProxy's updateRoutes() is already thread-safe + - Sequential processing prevents race conditions + - Challenge routes are added/removed atomically -3. **Rollback Plan** - - Keep old certificate module in separate branch - - Document rollback procedures - - Test rollback scenarios \ No newline at end of file +3. **Port 80 Conflicts** + - Priority-based routing ensures ACME routes match first + - Path-based matching (`/.well-known/acme-challenge/*`) is specific + - Other routes on port 80 won't interfere + +4. **Error Recovery** + - SmartAcme initialization failures are handled gracefully + - Null checks prevent crashes if ACME isn't available + - Routes continue to work without certificates + +5. **Testing Strategy** + - Test concurrent ACME challenges + - Test route priority conflicts + - Test certificate renewal during high traffic + - Test the new configuration format only + +6. **No Migration Path** + - Breaking change is intentional + - Old configurations must be manually updated + - No compatibility shims or helpers provided \ No newline at end of file