@push.rocks/smartproxy 30.0.1 → 31.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.
Files changed (40) hide show
  1. package/.smartconfig.json +5 -3
  2. package/changelog.md +91 -0
  3. package/dist_rust/rustproxy_linux_amd64_musl +0 -0
  4. package/dist_rust/rustproxy_linux_amd64_musl.third-party-notices.json +3358 -0
  5. package/dist_rust/rustproxy_linux_amd64_musl.third-party-notices.txt +44997 -0
  6. package/dist_rust/rustproxy_linux_amd64_musl.tsrust-build.json +5 -5
  7. package/dist_rust/rustproxy_linux_arm64_musl +0 -0
  8. package/dist_rust/rustproxy_linux_arm64_musl.third-party-notices.json +3358 -0
  9. package/dist_rust/rustproxy_linux_arm64_musl.third-party-notices.txt +44997 -0
  10. package/dist_rust/rustproxy_linux_arm64_musl.tsrust-build.json +5 -5
  11. package/dist_ts/00_commitinfo_data.js +1 -1
  12. package/dist_ts/index.d.ts +1 -1
  13. package/dist_ts/proxies/index.d.ts +1 -1
  14. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +16 -3
  15. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +27 -0
  16. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  17. package/dist_ts/proxies/smart-proxy/models/rust-types.d.ts +18 -2
  18. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +7 -2
  19. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +8 -3
  20. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +18 -1
  21. package/dist_ts/proxies/smart-proxy/smart-proxy.js +106 -38
  22. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +46 -3
  23. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +293 -3
  24. package/dist_ts/proxies/smart-proxy/utils/rust-config.d.ts +2 -2
  25. package/dist_ts/proxies/smart-proxy/utils/rust-config.js +9 -5
  26. package/native-notices/manifest.json +13 -1
  27. package/native-notices/readme.md +4 -2
  28. package/native-notices/runtime/libunwind-22.1.2/license.txt +311 -0
  29. package/package.json +10 -12
  30. package/readme.md +70 -13
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/index.ts +1 -1
  33. package/ts/proxies/index.ts +1 -1
  34. package/ts/proxies/smart-proxy/models/interfaces.ts +17 -3
  35. package/ts/proxies/smart-proxy/models/route-types.ts +28 -1
  36. package/ts/proxies/smart-proxy/models/rust-types.ts +21 -3
  37. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +13 -3
  38. package/ts/proxies/smart-proxy/smart-proxy.ts +115 -40
  39. package/ts/proxies/smart-proxy/utils/route-validator.ts +329 -4
  40. package/ts/proxies/smart-proxy/utils/rust-config.ts +10 -5
package/readme.md CHANGED
@@ -118,6 +118,35 @@ SmartProxy supports three TLS handling modes:
118
118
  | `terminate` | Decrypt at proxy, forward plain HTTP to backend | Standard reverse proxy |
119
119
  | `terminate-and-reencrypt` | Decrypt at proxy, re-encrypt to backend. HTTP traffic gets full per-request routing (Host header, path matching) via the HTTP proxy; non-HTTP traffic uses a raw TLS-to-TLS tunnel | Zero-trust / defense-in-depth environments |
120
120
 
