@push.rocks/smartproxy 19.6.17 → 20.0.1
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 +142 -0
- package/dist_ts/core/utils/shared-security-manager.js +30 -5
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +4 -0
- package/dist_ts/proxies/http-proxy/request-handler.js +104 -21
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +4 -0
- package/dist_ts/proxies/http-proxy/websocket-handler.js +78 -8
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +19 -2
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +14 -11
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +112 -28
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +23 -23
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +13 -13
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +4 -7
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +41 -25
- package/package.json +3 -2
- package/readme.plan.md +139 -266
- package/ts/core/utils/shared-security-manager.ts +33 -4
- package/ts/proxies/http-proxy/request-handler.ts +124 -21
- package/ts/proxies/http-proxy/websocket-handler.ts +96 -8
- package/ts/proxies/smart-proxy/models/route-types.ts +34 -8
- package/ts/proxies/smart-proxy/nftables-manager.ts +14 -10
- package/ts/proxies/smart-proxy/route-connection-handler.ts +132 -28
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +14 -14
- package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -6
- package/ts/proxies/smart-proxy/utils/route-utils.ts +3 -6
- package/ts/proxies/smart-proxy/utils/route-validators.ts +38 -21
package/readme.plan.md
CHANGED
|
@@ -1,281 +1,154 @@
|
|
|
1
|
-
# SmartProxy
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
###
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
###
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
# SmartProxy Enhanced Routing Plan
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Implement enhanced routing structure with multiple targets per route, sub-matching capabilities, and target-specific overrides to enable more elegant and DRY configurations.
|
|
5
|
+
|
|
6
|
+
## Key Changes
|
|
7
|
+
|
|
8
|
+
### 1. Update Route Target Interface
|
|
9
|
+
- Add `match` property to `IRouteTarget` for sub-matching within routes
|
|
10
|
+
- Add target-specific override properties (tls, websocket, loadBalancing, etc.)
|
|
11
|
+
- Add priority field for controlling match order
|
|
12
|
+
|
|
13
|
+
### 2. Update Route Action Interface
|
|
14
|
+
- Remove singular `target` property
|
|
15
|
+
- Use only `targets` array (single target = array with one element)
|
|
16
|
+
- Maintain backwards compatibility during migration
|
|
17
|
+
|
|
18
|
+
### 3. Implementation Steps
|
|
19
|
+
|
|
20
|
+
#### Phase 1: Type Updates
|
|
21
|
+
- [x] Update `IRouteTarget` interface in `route-types.ts`
|
|
22
|
+
- Add `match?: ITargetMatch` property
|
|
23
|
+
- Add override properties (tls, websocket, etc.)
|
|
24
|
+
- Add `priority?: number` field
|
|
25
|
+
- [x] Create `ITargetMatch` interface for sub-matching criteria
|
|
26
|
+
- [x] Update `IRouteAction` to use only `targets: IRouteTarget[]`
|
|
27
|
+
|
|
28
|
+
#### Phase 2: Route Resolution Logic
|
|
29
|
+
- [x] Update route matching logic to handle multiple targets
|
|
30
|
+
- [x] Implement target sub-matching algorithm:
|
|
31
|
+
1. Sort targets by priority (highest first)
|
|
32
|
+
2. For each target with a match property, check if request matches
|
|
33
|
+
3. Use first matching target, or fallback to target without match
|
|
34
|
+
- [x] Ensure target-specific settings override route-level settings
|
|
35
|
+
|
|
36
|
+
#### Phase 3: Code Migration
|
|
37
|
+
- [x] Find all occurrences of `action.target` and update to use `action.targets`
|
|
38
|
+
- [x] Update route helpers and utilities
|
|
39
|
+
- [x] Update certificate manager to handle multiple targets
|
|
40
|
+
- [x] Update connection handlers
|
|
41
|
+
|
|
42
|
+
#### Phase 4: Testing
|
|
43
|
+
- [x] Update existing tests to use new format
|
|
44
|
+
- [ ] Add tests for multi-target scenarios
|
|
45
|
+
- [ ] Add tests for sub-matching logic
|
|
46
|
+
- [ ] Add tests for setting overrides
|
|
47
|
+
|
|
48
|
+
#### Phase 5: Documentation
|
|
49
|
+
- [ ] Update type documentation
|
|
50
|
+
- [ ] Add examples of new routing patterns
|
|
51
|
+
- [ ] Document migration path for existing configs
|
|
52
|
+
|
|
53
|
+
## Example Configurations
|
|
54
|
+
|
|
55
|
+
### Before (Current)
|
|
36
56
|
```typescript
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else {
|
|
54
|
-
// Use custom certificate
|
|
55
|
-
const customCert = result as plugins.tsclass.network.ICert;
|
|
56
|
-
|
|
57
|
-
// Convert to internal certificate format
|
|
58
|
-
const certData: ICertificateData = {
|
|
59
|
-
cert: customCert.cert,
|
|
60
|
-
key: customCert.key,
|
|
61
|
-
ca: customCert.ca || '',
|
|
62
|
-
issueDate: new Date(),
|
|
63
|
-
expiryDate: this.extractExpiryDate(customCert.cert)
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Store and apply certificate
|
|
67
|
-
await this.certStore.saveCertificate(routeName, certData);
|
|
68
|
-
await this.applyCertificate(primaryDomain, certData);
|
|
69
|
-
this.updateCertStatus(routeName, 'valid', 'custom', certData);
|
|
70
|
-
|
|
71
|
-
logger.log('info', `Custom certificate applied for ${primaryDomain}`, {
|
|
72
|
-
domain: primaryDomain,
|
|
73
|
-
expiryDate: certData.expiryDate
|
|
74
|
-
});
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
logger.log('error', `Custom cert provision failed for ${primaryDomain}: ${error.message}`, {
|
|
79
|
-
domain: primaryDomain,
|
|
80
|
-
error: error.message
|
|
81
|
-
});
|
|
82
|
-
// Configuration option to control fallback behavior
|
|
83
|
-
if (this.smartProxy.settings.certProvisionFallbackToAcme !== false) {
|
|
84
|
-
logger.log('info', `Falling back to Let's Encrypt for ${primaryDomain}`);
|
|
85
|
-
} else {
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
57
|
+
// Need separate routes for different ports/paths
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
match: { domains: ['api.example.com'], ports: [80] },
|
|
61
|
+
action: {
|
|
62
|
+
type: 'forward',
|
|
63
|
+
target: { host: 'backend', port: 8080 },
|
|
64
|
+
tls: { mode: 'terminate' }
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
match: { domains: ['api.example.com'], ports: [443] },
|
|
69
|
+
action: {
|
|
70
|
+
type: 'forward',
|
|
71
|
+
target: { host: 'backend', port: 8081 },
|
|
72
|
+
tls: { mode: 'passthrough' }
|
|
88
73
|
}
|
|
89
74
|
}
|
|
90
|
-
|
|
91
|
-
// Existing Let's Encrypt logic continues here...
|
|
92
|
-
if (!this.smartAcme) {
|
|
93
|
-
throw new Error('SmartAcme not initialized...');
|
|
94
|
-
}
|
|
95
|
-
// ... rest of existing code
|
|
96
|
-
}
|
|
75
|
+
]
|
|
97
76
|
```
|
|
98
77
|
|
|
99
|
-
|
|
100
|
-
**New method**: `extractExpiryDate()`
|
|
101
|
-
|
|
102
|
-
- [ ] Parse PEM certificate to extract expiry date
|
|
103
|
-
- [ ] Use existing certificate parsing utilities
|
|
104
|
-
- [ ] Handle parse errors gracefully
|
|
105
|
-
|
|
78
|
+
### After (Enhanced)
|
|
106
79
|
```typescript
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
// Single route with multiple targets
|
|
81
|
+
{
|
|
82
|
+
match: { domains: ['api.example.com'], ports: [80, 443] },
|
|
83
|
+
action: {
|
|
84
|
+
type: 'forward',
|
|
85
|
+
targets: [
|
|
86
|
+
{
|
|
87
|
+
match: { ports: [80] },
|
|
88
|
+
host: 'backend',
|
|
89
|
+
port: 8080,
|
|
90
|
+
tls: { mode: 'terminate' }
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
match: { ports: [443] },
|
|
94
|
+
host: 'backend',
|
|
95
|
+
port: 8081,
|
|
96
|
+
tls: { mode: 'passthrough' }
|
|
97
|
+
}
|
|
98
|
+
]
|
|
115
99
|
}
|
|
116
100
|
}
|
|
117
101
|
```
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
**File**: `ts/proxies/smart-proxy/index.ts`
|
|
121
|
-
|
|
122
|
-
- [ ] Pass `certProvisionFunction` from options to CertificateManager
|
|
123
|
-
- [ ] Validate function if provided
|
|
124
|
-
|
|
125
|
-
#### 5. Add Type Safety and Validation
|
|
126
|
-
**Tasks**:
|
|
127
|
-
- [ ] Validate returned certificate has required fields (cert, key, ca)
|
|
128
|
-
- [ ] Check certificate validity dates
|
|
129
|
-
- [ ] Ensure certificate matches requested domain
|
|
130
|
-
|
|
131
|
-
#### 6. Update Certificate Renewal Logic
|
|
132
|
-
**Location**: `checkAndRenewCertificates()`
|
|
133
|
-
|
|
134
|
-
- [ ] Ensure renewal checks work for both ACME and custom certificates
|
|
135
|
-
- [ ] Custom certificates should go through the same `provisionAcmeCertificate()` path
|
|
136
|
-
- [ ] The existing renewal logic already calls `provisionCertificate()` which will use our modified flow
|
|
137
|
-
|
|
103
|
+
### Advanced Example
|
|
138
104
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
105
|
+
{
|
|
106
|
+
match: { domains: ['app.example.com'], ports: [443] },
|
|
107
|
+
action: {
|
|
108
|
+
type: 'forward',
|
|
109
|
+
tls: { mode: 'terminate', certificate: 'auto' }, // Route-level default
|
|
110
|
+
websocket: { enabled: true }, // Route-level default
|
|
111
|
+
targets: [
|
|
112
|
+
{
|
|
113
|
+
match: { path: '/api/v2/*' },
|
|
114
|
+
host: 'api-v2',
|
|
115
|
+
port: 8082,
|
|
116
|
+
priority: 10
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
match: { path: '/api/*', headers: { 'X-Version': 'v1' } },
|
|
120
|
+
host: 'api-v1',
|
|
121
|
+
port: 8081,
|
|
122
|
+
priority: 5
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
match: { path: '/ws/*' },
|
|
126
|
+
host: 'websocket-server',
|
|
127
|
+
port: 8090,
|
|
128
|
+
websocket: {
|
|
129
|
+
enabled: true,
|
|
130
|
+
rewritePath: '/' // Strip /ws prefix
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
// Default target (no match property)
|
|
135
|
+
host: 'web-backend',
|
|
136
|
+
port: 8080
|
|
137
|
+
}
|
|
138
|
+
]
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
```
|
|
152
142
|
|
|
153
|
-
|
|
154
|
-
**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- [ ] Document ICert structure requirements
|
|
166
|
-
|
|
167
|
-
### API Design
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
// Example usage
|
|
171
|
-
const proxy = new SmartProxy({
|
|
172
|
-
certProvisionFunction: async (domain: string) => {
|
|
173
|
-
// Option 1: Return custom certificate
|
|
174
|
-
const customCert = await myCustomCA.generateCert(domain);
|
|
175
|
-
return {
|
|
176
|
-
cert: customCert.certificate,
|
|
177
|
-
key: customCert.privateKey,
|
|
178
|
-
ca: customCert.chain
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Option 2: Use Let's Encrypt for certain domains
|
|
182
|
-
if (domain.endsWith('.internal')) {
|
|
183
|
-
return customCert;
|
|
184
|
-
}
|
|
185
|
-
return 'http01'; // Fallback to Let's Encrypt
|
|
186
|
-
},
|
|
187
|
-
certProvisionFallbackToAcme: true, // Default: true
|
|
188
|
-
routes: [...]
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Configuration Options to Add
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
interface ISmartProxyOptions {
|
|
196
|
-
// Existing options...
|
|
197
|
-
|
|
198
|
-
// Custom certificate provision function
|
|
199
|
-
certProvisionFunction?: (domain: string) => Promise<TSmartProxyCertProvisionObject>;
|
|
200
|
-
|
|
201
|
-
// Whether to fallback to ACME if custom provision fails
|
|
202
|
-
certProvisionFallbackToAcme?: boolean; // Default: true
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### Error Handling Strategy
|
|
207
|
-
|
|
208
|
-
1. **Custom Function Errors**:
|
|
209
|
-
- Log detailed error with domain context
|
|
210
|
-
- Option A: Fallback to Let's Encrypt (safer)
|
|
211
|
-
- Option B: Fail certificate provisioning (stricter)
|
|
212
|
-
- Make this configurable via option?
|
|
213
|
-
|
|
214
|
-
2. **Invalid Certificate Returns**:
|
|
215
|
-
- Validate certificate structure
|
|
216
|
-
- Check expiry dates
|
|
217
|
-
- Verify domain match
|
|
218
|
-
|
|
219
|
-
### Testing Plan
|
|
220
|
-
|
|
221
|
-
1. **Unit Tests**:
|
|
222
|
-
- Mock certProvisionFunction returns
|
|
223
|
-
- Test validation logic
|
|
224
|
-
- Test error scenarios
|
|
225
|
-
|
|
226
|
-
2. **Integration Tests**:
|
|
227
|
-
- Real certificate generation
|
|
228
|
-
- Renewal cycle testing
|
|
229
|
-
- Mixed custom/Let's Encrypt scenarios
|
|
230
|
-
|
|
231
|
-
### Backward Compatibility
|
|
232
|
-
|
|
233
|
-
- If no `certProvisionFunction` provided, behavior unchanged
|
|
234
|
-
- Existing routes with 'auto' certificates continue using Let's Encrypt
|
|
235
|
-
- No breaking changes to existing API
|
|
236
|
-
|
|
237
|
-
### Future Enhancements
|
|
238
|
-
|
|
239
|
-
1. **Per-Route Custom Functions**:
|
|
240
|
-
- Allow different provision functions per route
|
|
241
|
-
- Override global function at route level
|
|
242
|
-
|
|
243
|
-
2. **Certificate Events**:
|
|
244
|
-
- Emit events for custom cert provisioning
|
|
245
|
-
- Allow monitoring/logging hooks
|
|
246
|
-
|
|
247
|
-
3. **Async Certificate Updates**:
|
|
248
|
-
- Support updating certificates outside renewal cycle
|
|
249
|
-
- Hot-reload certificates without restart
|
|
250
|
-
|
|
251
|
-
### Implementation Notes
|
|
252
|
-
|
|
253
|
-
1. **Certificate Status Tracking**:
|
|
254
|
-
- The `updateCertStatus()` method needs to support a new type: 'custom'
|
|
255
|
-
- Current types are 'acme' and 'static'
|
|
256
|
-
- This helps distinguish custom certificates in monitoring/logs
|
|
257
|
-
|
|
258
|
-
2. **Certificate Store Integration**:
|
|
259
|
-
- Custom certificates are stored the same way as ACME certificates
|
|
260
|
-
- They participate in the same renewal cycle
|
|
261
|
-
- The store handles persistence across restarts
|
|
262
|
-
|
|
263
|
-
3. **Existing Methods to Reuse**:
|
|
264
|
-
- `applyCertificate()` - Already handles applying certs to routes
|
|
265
|
-
- `isCertificateValid()` - Can validate custom certificates
|
|
266
|
-
- `certStore.saveCertificate()` - Handles storage
|
|
267
|
-
|
|
268
|
-
### Implementation Priority
|
|
269
|
-
|
|
270
|
-
1. Core functionality (steps 1-3)
|
|
271
|
-
2. Type safety and validation (step 5)
|
|
272
|
-
3. Renewal support (step 6)
|
|
273
|
-
4. Tests (step 7)
|
|
274
|
-
5. Documentation (step 8)
|
|
275
|
-
|
|
276
|
-
### Estimated Effort
|
|
277
|
-
|
|
278
|
-
- Core implementation: 4-6 hours
|
|
279
|
-
- Testing: 2-3 hours
|
|
280
|
-
- Documentation: 1 hour
|
|
281
|
-
- Total: ~8-10 hours
|
|
143
|
+
## Benefits
|
|
144
|
+
1. **DRY Configuration**: No need to duplicate common settings across routes
|
|
145
|
+
2. **Flexibility**: Different backends for different ports/paths within same domain
|
|
146
|
+
3. **Clarity**: All routing for a domain in one place
|
|
147
|
+
4. **Performance**: Single route lookup instead of multiple
|
|
148
|
+
5. **Backwards Compatible**: Can migrate gradually
|
|
149
|
+
|
|
150
|
+
## Migration Strategy
|
|
151
|
+
1. Keep support for `target` temporarily with deprecation warning
|
|
152
|
+
2. Auto-convert `target` to `targets: [target]` internally
|
|
153
|
+
3. Update documentation with migration examples
|
|
154
|
+
4. Remove `target` support in next major version
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
trackConnection,
|
|
14
14
|
removeConnection,
|
|
15
15
|
cleanupExpiredRateLimits,
|
|
16
|
-
parseBasicAuthHeader
|
|
16
|
+
parseBasicAuthHeader,
|
|
17
|
+
normalizeIP
|
|
17
18
|
} from './security-utils.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -78,7 +79,15 @@ export class SharedSecurityManager {
|
|
|
78
79
|
* @returns Number of connections from this IP
|
|
79
80
|
*/
|
|
80
81
|
public getConnectionCountByIP(ip: string): number {
|
|
81
|
-
|
|
82
|
+
// Check all normalized variants of the IP
|
|
83
|
+
const variants = normalizeIP(ip);
|
|
84
|
+
for (const variant of variants) {
|
|
85
|
+
const info = this.connectionsByIP.get(variant);
|
|
86
|
+
if (info) {
|
|
87
|
+
return info.connections.size;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return 0;
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
/**
|
|
@@ -88,7 +97,19 @@ export class SharedSecurityManager {
|
|
|
88
97
|
* @param connectionId - The connection ID to associate
|
|
89
98
|
*/
|
|
90
99
|
public trackConnectionByIP(ip: string, connectionId: string): void {
|
|
91
|
-
|
|
100
|
+
// Check if any variant already exists
|
|
101
|
+
const variants = normalizeIP(ip);
|
|
102
|
+
let existingKey: string | null = null;
|
|
103
|
+
|
|
104
|
+
for (const variant of variants) {
|
|
105
|
+
if (this.connectionsByIP.has(variant)) {
|
|
106
|
+
existingKey = variant;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Use existing key or the original IP
|
|
112
|
+
trackConnection(existingKey || ip, connectionId, this.connectionsByIP);
|
|
92
113
|
}
|
|
93
114
|
|
|
94
115
|
/**
|
|
@@ -98,7 +119,15 @@ export class SharedSecurityManager {
|
|
|
98
119
|
* @param connectionId - The connection ID to remove
|
|
99
120
|
*/
|
|
100
121
|
public removeConnectionByIP(ip: string, connectionId: string): void {
|
|
101
|
-
|
|
122
|
+
// Check all variants to find where the connection is tracked
|
|
123
|
+
const variants = normalizeIP(ip);
|
|
124
|
+
|
|
125
|
+
for (const variant of variants) {
|
|
126
|
+
if (this.connectionsByIP.has(variant)) {
|
|
127
|
+
removeConnection(variant, connectionId, this.connectionsByIP);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
102
131
|
}
|
|
103
132
|
|
|
104
133
|
/**
|