@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.
- package/.smartconfig.json +5 -3
- package/changelog.md +91 -0
- package/dist_rust/rustproxy_linux_amd64_musl +0 -0
- package/dist_rust/rustproxy_linux_amd64_musl.third-party-notices.json +3358 -0
- package/dist_rust/rustproxy_linux_amd64_musl.third-party-notices.txt +44997 -0
- package/dist_rust/rustproxy_linux_amd64_musl.tsrust-build.json +5 -5
- package/dist_rust/rustproxy_linux_arm64_musl +0 -0
- package/dist_rust/rustproxy_linux_arm64_musl.third-party-notices.json +3358 -0
- package/dist_rust/rustproxy_linux_arm64_musl.third-party-notices.txt +44997 -0
- package/dist_rust/rustproxy_linux_arm64_musl.tsrust-build.json +5 -5
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/proxies/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +16 -3
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +27 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/models/rust-types.d.ts +18 -2
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +7 -2
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +8 -3
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +18 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +106 -38
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +46 -3
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +293 -3
- package/dist_ts/proxies/smart-proxy/utils/rust-config.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/utils/rust-config.js +9 -5
- package/native-notices/manifest.json +13 -1
- package/native-notices/readme.md +4 -2
- package/native-notices/runtime/libunwind-22.1.2/license.txt +311 -0
- package/package.json +10 -12
- package/readme.md +70 -13
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -1
- package/ts/proxies/index.ts +1 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +17 -3
- package/ts/proxies/smart-proxy/models/route-types.ts +28 -1
- package/ts/proxies/smart-proxy/models/rust-types.ts +21 -3
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +13 -3
- package/ts/proxies/smart-proxy/smart-proxy.ts +115 -40
- package/ts/proxies/smart-proxy/utils/route-validator.ts +329 -4
- 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:
|
|
517
|
+
match: { ports: 5432 },
|
|
489
518
|
action: {
|
|
490
519
|
type: 'forward',
|
|
491
520
|
forwardingEngine: 'nftables',
|
|
492
|
-
targets: [{ host: '
|
|
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.
|
|
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;
|
|
1202
|
-
connectionRateLimitPerMinute?: number;
|
|
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
|
|
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 `
|
|
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
|
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: '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
|
|
package/ts/proxies/index.ts
CHANGED
|
@@ -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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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 };
|
|
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
|
-
|
|
306
|
-
|
|
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> {
|