@push.rocks/smartproxy 18.2.0 → 19.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/common/eventUtils.d.ts +1 -2
- package/dist_ts/common/eventUtils.js +2 -1
- package/dist_ts/core/models/common-types.d.ts +1 -1
- package/dist_ts/core/models/common-types.js +1 -1
- package/dist_ts/core/utils/event-utils.d.ts +9 -9
- package/dist_ts/core/utils/event-utils.js +6 -14
- package/dist_ts/http/models/http-types.d.ts +13 -1
- package/dist_ts/http/models/http-types.js +1 -1
- package/dist_ts/index.d.ts +4 -6
- package/dist_ts/index.js +4 -10
- package/dist_ts/proxies/index.d.ts +3 -2
- package/dist_ts/proxies/index.js +4 -5
- package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +31 -49
- package/dist_ts/proxies/network-proxy/certificate-manager.js +77 -374
- package/dist_ts/proxies/network-proxy/models/types.d.ts +12 -1
- package/dist_ts/proxies/network-proxy/models/types.js +1 -1
- package/dist_ts/proxies/network-proxy/network-proxy.d.ts +2 -7
- package/dist_ts/proxies/network-proxy/network-proxy.js +10 -19
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +6 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +24 -5
- package/dist_ts/proxies/smart-proxy/models/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/models/index.js +1 -5
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +30 -1
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/route-manager.js +7 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +112 -26
- package/package.json +1 -2
- package/readme.hints.md +31 -1
- package/readme.md +82 -6
- package/readme.plan.md +109 -1417
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/common/eventUtils.ts +2 -2
- package/ts/core/models/common-types.ts +1 -1
- package/ts/core/utils/event-utils.ts +12 -21
- package/ts/http/models/http-types.ts +8 -4
- package/ts/index.ts +11 -14
- package/ts/proxies/index.ts +7 -4
- package/ts/proxies/network-proxy/certificate-manager.ts +92 -417
- package/ts/proxies/network-proxy/models/types.ts +14 -2
- package/ts/proxies/network-proxy/network-proxy.ts +10 -19
- package/ts/proxies/smart-proxy/certificate-manager.ts +31 -4
- package/ts/proxies/smart-proxy/models/index.ts +2 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +31 -2
- package/ts/proxies/smart-proxy/models/route-types.ts +1 -1
- package/ts/proxies/smart-proxy/route-manager.ts +7 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +142 -25
- package/ts/certificate/acme/acme-factory.ts +0 -48
- package/ts/certificate/acme/challenge-handler.ts +0 -110
- package/ts/certificate/acme/index.ts +0 -3
- package/ts/certificate/events/certificate-events.ts +0 -36
- package/ts/certificate/index.ts +0 -75
- package/ts/certificate/models/certificate-types.ts +0 -109
- package/ts/certificate/providers/cert-provisioner.ts +0 -519
- package/ts/certificate/providers/index.ts +0 -3
- package/ts/certificate/storage/file-storage.ts +0 -234
- package/ts/certificate/storage/index.ts +0 -3
- package/ts/certificate/utils/certificate-helpers.ts +0 -50
- package/ts/http/port80/acme-interfaces.ts +0 -169
- package/ts/http/port80/challenge-responder.ts +0 -246
- package/ts/http/port80/index.ts +0 -13
- package/ts/http/port80/port80-handler.ts +0 -728
package/ts/certificate/index.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Certificate management module for SmartProxy
|
|
3
|
-
* Provides certificate provisioning, storage, and management capabilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Certificate types and models
|
|
7
|
-
export * from './models/certificate-types.js';
|
|
8
|
-
|
|
9
|
-
// Certificate events
|
|
10
|
-
export * from './events/certificate-events.js';
|
|
11
|
-
|
|
12
|
-
// Certificate providers
|
|
13
|
-
export * from './providers/cert-provisioner.js';
|
|
14
|
-
|
|
15
|
-
// ACME related exports
|
|
16
|
-
export * from './acme/acme-factory.js';
|
|
17
|
-
export * from './acme/challenge-handler.js';
|
|
18
|
-
|
|
19
|
-
// Certificate utilities
|
|
20
|
-
export * from './utils/certificate-helpers.js';
|
|
21
|
-
|
|
22
|
-
// Certificate storage
|
|
23
|
-
export * from './storage/file-storage.js';
|
|
24
|
-
|
|
25
|
-
// Convenience function to create a certificate provisioner with common settings
|
|
26
|
-
import { CertProvisioner } from './providers/cert-provisioner.js';
|
|
27
|
-
import type { TCertProvisionObject } from './providers/cert-provisioner.js';
|
|
28
|
-
import { buildPort80Handler } from './acme/acme-factory.js';
|
|
29
|
-
import type { IAcmeOptions, IRouteForwardConfig } from './models/certificate-types.js';
|
|
30
|
-
import type { IRouteConfig } from '../proxies/smart-proxy/models/route-types.js';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Interface for NetworkProxyBridge used by CertProvisioner
|
|
34
|
-
*/
|
|
35
|
-
interface ICertNetworkProxyBridge {
|
|
36
|
-
applyExternalCertificate(certData: any): void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Creates a complete certificate provisioning system with default settings
|
|
41
|
-
* @param routeConfigs Route configurations that may need certificates
|
|
42
|
-
* @param acmeOptions ACME options for certificate provisioning
|
|
43
|
-
* @param networkProxyBridge Bridge to apply certificates to network proxy
|
|
44
|
-
* @param certProvider Optional custom certificate provider
|
|
45
|
-
* @returns Configured CertProvisioner
|
|
46
|
-
*/
|
|
47
|
-
export function createCertificateProvisioner(
|
|
48
|
-
routeConfigs: IRouteConfig[],
|
|
49
|
-
acmeOptions: IAcmeOptions,
|
|
50
|
-
networkProxyBridge: ICertNetworkProxyBridge,
|
|
51
|
-
certProvider?: (domain: string) => Promise<TCertProvisionObject>
|
|
52
|
-
): CertProvisioner {
|
|
53
|
-
// Build the Port80Handler for ACME challenges
|
|
54
|
-
const port80Handler = buildPort80Handler(acmeOptions);
|
|
55
|
-
|
|
56
|
-
// Extract ACME-specific configuration
|
|
57
|
-
const {
|
|
58
|
-
renewThresholdDays = 30,
|
|
59
|
-
renewCheckIntervalHours = 24,
|
|
60
|
-
autoRenew = true,
|
|
61
|
-
routeForwards = []
|
|
62
|
-
} = acmeOptions;
|
|
63
|
-
|
|
64
|
-
// Create and return the certificate provisioner
|
|
65
|
-
return new CertProvisioner(
|
|
66
|
-
routeConfigs,
|
|
67
|
-
port80Handler,
|
|
68
|
-
networkProxyBridge,
|
|
69
|
-
certProvider,
|
|
70
|
-
renewThresholdDays,
|
|
71
|
-
renewCheckIntervalHours,
|
|
72
|
-
autoRenew,
|
|
73
|
-
routeForwards
|
|
74
|
-
);
|
|
75
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Certificate data structure containing all necessary information
|
|
6
|
-
* about a certificate
|
|
7
|
-
*/
|
|
8
|
-
export interface ICertificateData {
|
|
9
|
-
domain: string;
|
|
10
|
-
certificate: string;
|
|
11
|
-
privateKey: string;
|
|
12
|
-
expiryDate: Date;
|
|
13
|
-
// Optional source and renewal information for event emissions
|
|
14
|
-
source?: 'static' | 'http01' | 'dns01';
|
|
15
|
-
isRenewal?: boolean;
|
|
16
|
-
// Reference to the route that requested this certificate (if available)
|
|
17
|
-
routeReference?: {
|
|
18
|
-
routeId?: string;
|
|
19
|
-
routeName?: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Certificates pair (private and public keys)
|
|
25
|
-
*/
|
|
26
|
-
export interface ICertificates {
|
|
27
|
-
privateKey: string;
|
|
28
|
-
publicKey: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Certificate failure payload type
|
|
33
|
-
*/
|
|
34
|
-
export interface ICertificateFailure {
|
|
35
|
-
domain: string;
|
|
36
|
-
error: string;
|
|
37
|
-
isRenewal: boolean;
|
|
38
|
-
routeReference?: {
|
|
39
|
-
routeId?: string;
|
|
40
|
-
routeName?: string;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Certificate expiry payload type
|
|
46
|
-
*/
|
|
47
|
-
export interface ICertificateExpiring {
|
|
48
|
-
domain: string;
|
|
49
|
-
expiryDate: Date;
|
|
50
|
-
daysRemaining: number;
|
|
51
|
-
routeReference?: {
|
|
52
|
-
routeId?: string;
|
|
53
|
-
routeName?: string;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Route-specific forwarding configuration for ACME challenges
|
|
59
|
-
*/
|
|
60
|
-
export interface IRouteForwardConfig {
|
|
61
|
-
domain: string;
|
|
62
|
-
target: {
|
|
63
|
-
host: string;
|
|
64
|
-
port: number;
|
|
65
|
-
};
|
|
66
|
-
sslRedirect?: boolean;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Domain configuration options for Port80Handler
|
|
71
|
-
*
|
|
72
|
-
* This is used internally by the Port80Handler to manage domains
|
|
73
|
-
* but will eventually be replaced with route-based options.
|
|
74
|
-
*/
|
|
75
|
-
export interface IDomainOptions {
|
|
76
|
-
domainName: string;
|
|
77
|
-
sslRedirect: boolean; // if true redirects the request to port 443
|
|
78
|
-
acmeMaintenance: boolean; // tries to always have a valid cert for this domain
|
|
79
|
-
forward?: {
|
|
80
|
-
ip: string;
|
|
81
|
-
port: number;
|
|
82
|
-
}; // forwards all http requests to that target
|
|
83
|
-
acmeForward?: {
|
|
84
|
-
ip: string;
|
|
85
|
-
port: number;
|
|
86
|
-
}; // forwards letsencrypt requests to this config
|
|
87
|
-
routeReference?: {
|
|
88
|
-
routeId?: string;
|
|
89
|
-
routeName?: string;
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Unified ACME configuration options used across proxies and handlers
|
|
95
|
-
*/
|
|
96
|
-
export interface IAcmeOptions {
|
|
97
|
-
accountEmail?: string; // Email for Let's Encrypt account
|
|
98
|
-
enabled?: boolean; // Whether ACME is enabled
|
|
99
|
-
port?: number; // Port to listen on for ACME challenges (default: 80)
|
|
100
|
-
useProduction?: boolean; // Use production environment (default: staging)
|
|
101
|
-
httpsRedirectPort?: number; // Port to redirect HTTP requests to HTTPS (default: 443)
|
|
102
|
-
renewThresholdDays?: number; // Days before expiry to renew certificates
|
|
103
|
-
renewCheckIntervalHours?: number; // How often to check for renewals (in hours)
|
|
104
|
-
autoRenew?: boolean; // Whether to automatically renew certificates
|
|
105
|
-
certificateStore?: string; // Directory to store certificates
|
|
106
|
-
skipConfiguredCerts?: boolean; // Skip domains with existing certificates
|
|
107
|
-
routeForwards?: IRouteForwardConfig[]; // Route-specific forwarding configs
|
|
108
|
-
}
|
|
109
|
-
|
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
|
3
|
-
import type { ICertificateData, IRouteForwardConfig, IDomainOptions } from '../models/certificate-types.js';
|
|
4
|
-
import { Port80HandlerEvents, CertProvisionerEvents } from '../events/certificate-events.js';
|
|
5
|
-
import { Port80Handler } from '../../http/port80/port80-handler.js';
|
|
6
|
-
|
|
7
|
-
// Interface for NetworkProxyBridge
|
|
8
|
-
interface INetworkProxyBridge {
|
|
9
|
-
applyExternalCertificate(certData: ICertificateData): void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Type for static certificate provisioning
|
|
14
|
-
*/
|
|
15
|
-
export type TCertProvisionObject = plugins.tsclass.network.ICert | 'http01' | 'dns01';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Interface for routes that need certificates
|
|
19
|
-
*/
|
|
20
|
-
interface ICertRoute {
|
|
21
|
-
domain: string;
|
|
22
|
-
route: IRouteConfig;
|
|
23
|
-
tlsMode: 'terminate' | 'terminate-and-reencrypt';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* CertProvisioner manages certificate provisioning and renewal workflows,
|
|
28
|
-
* unifying static certificates and HTTP-01 challenges via Port80Handler.
|
|
29
|
-
*
|
|
30
|
-
* This class directly works with route configurations instead of converting to domain configs.
|
|
31
|
-
*/
|
|
32
|
-
export class CertProvisioner extends plugins.EventEmitter {
|
|
33
|
-
private routeConfigs: IRouteConfig[];
|
|
34
|
-
private certRoutes: ICertRoute[] = [];
|
|
35
|
-
private port80Handler: Port80Handler;
|
|
36
|
-
private networkProxyBridge: INetworkProxyBridge;
|
|
37
|
-
private certProvisionFunction?: (domain: string) => Promise<TCertProvisionObject>;
|
|
38
|
-
private routeForwards: IRouteForwardConfig[];
|
|
39
|
-
private renewThresholdDays: number;
|
|
40
|
-
private renewCheckIntervalHours: number;
|
|
41
|
-
private autoRenew: boolean;
|
|
42
|
-
private renewManager?: plugins.taskbuffer.TaskManager;
|
|
43
|
-
// Track provisioning type per domain
|
|
44
|
-
private provisionMap: Map<string, { type: 'http01' | 'dns01' | 'static', routeRef?: ICertRoute }>;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Extract routes that need certificates
|
|
48
|
-
* @param routes Route configurations
|
|
49
|
-
*/
|
|
50
|
-
private extractCertificateRoutesFromRoutes(routes: IRouteConfig[]): ICertRoute[] {
|
|
51
|
-
const certRoutes: ICertRoute[] = [];
|
|
52
|
-
|
|
53
|
-
// Process all HTTPS routes that need certificates
|
|
54
|
-
for (const route of routes) {
|
|
55
|
-
// Only process routes with TLS termination that need certificates
|
|
56
|
-
if (route.action.type === 'forward' &&
|
|
57
|
-
route.action.tls &&
|
|
58
|
-
(route.action.tls.mode === 'terminate' || route.action.tls.mode === 'terminate-and-reencrypt') &&
|
|
59
|
-
route.match.domains) {
|
|
60
|
-
|
|
61
|
-
// Extract domains from the route
|
|
62
|
-
const domains = Array.isArray(route.match.domains)
|
|
63
|
-
? route.match.domains
|
|
64
|
-
: [route.match.domains];
|
|
65
|
-
|
|
66
|
-
// For each domain in the route, create a certRoute entry
|
|
67
|
-
for (const domain of domains) {
|
|
68
|
-
// Skip wildcard domains that can't use ACME unless we have a certProvider
|
|
69
|
-
if (domain.includes('*') && (!this.certProvisionFunction || this.certProvisionFunction.length === 0)) {
|
|
70
|
-
console.warn(`Skipping wildcard domain that requires a certProvisionFunction: ${domain}`);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
certRoutes.push({
|
|
75
|
-
domain,
|
|
76
|
-
route,
|
|
77
|
-
tlsMode: route.action.tls.mode
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return certRoutes;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Constructor for CertProvisioner
|
|
88
|
-
*
|
|
89
|
-
* @param routeConfigs Array of route configurations
|
|
90
|
-
* @param port80Handler HTTP-01 challenge handler instance
|
|
91
|
-
* @param networkProxyBridge Bridge for applying external certificates
|
|
92
|
-
* @param certProvider Optional callback returning a static cert or 'http01'
|
|
93
|
-
* @param renewThresholdDays Days before expiry to trigger renewals
|
|
94
|
-
* @param renewCheckIntervalHours Interval in hours to check for renewals
|
|
95
|
-
* @param autoRenew Whether to automatically schedule renewals
|
|
96
|
-
* @param routeForwards Route-specific forwarding configs for ACME challenges
|
|
97
|
-
*/
|
|
98
|
-
constructor(
|
|
99
|
-
routeConfigs: IRouteConfig[],
|
|
100
|
-
port80Handler: Port80Handler,
|
|
101
|
-
networkProxyBridge: INetworkProxyBridge,
|
|
102
|
-
certProvider?: (domain: string) => Promise<TCertProvisionObject>,
|
|
103
|
-
renewThresholdDays: number = 30,
|
|
104
|
-
renewCheckIntervalHours: number = 24,
|
|
105
|
-
autoRenew: boolean = true,
|
|
106
|
-
routeForwards: IRouteForwardConfig[] = []
|
|
107
|
-
) {
|
|
108
|
-
super();
|
|
109
|
-
this.routeConfigs = routeConfigs;
|
|
110
|
-
this.port80Handler = port80Handler;
|
|
111
|
-
this.networkProxyBridge = networkProxyBridge;
|
|
112
|
-
this.certProvisionFunction = certProvider;
|
|
113
|
-
this.renewThresholdDays = renewThresholdDays;
|
|
114
|
-
this.renewCheckIntervalHours = renewCheckIntervalHours;
|
|
115
|
-
this.autoRenew = autoRenew;
|
|
116
|
-
this.provisionMap = new Map();
|
|
117
|
-
this.routeForwards = routeForwards;
|
|
118
|
-
|
|
119
|
-
// Extract certificate routes during instantiation
|
|
120
|
-
this.certRoutes = this.extractCertificateRoutesFromRoutes(routeConfigs);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Start initial provisioning and schedule renewals.
|
|
125
|
-
*/
|
|
126
|
-
public async start(): Promise<void> {
|
|
127
|
-
// Subscribe to Port80Handler certificate events
|
|
128
|
-
this.setupEventSubscriptions();
|
|
129
|
-
|
|
130
|
-
// Apply route forwarding for ACME challenges
|
|
131
|
-
this.setupForwardingConfigs();
|
|
132
|
-
|
|
133
|
-
// Initial provisioning for all domains in routes
|
|
134
|
-
await this.provisionAllCertificates();
|
|
135
|
-
|
|
136
|
-
// Schedule renewals if enabled
|
|
137
|
-
if (this.autoRenew) {
|
|
138
|
-
this.scheduleRenewals();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Set up event subscriptions for certificate events
|
|
144
|
-
*/
|
|
145
|
-
private setupEventSubscriptions(): void {
|
|
146
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (data: ICertificateData) => {
|
|
147
|
-
// Add route reference if we have it
|
|
148
|
-
const routeRef = this.findRouteForDomain(data.domain);
|
|
149
|
-
const enhancedData: ICertificateData = {
|
|
150
|
-
...data,
|
|
151
|
-
source: 'http01',
|
|
152
|
-
isRenewal: false,
|
|
153
|
-
routeReference: routeRef ? {
|
|
154
|
-
routeId: routeRef.route.name,
|
|
155
|
-
routeName: routeRef.route.name
|
|
156
|
-
} : undefined
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, enhancedData);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (data: ICertificateData) => {
|
|
163
|
-
// Add route reference if we have it
|
|
164
|
-
const routeRef = this.findRouteForDomain(data.domain);
|
|
165
|
-
const enhancedData: ICertificateData = {
|
|
166
|
-
...data,
|
|
167
|
-
source: 'http01',
|
|
168
|
-
isRenewal: true,
|
|
169
|
-
routeReference: routeRef ? {
|
|
170
|
-
routeId: routeRef.route.name,
|
|
171
|
-
routeName: routeRef.route.name
|
|
172
|
-
} : undefined
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_RENEWED, enhancedData);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (error) => {
|
|
179
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_FAILED, error);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Find a route for a given domain
|
|
185
|
-
*/
|
|
186
|
-
private findRouteForDomain(domain: string): ICertRoute | undefined {
|
|
187
|
-
return this.certRoutes.find(certRoute => certRoute.domain === domain);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Set up forwarding configurations for the Port80Handler
|
|
192
|
-
*/
|
|
193
|
-
private setupForwardingConfigs(): void {
|
|
194
|
-
for (const config of this.routeForwards) {
|
|
195
|
-
const domainOptions: IDomainOptions = {
|
|
196
|
-
domainName: config.domain,
|
|
197
|
-
sslRedirect: config.sslRedirect || false,
|
|
198
|
-
acmeMaintenance: false,
|
|
199
|
-
forward: config.target ? {
|
|
200
|
-
ip: config.target.host,
|
|
201
|
-
port: config.target.port
|
|
202
|
-
} : undefined
|
|
203
|
-
};
|
|
204
|
-
this.port80Handler.addDomain(domainOptions);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Provision certificates for all routes that need them
|
|
210
|
-
*/
|
|
211
|
-
private async provisionAllCertificates(): Promise<void> {
|
|
212
|
-
for (const certRoute of this.certRoutes) {
|
|
213
|
-
await this.provisionCertificateForRoute(certRoute);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Provision a certificate for a route
|
|
219
|
-
*/
|
|
220
|
-
private async provisionCertificateForRoute(certRoute: ICertRoute): Promise<void> {
|
|
221
|
-
const { domain, route } = certRoute;
|
|
222
|
-
const isWildcard = domain.includes('*');
|
|
223
|
-
let provision: TCertProvisionObject = 'http01';
|
|
224
|
-
|
|
225
|
-
// Try to get a certificate from the provision function
|
|
226
|
-
if (this.certProvisionFunction) {
|
|
227
|
-
try {
|
|
228
|
-
provision = await this.certProvisionFunction(domain);
|
|
229
|
-
} catch (err) {
|
|
230
|
-
console.error(`certProvider error for ${domain} on route ${route.name || 'unnamed'}:`, err);
|
|
231
|
-
}
|
|
232
|
-
} else if (isWildcard) {
|
|
233
|
-
// No certProvider: cannot handle wildcard without DNS-01 support
|
|
234
|
-
console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Store the route reference with the provision type
|
|
239
|
-
this.provisionMap.set(domain, {
|
|
240
|
-
type: provision === 'http01' || provision === 'dns01' ? provision : 'static',
|
|
241
|
-
routeRef: certRoute
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Handle different provisioning methods
|
|
245
|
-
if (provision === 'http01') {
|
|
246
|
-
if (isWildcard) {
|
|
247
|
-
console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
this.port80Handler.addDomain({
|
|
252
|
-
domainName: domain,
|
|
253
|
-
sslRedirect: true,
|
|
254
|
-
acmeMaintenance: true,
|
|
255
|
-
routeReference: {
|
|
256
|
-
routeId: route.name || domain,
|
|
257
|
-
routeName: route.name
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
} else if (provision === 'dns01') {
|
|
261
|
-
// DNS-01 challenges would be handled by the certProvisionFunction
|
|
262
|
-
// DNS-01 handling would go here if implemented
|
|
263
|
-
console.log(`DNS-01 challenge type set for ${domain}`);
|
|
264
|
-
} else {
|
|
265
|
-
// Static certificate (e.g., DNS-01 provisioned or user-provided)
|
|
266
|
-
const certObj = provision as plugins.tsclass.network.ICert;
|
|
267
|
-
const certData: ICertificateData = {
|
|
268
|
-
domain: certObj.domainName,
|
|
269
|
-
certificate: certObj.publicKey,
|
|
270
|
-
privateKey: certObj.privateKey,
|
|
271
|
-
expiryDate: new Date(certObj.validUntil),
|
|
272
|
-
source: 'static',
|
|
273
|
-
isRenewal: false,
|
|
274
|
-
routeReference: {
|
|
275
|
-
routeId: route.name || domain,
|
|
276
|
-
routeName: route.name
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
this.networkProxyBridge.applyExternalCertificate(certData);
|
|
281
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, certData);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Schedule certificate renewals using a task manager
|
|
287
|
-
*/
|
|
288
|
-
private scheduleRenewals(): void {
|
|
289
|
-
this.renewManager = new plugins.taskbuffer.TaskManager();
|
|
290
|
-
|
|
291
|
-
const renewTask = new plugins.taskbuffer.Task({
|
|
292
|
-
name: 'CertificateRenewals',
|
|
293
|
-
taskFunction: async () => await this.performRenewals()
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const hours = this.renewCheckIntervalHours;
|
|
297
|
-
const cronExpr = `0 0 */${hours} * * *`;
|
|
298
|
-
|
|
299
|
-
this.renewManager.addAndScheduleTask(renewTask, cronExpr);
|
|
300
|
-
this.renewManager.start();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Perform renewals for all domains that need it
|
|
305
|
-
*/
|
|
306
|
-
private async performRenewals(): Promise<void> {
|
|
307
|
-
for (const [domain, info] of this.provisionMap.entries()) {
|
|
308
|
-
// Skip wildcard domains for HTTP-01 challenges
|
|
309
|
-
if (domain.includes('*') && info.type === 'http01') continue;
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
await this.renewCertificateForDomain(domain, info.type, info.routeRef);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
console.error(`Renewal error for ${domain}:`, err);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Renew a certificate for a specific domain
|
|
321
|
-
* @param domain Domain to renew
|
|
322
|
-
* @param provisionType Type of provisioning for this domain
|
|
323
|
-
* @param certRoute The route reference for this domain
|
|
324
|
-
*/
|
|
325
|
-
private async renewCertificateForDomain(
|
|
326
|
-
domain: string,
|
|
327
|
-
provisionType: 'http01' | 'dns01' | 'static',
|
|
328
|
-
certRoute?: ICertRoute
|
|
329
|
-
): Promise<void> {
|
|
330
|
-
if (provisionType === 'http01') {
|
|
331
|
-
await this.port80Handler.renewCertificate(domain);
|
|
332
|
-
} else if ((provisionType === 'static' || provisionType === 'dns01') && this.certProvisionFunction) {
|
|
333
|
-
const provision = await this.certProvisionFunction(domain);
|
|
334
|
-
|
|
335
|
-
if (provision !== 'http01' && provision !== 'dns01') {
|
|
336
|
-
const certObj = provision as plugins.tsclass.network.ICert;
|
|
337
|
-
const routeRef = certRoute?.route;
|
|
338
|
-
|
|
339
|
-
const certData: ICertificateData = {
|
|
340
|
-
domain: certObj.domainName,
|
|
341
|
-
certificate: certObj.publicKey,
|
|
342
|
-
privateKey: certObj.privateKey,
|
|
343
|
-
expiryDate: new Date(certObj.validUntil),
|
|
344
|
-
source: 'static',
|
|
345
|
-
isRenewal: true,
|
|
346
|
-
routeReference: routeRef ? {
|
|
347
|
-
routeId: routeRef.name || domain,
|
|
348
|
-
routeName: routeRef.name
|
|
349
|
-
} : undefined
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
this.networkProxyBridge.applyExternalCertificate(certData);
|
|
353
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_RENEWED, certData);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Stop all scheduled renewal tasks.
|
|
360
|
-
*/
|
|
361
|
-
public async stop(): Promise<void> {
|
|
362
|
-
if (this.renewManager) {
|
|
363
|
-
this.renewManager.stop();
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Request a certificate on-demand for the given domain.
|
|
369
|
-
* This will look for a matching route configuration and provision accordingly.
|
|
370
|
-
*
|
|
371
|
-
* @param domain Domain name to provision
|
|
372
|
-
*/
|
|
373
|
-
public async requestCertificate(domain: string): Promise<void> {
|
|
374
|
-
const isWildcard = domain.includes('*');
|
|
375
|
-
// Find matching route
|
|
376
|
-
const certRoute = this.findRouteForDomain(domain);
|
|
377
|
-
|
|
378
|
-
// Determine provisioning method
|
|
379
|
-
let provision: TCertProvisionObject = 'http01';
|
|
380
|
-
|
|
381
|
-
if (this.certProvisionFunction) {
|
|
382
|
-
provision = await this.certProvisionFunction(domain);
|
|
383
|
-
} else if (isWildcard) {
|
|
384
|
-
// Cannot perform HTTP-01 on wildcard without certProvider
|
|
385
|
-
throw new Error(`Cannot request certificate for wildcard domain without certProvisionFunction: ${domain}`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (provision === 'http01') {
|
|
389
|
-
if (isWildcard) {
|
|
390
|
-
throw new Error(`Cannot request HTTP-01 certificate for wildcard domain: ${domain}`);
|
|
391
|
-
}
|
|
392
|
-
await this.port80Handler.renewCertificate(domain);
|
|
393
|
-
} else if (provision === 'dns01') {
|
|
394
|
-
// DNS-01 challenges would be handled by external mechanisms
|
|
395
|
-
console.log(`DNS-01 challenge requested for ${domain}`);
|
|
396
|
-
} else {
|
|
397
|
-
// Static certificate (e.g., DNS-01 provisioned) supports wildcards
|
|
398
|
-
const certObj = provision as plugins.tsclass.network.ICert;
|
|
399
|
-
const certData: ICertificateData = {
|
|
400
|
-
domain: certObj.domainName,
|
|
401
|
-
certificate: certObj.publicKey,
|
|
402
|
-
privateKey: certObj.privateKey,
|
|
403
|
-
expiryDate: new Date(certObj.validUntil),
|
|
404
|
-
source: 'static',
|
|
405
|
-
isRenewal: false,
|
|
406
|
-
routeReference: certRoute ? {
|
|
407
|
-
routeId: certRoute.route.name || domain,
|
|
408
|
-
routeName: certRoute.route.name
|
|
409
|
-
} : undefined
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
this.networkProxyBridge.applyExternalCertificate(certData);
|
|
413
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, certData);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Add a new domain for certificate provisioning
|
|
419
|
-
*
|
|
420
|
-
* @param domain Domain to add
|
|
421
|
-
* @param options Domain configuration options
|
|
422
|
-
*/
|
|
423
|
-
public async addDomain(domain: string, options?: {
|
|
424
|
-
sslRedirect?: boolean;
|
|
425
|
-
acmeMaintenance?: boolean;
|
|
426
|
-
routeId?: string;
|
|
427
|
-
routeName?: string;
|
|
428
|
-
}): Promise<void> {
|
|
429
|
-
const domainOptions: IDomainOptions = {
|
|
430
|
-
domainName: domain,
|
|
431
|
-
sslRedirect: options?.sslRedirect ?? true,
|
|
432
|
-
acmeMaintenance: options?.acmeMaintenance ?? true,
|
|
433
|
-
routeReference: {
|
|
434
|
-
routeId: options?.routeId,
|
|
435
|
-
routeName: options?.routeName
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
this.port80Handler.addDomain(domainOptions);
|
|
440
|
-
|
|
441
|
-
// Find matching route or create a generic one
|
|
442
|
-
const existingRoute = this.findRouteForDomain(domain);
|
|
443
|
-
if (existingRoute) {
|
|
444
|
-
await this.provisionCertificateForRoute(existingRoute);
|
|
445
|
-
} else {
|
|
446
|
-
// We don't have a route, just provision the domain
|
|
447
|
-
const isWildcard = domain.includes('*');
|
|
448
|
-
let provision: TCertProvisionObject = 'http01';
|
|
449
|
-
|
|
450
|
-
if (this.certProvisionFunction) {
|
|
451
|
-
provision = await this.certProvisionFunction(domain);
|
|
452
|
-
} else if (isWildcard) {
|
|
453
|
-
throw new Error(`Cannot request certificate for wildcard domain without certProvisionFunction: ${domain}`);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
this.provisionMap.set(domain, {
|
|
457
|
-
type: provision === 'http01' || provision === 'dns01' ? provision : 'static'
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
if (provision !== 'http01' && provision !== 'dns01') {
|
|
461
|
-
const certObj = provision as plugins.tsclass.network.ICert;
|
|
462
|
-
const certData: ICertificateData = {
|
|
463
|
-
domain: certObj.domainName,
|
|
464
|
-
certificate: certObj.publicKey,
|
|
465
|
-
privateKey: certObj.privateKey,
|
|
466
|
-
expiryDate: new Date(certObj.validUntil),
|
|
467
|
-
source: 'static',
|
|
468
|
-
isRenewal: false,
|
|
469
|
-
routeReference: {
|
|
470
|
-
routeId: options?.routeId,
|
|
471
|
-
routeName: options?.routeName
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
this.networkProxyBridge.applyExternalCertificate(certData);
|
|
476
|
-
this.emit(CertProvisionerEvents.CERTIFICATE_ISSUED, certData);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Update routes with new configurations
|
|
483
|
-
* This replaces all existing routes with new ones and re-provisions certificates as needed
|
|
484
|
-
*
|
|
485
|
-
* @param newRoutes New route configurations to use
|
|
486
|
-
*/
|
|
487
|
-
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
|
488
|
-
// Store the new route configs
|
|
489
|
-
this.routeConfigs = newRoutes;
|
|
490
|
-
|
|
491
|
-
// Extract new certificate routes
|
|
492
|
-
const newCertRoutes = this.extractCertificateRoutesFromRoutes(newRoutes);
|
|
493
|
-
|
|
494
|
-
// Find domains that no longer need certificates
|
|
495
|
-
const oldDomains = new Set(this.certRoutes.map(r => r.domain));
|
|
496
|
-
const newDomains = new Set(newCertRoutes.map(r => r.domain));
|
|
497
|
-
|
|
498
|
-
// Domains to remove
|
|
499
|
-
const domainsToRemove = [...oldDomains].filter(d => !newDomains.has(d));
|
|
500
|
-
|
|
501
|
-
// Remove obsolete domains from provision map
|
|
502
|
-
for (const domain of domainsToRemove) {
|
|
503
|
-
this.provisionMap.delete(domain);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Update the cert routes
|
|
507
|
-
this.certRoutes = newCertRoutes;
|
|
508
|
-
|
|
509
|
-
// Provision certificates for new routes
|
|
510
|
-
for (const certRoute of newCertRoutes) {
|
|
511
|
-
if (!oldDomains.has(certRoute.domain)) {
|
|
512
|
-
await this.provisionCertificateForRoute(certRoute);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Type alias for backward compatibility
|
|
519
|
-
export type TSmartProxyCertProvisionObject = TCertProvisionObject;
|