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
|
- During attacks or high-volume scenarios, logs are flushed more frequently
|
||||||
- If 50+ events occur within 1 second, immediate flush is triggered
|
- If 50+ events occur within 1 second, immediate flush is triggered
|
||||||
- Prevents memory buildup during flooding attacks
|
- 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
|
• Efficient SNI extraction
|
||||||
• Minimal overhead routing
|
• 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:
|
Listen for certificate events via EventEmitter:
|
||||||
- **SmartProxy**:
|
- **SmartProxy**:
|
||||||
- `certificate` (domain, publicKey, privateKey, expiryDate, source, isRenewal)
|
- `certificate` (domain, publicKey, privateKey, expiryDate, source, isRenewal)
|
||||||
- Events from CertManager are propagated
|
- 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
|
## SmartProxy: Common Use Cases
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user