smartproxy/readme.plan.md

13 KiB

SmartProxy Unified Forwarding Configuration Plan

Project Goal

Create a clean, use-case driven forwarding configuration interface for SmartProxy that elegantly handles all forwarding scenarios: SNI-based forwarding, termination-based forwarding (NetworkProxy), HTTP forwarding, and ACME challenge forwarding.

Current State

Currently, SmartProxy has several different forwarding mechanisms configured separately:

  1. HTTPS/SNI forwarding via IDomainConfig properties
  2. NetworkProxy forwarding via useNetworkProxy in domain configs
  3. HTTP forwarding via Port80Handler's forward configuration
  4. ACME challenge forwarding via acmeForward configuration

This separation creates configuration complexity and reduced cohesion between related settings.

Proposed Solution: Clean Use-Case Driven Forwarding Interface

Phase 1: Design Streamlined Forwarding Interface

  • Create a use-case driven IForwardConfig interface that simplifies configuration:
export interface IForwardConfig {
  // Define the primary forwarding type - use-case driven approach
  type: 'http-only' | 'https-passthrough' | 'https-terminate-to-http' | 'https-terminate-to-https';
  
  // Target configuration
  target: {
    host: string | string[];  // Support single host or round-robin
    port: number;
  };
  
  // HTTP-specific options
  http?: {
    enabled?: boolean;                 // Defaults to true for http-only, optional for others
    redirectToHttps?: boolean;         // Redirect HTTP to HTTPS
    headers?: Record<string, string>;  // Custom headers for HTTP responses
  };
  
  // HTTPS-specific options
  https?: {
    customCert?: {                    // Use custom cert instead of auto-provisioned
      key: string;
      cert: string;
    };
    forwardSni?: boolean;             // Forward SNI info in passthrough mode
  };
  
  // ACME certificate handling
  acme?: {
    enabled?: boolean;                // Enable ACME certificate provisioning  
    maintenance?: boolean;            // Auto-renew certificates
    production?: boolean;             // Use production ACME servers
    forwardChallenges?: {             // Forward ACME challenges
      host: string;
      port: number;
      useTls?: boolean;
    };
  };
  
  // Security options
  security?: { 
    allowedIps?: string[];            // IPs allowed to connect
    blockedIps?: string[];            // IPs blocked from connecting
    maxConnections?: number;          // Max simultaneous connections
  };
  
  // Advanced options
  advanced?: {
    portRanges?: Array<{ from: number; to: number }>; // Allowed port ranges
    networkProxyPort?: number;        // Custom NetworkProxy port if using terminate mode
    keepAlive?: boolean;              // Enable TCP keepalive
    timeout?: number;                 // Connection timeout in ms
    headers?: Record<string, string>; // Custom headers with support for variables like {sni}
  };
}

Phase 2: Create New Domain Configuration Interface

  • Replace existing IDomainConfig interface with a new one using the forwarding pattern:
export interface IDomainConfig {
  // Core properties
  domains: string[];  // Domain patterns to match
  
  // Unified forwarding configuration
  forwarding: IForwardConfig;
}

Phase 3: Implement Forwarding Handler System

  • Create an implementation strategy focused on the new forwarding types:
/**
 * Base class for all forwarding handlers
 */
abstract class ForwardingHandler {
  constructor(protected config: IForwardConfig) {}
  
  abstract handleConnection(socket: Socket): void;
  abstract handleHttpRequest(req: IncomingMessage, res: ServerResponse): void;
}

/**
 * Factory for creating the appropriate handler based on forwarding type
 */
class ForwardingHandlerFactory {
  public static createHandler(config: IForwardConfig): ForwardingHandler {
    switch (config.type) {
      case 'http-only':
        return new HttpForwardingHandler(config);
        
      case 'https-passthrough':
        return new HttpsPassthroughHandler(config);
        
      case 'https-terminate-to-http':
        return new HttpsTerminateToHttpHandler(config);
        
      case 'https-terminate-to-https':
        return new HttpsTerminateToHttpsHandler(config);
        
      default:
        throw new Error(`Unknown forwarding type: ${config.type}`);
    }
  }
}

Usage Examples for Common Scenarios

1. Basic HTTP Server

{
  domains: ['example.com'],
  forwarding: {
    type: 'http-only',
    target: {
      host: 'localhost',
      port: 3000
    }
  }
}

