feat(certificates): add custom provisioning option
This commit is contained in:
@ -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
|
||||
- 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<TSmartProxyCertProvisionObject>` 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
|
107
readme.md
107
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<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
|
||||
|
||||
|
Reference in New Issue
Block a user