121
+ #### Backend TLS verification
122
+
123
+ The proxy verifies the certificate of every backend it connects to over TLS: `terminate-and-reencrypt` routes (HTTP, the raw TLS tunnel and raw QUIC streams), targets with `tls` behind a `terminate` route, WebSocket upgrades, and HTTP/3 backends reached over QUIC. By default it trusts the system root store, so a backend with a self-signed or private-CA certificate is refused (`502`, or a closed tunnel) until its route says how to trust it:
124
+
125
+ ```typescript
126
+ {
127
+ name: 'internal-api',
128
+ match: { ports: 443, domains: 'api.example.com' },
129
+ action: {
130
+ type: 'forward',
131
+ targets: [{ host: '10.0.0.5', port: 8443 }],
132
+ tls: {
133
+ mode: 'terminate-and-reencrypt',
134
+ certificate: 'auto',
135
+ backend: {
136
+ ca: internalCaPem, // replaces the system roots for this backend
137
+ serverName: 'api.internal', // sent as SNI; the certificate must carry it
138
+ },
139
+ },
140
+ },
141
+ }
142
+ ```
143
+
144
+ - `ca` replaces the system root store, as Node's `ca` option does. Its certificates are trusted as issuers, so a self-signed backend certificate must be an end-entity certificate (`CA:FALSE`); a self-signed CA certificate serving as the backend's own certificate is refused.
145
+ - `serverName` defaults to the requested domain on routes that match named domains, and to the target host on routes without domains or with `*`.
146
+ - `rejectUnauthorized: false` accepts any backend certificate; it cannot be combined with `ca`.
147
+ - A target's `tls.backend` replaces the route's `action.tls.backend`, which replaces the proxy-wide `backendTls` option (`ca`, `rejectUnauthorized`). The whole object applies; fields are not merged.
148
+ - Validation refuses `tls.backend` on TLS passthrough and dynamic-target routes, which never connect to the backend over TLS, as well as an empty `ca` or `serverName` and a `ca` without certificates. When the system has no root certificates, the routes that rely on them are refused at `start()` and `updateRoutes()`.
149
+
121
150
  ## 💡 Common Use Cases
122
151
 
123
152
  ### 🌐 HTTP to HTTPS Redirect
