diff --git a/readme.hints.md b/readme.hints.md index 6f55fc9..4402664 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -295,4 +295,54 @@ You'll see: - During attacks or high-volume scenarios, logs are flushed more frequently - If 50+ events occur within 1 second, immediate flush is triggered - Prevents memory buildup during flooding attacks -- Maintains real-time visibility during incidents \ No newline at end of file +- Maintains real-time visibility during incidents + +## Custom Certificate Provision Function + +The `certProvisionFunction` feature has been implemented to allow users to provide their own certificate generation logic. + +### Implementation Details + +1. **Type Definition**: The function must return `Promise` where: + - `TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01'` + - Return `'http01'` to fallback to Let's Encrypt + - Return a certificate object for custom certificates + +2. **Certificate Manager Changes**: + - Added `certProvisionFunction` property to CertificateManager + - Modified `provisionAcmeCertificate()` to check custom function first + - Custom certificates are stored with source type 'custom' + - Expiry date extraction currently defaults to 90 days + +3. **Configuration Options**: + - `certProvisionFunction`: The custom provision function + - `certProvisionFallbackToAcme`: Whether to fallback to ACME on error (default: true) + +4. **Usage Example**: +```typescript +new SmartProxy({ + certProvisionFunction: async (domain: string) => { + if (domain === 'internal.example.com') { + return { + cert: customCert, + key: customKey, + ca: customCA + } as unknown as TSmartProxyCertProvisionObject; + } + return 'http01'; // Use Let's Encrypt + }, + certProvisionFallbackToAcme: true +}) +``` + +5. **Testing Notes**: + - Type assertions through `unknown` are needed in tests due to strict interface typing + - Mock certificate objects work for testing but need proper type casting + - The actual certificate parsing for expiry dates would need a proper X.509 parser + +### Future Improvements + +1. Implement proper certificate expiry date extraction using X.509 parsing +2. Add support for returning expiry date with custom certificates +3. Consider adding validation for custom certificate format +4. Add events/hooks for certificate provisioning lifecycle \ No newline at end of file diff --git a/readme.md b/readme.md index 05f7640..e68cb47 100644 --- a/readme.md +++ b/readme.md @@ -2336,14 +2336,117 @@ sequenceDiagram • Efficient SNI extraction • Minimal overhead routing -## Certificate Hooks & Events +## Certificate Management + +### Custom Certificate Provision Function + +SmartProxy supports a custom certificate provision function that allows you to provide your own certificate generation logic while maintaining compatibility with Let's Encrypt: + +```typescript +const proxy = new SmartProxy({ + certProvisionFunction: async (domain: string): Promise => { + // Option 1: Return a custom certificate + if (domain === 'internal.example.com') { + return { + cert: customCertPEM, + key: customKeyPEM, + ca: customCAPEM // Optional CA chain + }; + } + + // Option 2: Fallback to Let's Encrypt + return 'http01'; + }, + + // Control fallback behavior when custom provision fails + certProvisionFallbackToAcme: true, // Default: true + + routes: [...] +}); +``` + +**Key Features:** +- Called for any route with `certificate: 'auto'` +- Return custom certificate object or `'http01'` to use Let's Encrypt +- Participates in automatic renewal cycle (checked every 12 hours) +- Custom certificates stored with source type 'custom' for tracking + +**Configuration Options:** +- `certProvisionFunction`: Async function that receives domain and returns certificate or 'http01' +- `certProvisionFallbackToAcme`: Whether to fallback to Let's Encrypt if custom provision fails (default: true) + +**Advanced Example with Certificate Manager:** + +```typescript +const certManager = new MyCertificateManager(); + +const proxy = new SmartProxy({ + certProvisionFunction: async (domain: string) => { + try { + // Check if we have a custom certificate for this domain + if (await certManager.hasCustomCert(domain)) { + const cert = await certManager.getCertificate(domain); + return { + cert: cert.certificate, + key: cert.privateKey, + ca: cert.chain + }; + } + + // Use Let's Encrypt for public domains + if (domain.endsWith('.example.com')) { + return 'http01'; + } + + // Generate self-signed for internal domains + if (domain.endsWith('.internal')) { + const selfSigned = await certManager.generateSelfSigned(domain); + return { + cert: selfSigned.cert, + key: selfSigned.key, + ca: '' + }; + } + + // Default to Let's Encrypt + return 'http01'; + } catch (error) { + console.error(`Certificate provision failed for ${domain}:`, error); + // Will fallback to Let's Encrypt if certProvisionFallbackToAcme is true + throw error; + } + }, + + certProvisionFallbackToAcme: true, + + routes: [ + // Routes that use automatic certificates + { + match: { ports: 443, domains: ['app.example.com', '*.internal'] }, + action: { + type: 'forward', + target: { host: 'localhost', port: 8080 }, + tls: { mode: 'terminate', certificate: 'auto' } + } + } + ] +}); +``` + +### Certificate Events Listen for certificate events via EventEmitter: - **SmartProxy**: - `certificate` (domain, publicKey, privateKey, expiryDate, source, isRenewal) - Events from CertManager are propagated -Provide a `certProvisionFunction(domain)` in SmartProxy settings to supply static certs or return `'http01'`. +```typescript +proxy.on('certificate', (domain, cert, key, expiryDate, source, isRenewal) => { + console.log(`Certificate ${isRenewal ? 'renewed' : 'provisioned'} for ${domain}`); + console.log(`Source: ${source}`); // 'acme', 'static', or 'custom' + console.log(`Expires: ${expiryDate}`); +}); +``` ## SmartProxy: Common Use Cases