feat(acme): Improve certificate management by adding global ACME configuration support and allowing route-level overrides. Enhanced error messages help identify missing ACME email and misconfigurations (e.g. wildcard domains). Documentation has been updated and new tests added to verify SmartCertManager behavior, ensuring a clearer migration path from legacy implementations.
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import { NetworkProxy } from '../network-proxy/index.js';
|
||||
import type { IRouteConfig, IRouteTls } from './models/route-types.js';
|
||||
import type { IAcmeOptions } from './models/interfaces.js';
|
||||
import { CertStore } from './cert-store.js';
|
||||
|
||||
export interface ICertStatus {
|
||||
@ -31,6 +32,9 @@ export class SmartCertManager {
|
||||
// Track certificate status by route name
|
||||
private certStatus: Map<string, ICertStatus> = new Map();
|
||||
|
||||
// Global ACME defaults from top-level configuration
|
||||
private globalAcmeDefaults: IAcmeOptions | null = null;
|
||||
|
||||
// Callback to update SmartProxy routes for challenges
|
||||
private updateRoutesCallback?: (routes: IRouteConfig[]) => Promise<void>;
|
||||
|
||||
@ -50,6 +54,13 @@ export class SmartCertManager {
|
||||
this.networkProxy = networkProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global ACME defaults from top-level configuration
|
||||
*/
|
||||
public setGlobalAcmeDefaults(defaults: IAcmeOptions): void {
|
||||
this.globalAcmeDefaults = defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set callback for updating routes (used for challenge routes)
|
||||
*/
|
||||
@ -146,7 +157,12 @@ export class SmartCertManager {
|
||||
domains: string[]
|
||||
): Promise<void> {
|
||||
if (!this.smartAcme) {
|
||||
throw new Error('SmartAcme not initialized');
|
||||
throw new Error(
|
||||
'SmartAcme not initialized. This usually means no ACME email was provided. ' +
|
||||
'Please ensure you have configured ACME with an email address either:\n' +
|
||||
'1. In the top-level "acme" configuration\n' +
|
||||
'2. In the route\'s "tls.acme" configuration'
|
||||
);
|
||||
}
|
||||
|
||||
const primaryDomain = domains[0];
|
||||
@ -161,7 +177,12 @@ export class SmartCertManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Requesting ACME certificate for ${domains.join(', ')}`);
|
||||
// Apply renewal threshold from global defaults or route config
|
||||
const renewThreshold = route.action.tls?.acme?.renewBeforeDays ||
|
||||
this.globalAcmeDefaults?.renewThresholdDays ||
|
||||
30;
|
||||
|
||||
console.log(`Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`);
|
||||
this.updateCertStatus(routeName, 'pending', 'acme');
|
||||
|
||||
try {
|
||||
@ -303,7 +324,10 @@ export class SmartCertManager {
|
||||
*/
|
||||
private isCertificateValid(cert: ICertificateData): boolean {
|
||||
const now = new Date();
|
||||
const expiryThreshold = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days
|
||||
|
||||
// Use renewal threshold from global defaults or fallback to 30 days
|
||||
const renewThresholdDays = this.globalAcmeDefaults?.renewThresholdDays || 30;
|
||||
const expiryThreshold = new Date(now.getTime() + renewThresholdDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
return cert.expiryDate > expiryThreshold;
|
||||
}
|
||||
@ -417,12 +441,15 @@ export class SmartCertManager {
|
||||
* Setup challenge handler integration with SmartProxy routing
|
||||
*/
|
||||
private setupChallengeHandler(http01Handler: plugins.smartacme.handlers.Http01MemoryHandler): void {
|
||||
// Use challenge port from global config or default to 80
|
||||
const challengePort = this.globalAcmeDefaults?.port || 80;
|
||||
|
||||
// Create a challenge route that delegates to SmartAcme's HTTP-01 handler
|
||||
const challengeRoute: IRouteConfig = {
|
||||
name: 'acme-challenge',
|
||||
priority: 1000, // High priority
|
||||
match: {
|
||||
ports: 80,
|
||||
ports: challengePort,
|
||||
path: '/.well-known/acme-challenge/*'
|
||||
},
|
||||
action: {
|
||||
|
Reference in New Issue
Block a user