@@ -485,12 +514,11 @@ import { SmartProxy } from '@push.rocks/smartproxy';
485
514
  const proxy = new SmartProxy({
486
515
  routes: [{
487
516
  name: 'nftables-fast',
488
- match: { ports: 443, domains: 'fast.example.com' },
517
+ match: { ports: 5432 },
489
518
  action: {
490
519
  type: 'forward',
491
520
  forwardingEngine: 'nftables',
492
- targets: [{ host: 'backend', port: 8080 }],
493
- tls: { mode: 'terminate', certificate: 'auto' },
521
+ targets: [{ host: '10.0.0.20', port: 5432 }],
494
522
  nftables: {
495
523
  protocol: 'tcp',
496
524
  preserveSourceIP: true // Backend sees real client IP
@@ -500,6 +528,8 @@ const proxy = new SmartProxy({
500
528
  });
501
529
  ```
502
530
 
531
+ The kernel forwards every packet on the listed ports to one IPv4 target before the proxy sees it, so an nftables route matches by port only. Route validation refuses an nftables route that sets `security` or `headers`; `match.domains`, `path`, `headers`, `clientIp`, `tlsVersion`, `protocol` or `inboundProxyProtocol`, or a port range; TLS termination, `websocket`, `advanced` or `sendProxyProtocol`; or more than one target, a target `match`, or a target host other than one IPv4 address. A route with `enabled: false` installs no rules.
532
+
503
533
  ### 🔒 SNI Passthrough (TLS Passthrough)
504
534
 
505
535
  Forward encrypted traffic to backends without terminating TLS — the proxy routes based on the SNI hostname alone:
@@ -532,13 +562,13 @@ const proxy = new SmartProxy({
532
562
  name: 'dynamic-backend',
533
563
  match: {
534
564
  ports: 443,
535
- domains: 'app.example.com'
565
+ domains: ['app.example.com', 'premium.example.com']
536
566
  },
537
567
  action: {
538
568
  type: 'forward',
539
569
  targets: [{
540
570
  host: (context) => {
541
- return context.path?.startsWith('/premium')
571
+ return context.domain === 'premium.example.com'
542
572
  ? 'premium-backend'
543
573
  : 'standard-backend';
544
574
  },
@@ -550,7 +580,9 @@ const proxy = new SmartProxy({
550
580
  });
551
581
  ```
552
582
 
553
- > **Note:** Routes with dynamic functions (host/port callbacks) are automatically relayed through the TypeScript socket handler server, since JavaScript functions can't be serialized to Rust.
583
+ > **Note:** Routes with dynamic functions (host/port callbacks) are automatically relayed through the TypeScript socket handler server, since JavaScript functions can't be serialized to Rust. The relay resolves one target per connection and forwards the whole connection to it. The functions see `context.domain` (the SNI, or the `Host` of a plaintext HTTP request), `context.clientIp` and `context.port`, but no request data such as `context.path`. Header and URL rewrites (`headers`, `advanced`) do not apply on these routes.
584
+ >
585
+ > Dynamic targets work with IP filtering, `maxConnections`, TLS `terminate` and `passthrough`, and `port: 'preserve'`. Route validation refuses dynamic targets together with settings the relay cannot honour: more than one target, a target `match`, `sendProxyProtocol`, TLS `terminate-and-reencrypt` or `tls.backend`, `backendTransport: 'udp'`, `match.path`, `match.headers`, `match.transport: 'udp'` or `'all'`, and the HTTP-only controls `basicAuth`, `jwtAuth`, `rateLimit`, `challenge` and `websocket.allowedOrigins`. Use static targets for those.
554
586
 
555
587
  ### 🔀 Protocol-Specific Routing
556
588
 
@@ -588,6 +620,8 @@ const tcpOnlyRoute: IRouteConfig = {
588
620
 
589
621
  > **Note:** Omitting `protocol` (the default) matches any protocol. For TLS routes, protocol detection happens *after* TLS termination — during the initial SNI-based route match, `protocol` is not yet known and the route is allowed to match. The protocol restriction is enforced after the proxy peeks at the decrypted data.
590
622
 
623
+ > **HTTP/3:** HTTP/3 requests arrive over QUIC on the UDP listener, so only routes that listen on UDP (`transport: 'udp'` or `'all'`) serve them, and a request only a TCP route serves is answered `421 Misdirected Request`; `protocol: 'quic'`, `'http3'`, `'http'` and an omitted `protocol` all match them. The QUIC connection itself is accepted under a UDP-listening route with `protocol: 'quic'`, `'http3'` or no `protocol`, or with `protocol: 'http'` and `action.udp.quic.enableHttp3`. Only routes with `enableHttp3` terminate HTTP/3; the others forward the QUIC connection raw.
624
+
591
625
  ### 🔒 Security Controls
592
626
 
593
627
  Comprehensive per-route security options:
@@ -619,14 +653,18 @@ Comprehensive per-route security options:
619
653
  },
620
654
 
621
655
  // Authentication
622
- basicAuth: { users: [{ username: 'admin', password: 'secret' }] },
623
- jwtAuth: { secret: 'your-jwt-secret', algorithm: 'HS256' }
656
+ basicAuth: { enabled: true, users: [{ username: 'admin', password: 'secret' }] },
657
+ jwtAuth: { enabled: true, secret: 'your-jwt-secret', algorithm: 'HS256' }
624
658
  }
625
659
  }
626
660
  ```
627
661
 
628
662
  Security options are configured directly on each route's `security` property — no separate helpers needed.
629
663
 
664
+ `maxConnections` caps a route's concurrent connections across all listeners: an HTTP request or upgraded WebSocket holds a slot while it is in flight, and a raw TCP connection, a raw QUIC connection or a UDP session holds one for its lifetime. A connection above the cap is closed (HTTP requests get `429`).
665
+
666
+ Basic auth, JWT auth, rate limits, challenges and `action.websocket.allowedOrigins` run in the HTTP layer only, so a route that enables one of them must serve HTTP. Validation refuses such a route when it is a socket-handler route, uses TLS passthrough, matches `protocol: 'tcp'`, or listens on UDP (`transport: 'udp'` or `'all'`) without `action.udp.quic.enableHttp3`.
667
+
630
668
  IP entries are validated against what the Rust engine can match, and an entry the engine would never match is refused:
631
669
 
632
670
  - **Security lists** (`ipAllowList` plain and domain-scoped, `ipBlockList`, `defaults.security`, `securityPolicy.blockedIps`/`blockedCidrs`): `*`, an IPv4 or IPv6 address in any textual form, or an address prefix such as `192.0.2.0/24` or `2001:db8::1/128` (prefix lengths up to 32 or 128; host bits may be set). Clients that arrive as IPv4-mapped IPv6 are matched as IPv4, so write `192.0.2.1`, not `::ffff:192.0.2.1`. Wildcards and ranges are refused.
@@ -778,6 +816,8 @@ const proxy = new SmartProxy({
778
816
 
779
817
  Inbound PROXY protocol is never enabled by the global trust list alone. Declare `match.inboundProxyProtocol` on the listener route with `mode: 'optional'` or `mode: 'required'`; omit it or use `mode: 'reject'` for direct-client listeners. A policy can define its own `trustedProxyIPs`; otherwise explicit `optional` and `required` policies inherit the global `trustedProxyIPs` list.
780
818
 
819
+ On a UDP listener that serves QUIC, an `optional` or `required` policy puts a relay in front of the QUIC endpoint to read the PROXY v2 headers. A new source address gets a relay session only for a datagram that starts a QUIC connection (a long-header packet in a datagram of at least 1200 bytes) or continues one the endpoint issued (a client that moved to a new address); other datagrams are dropped. The relay holds at most 16,384 sessions and 256 per client IP address (the PROXY source IP behind a trusted proxy), and closes a session after about two minutes without traffic.
820
+
781
821
  ### 🏗️ Custom Certificate Provisioning
782
822
 
783
823
  Supply your own certificates or integrate with external certificate providers:
@@ -800,10 +840,13 @@ const proxy = new SmartProxy({
800
840
  };
801
841
  },
802
842
  certProvisionFallbackToAcme: true, // Fall back to ACME if callback fails
843
+ acme: { email: 'ssl@example.com' }, // 'http01' and the fallback order through the built-in ACME client
803
844
  routes: [...]
804
845
  });
805
846
  ```
806
847
 
848
+ `'http01'` and the ACME fallback order the domain's certificate through the Rust engine's ACME client, which answers the HTTP-01 challenge on `acme.port`. Both need `acme` with an `email`. The engine serves the issued certificate, and SmartProxy records its validity, so sweeps skip the domain until its renewal window. While a `certProvisionFunction` is set, the engine orders only on these requests and the provisioning sweeps own renewal. A failed order emits `certificate-failed` and starts the failure cooldown, and a failed `'http01'` order takes no ACME fallback. Certificates the engine issues are not passed to `certStore.save()`, so a restart orders them again.
849
+
807
850
  ### 💾 Consumer-Managed Certificate Storage
808
851
 
809
852
  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:
@@ -926,6 +969,8 @@ interface IRouteMatch {
926
969
  type TPortRange = number | Array<number | { from: number; to: number }>;
927
970
  ```
928
971
 
972
+ > **Path and headers:** A TCP connection is routed by its port, SNI or `Host`, and client address before its requests are read; `path` and `headers` are matched for each HTTP request. A connection whose TLS a route terminates (or plain HTTP to a route without TLS) is admitted under that route while only its path or headers are unknown, and each request then goes to the route that matches it, under that route's security; a request no route serves is answered `502 No route matched`. A route with `path` or `headers` serves only HTTP, so other data on its connections is closed.
973
+
929
974
  ### Action Types
930
975
 
931
976
  | Type | Description |
@@ -976,6 +1021,13 @@ interface IRouteTls {
976
1021
  ciphers?: string;
977
1022
  honorCipherOrder?: boolean;
978
1023
  sessionTimeout?: number;
1024
+ backend?: IRouteBackendTls; // Backend certificate verification (see "Backend TLS verification")
1025
+ }
1026
+
1027
+ interface IRouteBackendTls {
1028
+ rejectUnauthorized?: boolean; // false accepts any backend certificate (default: verify)
1029
+ ca?: string; // PEM certificates replacing the system root store
1030
+ serverName?: string; // SNI and checked name (default: requested domain, else target host)
979
1031
  }
980
1032
  ```
981
1033
 
@@ -1189,6 +1241,9 @@ interface ISmartProxyOptions {
1189
1241
  trustedProxyIPs?: string[]; // Global trusted IP/CIDR list for explicit inbound policies
1190
1242
  sendProxyProtocol?: boolean; // Send PROXY protocol to targets
1191
1243
 
1244
+ // Default backend certificate verification; a route or target tls.backend replaces it
1245
+ backendTls?: { ca?: string; rejectUnauthorized?: boolean };
1246
+
1192
1247
  // Timeouts
1193
1248
  connectionTimeout?: number; // Backend connection timeout (default: 60s)
1194
1249
  initialDataTimeout?: number; // Initial data/SNI timeout (default: 60s)
@@ -1198,8 +1253,8 @@ interface ISmartProxyOptions {
1198
1253
  gracefulShutdownTimeout?: number; // Shutdown grace period (default: 30s)
1199
1254
 
1200
1255
  // Connection limits
1201
- maxConnectionsPerIP?: number; // Per-IP connection limit (default: 100)
1202
- connectionRateLimitPerMinute?: number; // Per-IP rate limit (default: 300/min)
1256
+ maxConnectionsPerIP?: number | null; // Per-IP simultaneous connections (default: 100; null: no limit)
1257
+ connectionRateLimitPerMinute?: number | null; // Per-IP new connections per minute (default: 300; null: no limit)
1203
1258
  securityPolicy?: {
1204
1259
  blockedIps?: string[];
1205
1260
  blockedCidrs?: string[];
@@ -1341,7 +1396,7 @@ try {
1341
1396
 
1342
1397
  ### Certificate Issues
1343
1398
  - ✅ Ensure domain DNS points to your server
1344
- - ✅ Port 80 must be accessible for ACME HTTP-01 challenges
1399
+ - ✅ Port 80 (`acme.port`) must be reachable for ACME HTTP-01 challenges. When a route listens on that port, the proxy answers pending challenge requests there before route matching; otherwise it binds the port only while an order runs
1345
1400
  - ✅ Check DNS propagation with `dig` or `nslookup`
1346
1401
  - ✅ Verify the email in ACME configuration is valid
1347
1402
  - ✅ Use `getCertificateStatus('route-name')` to check cert state
@@ -1351,6 +1406,7 @@ try {
1351
1406
  - ✅ Verify security rules aren't blocking legitimate traffic
1352
1407
  - ✅ Test with `curl -v` for detailed connection output
1353
1408
  - ✅ Enable debug logging with `enableDetailedLogging: true`
1409
+ - ✅ A `502` from a TLS backend with a self-signed or private-CA certificate: set `tls.backend.ca` or `rejectUnauthorized: false` (see "Backend TLS verification")
1354
1410
 
1355
1411
  ### Rust Binary Not Found
1356
1412
 
@@ -1358,7 +1414,7 @@ SmartProxy searches for the Rust binary in this order:
1358
1414
  1. `rustBinaryPath` option in `ISmartProxyOptions`
1359
1415
  2. `SMARTPROXY_RUST_BINARY` environment variable
1360
1416
  3. Platform-specific npm package (`@push.rocks/smartproxy-linux-x64`, etc.)
1361
- 4. `dist_rust/rustproxy_linux_amd64_musl` / `dist_rust/rustproxy_linux_arm64_musl` relative to the package root (static musl binaries built by `tsrust` and shipped in the package; they run on glibc and musl hosts alike), then `dist_rust/rustproxy`
1417
+ 4. `dist_rust/rustproxy_linux_amd64_musl` / `dist_rust/rustproxy_linux_arm64_musl` relative to the package root (static musl binaries built by `rsrepo` and shipped in the package; they run on glibc and musl hosts alike), then `dist_rust/rustproxy`
1362
1418
  5. Local dev build (`./rust/target/release/rustproxy`)
1363
1419
  6. System PATH (`rustproxy`)
1364
1420
 
@@ -1366,6 +1422,7 @@ SmartProxy searches for the Rust binary in this order:
1366
1422
 
1367
1423
  ### QUIC / HTTP3 Caveats
1368
1424
  - **GREASE frames are disabled.** The underlying h3 crate sends [GREASE frames](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) by default to test protocol extensibility. However, some HTTP/3 clients and servers don't properly ignore unknown frame types, causing 400/500 errors or stream hangs ([h3#206](https://github.com/hyperium/h3/issues/206)). SmartProxy disables GREASE on both the server side (for incoming H3 requests) and the client side (for H3 backend connections) to maximize compatibility.
1425
+ - **Raw QUIC routes open a TCP connection per stream.** A route without `enableHttp3` forwards each bidirectional QUIC stream to its first target over its own TCP connection: plain with `tls.mode: 'terminate'`, verified TLS with `'terminate-and-reencrypt'` (see [Backend TLS verification](#backend-tls-verification)). A stream that sees no data for 60 seconds, counting the backend connect and handshake, is closed.
1369
1426
  - **Route security follows the SNI.** Before the QUIC handshake the client's hostname is unknown, so only a route that matches every hostname has its `security` applied there. Otherwise the route is chosen from the handshake's SNI, as on TCP, and that route's `security` applies; a connection without SNI matches no domain-restricted route. HTTP/3 requests are then routed and checked by their `:authority`.
1370
1427
  - **HTTP/3 is pre-release.** The h3 ecosystem (h3 0.0.8, h3-quinn 0.0.10, quinn 0.11) is still pre-1.0. Expect rough edges.
1371
1428
 
@@ -1373,7 +1430,7 @@ SmartProxy searches for the Rust binary in this order:
1373
1430
  - ✅ Use NFTables forwarding for high-traffic routes (Linux only)
1374
1431
  - ✅ Enable connection keep-alive where appropriate
1375
1432
  - ✅ Use `getMetrics()` and `getStatistics()` to identify bottlenecks
1376
- - ✅ Adjust `maxConnectionsPerIP` and `connectionRateLimitPerMinute` based on your workload
1433
+ - ✅ Adjust `maxConnectionsPerIP` and `connectionRateLimitPerMinute` based on your workload. Both count connections per source IP as the listener sees it, before the SNI or any request is read. Behind a reverse proxy that does not send PROXY protocol (such as an HTTP hop that pools its backend connections), every client arrives from the proxy's address and shares one budget; set the limits to `null` there to turn them off and enforce them at the edge that sees the real clients. Omitting a limit keeps its default; `0`, negative and non-integer values are refused, `0` never read as "no limit".
1377
1434
  - ✅ Use `passthrough` TLS mode when backend can handle TLS directly
1378
1435
 
1379
1436
  ## 🏆 Best Practices
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '30.0.1',
6
+ version: '31.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
  }
package/ts/index.ts CHANGED
@@ -7,7 +7,7 @@ export { SmartProxy } from './proxies/smart-proxy/index.js';
7
7
  export { SharedRouteManager as RouteManager } from './core/routing/route-manager.js';
8
8
 
9
9
  // Export smart-proxy models
10
- export type { ISmartProxyOptions, ISmartProxySecurityPolicy, ISmartProxyChallengeOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteFixedResponse, IRouteTls, IRouteContext, TDatagramHandler, IDatagramInfo, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions, IActiveConnectionSnapshotPage } from './proxies/smart-proxy/models/index.js';
10
+ export type { ISmartProxyOptions, ISmartProxySecurityPolicy, ISmartProxyChallengeOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteFixedResponse, IRouteTls, IRouteBackendTls, IRouteContext, TDatagramHandler, IDatagramInfo, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions, IActiveConnectionSnapshotPage } from './proxies/smart-proxy/models/index.js';
11
11
  export type { TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './proxies/smart-proxy/models/interfaces.js';
12
12
  export * from './proxies/smart-proxy/utils/index.js';
13
13
 
@@ -7,4 +7,4 @@ export { SmartProxy } from './smart-proxy/index.js';
7
7
  export { SharedRouteManager as SmartProxyRouteManager } from '../core/routing/route-manager.js';
8
8
  export * from './smart-proxy/utils/index.js';
9
9
  // Export smart-proxy models except IAcmeOptions
10
- export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './smart-proxy/models/index.js';
10
+ export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteBackendTls, IRouteContext } from './smart-proxy/models/index.js';
@@ -27,7 +27,7 @@ export interface ISmartProxyCertStore {
27
27
  /** Remove a cert (optional) */
28
28
  remove?: (domain: string) => Promise<void>;
29
29
  }
30
- import type { IRouteConfig } from './route-types.js';
30
+ import type { IRouteBackendTls, IRouteConfig } from './route-types.js';
31
31
 
32
32
  export interface ISmartProxySecurityPolicy {
33
33
  blockedIps?: string[];
@@ -147,6 +147,12 @@ export interface ISmartProxyOptions {
147
147
  trustedProxyIPs?: string[]; // Global trusted IP/CIDR list for inbound PROXY protocol
148
148
  sendProxyProtocol?: boolean; // Global option to send PROXY protocol to all targets
149
149
 
150
+ /**
151
+ * Default verification of TLS backends; a route's or target's `tls.backend` replaces it.
152
+ * `serverName` belongs to routes and targets.
153
+ */
154
+ backendTls?: Omit<IRouteBackendTls, 'serverName'>;
155
+
150
156
  // Global/default settings
151
157
  defaults?: {
152
158
  target?: {
@@ -199,8 +205,16 @@ export interface ISmartProxyOptions {
199
205
  enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd
200
206
 
201
207
  // Rate limiting and security
202
- maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
203
- connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
208
+ /**
209
+ * Maximum simultaneous connections from a single source IP. Omitted: 100. `null`: no per-IP
210
+ * limit. Must be a positive integer otherwise; 0 and negative values are refused.
211
+ */
212
+ maxConnectionsPerIP?: number | null;
213
+ /**
214
+ * Maximum new connections per minute from a single source IP. Omitted: 300. `null`: no per-IP
215
+ * rate limit. Must be a positive integer otherwise; 0 and negative values are refused.
216
+ */
217
+ connectionRateLimitPerMinute?: number | null;
204
218
  securityPolicy?: ISmartProxySecurityPolicy; // Global ingress block policy, enforced before routing
205
219
 
206
220
  /** Runtime-only challenge enforcement settings. Never store this in route configs. */
@@ -134,6 +134,30 @@ export interface IRouteTls {
134
134
  ciphers?: string; // OpenSSL cipher string
135
135
  honorCipherOrder?: boolean; // Use server's cipher preferences
136
136
  sessionTimeout?: number; // TLS session timeout in seconds
137
+ backend?: IRouteBackendTls; // How the proxy verifies a TLS backend (terminate-and-reencrypt, TLS targets, HTTP/3)
138
+ }
139
+
140
+ /**
141
+ * Verification of a TLS backend the proxy connects to.
142
+ *
143
+ * The whole object applies: a target's `tls.backend`, else the route's `action.tls.backend`,
144
+ * else the proxy-wide `backendTls`, else the system root store. Without `ca` or
145
+ * `rejectUnauthorized: false`, a self-signed or private-CA backend fails verification.
146
+ */
147
+ export interface IRouteBackendTls {
148
+ /** `false` accepts any backend certificate (default: verify) */
149
+ rejectUnauthorized?: boolean;
150
+ /**
151
+ * PEM certificates that replace the system root store for this backend, like Node's `ca`.
152
+ * A certificate is trusted as an issuer, so a self-signed backend certificate must not be a
153
+ * CA certificate (`basicConstraints CA:TRUE`).
154
+ */
155
+ ca?: string;
156
+ /**
157
+ * Name sent as SNI and checked against the backend certificate. Defaults to the requested
158
+ * domain on routes with named domains, else the backend host.
159
+ */
160
+ serverName?: string;
137
161
  }
138
162
 
139
163
  /**
@@ -169,7 +193,10 @@ export interface IRouteSecurity {
169
193
  // Connection limits
170
194
  maxConnections?: number; // Maximum concurrent connections
171
195
 
172
- // Authentication
196
+ /**
197
+ * @deprecated Not supported: route validation refuses a route that sets it.
198
+ * Use basicAuth or jwtAuth.
199
+ */
173
200
  authentication?: IRouteAuthentication;
174
201
 
175
202
  // Rate limiting
@@ -64,7 +64,15 @@ export interface IRustRouteValidationErrorData {
64
64
  routes: IRustRouteValidationIssue[];
65
65
  }
66
66
 
67
- export interface IRustAcmeOptions extends Omit<IAcmeOptions, 'routeForwards'> {}
67
+ export interface IRustAcmeOptions extends Omit<IAcmeOptions, 'routeForwards'> {
68
+ /**
69
+ * Whether the engine orders certificates for `certificate: 'auto'` routes at start
70
+ * and renews them on its own timer (default: true). SmartProxy sets it to false when
71
+ * a `certProvisionFunction` owns provisioning: the engine then orders a certificate
72
+ * only when `provisionCertificate` asks for one.
73
+ */
74
+ autoProvision?: boolean;
75
+ }
68
76
 
69
77
  export interface IRustChallengeOptions extends Required<Pick<
70
78
  ISmartProxyChallengeOptions,
@@ -76,6 +84,7 @@ export interface IRustProxyOptions {
76
84
  preserveSourceIp?: boolean;
77
85
  trustedProxyIPs?: string[];
78
86
  sendProxyProtocol?: boolean;
87
+ backendTls?: ISmartProxyOptions['backendTls'];
79
88
  defaults?: IRustDefaultConfig;
80
89
  connectionTimeout?: number;
81
90
  initialDataTimeout?: number;
@@ -93,8 +102,10 @@ export interface IRustProxyOptions {
93
102
  enableDetailedLogging?: boolean;
94
103
  enableTlsDebugLogging?: boolean;
95
104
  enableRandomizedTimeouts?: boolean;
96
- maxConnectionsPerIp?: number;
97
- connectionRateLimitPerMinute?: number;
105
+ /** `null`: no per-IP limit (the engine's `None`). */
106
+ maxConnectionsPerIp?: number | null;
107
+ /** `null`: no per-IP rate limit (the engine's `None`). */
108
+ connectionRateLimitPerMinute?: number | null;
98
109
  keepAliveTreatment?: ISmartProxyOptions['keepAliveTreatment'];
99
110
  keepAliveInactivityMultiplier?: number;
100
111
  extendedKeepAliveLifetime?: number;
@@ -119,6 +130,13 @@ export interface IRustCertificateStatus {
119
130
  isValid: boolean;
120
131
  }
121
132
 
133
+ /** A certificate the engine ordered through ACME and now serves. */
134
+ export interface IRustProvisionedCertificate {
135
+ domain: string;
136
+ /** The issued certificate chain in PEM; the private key stays in the engine. */
137
+ cert: string;
138
+ }
139
+
122
140
  export interface IRustThroughputSample {
123
141
  timestampMs: number;
124
142
  bytesIn: number;
@@ -3,6 +3,7 @@ import { logger } from '../../core/utils/logger.js';
3
3
  import {
4
4
  RUST_ROUTE_VALIDATION_ERROR_CODE,
5
5
  type IRustCertificateStatus,
6
+ type IRustProvisionedCertificate,
6
7
  type IRustChallengeOptions,
7
8
  type IRustMetricsSnapshot,
8
9
  type IRustProxyOptions,
@@ -105,7 +106,7 @@ type TSmartProxyCommands = {
105
106
  getStatistics: { params: Record<string, never>; result: IRustStatistics };
106
107
  getActiveConnectionSnapshots: { params: IActiveConnectionSnapshotOptions; result: IActiveConnectionSnapshot[] };
107
108
  getActiveConnectionSnapshotPage: { params: IActiveConnectionSnapshotOptions; result: IActiveConnectionSnapshotPage };
108
- provisionCertificate: { params: { routeName: string }; result: void };
109
+ provisionCertificate: { params: { routeName: string; domain?: string }; result: IRustProvisionedCertificate };
109
110
  renewCertificate: { params: { routeName: string }; result: void };
110
111
  getCertificateStatus: { params: { routeName: string }; result: IRustCertificateStatus | null };
111
112
  getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
@@ -302,8 +303,17 @@ export class RustProxyBridge extends plugins.EventEmitter {
302
303
  return this.bridge.sendCommand('getActiveConnectionSnapshotPage', options);
303
304
  }
304
305
 
305
- public async provisionCertificate(routeName: string): Promise<void> {
306
- await this.bridge.sendCommand('provisionCertificate', { routeName });
306
+ /**
307
+ * Order a certificate through the engine's ACME client: for `domain`, which the
308
+ * route must serve, or else for the route's first domain. `timeoutMs` overrides
309
+ * the bridge request timeout; an order can take minutes.
310
+ */
311
+ public async provisionCertificate(
312
+ routeName: string,
313
+ domain?: string,
314
+ timeoutMs?: number,
315
+ ): Promise<IRustProvisionedCertificate> {
316
+ return this.bridge.sendCommand('provisionCertificate', { routeName, domain }, { timeoutMs });
307
317
  }
308
318
 
309
319
  public async renewCertificate(routeName: string): Promise<void> {