feat(certificates): add custom provisioning option

This commit is contained in:
Juergen Kunz
2025-07-13 00:27:49 +00:00
parent 257a5dc319
commit 2d2e9e9475
2 changed files with 156 additions and 3 deletions

107
readme.md
View File

@ -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<TSmartProxyCertProvisionObject> => {
// 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