@push.rocks/smartproxy 18.1.0 → 19.0.0
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/certificate/certificate-manager.d.ts +150 -0
- package/dist_ts/certificate/certificate-manager.js +505 -0
- package/dist_ts/certificate/events/simplified-events.d.ts +56 -0
- package/dist_ts/certificate/events/simplified-events.js +13 -0
- package/dist_ts/certificate/models/certificate-errors.d.ts +69 -0
- package/dist_ts/certificate/models/certificate-errors.js +141 -0
- package/dist_ts/certificate/models/certificate-strategy.d.ts +60 -0
- package/dist_ts/certificate/models/certificate-strategy.js +73 -0
- package/dist_ts/certificate/simplified-certificate-manager.d.ts +150 -0
- package/dist_ts/certificate/simplified-certificate-manager.js +501 -0
- 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/index.d.ts +1 -9
- package/dist_ts/http/index.js +5 -11
- 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/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +4 -2
- 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 +12 -19
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.d.ts +48 -0
- package/dist_ts/proxies/network-proxy/simplified-certificate-bridge.js +76 -0
- package/dist_ts/proxies/network-proxy/websocket-handler.js +21 -7
- package/dist_ts/proxies/smart-proxy/cert-store.d.ts +10 -0
- package/dist_ts/proxies/smart-proxy/cert-store.js +70 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +116 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +401 -0
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.d.ts +168 -0
- package/dist_ts/proxies/smart-proxy/legacy-smart-proxy.js +642 -0
- 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 +13 -1
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +26 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.d.ts +65 -0
- package/dist_ts/proxies/smart-proxy/models/simplified-smartproxy-config.js +31 -0
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.d.ts +102 -0
- package/dist_ts/proxies/smart-proxy/models/smartproxy-options.js +73 -0
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +10 -44
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +66 -202
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +62 -2
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.d.ts +41 -0
- package/dist_ts/proxies/smart-proxy/simplified-smart-proxy.js +132 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +18 -13
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +80 -198
- package/package.json +5 -3
- package/readme.md +13 -5
- package/readme.plan.md +1422 -617
- 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/index.ts +5 -12
- package/ts/http/models/http-types.ts +8 -4
- package/ts/index.ts +11 -14
- package/ts/plugins.ts +4 -1
- 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 +13 -19
- package/ts/proxies/network-proxy/websocket-handler.ts +18 -6
- package/ts/proxies/smart-proxy/cert-store.ts +86 -0
- package/ts/proxies/smart-proxy/certificate-manager.ts +506 -0
- package/ts/proxies/smart-proxy/models/index.ts +2 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +14 -1
- package/ts/proxies/smart-proxy/models/route-types.ts +34 -4
- package/ts/proxies/smart-proxy/network-proxy-bridge.ts +86 -239
- package/ts/proxies/smart-proxy/route-connection-handler.ts +74 -1
- package/ts/proxies/smart-proxy/smart-proxy.ts +106 -224
- 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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { NetworkProxy } from '../network-proxy/index.js';
|
|
2
|
+
import type { IRouteConfig } from './models/route-types.js';
|
|
3
|
+
export interface ICertStatus {
|
|
4
|
+
domain: string;
|
|
5
|
+
status: 'valid' | 'pending' | 'expired' | 'error';
|
|
6
|
+
expiryDate?: Date;
|
|
7
|
+
issueDate?: Date;
|
|
8
|
+
source: 'static' | 'acme';
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ICertificateData {
|
|
12
|
+
cert: string;
|
|
13
|
+
key: string;
|
|
14
|
+
ca?: string;
|
|
15
|
+
expiryDate: Date;
|
|
16
|
+
issueDate: Date;
|
|
17
|
+
}
|
|
18
|
+
export declare class SmartCertManager {
|
|
19
|
+
private routes;
|
|
20
|
+
private certDir;
|
|
21
|
+
private acmeOptions?;
|
|
22
|
+
private certStore;
|
|
23
|
+
private smartAcme;
|
|
24
|
+
private networkProxy;
|
|
25
|
+
private renewalTimer;
|
|
26
|
+
private pendingChallenges;
|
|
27
|
+
private challengeRoute;
|
|
28
|
+
private certStatus;
|
|
29
|
+
private updateRoutesCallback?;
|
|
30
|
+
constructor(routes: IRouteConfig[], certDir?: string, acmeOptions?: {
|
|
31
|
+
email?: string;
|
|
32
|
+
useProduction?: boolean;
|
|
33
|
+
port?: number;
|
|
34
|
+
});
|
|
35
|
+
setNetworkProxy(networkProxy: NetworkProxy): void;
|
|
36
|
+
/**
|
|
37
|
+
* Set callback for updating routes (used for challenge routes)
|
|
38
|
+
*/
|
|
39
|
+
setUpdateRoutesCallback(callback: (routes: IRouteConfig[]) => Promise<void>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Initialize certificate manager and provision certificates for all routes
|
|
42
|
+
*/
|
|
43
|
+
initialize(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Provision certificates for all routes that need them
|
|
46
|
+
*/
|
|
47
|
+
private provisionAllCertificates;
|
|
48
|
+
/**
|
|
49
|
+
* Provision certificate for a single route
|
|
50
|
+
*/
|
|
51
|
+
provisionCertificate(route: IRouteConfig): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Provision ACME certificate
|
|
54
|
+
*/
|
|
55
|
+
private provisionAcmeCertificate;
|
|
56
|
+
/**
|
|
57
|
+
* Provision static certificate
|
|
58
|
+
*/
|
|
59
|
+
private provisionStaticCertificate;
|
|
60
|
+
/**
|
|
61
|
+
* Apply certificate to NetworkProxy
|
|
62
|
+
*/
|
|
63
|
+
private applyCertificate;
|
|
64
|
+
/**
|
|
65
|
+
* Extract domains from route configuration
|
|
66
|
+
*/
|
|
67
|
+
private extractDomainsFromRoute;
|
|
68
|
+
/**
|
|
69
|
+
* Check if certificate is valid
|
|
70
|
+
*/
|
|
71
|
+
private isCertificateValid;
|
|
72
|
+
/**
|
|
73
|
+
* Add challenge route to SmartProxy
|
|
74
|
+
*/
|
|
75
|
+
private addChallengeRoute;
|
|
76
|
+
/**
|
|
77
|
+
* Remove challenge route from SmartProxy
|
|
78
|
+
*/
|
|
79
|
+
private removeChallengeRoute;
|
|
80
|
+
/**
|
|
81
|
+
* Start renewal timer
|
|
82
|
+
*/
|
|
83
|
+
private startRenewalTimer;
|
|
84
|
+
/**
|
|
85
|
+
* Check and renew certificates that are expiring
|
|
86
|
+
*/
|
|
87
|
+
private checkAndRenewCertificates;
|
|
88
|
+
/**
|
|
89
|
+
* Update certificate status
|
|
90
|
+
*/
|
|
91
|
+
private updateCertStatus;
|
|
92
|
+
/**
|
|
93
|
+
* Get certificate status for a route
|
|
94
|
+
*/
|
|
95
|
+
getCertificateStatus(routeName: string): ICertStatus | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Force renewal of a certificate
|
|
98
|
+
*/
|
|
99
|
+
renewCertificate(routeName: string): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Setup challenge handler integration with SmartProxy routing
|
|
102
|
+
*/
|
|
103
|
+
private setupChallengeHandler;
|
|
104
|
+
/**
|
|
105
|
+
* Stop certificate manager
|
|
106
|
+
*/
|
|
107
|
+
stop(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Get ACME options (for recreating after route updates)
|
|
110
|
+
*/
|
|
111
|
+
getAcmeOptions(): {
|
|
112
|
+
email?: string;
|
|
113
|
+
useProduction?: boolean;
|
|
114
|
+
port?: number;
|
|
115
|
+
} | undefined;
|
|
116
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { NetworkProxy } from '../network-proxy/index.js';
|
|
3
|
+
import { CertStore } from './cert-store.js';
|
|
4
|
+
export class SmartCertManager {
|
|
5
|
+
constructor(routes, certDir = './certs', acmeOptions) {
|
|
6
|
+
this.routes = routes;
|
|
7
|
+
this.certDir = certDir;
|
|
8
|
+
this.acmeOptions = acmeOptions;
|
|
9
|
+
this.smartAcme = null;
|
|
10
|
+
this.networkProxy = null;
|
|
11
|
+
this.renewalTimer = null;
|
|
12
|
+
this.pendingChallenges = new Map();
|
|
13
|
+
this.challengeRoute = null;
|
|
14
|
+
// Track certificate status by route name
|
|
15
|
+
this.certStatus = new Map();
|
|
16
|
+
this.certStore = new CertStore(certDir);
|
|
17
|
+
}
|
|
18
|
+
setNetworkProxy(networkProxy) {
|
|
19
|
+
this.networkProxy = networkProxy;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Set callback for updating routes (used for challenge routes)
|
|
23
|
+
*/
|
|
24
|
+
setUpdateRoutesCallback(callback) {
|
|
25
|
+
this.updateRoutesCallback = callback;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Initialize certificate manager and provision certificates for all routes
|
|
29
|
+
*/
|
|
30
|
+
async initialize() {
|
|
31
|
+
// Create certificate directory if it doesn't exist
|
|
32
|
+
await this.certStore.initialize();
|
|
33
|
+
// Initialize SmartAcme if we have any ACME routes
|
|
34
|
+
const hasAcmeRoutes = this.routes.some(r => r.action.tls?.certificate === 'auto');
|
|
35
|
+
if (hasAcmeRoutes && this.acmeOptions?.email) {
|
|
36
|
+
// Create HTTP-01 challenge handler
|
|
37
|
+
const http01Handler = new plugins.smartacme.handlers.Http01MemoryHandler();
|
|
38
|
+
// Set up challenge handler integration with our routing
|
|
39
|
+
this.setupChallengeHandler(http01Handler);
|
|
40
|
+
// Create SmartAcme instance with built-in MemoryCertManager and HTTP-01 handler
|
|
41
|
+
this.smartAcme = new plugins.smartacme.SmartAcme({
|
|
42
|
+
accountEmail: this.acmeOptions.email,
|
|
43
|
+
environment: this.acmeOptions.useProduction ? 'production' : 'integration',
|
|
44
|
+
certManager: new plugins.smartacme.certmanagers.MemoryCertManager(),
|
|
45
|
+
challengeHandlers: [http01Handler]
|
|
46
|
+
});
|
|
47
|
+
await this.smartAcme.start();
|
|
48
|
+
}
|
|
49
|
+
// Provision certificates for all routes
|
|
50
|
+
await this.provisionAllCertificates();
|
|
51
|
+
// Start renewal timer
|
|
52
|
+
this.startRenewalTimer();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Provision certificates for all routes that need them
|
|
56
|
+
*/
|
|
57
|
+
async provisionAllCertificates() {
|
|
58
|
+
const certRoutes = this.routes.filter(r => r.action.tls?.mode === 'terminate' ||
|
|
59
|
+
r.action.tls?.mode === 'terminate-and-reencrypt');
|
|
60
|
+
for (const route of certRoutes) {
|
|
61
|
+
try {
|
|
62
|
+
await this.provisionCertificate(route);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Provision certificate for a single route
|
|
71
|
+
*/
|
|
72
|
+
async provisionCertificate(route) {
|
|
73
|
+
const tls = route.action.tls;
|
|
74
|
+
if (!tls || (tls.mode !== 'terminate' && tls.mode !== 'terminate-and-reencrypt')) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const domains = this.extractDomainsFromRoute(route);
|
|
78
|
+
if (domains.length === 0) {
|
|
79
|
+
console.warn(`Route ${route.name} has TLS termination but no domains`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const primaryDomain = domains[0];
|
|
83
|
+
if (tls.certificate === 'auto') {
|
|
84
|
+
// ACME certificate
|
|
85
|
+
await this.provisionAcmeCertificate(route, domains);
|
|
86
|
+
}
|
|
87
|
+
else if (typeof tls.certificate === 'object') {
|
|
88
|
+
// Static certificate
|
|
89
|
+
await this.provisionStaticCertificate(route, primaryDomain, tls.certificate);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Provision ACME certificate
|
|
94
|
+
*/
|
|
95
|
+
async provisionAcmeCertificate(route, domains) {
|
|
96
|
+
if (!this.smartAcme) {
|
|
97
|
+
throw new Error('SmartAcme not initialized');
|
|
98
|
+
}
|
|
99
|
+
const primaryDomain = domains[0];
|
|
100
|
+
const routeName = route.name || primaryDomain;
|
|
101
|
+
// Check if we already have a valid certificate
|
|
102
|
+
const existingCert = await this.certStore.getCertificate(routeName);
|
|
103
|
+
if (existingCert && this.isCertificateValid(existingCert)) {
|
|
104
|
+
console.log(`Using existing valid certificate for ${primaryDomain}`);
|
|
105
|
+
await this.applyCertificate(primaryDomain, existingCert);
|
|
106
|
+
this.updateCertStatus(routeName, 'valid', 'acme', existingCert);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
console.log(`Requesting ACME certificate for ${domains.join(', ')}`);
|
|
110
|
+
this.updateCertStatus(routeName, 'pending', 'acme');
|
|
111
|
+
try {
|
|
112
|
+
// Add challenge route before requesting certificate
|
|
113
|
+
await this.addChallengeRoute();
|
|
114
|
+
try {
|
|
115
|
+
// Use smartacme to get certificate
|
|
116
|
+
const cert = await this.smartAcme.getCertificateForDomain(primaryDomain);
|
|
117
|
+
// SmartAcme's Cert object has these properties:
|
|
118
|
+
// - publicKey: The certificate PEM string
|
|
119
|
+
// - privateKey: The private key PEM string
|
|
120
|
+
// - csr: Certificate signing request
|
|
121
|
+
// - validUntil: Timestamp in milliseconds
|
|
122
|
+
// - domainName: The domain name
|
|
123
|
+
const certData = {
|
|
124
|
+
cert: cert.publicKey,
|
|
125
|
+
key: cert.privateKey,
|
|
126
|
+
ca: cert.publicKey, // Use same as cert for now
|
|
127
|
+
expiryDate: new Date(cert.validUntil),
|
|
128
|
+
issueDate: new Date(cert.created)
|
|
129
|
+
};
|
|
130
|
+
await this.certStore.saveCertificate(routeName, certData);
|
|
131
|
+
await this.applyCertificate(primaryDomain, certData);
|
|
132
|
+
this.updateCertStatus(routeName, 'valid', 'acme', certData);
|
|
133
|
+
console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
|
|
137
|
+
this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
// Always remove challenge route after provisioning
|
|
142
|
+
await this.removeChallengeRoute();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// Handle outer try-catch from adding challenge route
|
|
147
|
+
console.error(`Failed to setup ACME challenge for ${primaryDomain}: ${error}`);
|
|
148
|
+
this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Provision static certificate
|
|
154
|
+
*/
|
|
155
|
+
async provisionStaticCertificate(route, domain, certConfig) {
|
|
156
|
+
const routeName = route.name || domain;
|
|
157
|
+
try {
|
|
158
|
+
let key = certConfig.key;
|
|
159
|
+
let cert = certConfig.cert;
|
|
160
|
+
// Load from files if paths are provided
|
|
161
|
+
if (certConfig.keyFile) {
|
|
162
|
+
const keyFile = await plugins.smartfile.SmartFile.fromFilePath(certConfig.keyFile);
|
|
163
|
+
key = keyFile.contents.toString();
|
|
164
|
+
}
|
|
165
|
+
if (certConfig.certFile) {
|
|
166
|
+
const certFile = await plugins.smartfile.SmartFile.fromFilePath(certConfig.certFile);
|
|
167
|
+
cert = certFile.contents.toString();
|
|
168
|
+
}
|
|
169
|
+
// Parse certificate to get dates
|
|
170
|
+
// Parse certificate to get dates - for now just use defaults
|
|
171
|
+
// TODO: Implement actual certificate parsing if needed
|
|
172
|
+
const certInfo = { validTo: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), validFrom: new Date() };
|
|
173
|
+
const certData = {
|
|
174
|
+
cert,
|
|
175
|
+
key,
|
|
176
|
+
expiryDate: certInfo.validTo,
|
|
177
|
+
issueDate: certInfo.validFrom
|
|
178
|
+
};
|
|
179
|
+
// Save to store for consistency
|
|
180
|
+
await this.certStore.saveCertificate(routeName, certData);
|
|
181
|
+
await this.applyCertificate(domain, certData);
|
|
182
|
+
this.updateCertStatus(routeName, 'valid', 'static', certData);
|
|
183
|
+
console.log(`Successfully loaded static certificate for ${domain}`);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(`Failed to provision static certificate for ${domain}: ${error}`);
|
|
187
|
+
this.updateCertStatus(routeName, 'error', 'static', undefined, error.message);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Apply certificate to NetworkProxy
|
|
193
|
+
*/
|
|
194
|
+
async applyCertificate(domain, certData) {
|
|
195
|
+
if (!this.networkProxy) {
|
|
196
|
+
console.warn('NetworkProxy not set, cannot apply certificate');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Apply certificate to NetworkProxy
|
|
200
|
+
this.networkProxy.updateCertificate(domain, certData.cert, certData.key);
|
|
201
|
+
// Also apply for wildcard if it's a subdomain
|
|
202
|
+
if (domain.includes('.') && !domain.startsWith('*.')) {
|
|
203
|
+
const parts = domain.split('.');
|
|
204
|
+
if (parts.length >= 2) {
|
|
205
|
+
const wildcardDomain = `*.${parts.slice(-2).join('.')}`;
|
|
206
|
+
this.networkProxy.updateCertificate(wildcardDomain, certData.cert, certData.key);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Extract domains from route configuration
|
|
212
|
+
*/
|
|
213
|
+
extractDomainsFromRoute(route) {
|
|
214
|
+
if (!route.match.domains) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
const domains = Array.isArray(route.match.domains)
|
|
218
|
+
? route.match.domains
|
|
219
|
+
: [route.match.domains];
|
|
220
|
+
// Filter out wildcards and patterns
|
|
221
|
+
return domains.filter(d => !d.includes('*') &&
|
|
222
|
+
!d.includes('{') &&
|
|
223
|
+
d.includes('.'));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Check if certificate is valid
|
|
227
|
+
*/
|
|
228
|
+
isCertificateValid(cert) {
|
|
229
|
+
const now = new Date();
|
|
230
|
+
const expiryThreshold = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days
|
|
231
|
+
return cert.expiryDate > expiryThreshold;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Add challenge route to SmartProxy
|
|
235
|
+
*/
|
|
236
|
+
async addChallengeRoute() {
|
|
237
|
+
if (!this.updateRoutesCallback) {
|
|
238
|
+
throw new Error('No route update callback set');
|
|
239
|
+
}
|
|
240
|
+
if (!this.challengeRoute) {
|
|
241
|
+
throw new Error('Challenge route not initialized');
|
|
242
|
+
}
|
|
243
|
+
const challengeRoute = this.challengeRoute;
|
|
244
|
+
const updatedRoutes = [...this.routes, challengeRoute];
|
|
245
|
+
await this.updateRoutesCallback(updatedRoutes);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Remove challenge route from SmartProxy
|
|
249
|
+
*/
|
|
250
|
+
async removeChallengeRoute() {
|
|
251
|
+
if (!this.updateRoutesCallback) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
|
|
255
|
+
await this.updateRoutesCallback(filteredRoutes);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Start renewal timer
|
|
259
|
+
*/
|
|
260
|
+
startRenewalTimer() {
|
|
261
|
+
// Check for renewals every 12 hours
|
|
262
|
+
this.renewalTimer = setInterval(() => {
|
|
263
|
+
this.checkAndRenewCertificates();
|
|
264
|
+
}, 12 * 60 * 60 * 1000);
|
|
265
|
+
// Also do an immediate check
|
|
266
|
+
this.checkAndRenewCertificates();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check and renew certificates that are expiring
|
|
270
|
+
*/
|
|
271
|
+
async checkAndRenewCertificates() {
|
|
272
|
+
for (const route of this.routes) {
|
|
273
|
+
if (route.action.tls?.certificate === 'auto') {
|
|
274
|
+
const routeName = route.name || this.extractDomainsFromRoute(route)[0];
|
|
275
|
+
const cert = await this.certStore.getCertificate(routeName);
|
|
276
|
+
if (cert && !this.isCertificateValid(cert)) {
|
|
277
|
+
console.log(`Certificate for ${routeName} needs renewal`);
|
|
278
|
+
try {
|
|
279
|
+
await this.provisionCertificate(route);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error(`Failed to renew certificate for ${routeName}: ${error}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Update certificate status
|
|
290
|
+
*/
|
|
291
|
+
updateCertStatus(routeName, status, source, certData, error) {
|
|
292
|
+
this.certStatus.set(routeName, {
|
|
293
|
+
domain: routeName,
|
|
294
|
+
status,
|
|
295
|
+
source,
|
|
296
|
+
expiryDate: certData?.expiryDate,
|
|
297
|
+
issueDate: certData?.issueDate,
|
|
298
|
+
error
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get certificate status for a route
|
|
303
|
+
*/
|
|
304
|
+
getCertificateStatus(routeName) {
|
|
305
|
+
return this.certStatus.get(routeName);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Force renewal of a certificate
|
|
309
|
+
*/
|
|
310
|
+
async renewCertificate(routeName) {
|
|
311
|
+
const route = this.routes.find(r => r.name === routeName);
|
|
312
|
+
if (!route) {
|
|
313
|
+
throw new Error(`Route ${routeName} not found`);
|
|
314
|
+
}
|
|
315
|
+
// Remove existing certificate to force renewal
|
|
316
|
+
await this.certStore.deleteCertificate(routeName);
|
|
317
|
+
await this.provisionCertificate(route);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Setup challenge handler integration with SmartProxy routing
|
|
321
|
+
*/
|
|
322
|
+
setupChallengeHandler(http01Handler) {
|
|
323
|
+
// Create a challenge route that delegates to SmartAcme's HTTP-01 handler
|
|
324
|
+
const challengeRoute = {
|
|
325
|
+
name: 'acme-challenge',
|
|
326
|
+
priority: 1000, // High priority
|
|
327
|
+
match: {
|
|
328
|
+
ports: 80,
|
|
329
|
+
path: '/.well-known/acme-challenge/*'
|
|
330
|
+
},
|
|
331
|
+
action: {
|
|
332
|
+
type: 'static',
|
|
333
|
+
handler: async (context) => {
|
|
334
|
+
// Extract the token from the path
|
|
335
|
+
const token = context.path?.split('/').pop();
|
|
336
|
+
if (!token) {
|
|
337
|
+
return { status: 404, body: 'Not found' };
|
|
338
|
+
}
|
|
339
|
+
// Create mock request/response objects for SmartAcme
|
|
340
|
+
const mockReq = {
|
|
341
|
+
url: context.path,
|
|
342
|
+
method: 'GET',
|
|
343
|
+
headers: context.headers || {}
|
|
344
|
+
};
|
|
345
|
+
let responseData = null;
|
|
346
|
+
const mockRes = {
|
|
347
|
+
statusCode: 200,
|
|
348
|
+
setHeader: (name, value) => { },
|
|
349
|
+
end: (data) => {
|
|
350
|
+
responseData = data;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
// Use SmartAcme's handler
|
|
354
|
+
const handled = await new Promise((resolve) => {
|
|
355
|
+
http01Handler.handleRequest(mockReq, mockRes, () => {
|
|
356
|
+
resolve(false);
|
|
357
|
+
});
|
|
358
|
+
// Give it a moment to process
|
|
359
|
+
setTimeout(() => resolve(true), 100);
|
|
360
|
+
});
|
|
361
|
+
if (handled && responseData) {
|
|
362
|
+
return {
|
|
363
|
+
status: mockRes.statusCode,
|
|
364
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
365
|
+
body: responseData
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
return { status: 404, body: 'Not found' };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
// Store the challenge route to add it when needed
|
|
375
|
+
this.challengeRoute = challengeRoute;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Stop certificate manager
|
|
379
|
+
*/
|
|
380
|
+
async stop() {
|
|
381
|
+
if (this.renewalTimer) {
|
|
382
|
+
clearInterval(this.renewalTimer);
|
|
383
|
+
this.renewalTimer = null;
|
|
384
|
+
}
|
|
385
|
+
if (this.smartAcme) {
|
|
386
|
+
await this.smartAcme.stop();
|
|
387
|
+
}
|
|
388
|
+
// Remove any active challenge routes
|
|
389
|
+
if (this.pendingChallenges.size > 0) {
|
|
390
|
+
this.pendingChallenges.clear();
|
|
391
|
+
await this.removeChallengeRoute();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get ACME options (for recreating after route updates)
|
|
396
|
+
*/
|
|
397
|
+
getAcmeOptions() {
|
|
398
|
+
return this.acmeOptions;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydGlmaWNhdGUtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvY2VydGlmaWNhdGUtbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV6RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFtQjVDLE1BQU0sT0FBTyxnQkFBZ0I7SUFjM0IsWUFDVSxNQUFzQixFQUN0QixVQUFrQixTQUFTLEVBQzNCLFdBSVA7UUFOTyxXQUFNLEdBQU4sTUFBTSxDQUFnQjtRQUN0QixZQUFPLEdBQVAsT0FBTyxDQUFvQjtRQUMzQixnQkFBVyxHQUFYLFdBQVcsQ0FJbEI7UUFuQkssY0FBUyxHQUF1QyxJQUFJLENBQUM7UUFDckQsaUJBQVksR0FBd0IsSUFBSSxDQUFDO1FBQ3pDLGlCQUFZLEdBQTBCLElBQUksQ0FBQztRQUMzQyxzQkFBaUIsR0FBd0IsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUNuRCxtQkFBYyxHQUF3QixJQUFJLENBQUM7UUFFbkQseUNBQXlDO1FBQ2pDLGVBQVUsR0FBNkIsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQWN2RCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFFTSxlQUFlLENBQUMsWUFBMEI7UUFDL0MsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUM7SUFDbkMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksdUJBQXVCLENBQUMsUUFBbUQ7UUFDaEYsSUFBSSxDQUFDLG9CQUFvQixHQUFHLFFBQVEsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsVUFBVTtRQUNyQixtREFBbUQ7UUFDbkQsTUFBTSxJ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
|