@push.rocks/smartproxy 23.1.5 → 24.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/changelog.md +22 -0
- package/dist_rust/rustproxy_linux_amd64 +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/models/common-types.d.ts +0 -1
- package/dist_ts/proxies/smart-proxy/models/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +29 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +8 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +120 -21
- package/dist_ts/proxies/smart-proxy/utils/default-cert-generator.d.ts +8 -0
- package/dist_ts/proxies/smart-proxy/utils/default-cert-generator.js +30 -0
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -1
- package/package.json +1 -1
- package/readme.md +89 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/models/common-types.ts +0 -1
- package/ts/proxies/smart-proxy/models/index.ts +1 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +27 -1
- package/ts/proxies/smart-proxy/smart-proxy.ts +123 -21
- package/ts/proxies/smart-proxy/utils/default-cert-generator.ts +36 -0
- package/ts/proxies/smart-proxy/utils/index.ts +3 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-02-13 - 24.0.0 - BREAKING CHANGE(smart-proxy)
|
|
4
|
+
move certificate persistence to an in-memory store and introduce consumer-managed certStore API; add default self-signed fallback cert and change ACME account handling
|
|
5
|
+
|
|
6
|
+
- Cert persistence removed from Rust side: CertStore is now an in-memory cache (no filesystem reads/writes). Rust no longer persists or loads certs from disk.
|
|
7
|
+
- ACME account credentials are no longer persisted by the library; AcmeClient uses ephemeral accounts only and account persistence APIs were removed.
|
|
8
|
+
- TypeScript API changes: removed certificateStore option and added ISmartProxyCertStore + certStore option for consumer-provided persistence (loadAll, save, optional remove).
|
|
9
|
+
- Default self-signed fallback certificate added (generateDefaultCertificate) and loaded as '*' unless disableDefaultCert is set.
|
|
10
|
+
- SmartProxy now pre-loads certificates from consumer certStore on startup and persists certificates by calling certStore.save() after provisioning.
|
|
11
|
+
- provisionCertificatesViaCallback signature changed to accept preloaded domains (prevents re-provisioning), and ACME fallback behavior adjusted with clearer logging.
|
|
12
|
+
- Rust cert manager methods made infallible for cache-only operations (load_static/store no longer return errors for cache insertions); removed store-backed load_all/remove/base_dir APIs.
|
|
13
|
+
- TCP listener tls_configs concurrency improved: switched to ArcSwap<HashMap<...>> so accept loops see hot-reloads immediately.
|
|
14
|
+
- Removed dependencies related to filesystem cert persistence from the tls crate (serde_json, tempfile) and corresponding Cargo.lock changes and test updates.
|
|
15
|
+
|
|
16
|
+
## 2026-02-13 - 23.1.6 - fix(smart-proxy)
|
|
17
|
+
disable built-in Rust ACME when a certProvisionFunction is provided and improve certificate provisioning flow
|
|
18
|
+
|
|
19
|
+
- Pass an optional ACME override into buildRustConfig so Rust ACME can be disabled per-run
|
|
20
|
+
- Disable Rust ACME when certProvisionFunction is configured to avoid provisioning race conditions
|
|
21
|
+
- Normalize routing glob patterns into concrete domain identifiers for certificate provisioning (expand leading-star globs and warn on unsupported patterns)
|
|
22
|
+
- Deduplicate domains during provisioning to avoid repeated attempts
|
|
23
|
+
- When the callback returns 'http01', explicitly trigger Rust ACME for the route via bridge.provisionCertificate and log success/failure
|
|
24
|
+
|
|
3
25
|
## 2026-02-13 - 23.1.5 - fix(smart-proxy)
|
|
4
26
|
provision certificates for wildcard domains instead of skipping them
|
|
5
27
|
|
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '
|
|
6
|
+
version: '24.0.0',
|
|
7
7
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHFQQUFxUDtDQUNuUSxDQUFBIn0=
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SmartProxy models
|
|
3
3
|
*/
|
|
4
|
-
export type { ISmartProxyOptions, IConnectionRecord, TSmartProxyCertProvisionObject } from './interfaces.js';
|
|
4
|
+
export type { ISmartProxyOptions, ISmartProxyCertStore, IConnectionRecord, TSmartProxyCertProvisionObject } from './interfaces.js';
|
|
5
5
|
export * from './route-types.js';
|
|
6
6
|
export * from './metrics-types.js';
|
|
@@ -9,11 +9,27 @@ export interface IAcmeOptions {
|
|
|
9
9
|
useProduction?: boolean;
|
|
10
10
|
renewThresholdDays?: number;
|
|
11
11
|
autoRenew?: boolean;
|
|
12
|
-
certificateStore?: string;
|
|
13
12
|
skipConfiguredCerts?: boolean;
|
|
14
13
|
renewCheckIntervalHours?: number;
|
|
15
14
|
routeForwards?: any[];
|
|
16
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Consumer-provided certificate storage.
|
|
18
|
+
* SmartProxy never writes certs to disk — the consumer owns all persistence.
|
|
19
|
+
*/
|
|
20
|
+
export interface ISmartProxyCertStore {
|
|
21
|
+
/** Load all stored certs on startup (called once before cert provisioning) */
|
|
22
|
+
loadAll: () => Promise<Array<{
|
|
23
|
+
domain: string;
|
|
24
|
+
publicKey: string;
|
|
25
|
+
privateKey: string;
|
|
26
|
+
ca?: string;
|
|
27
|
+
}>>;
|
|
28
|
+
/** Save a cert after successful provisioning */
|
|
29
|
+
save: (domain: string, publicKey: string, privateKey: string, ca?: string) => Promise<void>;
|
|
30
|
+
/** Remove a cert (optional) */
|
|
31
|
+
remove?: (domain: string) => Promise<void>;
|
|
32
|
+
}
|
|
17
33
|
import type { IRouteConfig } from './route-types.js';
|
|
18
34
|
/**
|
|
19
35
|
* Provision object for static or HTTP-01 certificate
|
|
@@ -105,6 +121,18 @@ export interface ISmartProxyOptions {
|
|
|
105
121
|
* Default: true
|
|
106
122
|
*/
|
|
107
123
|
certProvisionFallbackToAcme?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Disable the default self-signed fallback certificate.
|
|
126
|
+
* When false (default), a self-signed cert is generated at startup and loaded
|
|
127
|
+
* as '*' so TLS handshakes never fail due to missing certs.
|
|
128
|
+
*/
|
|
129
|
+
disableDefaultCert?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Consumer-provided cert storage. SmartProxy never writes certs to disk.
|
|
132
|
+
* On startup, loadAll() is called to pre-load persisted certs.
|
|
133
|
+
* After each successful cert provision, save() is called.
|
|
134
|
+
*/
|
|
135
|
+
certStore?: ISmartProxyCertStore;
|
|
108
136
|
/**
|
|
109
137
|
* Path to the RustProxy binary. If not set, the binary is located
|
|
110
138
|
* automatically via env var, platform package, local build, or PATH.
|
|
@@ -86,5 +86,13 @@ export declare class SmartProxy extends plugins.EventEmitter {
|
|
|
86
86
|
* If it returns 'http01', let Rust handle ACME.
|
|
87
87
|
*/
|
|
88
88
|
private provisionCertificatesViaCallback;
|
|
89
|
+
/**
|
|
90
|
+
* Normalize routing glob patterns into valid domain identifiers for cert provisioning.
|
|
91
|
+
* - `*nevermind.cloud` → `['nevermind.cloud', '*.nevermind.cloud']`
|
|
92
|
+
* - `*.lossless.digital` → `['*.lossless.digital']` (already valid wildcard)
|
|
93
|
+
* - `code.foss.global` → `['code.foss.global']` (plain domain)
|
|
94
|
+
* - `*mid*.example.com` → skipped with warning (unsupported glob)
|
|
95
|
+
*/
|
|
96
|
+
private normalizeDomainsForCertProvisioning;
|
|
89
97
|
private isValidDomain;
|
|
90
98
|
}
|
|
@@ -8,6 +8,7 @@ import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
|
8
8
|
// Route management
|
|
9
9
|
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
10
10
|
import { RouteValidator } from './utils/route-validator.js';
|
|
11
|
+
import { generateDefaultCertificate } from './utils/default-cert-generator.js';
|
|
11
12
|
import { Mutex } from './utils/mutex.js';
|
|
12
13
|
/**
|
|
13
14
|
* SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
|
|
@@ -50,7 +51,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
50
51
|
useProduction: this.settings.acme.useProduction || false,
|
|
51
52
|
renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
|
|
52
53
|
autoRenew: this.settings.acme.autoRenew !== false,
|
|
53
|
-
certificateStore: this.settings.acme.certificateStore || './certs',
|
|
54
54
|
skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
|
|
55
55
|
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours || 24,
|
|
56
56
|
routeForwards: this.settings.acme.routeForwards || [],
|
|
@@ -111,16 +111,49 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
111
111
|
}
|
|
112
112
|
// Preprocess routes (strip JS functions, convert socket-handler routes)
|
|
113
113
|
const rustRoutes = this.preprocessor.preprocessForRust(this.settings.routes);
|
|
114
|
+
// When certProvisionFunction handles cert provisioning,
|
|
115
|
+
// disable Rust's built-in ACME to prevent race condition.
|
|
116
|
+
let acmeForRust = this.settings.acme;
|
|
117
|
+
if (this.settings.certProvisionFunction && acmeForRust?.enabled) {
|
|
118
|
+
acmeForRust = { ...acmeForRust, enabled: false };
|
|
119
|
+
logger.log('info', 'Rust ACME disabled — certProvisionFunction will handle certificate provisioning', { component: 'smart-proxy' });
|
|
120
|
+
}
|
|
114
121
|
// Build Rust config
|
|
115
|
-
const config = this.buildRustConfig(rustRoutes);
|
|
122
|
+
const config = this.buildRustConfig(rustRoutes, acmeForRust);
|
|
116
123
|
// Start the Rust proxy
|
|
117
124
|
await this.bridge.startProxy(config);
|
|
118
125
|
// Now that Rust proxy is running, configure socket handler relay
|
|
119
126
|
if (this.socketHandlerServer) {
|
|
120
127
|
await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
|
|
121
128
|
}
|
|
129
|
+
// Load default self-signed fallback certificate (domain: '*')
|
|
130
|
+
if (!this.settings.disableDefaultCert) {
|
|
131
|
+
try {
|
|
132
|
+
const defaultCert = generateDefaultCertificate();
|
|
133
|
+
await this.bridge.loadCertificate('*', defaultCert.cert, defaultCert.key);
|
|
134
|
+
logger.log('info', 'Default self-signed fallback certificate loaded', { component: 'smart-proxy' });
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
logger.log('warn', `Failed to generate default certificate: ${err.message}`, { component: 'smart-proxy' });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Load consumer-stored certificates
|
|
141
|
+
const preloadedDomains = new Set();
|
|
142
|
+
if (this.settings.certStore) {
|
|
143
|
+
try {
|
|
144
|
+
const stored = await this.settings.certStore.loadAll();
|
|
145
|
+
for (const entry of stored) {
|
|
146
|
+
await this.bridge.loadCertificate(entry.domain, entry.publicKey, entry.privateKey, entry.ca);
|
|
147
|
+
preloadedDomains.add(entry.domain);
|
|
148
|
+
}
|
|
149
|
+
logger.log('info', `Loaded ${stored.length} certificate(s) from consumer store`, { component: 'smart-proxy' });
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
logger.log('warn', `Failed to load certificates from consumer store: ${err.message}`, { component: 'smart-proxy' });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
122
155
|
// Handle certProvisionFunction
|
|
123
|
-
await this.provisionCertificatesViaCallback();
|
|
156
|
+
await this.provisionCertificatesViaCallback(preloadedDomains);
|
|
124
157
|
// Start metrics polling
|
|
125
158
|
this.metricsAdapter.startPolling();
|
|
126
159
|
logger.log('info', 'SmartProxy started (Rust engine)', { component: 'smart-proxy' });
|
|
@@ -265,20 +298,20 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
265
298
|
/**
|
|
266
299
|
* Build the Rust configuration object from TS settings.
|
|
267
300
|
*/
|
|
268
|
-
buildRustConfig(routes) {
|
|
301
|
+
buildRustConfig(routes, acmeOverride) {
|
|
302
|
+
const acme = acmeOverride !== undefined ? acmeOverride : this.settings.acme;
|
|
269
303
|
return {
|
|
270
304
|
routes,
|
|
271
305
|
defaults: this.settings.defaults,
|
|
272
|
-
acme:
|
|
306
|
+
acme: acme
|
|
273
307
|
? {
|
|
274
|
-
enabled:
|
|
275
|
-
email:
|
|
276
|
-
useProduction:
|
|
277
|
-
port:
|
|
278
|
-
renewThresholdDays:
|
|
279
|
-
autoRenew:
|
|
280
|
-
|
|
281
|
-
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours,
|
|
308
|
+
enabled: acme.enabled,
|
|
309
|
+
email: acme.email,
|
|
310
|
+
useProduction: acme.useProduction,
|
|
311
|
+
port: acme.port,
|
|
312
|
+
renewThresholdDays: acme.renewThresholdDays,
|
|
313
|
+
autoRenew: acme.autoRenew,
|
|
314
|
+
renewCheckIntervalHours: acme.renewCheckIntervalHours,
|
|
282
315
|
}
|
|
283
316
|
: undefined,
|
|
284
317
|
connectionTimeout: this.settings.connectionTimeout,
|
|
@@ -300,21 +333,36 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
300
333
|
* If the callback returns a cert object, load it into Rust.
|
|
301
334
|
* If it returns 'http01', let Rust handle ACME.
|
|
302
335
|
*/
|
|
303
|
-
async provisionCertificatesViaCallback() {
|
|
336
|
+
async provisionCertificatesViaCallback(skipDomains = new Set()) {
|
|
304
337
|
const provisionFn = this.settings.certProvisionFunction;
|
|
305
338
|
if (!provisionFn)
|
|
306
339
|
return;
|
|
340
|
+
const provisionedDomains = new Set(skipDomains);
|
|
307
341
|
for (const route of this.settings.routes) {
|
|
308
342
|
if (route.action.tls?.certificate !== 'auto')
|
|
309
343
|
continue;
|
|
310
344
|
if (!route.match.domains)
|
|
311
345
|
continue;
|
|
312
|
-
const
|
|
313
|
-
|
|
346
|
+
const rawDomains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
|
|
347
|
+
const certDomains = this.normalizeDomainsForCertProvisioning(rawDomains);
|
|
348
|
+
for (const domain of certDomains) {
|
|
349
|
+
if (provisionedDomains.has(domain))
|
|
350
|
+
continue;
|
|
351
|
+
provisionedDomains.add(domain);
|
|
314
352
|
try {
|
|
315
353
|
const result = await provisionFn(domain);
|
|
316
354
|
if (result === 'http01') {
|
|
317
|
-
//
|
|
355
|
+
// Callback wants HTTP-01 for this domain — trigger Rust ACME explicitly
|
|
356
|
+
if (route.name) {
|
|
357
|
+
try {
|
|
358
|
+
await this.bridge.provisionCertificate(route.name);
|
|
359
|
+
logger.log('info', `Triggered Rust ACME for ${domain} (route: ${route.name})`, { component: 'smart-proxy' });
|
|
360
|
+
}
|
|
361
|
+
catch (provisionErr) {
|
|
362
|
+
logger.log('warn', `Cannot provision cert for ${domain} — callback returned 'http01' but Rust ACME failed: ${provisionErr.message}. ` +
|
|
363
|
+
'Note: Rust ACME is disabled when certProvisionFunction is set.', { component: 'smart-proxy' });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
318
366
|
continue;
|
|
319
367
|
}
|
|
320
368
|
// Got a static cert object - load it into Rust
|
|
@@ -322,18 +370,69 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
322
370
|
const certObj = result;
|
|
323
371
|
await this.bridge.loadCertificate(domain, certObj.publicKey, certObj.privateKey);
|
|
324
372
|
logger.log('info', `Certificate loaded via provision function for ${domain}`, { component: 'smart-proxy' });
|
|
373
|
+
// Persist to consumer store
|
|
374
|
+
if (this.settings.certStore?.save) {
|
|
375
|
+
try {
|
|
376
|
+
await this.settings.certStore.save(domain, certObj.publicKey, certObj.privateKey);
|
|
377
|
+
}
|
|
378
|
+
catch (storeErr) {
|
|
379
|
+
logger.log('warn', `certStore.save() failed for ${domain}: ${storeErr.message}`, { component: 'smart-proxy' });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
325
382
|
}
|
|
326
383
|
}
|
|
327
384
|
catch (err) {
|
|
328
385
|
logger.log('warn', `certProvisionFunction failed for ${domain}: ${err.message}`, { component: 'smart-proxy' });
|
|
329
|
-
// Fallback to ACME if enabled
|
|
330
|
-
if (this.settings.certProvisionFallbackToAcme !== false) {
|
|
331
|
-
|
|
386
|
+
// Fallback to ACME if enabled and route has a name
|
|
387
|
+
if (this.settings.certProvisionFallbackToAcme !== false && route.name) {
|
|
388
|
+
try {
|
|
389
|
+
await this.bridge.provisionCertificate(route.name);
|
|
390
|
+
logger.log('info', `Falling back to Rust ACME for ${domain} (route: ${route.name})`, { component: 'smart-proxy' });
|
|
391
|
+
}
|
|
392
|
+
catch (acmeErr) {
|
|
393
|
+
logger.log('warn', `ACME fallback also failed for ${domain}: ${acmeErr.message}` +
|
|
394
|
+
(this.settings.disableDefaultCert
|
|
395
|
+
? ' — TLS will fail for this domain (disableDefaultCert is true)'
|
|
396
|
+
: ' — default self-signed fallback cert will be used'), { component: 'smart-proxy' });
|
|
397
|
+
}
|
|
332
398
|
}
|
|
333
399
|
}
|
|
334
400
|
}
|
|
335
401
|
}
|
|
336
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Normalize routing glob patterns into valid domain identifiers for cert provisioning.
|
|
405
|
+
* - `*nevermind.cloud` → `['nevermind.cloud', '*.nevermind.cloud']`
|
|
406
|
+
* - `*.lossless.digital` → `['*.lossless.digital']` (already valid wildcard)
|
|
407
|
+
* - `code.foss.global` → `['code.foss.global']` (plain domain)
|
|
408
|
+
* - `*mid*.example.com` → skipped with warning (unsupported glob)
|
|
409
|
+
*/
|
|
410
|
+
normalizeDomainsForCertProvisioning(rawDomains) {
|
|
411
|
+
const result = [];
|
|
412
|
+
for (const raw of rawDomains) {
|
|
413
|
+
// Plain domain — no glob characters
|
|
414
|
+
if (!raw.includes('*')) {
|
|
415
|
+
result.push(raw);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
// Valid wildcard: *.example.com
|
|
419
|
+
if (raw.startsWith('*.') && !raw.slice(2).includes('*')) {
|
|
420
|
+
result.push(raw);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
// Routing glob like *example.com (leading star, no dot after it)
|
|
424
|
+
// Convert to bare domain + wildcard pair
|
|
425
|
+
if (raw.startsWith('*') && !raw.startsWith('*.') && !raw.slice(1).includes('*')) {
|
|
426
|
+
const baseDomain = raw.slice(1); // Remove leading *
|
|
427
|
+
result.push(baseDomain);
|
|
428
|
+
result.push(`*.${baseDomain}`);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
// Unsupported glob pattern (e.g. *mid*.example.com)
|
|
432
|
+
logger.log('warn', `Skipping unsupported glob pattern for cert provisioning: ${raw}`, { component: 'smart-proxy' });
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
337
436
|
isValidDomain(domain) {
|
|
338
437
|
if (!domain || domain.length === 0)
|
|
339
438
|
return false;
|
|
@@ -343,4 +442,4 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
343
442
|
return validDomainRegex.test(domain);
|
|
344
443
|
}
|
|
345
444
|
}
|
|
346
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnQtcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3NtYXJ0LXByb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBELDBCQUEwQjtBQUMxQixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDekQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFL0QsbUJBQW1CO0FBQ25CLE9BQU8sRUFBRSxrQkFBa0IsSUFBSSxZQUFZLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDNUQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBT3pDOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxVQUFXLFNBQVEsT0FBTyxDQUFDLFlBQVk7SUFXbEQsWUFBWSxXQUErQjtRQUN6QyxLQUFLLEVBQUUsQ0FBQztRQU5GLHdCQUFtQixHQUErQixJQUFJLENBQUM7UUFHdkQsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUt2QixpQkFBaUI7UUFDakIsSUFBSSxDQUFDLFFBQVEsR0FBRztZQUNkLEdBQUcsV0FBVztZQUNkLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxrQkFBa0IsSUFBSSxNQUFNO1lBQzVELGFBQWEsRUFBRSxXQUFXLENBQUMsYUFBYSxJQUFJLE9BQU87WUFDbkQscUJBQXFCLEVBQUUsV0FBVyxDQUFDLHFCQUFxQixJQUFJLFFBQVE7WUFD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
|
|
445
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnQtcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3NtYXJ0LXByb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBELDBCQUEwQjtBQUMxQixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDekQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFL0QsbUJBQW1CO0FBQ25CLE9BQU8sRUFBRSxrQkFBa0IsSUFBSSxZQUFZLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDNUQsT0FBTyxFQUFFLDBCQUEwQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDL0UsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBT3pDOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxVQUFXLFNBQVEsT0FBTyxDQUFDLFlBQVk7SUFXbEQsWUFBWSxXQUErQjtRQUN6QyxLQUFLLEVBQUUsQ0FBQztRQU5GLHdCQUFtQixHQUErQixJQUFJLENBQUM7UUFHdkQsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUt2QixpQkFBaUI7UUFDakIsSUFBSSxDQUFDLFFBQVEsR0FBRztZQUNkLEdBQUcsV0FBVztZQUNkLGtCQUFrQixFQUFFLFdBQVcsQ0FBQyxrQkFBa0IsSUFBSSxNQUFNO1lBQzVELGFBQWEsRUFBRSxXQUFXLENBQUMsYUFBYSxJQUFJLE9BQU87WUFD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
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a self-signed fallback certificate (CN=SmartProxy Default Certificate, SAN=*).
|
|
3
|
+
* Used as the '*' wildcard fallback so TLS handshakes never reset due to missing certs.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateDefaultCertificate(): {
|
|
6
|
+
cert: string;
|
|
7
|
+
key: string;
|
|
8
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as plugins from '../../../plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a self-signed fallback certificate (CN=SmartProxy Default Certificate, SAN=*).
|
|
4
|
+
* Used as the '*' wildcard fallback so TLS handshakes never reset due to missing certs.
|
|
5
|
+
*/
|
|
6
|
+
export function generateDefaultCertificate() {
|
|
7
|
+
const forge = plugins.smartcrypto.nodeForge;
|
|
8
|
+
// Generate 2048-bit RSA keypair
|
|
9
|
+
const keypair = forge.pki.rsa.generateKeyPair({ bits: 2048 });
|
|
10
|
+
// Create self-signed X.509 certificate
|
|
11
|
+
const cert = forge.pki.createCertificate();
|
|
12
|
+
cert.publicKey = keypair.publicKey;
|
|
13
|
+
cert.serialNumber = '01';
|
|
14
|
+
cert.validity.notBefore = new Date();
|
|
15
|
+
cert.validity.notAfter = new Date();
|
|
16
|
+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
|
17
|
+
const attrs = [{ name: 'commonName', value: 'SmartProxy Default Certificate' }];
|
|
18
|
+
cert.setSubject(attrs);
|
|
19
|
+
cert.setIssuer(attrs);
|
|
20
|
+
// Add wildcard SAN
|
|
21
|
+
cert.setExtensions([
|
|
22
|
+
{ name: 'subjectAltName', altNames: [{ type: 2 /* DNS */, value: '*' }] },
|
|
23
|
+
]);
|
|
24
|
+
cert.sign(keypair.privateKey, forge.md.sha256.create());
|
|
25
|
+
return {
|
|
26
|
+
cert: forge.pki.certificateToPem(cert),
|
|
27
|
+
key: forge.pki.privateKeyToPem(keypair.privateKey),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVmYXVsdC1jZXJ0LWdlbmVyYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvdXRpbHMvZGVmYXVsdC1jZXJ0LWdlbmVyYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBRS9DOzs7R0FHRztBQUNILE1BQU0sVUFBVSwwQkFBMEI7SUFDeEMsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUM7SUFFNUMsZ0NBQWdDO0lBQ2hDLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBRTlELHVDQUF1QztJQUN2QyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLGlCQUFpQixFQUFFLENBQUM7SUFDM0MsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDO0lBQ25DLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDO0lBQ3pCLElBQUksQ0FBQyxRQUFRLENBQUMsU0FBUyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7SUFDckMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUNwQyxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsV0FBVyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFFOUUsTUFBTSxLQUFLLEdBQUcsQ0FBQyxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFFLGdDQUFnQyxFQUFFLENBQUMsQ0FBQztJQUNoRixJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3ZCLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7SUFFdEIsbUJBQW1CO0lBQ25CLElBQUksQ0FBQyxhQUFhLENBQUM7UUFDakIsRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLEVBQUUsUUFBUSxFQUFFLENBQUMsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRTtLQUMxRSxDQUFDLENBQUM7SUFFSCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUV4RCxPQUFPO1FBQ0wsSUFBSSxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDO1FBQ3RDLEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDO0tBQ25ELENBQUM7QUFDSixDQUFDIn0=
|
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
export * from './route-helpers.js';
|
|
8
8
|
export * from './route-validator.js';
|
|
9
9
|
export * from './route-utils.js';
|
|
10
|
+
export { generateDefaultCertificate } from './default-cert-generator.js';
|
|
10
11
|
export { createApiGatewayRoute, addRateLimiting, addBasicAuth, addJwtAuth } from './route-helpers.js';
|
|
@@ -10,6 +10,8 @@ export * from './route-helpers.js';
|
|
|
10
10
|
export * from './route-validator.js';
|
|
11
11
|
// Export route utilities for route operations
|
|
12
12
|
export * from './route-utils.js';
|
|
13
|
+
// Export default certificate generator
|
|
14
|
+
export { generateDefaultCertificate } from './default-cert-generator.js';
|
|
13
15
|
// Export additional functions from route-helpers that weren't already exported
|
|
14
16
|
export { createApiGatewayRoute, addRateLimiting, addBasicAuth, addJwtAuth } from './route-helpers.js';
|
|
15
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3V0aWxzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7OztHQUtHO0FBRUgseURBQXlEO0FBQ3pELGNBQWMsb0JBQW9CLENBQUM7QUFFbkMsMERBQTBEO0FBQzFELGNBQWMsc0JBQXNCLENBQUM7QUFFckMsOENBQThDO0FBQzlDLGNBQWMsa0JBQWtCLENBQUM7QUFFakMsdUNBQXVDO0FBQ3ZDLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBRXpFLCtFQUErRTtBQUMvRSxPQUFPLEVBQ0wscUJBQXFCLEVBQ3JCLGVBQWUsRUFDZixZQUFZLEVBQ1osVUFBVSxFQUNYLE1BQU0sb0JBQW9CLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "24.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/readme.md
CHANGED
|
@@ -36,6 +36,7 @@ Whether you're building microservices, deploying edge infrastructure, or need a
|
|
|
36
36
|
| 📊 **Live Metrics** | Real-time throughput, connection counts, and performance data |
|
|
37
37
|
| 🔧 **Dynamic Management** | Add/remove ports and routes at runtime without restarts |
|
|
38
38
|
| 🔄 **PROXY Protocol** | Full PROXY protocol v1/v2 support for preserving client information |
|
|
39
|
+
| 💾 **Consumer Cert Storage** | Bring your own persistence — SmartProxy never writes certs to disk |
|
|
39
40
|
|
|
40
41
|
## 🚀 Quick Start
|
|
41
42
|
|
|
@@ -456,6 +457,51 @@ const proxy = new SmartProxy({
|
|
|
456
457
|
});
|
|
457
458
|
```
|
|
458
459
|
|
|
460
|
+
### 💾 Consumer-Managed Certificate Storage
|
|
461
|
+
|
|
462
|
+
SmartProxy **never writes certificates to disk**. Instead, you own all persistence through the `certStore` interface. This gives you full control — store certs in a database, cloud KMS, encrypted vault, or wherever makes sense for your infrastructure:
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
const proxy = new SmartProxy({
|
|
466
|
+
routes: [...],
|
|
467
|
+
|
|
468
|
+
certProvisionFunction: async (domain) => myAcme.provision(domain),
|
|
469
|
+
|
|
470
|
+
// Your persistence layer — SmartProxy calls these hooks
|
|
471
|
+
certStore: {
|
|
472
|
+
// Called once on startup to pre-load persisted certs
|
|
473
|
+
loadAll: async () => {
|
|
474
|
+
const certs = await myDb.getAllCerts();
|
|
475
|
+
return certs.map(c => ({
|
|
476
|
+
domain: c.domain,
|
|
477
|
+
publicKey: c.certPem,
|
|
478
|
+
privateKey: c.keyPem,
|
|
479
|
+
ca: c.caPem, // optional
|
|
480
|
+
}));
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
// Called after each successful cert provision
|
|
484
|
+
save: async (domain, publicKey, privateKey, ca) => {
|
|
485
|
+
await myDb.upsertCert({ domain, certPem: publicKey, keyPem: privateKey, caPem: ca });
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
// Optional: called when a cert should be removed
|
|
489
|
+
remove: async (domain) => {
|
|
490
|
+
await myDb.deleteCert(domain);
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**Startup flow:**
|
|
497
|
+
1. Rust engine starts
|
|
498
|
+
2. Default self-signed `*` fallback cert is loaded (unless `disableDefaultCert: true`)
|
|
499
|
+
3. `certStore.loadAll()` is called → all returned certs are loaded into the Rust TLS stack
|
|
500
|
+
4. `certProvisionFunction` runs for any remaining `certificate: 'auto'` routes (skipping domains already loaded from the store)
|
|
501
|
+
5. After each successful provision, `certStore.save()` is called
|
|
502
|
+
|
|
503
|
+
This means your second startup is instant — no re-provisioning needed for domains that already have valid certs in your store.
|
|
504
|
+
|
|
459
505
|
## 🏛️ Architecture
|
|
460
506
|
|
|
461
507
|
SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
|
@@ -488,7 +534,7 @@ SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
|
|
488
534
|
|
|
489
535
|
- **Rust Engine** handles all networking, TLS, HTTP proxying, connection management, security, and metrics
|
|
490
536
|
- **TypeScript** provides the npm API, configuration types, route helpers, validation, and socket handler callbacks
|
|
491
|
-
- **IPC** — The TypeScript wrapper uses
|
|
537
|
+
- **IPC** — The TypeScript wrapper uses JSON commands/events over stdin/stdout to communicate with the Rust binary
|
|
492
538
|
- **Socket Relay** — A Unix domain socket server for routes requiring TypeScript-side handling (socket handlers, dynamic host/port functions)
|
|
493
539
|
|
|
494
540
|
## 🎯 Route Configuration Reference
|
|
@@ -497,7 +543,7 @@ SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
|
|
497
543
|
|
|
498
544
|
```typescript
|
|
499
545
|
interface IRouteMatch {
|
|
500
|
-
ports: number | number[] | Array<{ from: number; to: number }>; //
|
|
546
|
+
ports: number | number[] | Array<{ from: number; to: number }>; // Required — port(s) to listen on
|
|
501
547
|
domains?: string | string[]; // 'example.com', '*.example.com'
|
|
502
548
|
path?: string; // '/api/*', '/users/:id'
|
|
503
549
|
clientIp?: string[]; // ['10.0.0.0/8', '192.168.*']
|
|
@@ -517,11 +563,16 @@ interface IRouteMatch {
|
|
|
517
563
|
|
|
518
564
|
```typescript
|
|
519
565
|
interface IRouteTarget {
|
|
520
|
-
host: string | string[] | ((context: IRouteContext) => string);
|
|
566
|
+
host: string | string[] | ((context: IRouteContext) => string | string[]);
|
|
521
567
|
port: number | 'preserve' | ((context: IRouteContext) => number);
|
|
522
|
-
tls?:
|
|
523
|
-
priority?: number;
|
|
524
|
-
match?: ITargetMatch;
|
|
568
|
+
tls?: IRouteTls; // Per-target TLS override
|
|
569
|
+
priority?: number; // Target priority
|
|
570
|
+
match?: ITargetMatch; // Sub-match within a route (by port, path, headers, method)
|
|
571
|
+
websocket?: IRouteWebSocket;
|
|
572
|
+
loadBalancing?: IRouteLoadBalancing;
|
|
573
|
+
sendProxyProtocol?: boolean;
|
|
574
|
+
headers?: IRouteHeaders;
|
|
575
|
+
advanced?: IRouteAdvanced;
|
|
525
576
|
}
|
|
526
577
|
```
|
|
527
578
|
|
|
@@ -613,6 +664,7 @@ import {
|
|
|
613
664
|
createPortMappingRoute, // Port mapping with context
|
|
614
665
|
createOffsetPortMappingRoute, // Simple port offset
|
|
615
666
|
createDynamicRoute, // Dynamic host/port via functions
|
|
667
|
+
createPortOffset, // Port offset factory
|
|
616
668
|
|
|
617
669
|
// Security Modifiers
|
|
618
670
|
addRateLimiting, // Add rate limiting to any route
|
|
@@ -680,7 +732,6 @@ interface ISmartProxyOptions {
|
|
|
680
732
|
port?: number; // HTTP-01 challenge port (default: 80)
|
|
681
733
|
renewThresholdDays?: number; // Days before expiry to renew (default: 30)
|
|
682
734
|
autoRenew?: boolean; // Enable auto-renewal (default: true)
|
|
683
|
-
certificateStore?: string; // Directory to store certs (default: './certs')
|
|
684
735
|
renewCheckIntervalHours?: number; // Renewal check interval (default: 24)
|
|
685
736
|
};
|
|
686
737
|
|
|
@@ -688,6 +739,12 @@ interface ISmartProxyOptions {
|
|
|
688
739
|
certProvisionFunction?: (domain: string) => Promise<ICert | 'http01'>;
|
|
689
740
|
certProvisionFallbackToAcme?: boolean; // Fall back to ACME on failure (default: true)
|
|
690
741
|
|
|
742
|
+
// Consumer-managed certificate persistence (see "Consumer-Managed Certificate Storage")
|
|
743
|
+
certStore?: ISmartProxyCertStore;
|
|
744
|
+
|
|
745
|
+
// Self-signed fallback
|
|
746
|
+
disableDefaultCert?: boolean; // Disable '*' self-signed fallback (default: false)
|
|
747
|
+
|
|
691
748
|
// Global defaults
|
|
692
749
|
defaults?: {
|
|
693
750
|
target?: { host: string; port: number };
|
|
@@ -729,6 +786,26 @@ interface ISmartProxyOptions {
|
|
|
729
786
|
}
|
|
730
787
|
```
|
|
731
788
|
|
|
789
|
+
### ISmartProxyCertStore Interface
|
|
790
|
+
|
|
791
|
+
```typescript
|
|
792
|
+
interface ISmartProxyCertStore {
|
|
793
|
+
/** Called once on startup to pre-load persisted certs */
|
|
794
|
+
loadAll: () => Promise<Array<{
|
|
795
|
+
domain: string;
|
|
796
|
+
publicKey: string;
|
|
797
|
+
privateKey: string;
|
|
798
|
+
ca?: string;
|
|
799
|
+
}>>;
|
|
800
|
+
|
|
801
|
+
/** Called after each successful cert provision */
|
|
802
|
+
save: (domain: string, publicKey: string, privateKey: string, ca?: string) => Promise<void>;
|
|
803
|
+
|
|
804
|
+
/** Optional: remove a cert from storage */
|
|
805
|
+
remove?: (domain: string) => Promise<void>;
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
732
809
|
### IMetrics Interface
|
|
733
810
|
|
|
734
811
|
The `getMetrics()` method returns a cached metrics adapter that polls the Rust engine:
|
|
@@ -758,6 +835,10 @@ metrics.requests.total(); // Total requests
|
|
|
758
835
|
metrics.totals.bytesIn(); // Total bytes received
|
|
759
836
|
metrics.totals.bytesOut(); // Total bytes sent
|
|
760
837
|
metrics.totals.connections(); // Total connections
|
|
838
|
+
|
|
839
|
+
// Percentiles
|
|
840
|
+
metrics.percentiles.connectionDuration(); // { p50, p95, p99 }
|
|
841
|
+
metrics.percentiles.bytesTransferred(); // { in: { p50, p95, p99 }, out: { p50, p95, p99 } }
|
|
761
842
|
```
|
|
762
843
|
|
|
763
844
|
## 🐛 Troubleshooting
|
|
@@ -802,6 +883,7 @@ SmartProxy searches for the Rust binary in this order:
|
|
|
802
883
|
7. **✅ Validate Routes** — Use `RouteValidator.validateRoutes()` to catch config errors before deployment
|
|
803
884
|
8. **🔀 Atomic Updates** — Use `updateRoutes()` for hot-reloading routes (mutex-locked, no downtime)
|
|
804
885
|
9. **🎮 Use Socket Handlers** — For protocols beyond HTTP, implement custom socket handlers instead of fighting the proxy model
|
|
886
|
+
10. **💾 Use `certStore`** — Persist certs in your own storage to avoid re-provisioning on every restart
|
|
805
887
|
|
|
806
888
|
## License and Legal Information
|
|
807
889
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '
|
|
6
|
+
version: '24.0.0',
|
|
7
7
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
|
@@ -85,7 +85,6 @@ export interface IAcmeOptions {
|
|
|
85
85
|
renewThresholdDays?: number; // Days before expiry to renew certificates
|
|
86
86
|
renewCheckIntervalHours?: number; // How often to check for renewals (in hours)
|
|
87
87
|
autoRenew?: boolean; // Whether to automatically renew certificates
|
|
88
|
-
certificateStore?: string; // Directory to store certificates
|
|
89
88
|
skipConfiguredCerts?: boolean; // Skip domains with existing certificates
|
|
90
89
|
domainForwards?: IDomainForwardConfig[]; // Domain-specific forwarding configs
|
|
91
90
|
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* SmartProxy models
|
|
3
3
|
*/
|
|
4
4
|
// Export everything except IAcmeOptions from interfaces
|
|
5
|
-
export type { ISmartProxyOptions, IConnectionRecord, TSmartProxyCertProvisionObject } from './interfaces.js';
|
|
5
|
+
export type { ISmartProxyOptions, ISmartProxyCertStore, IConnectionRecord, TSmartProxyCertProvisionObject } from './interfaces.js';
|
|
6
6
|
export * from './route-types.js';
|
|
7
7
|
export * from './metrics-types.js';
|
|
@@ -10,11 +10,23 @@ export interface IAcmeOptions {
|
|
|
10
10
|
useProduction?: boolean; // Use Let's Encrypt production (default: false)
|
|
11
11
|
renewThresholdDays?: number; // Days before expiry to renew (default: 30)
|
|
12
12
|
autoRenew?: boolean; // Enable automatic renewal (default: true)
|
|
13
|
-
certificateStore?: string; // Directory to store certificates (default: './certs')
|
|
14
13
|
skipConfiguredCerts?: boolean;
|
|
15
14
|
renewCheckIntervalHours?: number; // How often to check for renewals (default: 24)
|
|
16
15
|
routeForwards?: any[];
|
|
17
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Consumer-provided certificate storage.
|
|
20
|
+
* SmartProxy never writes certs to disk — the consumer owns all persistence.
|
|
21
|
+
*/
|
|
22
|
+
export interface ISmartProxyCertStore {
|
|
23
|
+
/** Load all stored certs on startup (called once before cert provisioning) */
|
|
24
|
+
loadAll: () => Promise<Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }>>;
|
|
25
|
+
/** Save a cert after successful provisioning */
|
|
26
|
+
save: (domain: string, publicKey: string, privateKey: string, ca?: string) => Promise<void>;
|
|
27
|
+
/** Remove a cert (optional) */
|
|
28
|
+
remove?: (domain: string) => Promise<void>;
|
|
29
|
+
}
|
|
18
30
|
import type { IRouteConfig } from './route-types.js';
|
|
19
31
|
|
|
20
32
|
/**
|
|
@@ -136,6 +148,20 @@ export interface ISmartProxyOptions {
|
|
|
136
148
|
*/
|
|
137
149
|
certProvisionFallbackToAcme?: boolean;
|
|
138
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Disable the default self-signed fallback certificate.
|
|
153
|
+
* When false (default), a self-signed cert is generated at startup and loaded
|
|
154
|
+
* as '*' so TLS handshakes never fail due to missing certs.
|
|
155
|
+
*/
|
|
156
|
+
disableDefaultCert?: boolean;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Consumer-provided cert storage. SmartProxy never writes certs to disk.
|
|
160
|
+
* On startup, loadAll() is called to pre-load persisted certs.
|
|
161
|
+
* After each successful cert provision, save() is called.
|
|
162
|
+
*/
|
|
163
|
+
certStore?: ISmartProxyCertStore;
|
|
164
|
+
|
|
139
165
|
/**
|
|
140
166
|
* Path to the RustProxy binary. If not set, the binary is located
|
|
141
167
|
* automatically via env var, platform package, local build, or PATH.
|
|
@@ -10,10 +10,11 @@ import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
|
10
10
|
// Route management
|
|
11
11
|
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
12
12
|
import { RouteValidator } from './utils/route-validator.js';
|
|
13
|
+
import { generateDefaultCertificate } from './utils/default-cert-generator.js';
|
|
13
14
|
import { Mutex } from './utils/mutex.js';
|
|
14
15
|
|
|
15
16
|
// Types
|
|
16
|
-
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject } from './models/interfaces.js';
|
|
17
|
+
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject, IAcmeOptions } from './models/interfaces.js';
|
|
17
18
|
import type { IRouteConfig } from './models/route-types.js';
|
|
18
19
|
import type { IMetrics } from './models/metrics-types.js';
|
|
19
20
|
|
|
@@ -68,7 +69,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
68
69
|
useProduction: this.settings.acme.useProduction || false,
|
|
69
70
|
renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
|
|
70
71
|
autoRenew: this.settings.acme.autoRenew !== false,
|
|
71
|
-
certificateStore: this.settings.acme.certificateStore || './certs',
|
|
72
72
|
skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
|
|
73
73
|
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours || 24,
|
|
74
74
|
routeForwards: this.settings.acme.routeForwards || [],
|
|
@@ -146,8 +146,16 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
146
146
|
// Preprocess routes (strip JS functions, convert socket-handler routes)
|
|
147
147
|
const rustRoutes = this.preprocessor.preprocessForRust(this.settings.routes);
|
|
148
148
|
|
|
149
|
+
// When certProvisionFunction handles cert provisioning,
|
|
150
|
+
// disable Rust's built-in ACME to prevent race condition.
|
|
151
|
+
let acmeForRust = this.settings.acme;
|
|
152
|
+
if (this.settings.certProvisionFunction && acmeForRust?.enabled) {
|
|
153
|
+
acmeForRust = { ...acmeForRust, enabled: false };
|
|
154
|
+
logger.log('info', 'Rust ACME disabled — certProvisionFunction will handle certificate provisioning', { component: 'smart-proxy' });
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
// Build Rust config
|
|
150
|
-
const config = this.buildRustConfig(rustRoutes);
|
|
158
|
+
const config = this.buildRustConfig(rustRoutes, acmeForRust);
|
|
151
159
|
|
|
152
160
|
// Start the Rust proxy
|
|
153
161
|
await this.bridge.startProxy(config);
|
|
@@ -157,8 +165,34 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
157
165
|
await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
|
|
158
166
|
}
|
|
159
167
|
|
|
168
|
+
// Load default self-signed fallback certificate (domain: '*')
|
|
169
|
+
if (!this.settings.disableDefaultCert) {
|
|
170
|
+
try {
|
|
171
|
+
const defaultCert = generateDefaultCertificate();
|
|
172
|
+
await this.bridge.loadCertificate('*', defaultCert.cert, defaultCert.key);
|
|
173
|
+
logger.log('info', 'Default self-signed fallback certificate loaded', { component: 'smart-proxy' });
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
logger.log('warn', `Failed to generate default certificate: ${err.message}`, { component: 'smart-proxy' });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Load consumer-stored certificates
|
|
180
|
+
const preloadedDomains = new Set<string>();
|
|
181
|
+
if (this.settings.certStore) {
|
|
182
|
+
try {
|
|
183
|
+
const stored = await this.settings.certStore.loadAll();
|
|
184
|
+
for (const entry of stored) {
|
|
185
|
+
await this.bridge.loadCertificate(entry.domain, entry.publicKey, entry.privateKey, entry.ca);
|
|
186
|
+
preloadedDomains.add(entry.domain);
|
|
187
|
+
}
|
|
188
|
+
logger.log('info', `Loaded ${stored.length} certificate(s) from consumer store`, { component: 'smart-proxy' });
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
logger.log('warn', `Failed to load certificates from consumer store: ${err.message}`, { component: 'smart-proxy' });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
160
194
|
// Handle certProvisionFunction
|
|
161
|
-
await this.provisionCertificatesViaCallback();
|
|
195
|
+
await this.provisionCertificatesViaCallback(preloadedDomains);
|
|
162
196
|
|
|
163
197
|
// Start metrics polling
|
|
164
198
|
this.metricsAdapter.startPolling();
|
|
@@ -334,20 +368,20 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
334
368
|
/**
|
|
335
369
|
* Build the Rust configuration object from TS settings.
|
|
336
370
|
*/
|
|
337
|
-
private buildRustConfig(routes: IRouteConfig[]): any {
|
|
371
|
+
private buildRustConfig(routes: IRouteConfig[], acmeOverride?: IAcmeOptions): any {
|
|
372
|
+
const acme = acmeOverride !== undefined ? acmeOverride : this.settings.acme;
|
|
338
373
|
return {
|
|
339
374
|
routes,
|
|
340
375
|
defaults: this.settings.defaults,
|
|
341
|
-
acme:
|
|
376
|
+
acme: acme
|
|
342
377
|
? {
|
|
343
|
-
enabled:
|
|
344
|
-
email:
|
|
345
|
-
useProduction:
|
|
346
|
-
port:
|
|
347
|
-
renewThresholdDays:
|
|
348
|
-
autoRenew:
|
|
349
|
-
|
|
350
|
-
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours,
|
|
378
|
+
enabled: acme.enabled,
|
|
379
|
+
email: acme.email,
|
|
380
|
+
useProduction: acme.useProduction,
|
|
381
|
+
port: acme.port,
|
|
382
|
+
renewThresholdDays: acme.renewThresholdDays,
|
|
383
|
+
autoRenew: acme.autoRenew,
|
|
384
|
+
renewCheckIntervalHours: acme.renewCheckIntervalHours,
|
|
351
385
|
}
|
|
352
386
|
: undefined,
|
|
353
387
|
connectionTimeout: this.settings.connectionTimeout,
|
|
@@ -370,22 +404,36 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
370
404
|
* If the callback returns a cert object, load it into Rust.
|
|
371
405
|
* If it returns 'http01', let Rust handle ACME.
|
|
372
406
|
*/
|
|
373
|
-
private async provisionCertificatesViaCallback(): Promise<void> {
|
|
407
|
+
private async provisionCertificatesViaCallback(skipDomains: Set<string> = new Set()): Promise<void> {
|
|
374
408
|
const provisionFn = this.settings.certProvisionFunction;
|
|
375
409
|
if (!provisionFn) return;
|
|
376
410
|
|
|
411
|
+
const provisionedDomains = new Set<string>(skipDomains);
|
|
412
|
+
|
|
377
413
|
for (const route of this.settings.routes) {
|
|
378
414
|
if (route.action.tls?.certificate !== 'auto') continue;
|
|
379
415
|
if (!route.match.domains) continue;
|
|
380
416
|
|
|
381
|
-
const
|
|
417
|
+
const rawDomains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
|
|
418
|
+
const certDomains = this.normalizeDomainsForCertProvisioning(rawDomains);
|
|
382
419
|
|
|
383
|
-
for (const domain of
|
|
420
|
+
for (const domain of certDomains) {
|
|
421
|
+
if (provisionedDomains.has(domain)) continue;
|
|
422
|
+
provisionedDomains.add(domain);
|
|
384
423
|
try {
|
|
385
424
|
const result: TSmartProxyCertProvisionObject = await provisionFn(domain);
|
|
386
425
|
|
|
387
426
|
if (result === 'http01') {
|
|
388
|
-
//
|
|
427
|
+
// Callback wants HTTP-01 for this domain — trigger Rust ACME explicitly
|
|
428
|
+
if (route.name) {
|
|
429
|
+
try {
|
|
430
|
+
await this.bridge.provisionCertificate(route.name);
|
|
431
|
+
logger.log('info', `Triggered Rust ACME for ${domain} (route: ${route.name})`, { component: 'smart-proxy' });
|
|
432
|
+
} catch (provisionErr: any) {
|
|
433
|
+
logger.log('warn', `Cannot provision cert for ${domain} — callback returned 'http01' but Rust ACME failed: ${provisionErr.message}. ` +
|
|
434
|
+
'Note: Rust ACME is disabled when certProvisionFunction is set.', { component: 'smart-proxy' });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
389
437
|
continue;
|
|
390
438
|
}
|
|
391
439
|
|
|
@@ -398,19 +446,73 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
398
446
|
certObj.privateKey,
|
|
399
447
|
);
|
|
400
448
|
logger.log('info', `Certificate loaded via provision function for ${domain}`, { component: 'smart-proxy' });
|
|
449
|
+
|
|
450
|
+
// Persist to consumer store
|
|
451
|
+
if (this.settings.certStore?.save) {
|
|
452
|
+
try {
|
|
453
|
+
await this.settings.certStore.save(domain, certObj.publicKey, certObj.privateKey);
|
|
454
|
+
} catch (storeErr: any) {
|
|
455
|
+
logger.log('warn', `certStore.save() failed for ${domain}: ${storeErr.message}`, { component: 'smart-proxy' });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
401
458
|
}
|
|
402
459
|
} catch (err: any) {
|
|
403
460
|
logger.log('warn', `certProvisionFunction failed for ${domain}: ${err.message}`, { component: 'smart-proxy' });
|
|
404
461
|
|
|
405
|
-
// Fallback to ACME if enabled
|
|
406
|
-
if (this.settings.certProvisionFallbackToAcme !== false) {
|
|
407
|
-
|
|
462
|
+
// Fallback to ACME if enabled and route has a name
|
|
463
|
+
if (this.settings.certProvisionFallbackToAcme !== false && route.name) {
|
|
464
|
+
try {
|
|
465
|
+
await this.bridge.provisionCertificate(route.name);
|
|
466
|
+
logger.log('info', `Falling back to Rust ACME for ${domain} (route: ${route.name})`, { component: 'smart-proxy' });
|
|
467
|
+
} catch (acmeErr: any) {
|
|
468
|
+
logger.log('warn', `ACME fallback also failed for ${domain}: ${acmeErr.message}` +
|
|
469
|
+
(this.settings.disableDefaultCert
|
|
470
|
+
? ' — TLS will fail for this domain (disableDefaultCert is true)'
|
|
471
|
+
: ' — default self-signed fallback cert will be used'), { component: 'smart-proxy' });
|
|
472
|
+
}
|
|
408
473
|
}
|
|
409
474
|
}
|
|
410
475
|
}
|
|
411
476
|
}
|
|
412
477
|
}
|
|
413
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Normalize routing glob patterns into valid domain identifiers for cert provisioning.
|
|
481
|
+
* - `*nevermind.cloud` → `['nevermind.cloud', '*.nevermind.cloud']`
|
|
482
|
+
* - `*.lossless.digital` → `['*.lossless.digital']` (already valid wildcard)
|
|
483
|
+
* - `code.foss.global` → `['code.foss.global']` (plain domain)
|
|
484
|
+
* - `*mid*.example.com` → skipped with warning (unsupported glob)
|
|
485
|
+
*/
|
|
486
|
+
private normalizeDomainsForCertProvisioning(rawDomains: string[]): string[] {
|
|
487
|
+
const result: string[] = [];
|
|
488
|
+
for (const raw of rawDomains) {
|
|
489
|
+
// Plain domain — no glob characters
|
|
490
|
+
if (!raw.includes('*')) {
|
|
491
|
+
result.push(raw);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Valid wildcard: *.example.com
|
|
496
|
+
if (raw.startsWith('*.') && !raw.slice(2).includes('*')) {
|
|
497
|
+
result.push(raw);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Routing glob like *example.com (leading star, no dot after it)
|
|
502
|
+
// Convert to bare domain + wildcard pair
|
|
503
|
+
if (raw.startsWith('*') && !raw.startsWith('*.') && !raw.slice(1).includes('*')) {
|
|
504
|
+
const baseDomain = raw.slice(1); // Remove leading *
|
|
505
|
+
result.push(baseDomain);
|
|
506
|
+
result.push(`*.${baseDomain}`);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Unsupported glob pattern (e.g. *mid*.example.com)
|
|
511
|
+
logger.log('warn', `Skipping unsupported glob pattern for cert provisioning: ${raw}`, { component: 'smart-proxy' });
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
}
|
|
515
|
+
|
|
414
516
|
private isValidDomain(domain: string): boolean {
|
|
415
517
|
if (!domain || domain.length === 0) return false;
|
|
416
518
|
if (domain.includes('*')) return false;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as plugins from '../../../plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a self-signed fallback certificate (CN=SmartProxy Default Certificate, SAN=*).
|
|
5
|
+
* Used as the '*' wildcard fallback so TLS handshakes never reset due to missing certs.
|
|
6
|
+
*/
|
|
7
|
+
export function generateDefaultCertificate(): { cert: string; key: string } {
|
|
8
|
+
const forge = plugins.smartcrypto.nodeForge;
|
|
9
|
+
|
|
10
|
+
// Generate 2048-bit RSA keypair
|
|
11
|
+
const keypair = forge.pki.rsa.generateKeyPair({ bits: 2048 });
|
|
12
|
+
|
|
13
|
+
// Create self-signed X.509 certificate
|
|
14
|
+
const cert = forge.pki.createCertificate();
|
|
15
|
+
cert.publicKey = keypair.publicKey;
|
|
16
|
+
cert.serialNumber = '01';
|
|
17
|
+
cert.validity.notBefore = new Date();
|
|
18
|
+
cert.validity.notAfter = new Date();
|
|
19
|
+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
|
20
|
+
|
|
21
|
+
const attrs = [{ name: 'commonName', value: 'SmartProxy Default Certificate' }];
|
|
22
|
+
cert.setSubject(attrs);
|
|
23
|
+
cert.setIssuer(attrs);
|
|
24
|
+
|
|
25
|
+
// Add wildcard SAN
|
|
26
|
+
cert.setExtensions([
|
|
27
|
+
{ name: 'subjectAltName', altNames: [{ type: 2 /* DNS */, value: '*' }] },
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
cert.sign(keypair.privateKey, forge.md.sha256.create());
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
cert: forge.pki.certificateToPem(cert),
|
|
34
|
+
key: forge.pki.privateKeyToPem(keypair.privateKey),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -14,6 +14,9 @@ export * from './route-validator.js';
|
|
|
14
14
|
// Export route utilities for route operations
|
|
15
15
|
export * from './route-utils.js';
|
|
16
16
|
|
|
17
|
+
// Export default certificate generator
|
|
18
|
+
export { generateDefaultCertificate } from './default-cert-generator.js';
|
|
19
|
+
|
|
17
20
|
// Export additional functions from route-helpers that weren't already exported
|
|
18
21
|
export {
|
|
19
22
|
createApiGatewayRoute,
|