2. HTTPS Termination with HTTP Backend

{
  domains: ['secure.example.com'],
  forwarding: {
    type: 'https-terminate-to-http',
    target: {
      host: 'localhost',
      port: 3000
    },
    acme: {
      production: true  // Use production Let's Encrypt
    }
  }
}

3. HTTPS Termination with HTTPS Backend

{
  domains: ['secure-backend.example.com'],
  forwarding: {
    type: 'https-terminate-to-https',
    target: {
      host: 'internal-api',
      port: 8443
    },
    http: {
      redirectToHttps: true  // Redirect HTTP requests to HTTPS
    }
  }
}

4. SNI Passthrough

{
  domains: ['passthrough.example.com'],
  forwarding: {
    type: 'https-passthrough',
    target: {
      host: '10.0.0.5',
      port: 443
    }
  }
}

5. Mixed HTTP/HTTPS with Custom ACME Forwarding

{
  domains: ['mixed.example.com'],
  forwarding: {
    type: 'https-terminate-to-http',
    target: {
      host: 'localhost',
      port: 3000
    },
    http: {
      redirectToHttps: false  // Allow both HTTP and HTTPS access
    },
    acme: {
      enabled: true,
      maintenance: true,
      forwardChallenges: {
        host: '192.168.1.100',
        port: 8080
      }
    }
  }
}

6. Load-Balanced Backend

{
  domains: ['api.example.com'],
  forwarding: {
    type: 'https-terminate-to-https',
    target: {
      host: ['10.0.0.10', '10.0.0.11', '10.0.0.12'], // Round-robin
      port: 8443
    },
    security: {
      allowedIps: ['10.0.0.*', '192.168.1.*']  // Restrict access
    }
  }
}

7. Advanced Proxy Chain with Custom Headers

{
  domains: ['secure-chain.example.com'],
  forwarding: {
    type: 'https-terminate-to-https',
    target: {
      host: 'backend-gateway.internal',
      port: 443
    },
    advanced: {
      // Pass original client info to backend
      headers: {
        'X-Original-SNI': '{sni}',
        'X-Client-IP': '{clientIp}'
      }
    }
  }
}

Implementation Plan

Task 1: Core Types and Interfaces (Week 1)

  • Create the new IForwardConfig interface in classes.pp.interfaces.ts
  • Design the new IDomainConfig interface using the forwarding property
  • Define the internal data types for expanded configuration

Task 2: Forwarding Handlers (Week 1-2)

  • Create abstract ForwardingHandler base class
  • Implement concrete handlers for each forwarding type:
    • HttpForwardingHandler - For HTTP-only configurations
    • HttpsPassthroughHandler - For SNI passthrough
    • HttpsTerminateToHttpHandler - For TLS termination to HTTP backends
    • HttpsTerminateToHttpsHandler - For TLS termination to HTTPS backends
  • Implement ForwardingHandlerFactory to create the appropriate handler

Task 3: SmartProxy Integration (Week 2-3)

  • Update SmartProxy class to use the new forwarding system
  • Modify ConnectionHandler to delegate to forwarding handlers
  • Refactor domain configuration processing to use forwarding types
  • Update Port80Handler integration to work with the new system

Task 4: Certificate Management (Week 3)

  • Create a certificate management system that works with forwarding types
  • Implement automatic ACME provisioning based on forwarding type
  • Add custom certificate support

Task 5: Testing & Helper Functions (Week 4)

  • Create helper functions for common forwarding patterns
  • Implement comprehensive test suite for each forwarding handler
  • Add validation for forwarding configurations

Task 6: Documentation (Week 4)

  • Create detailed documentation for the new forwarding system
  • Document the forwarding types and their use cases
  • Update README with the new configuration examples

Detailed Type Documentation

Core Forwarding Types

/**
 * The primary forwarding types supported by SmartProxy
 */
export type ForwardingType = 
  | 'http-only'                // HTTP forwarding only (no HTTPS)
  | 'https-passthrough'        // Pass-through TLS traffic (SNI forwarding)
  | 'https-terminate-to-http'  // Terminate TLS and forward to HTTP backend
  | 'https-terminate-to-https'; // Terminate TLS and forward to HTTPS backend

Type-Specific Behavior

Each forwarding type has specific default behavior:

HTTP-Only

  • Handles only HTTP traffic
  • No TLS/HTTPS support
  • No certificate management

