BREAKING CHANGE(smart-proxy): 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
This commit is contained in:
96
readme.md
96
readme.md
@@ -36,6 +36,7 @@ Whether you're building microservices, deploying edge infrastructure, or need a
|
||||
| 📊 **Live Metrics** | Real-time throughput, connection counts, and performance data |
|
||||
| 🔧 **Dynamic Management** | Add/remove ports and routes at runtime without restarts |
|
||||
| 🔄 **PROXY Protocol** | Full PROXY protocol v1/v2 support for preserving client information |
|
||||
| 💾 **Consumer Cert Storage** | Bring your own persistence — SmartProxy never writes certs to disk |
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
@@ -456,6 +457,51 @@ const proxy = new SmartProxy({
|
||||
});
|
||||
```
|
||||
|
||||
### 💾 Consumer-Managed Certificate Storage
|
||||
|
||||
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:
|
||||
|
||||
```typescript
|
||||
const proxy = new SmartProxy({
|
||||
routes: [...],
|
||||
|
||||
certProvisionFunction: async (domain) => myAcme.provision(domain),
|
||||
|
||||
// Your persistence layer — SmartProxy calls these hooks
|
||||
certStore: {
|
||||
// Called once on startup to pre-load persisted certs
|
||||
loadAll: async () => {
|
||||
const certs = await myDb.getAllCerts();
|
||||
return certs.map(c => ({
|
||||
domain: c.domain,
|
||||
publicKey: c.certPem,
|
||||
privateKey: c.keyPem,
|
||||
ca: c.caPem, // optional
|
||||
}));
|
||||
},
|
||||
|
||||
// Called after each successful cert provision
|
||||
save: async (domain, publicKey, privateKey, ca) => {
|
||||
await myDb.upsertCert({ domain, certPem: publicKey, keyPem: privateKey, caPem: ca });
|
||||
},
|
||||
|
||||
// Optional: called when a cert should be removed
|
||||
remove: async (domain) => {
|
||||
await myDb.deleteCert(domain);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Startup flow:**
|
||||
1. Rust engine starts
|
||||
2. Default self-signed `*` fallback cert is loaded (unless `disableDefaultCert: true`)
|
||||
3. `certStore.loadAll()` is called → all returned certs are loaded into the Rust TLS stack
|
||||
4. `certProvisionFunction` runs for any remaining `certificate: 'auto'` routes (skipping domains already loaded from the store)
|
||||
5. After each successful provision, `certStore.save()` is called
|
||||
|
||||
This means your second startup is instant — no re-provisioning needed for domains that already have valid certs in your store.
|
||||
|
||||
## 🏛️ Architecture
|
||||
|
||||
SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
||||
@@ -488,7 +534,7 @@ SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
||||
|
||||
- **Rust Engine** handles all networking, TLS, HTTP proxying, connection management, security, and metrics
|
||||
- **TypeScript** provides the npm API, configuration types, route helpers, validation, and socket handler callbacks
|
||||
- **IPC** — The TypeScript wrapper uses [`@push.rocks/smartrust`](https://code.foss.global/push.rocks/smartrust) for type-safe JSON commands/events over stdin/stdout
|
||||
- **IPC** — The TypeScript wrapper uses JSON commands/events over stdin/stdout to communicate with the Rust binary
|
||||
- **Socket Relay** — A Unix domain socket server for routes requiring TypeScript-side handling (socket handlers, dynamic host/port functions)
|
||||
|
||||
## 🎯 Route Configuration Reference
|
||||
@@ -497,7 +543,7 @@ SmartProxy uses a hybrid **Rust + TypeScript** architecture:
|
||||
|
||||
```typescript
|
||||
interface IRouteMatch {
|
||||
ports: number | number[] | Array<{ from: number; to: number }>; // Port(s) to listen on
|
||||
ports: number | number[] | Array<{ from: number; to: number }>; // Required — port(s) to listen on
|
||||
domains?: string | string[]; // 'example.com', '*.example.com'
|
||||
path?: string; // '/api/*', '/users/:id'
|
||||
clientIp?: string[]; // ['10.0.0.0/8', '192.168.*']
|
||||
@@ -517,11 +563,16 @@ interface IRouteMatch {
|
||||
|
||||
```typescript
|
||||
interface IRouteTarget {
|
||||
host: string | string[] | ((context: IRouteContext) => string);
|
||||
host: string | string[] | ((context: IRouteContext) => string | string[]);
|
||||
port: number | 'preserve' | ((context: IRouteContext) => number);
|
||||
tls?: { ... }; // Per-target TLS override
|
||||
priority?: number; // Target priority
|
||||
match?: ITargetMatch; // Sub-match within a route (by port, path, headers, method)
|
||||
tls?: IRouteTls; // Per-target TLS override
|
||||
priority?: number; // Target priority
|
||||
match?: ITargetMatch; // Sub-match within a route (by port, path, headers, method)
|
||||
websocket?: IRouteWebSocket;
|
||||
loadBalancing?: IRouteLoadBalancing;
|
||||
sendProxyProtocol?: boolean;
|
||||
headers?: IRouteHeaders;
|
||||
advanced?: IRouteAdvanced;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -613,6 +664,7 @@ import {
|
||||
createPortMappingRoute, // Port mapping with context
|
||||
createOffsetPortMappingRoute, // Simple port offset
|
||||
createDynamicRoute, // Dynamic host/port via functions
|
||||
createPortOffset, // Port offset factory
|
||||
|
||||
// Security Modifiers
|
||||
addRateLimiting, // Add rate limiting to any route
|
||||
@@ -680,7 +732,6 @@ interface ISmartProxyOptions {
|
||||
port?: number; // HTTP-01 challenge port (default: 80)
|
||||
renewThresholdDays?: number; // Days before expiry to renew (default: 30)
|
||||
autoRenew?: boolean; // Enable auto-renewal (default: true)
|
||||
certificateStore?: string; // Directory to store certs (default: './certs')
|
||||
renewCheckIntervalHours?: number; // Renewal check interval (default: 24)
|
||||
};
|
||||
|
||||
@@ -688,6 +739,12 @@ interface ISmartProxyOptions {
|
||||
certProvisionFunction?: (domain: string) => Promise<ICert | 'http01'>;
|
||||
certProvisionFallbackToAcme?: boolean; // Fall back to ACME on failure (default: true)
|
||||
|
||||
// Consumer-managed certificate persistence (see "Consumer-Managed Certificate Storage")
|
||||
certStore?: ISmartProxyCertStore;
|
||||
|
||||
// Self-signed fallback
|
||||
disableDefaultCert?: boolean; // Disable '*' self-signed fallback (default: false)
|
||||
|
||||
// Global defaults
|
||||
defaults?: {
|
||||
target?: { host: string; port: number };
|
||||
@@ -729,6 +786,26 @@ interface ISmartProxyOptions {
|
||||
}
|
||||
```
|
||||
|
||||
### ISmartProxyCertStore Interface
|
||||
|
||||
```typescript
|
||||
interface ISmartProxyCertStore {
|
||||
/** Called once on startup to pre-load persisted certs */
|
||||
loadAll: () => Promise<Array<{
|
||||
domain: string;
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
ca?: string;
|
||||
}>>;
|
||||
|
||||
/** Called after each successful cert provision */
|
||||
save: (domain: string, publicKey: string, privateKey: string, ca?: string) => Promise<void>;
|
||||
|
||||
/** Optional: remove a cert from storage */
|
||||
remove?: (domain: string) => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### IMetrics Interface
|
||||
|
||||
The `getMetrics()` method returns a cached metrics adapter that polls the Rust engine:
|
||||
@@ -758,6 +835,10 @@ metrics.requests.total(); // Total requests
|
||||
metrics.totals.bytesIn(); // Total bytes received
|
||||
metrics.totals.bytesOut(); // Total bytes sent
|
||||
metrics.totals.connections(); // Total connections
|
||||
|
||||
// Percentiles
|
||||
metrics.percentiles.connectionDuration(); // { p50, p95, p99 }
|
||||
metrics.percentiles.bytesTransferred(); // { in: { p50, p95, p99 }, out: { p50, p95, p99 } }
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
@@ -802,6 +883,7 @@ SmartProxy searches for the Rust binary in this order:
|
||||
7. **✅ Validate Routes** — Use `RouteValidator.validateRoutes()` to catch config errors before deployment
|
||||
8. **🔀 Atomic Updates** — Use `updateRoutes()` for hot-reloading routes (mutex-locked, no downtime)
|
||||
9. **🎮 Use Socket Handlers** — For protocols beyond HTTP, implement custom socket handlers instead of fighting the proxy model
|
||||
10. **💾 Use `certStore`** — Persist certs in your own storage to avoid re-provisioning on every restart
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
||||
Reference in New Issue
Block a user