HTTPS Passthrough

  • Forwards raw TLS traffic to backend (no termination)
  • Passes SNI information through
  • No HTTP support (TLS only)
  • No certificate management

HTTPS Terminate to HTTP

  • Terminates TLS at SmartProxy
  • Connects to backend using HTTP (non-TLS)
  • Manages certificates automatically (ACME)
  • Supports HTTP requests with option to redirect to HTTPS

HTTPS Terminate to HTTPS

  • Terminates client TLS at SmartProxy
  • Creates new TLS connection to backend
  • Manages certificates automatically (ACME)
  • Supports HTTP requests with option to redirect to HTTPS

Handler Implementation Strategy

/**
 * Handler for HTTP-only forwarding
 */
class HttpForwardingHandler extends ForwardingHandler {
  public handleConnection(socket: Socket): void {
    // Process HTTP connection
    // For HTTP-only, we'll mostly defer to handleHttpRequest
  }
  
  public handleHttpRequest(req: IncomingMessage, res: ServerResponse): void {
    // Forward HTTP request to target
    const target = this.getTargetFromConfig();
    this.proxyRequest(req, res, target);
  }
}

/**
 * Handler for HTTPS passthrough (SNI forwarding)
 */
class HttpsPassthroughHandler extends ForwardingHandler {
  public handleConnection(socket: Socket): void {
    // Extract SNI from TLS ClientHello if needed
    // Forward raw TLS traffic to target without termination
    const target = this.getTargetFromConfig();
    this.forwardTlsConnection(socket, target);
  }
  
  public handleHttpRequest(req: IncomingMessage, res: ServerResponse): void {
    // HTTP not supported in SNI passthrough mode
    res.statusCode = 404;
    res.end('HTTP not supported for this domain');
  }
}

/**
 * Handler for HTTPS termination with HTTP backend
 */
class HttpsTerminateToHttpHandler extends ForwardingHandler {
  private tlsContext: SecureContext;
  
  public async initialize(): Promise<void> {
    // Set up TLS termination context
    this.tlsContext = await this.createTlsContext();
  }
  
  public handleConnection(socket: Socket): void {
    // Terminate TLS
    const tlsSocket = this.createTlsSocket(socket, this.tlsContext);
    
    // Forward to HTTP backend after TLS termination
    tlsSocket.on('data', (data) => {
      this.forwardToHttpBackend(data);
    });
  }
  
  public handleHttpRequest(req: IncomingMessage, res: ServerResponse): void {
    if (this.config.http?.redirectToHttps) {
      // Redirect to HTTPS if configured
      this.redirectToHttps(req, res);
    } else {
      // Handle HTTP request
      const target = this.getTargetFromConfig();
      this.proxyRequest(req, res, target);
    }
  }
}

/**
 * Handler for HTTPS termination with HTTPS backend
 */
class HttpsTerminateToHttpsHandler extends ForwardingHandler {
  private tlsContext: SecureContext;
  
  public async initialize(): Promise<void> {
    // Set up TLS termination context
    this.tlsContext = await this.createTlsContext();
  }
  
  public handleConnection(socket: Socket): void {
    // Terminate client TLS
    const tlsSocket = this.createTlsSocket(socket, this.tlsContext);
    
    // Create new TLS connection to backend
    tlsSocket.on('data', (data) => {
      this.forwardToHttpsBackend(data);
    });
  }
  
  public handleHttpRequest(req: IncomingMessage, res: ServerResponse): void {
    if (this.config.http?.redirectToHttps) {
      // Redirect to HTTPS if configured
      this.redirectToHttps(req, res);
    } else {
      // Handle HTTP request via HTTPS to backend
      const target = this.getTargetFromConfig();
      this.proxyRequestOverHttps(req, res, target);
    }
  }
}

Benefits of This Approach

  1. Clean, Type-Driven Design

    • Forwarding types clearly express intent
    • No backward compatibility compromises
    • Code structure follows the domain model
  2. Explicit Configuration

    • Configuration directly maps to behavior
    • Reduced chance of unexpected behavior
  3. Modular Implementation

    • Each forwarding type handled by dedicated class
    • Clear separation of concerns
    • Easier to test and extend
  4. Simplified Mental Model

    • Users think in terms of use cases, not low-level settings
    • Configuration matches mental model
  5. Future-Proof

    • Easy to add new forwarding types
    • Clean extension points for new features