@push.rocks/smartproxy 19.5.2 → 19.5.3
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/proxies/http-proxy/models/types.js +2 -2
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +2 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +0 -1
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +5 -6
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +84 -5
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/route-manager.js +7 -8
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +10 -9
- package/package.json +2 -2
- package/readme.hints.md +131 -5
- package/readme.problems.md +86 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/http-proxy/models/types.ts +1 -1
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +1 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +0 -3
- package/ts/proxies/smart-proxy/nftables-manager.ts +4 -5
- package/ts/proxies/smart-proxy/route-connection-handler.ts +97 -4
- package/ts/proxies/smart-proxy/route-manager.ts +7 -8
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +11 -9
|
@@ -146,18 +146,42 @@ export class RouteConnectionHandler {
|
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
this.
|
|
149
|
+
// Handle the connection - wait for initial data to determine if it's TLS
|
|
150
|
+
this.handleInitialData(socket, record);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
|
-
* Handle
|
|
154
|
+
* Handle initial data from a connection to determine routing
|
|
155
155
|
*/
|
|
156
|
-
private
|
|
156
|
+
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
|
|
157
157
|
const connectionId = record.id;
|
|
158
158
|
const localPort = record.localPort;
|
|
159
159
|
let initialDataReceived = false;
|
|
160
160
|
|
|
161
|
+
// Check if any routes on this port require TLS handling
|
|
162
|
+
const allRoutes = this.routeManager.getAllRoutes();
|
|
163
|
+
const needsTlsHandling = allRoutes.some(route => {
|
|
164
|
+
// Check if route matches this port
|
|
165
|
+
const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
|
|
166
|
+
|
|
167
|
+
return matchesPort &&
|
|
168
|
+
route.action.type === 'forward' &&
|
|
169
|
+
route.action.tls &&
|
|
170
|
+
(route.action.tls.mode === 'terminate' ||
|
|
171
|
+
route.action.tls.mode === 'passthrough');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// If no routes require TLS handling and it's not port 443, route immediately
|
|
175
|
+
if (!needsTlsHandling && localPort !== 443) {
|
|
176
|
+
// Set up error handler
|
|
177
|
+
socket.on('error', this.connectionManager.handleError('incoming', record));
|
|
178
|
+
|
|
179
|
+
// Route immediately for non-TLS connections
|
|
180
|
+
this.routeConnection(socket, record, '', undefined);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Otherwise, wait for initial data to check if it's TLS
|
|
161
185
|
// Set an initial timeout for handshake data
|
|
162
186
|
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
|
|
163
187
|
if (!initialDataReceived) {
|
|
@@ -296,6 +320,12 @@ export class RouteConnectionHandler {
|
|
|
296
320
|
const localPort = record.localPort;
|
|
297
321
|
const remoteIP = record.remoteIP;
|
|
298
322
|
|
|
323
|
+
// Check if this is an HTTP proxy port
|
|
324
|
+
const isHttpProxyPort = this.settings.useHttpProxy?.includes(localPort);
|
|
325
|
+
|
|
326
|
+
// For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
|
|
327
|
+
const skipDomainCheck = isHttpProxyPort && !record.isTLS;
|
|
328
|
+
|
|
299
329
|
// Find matching route
|
|
300
330
|
const routeMatch = this.routeManager.findMatchingRoute({
|
|
301
331
|
port: localPort,
|
|
@@ -303,6 +333,7 @@ export class RouteConnectionHandler {
|
|
|
303
333
|
clientIp: remoteIP,
|
|
304
334
|
path: undefined, // We don't have path info at this point
|
|
305
335
|
tlsVersion: undefined, // We don't extract TLS version yet
|
|
336
|
+
skipDomainCheck: skipDomainCheck,
|
|
306
337
|
});
|
|
307
338
|
|
|
308
339
|
if (!routeMatch) {
|
|
@@ -382,6 +413,56 @@ export class RouteConnectionHandler {
|
|
|
382
413
|
});
|
|
383
414
|
}
|
|
384
415
|
|
|
416
|
+
// Apply route-specific security checks
|
|
417
|
+
if (route.security) {
|
|
418
|
+
// Check IP allow/block lists
|
|
419
|
+
if (route.security.ipAllowList || route.security.ipBlockList) {
|
|
420
|
+
const isIPAllowed = this.securityManager.isIPAuthorized(
|
|
421
|
+
remoteIP,
|
|
422
|
+
route.security.ipAllowList || [],
|
|
423
|
+
route.security.ipBlockList || []
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
if (!isIPAllowed) {
|
|
427
|
+
logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
|
|
428
|
+
connectionId,
|
|
429
|
+
remoteIP,
|
|
430
|
+
routeName: route.name || 'unnamed',
|
|
431
|
+
component: 'route-handler'
|
|
432
|
+
});
|
|
433
|
+
socket.end();
|
|
434
|
+
this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Check max connections per route
|
|
440
|
+
if (route.security.maxConnections !== undefined) {
|
|
441
|
+
// TODO: Implement per-route connection tracking
|
|
442
|
+
// For now, log that this feature is not yet implemented
|
|
443
|
+
if (this.settings.enableDetailedLogging) {
|
|
444
|
+
logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
|
|
445
|
+
connectionId,
|
|
446
|
+
routeName: route.name,
|
|
447
|
+
component: 'route-handler'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Check authentication requirements
|
|
453
|
+
if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
|
|
454
|
+
// Authentication checks would typically happen at the HTTP layer
|
|
455
|
+
// For non-HTTP connections or passthrough, we can't enforce authentication
|
|
456
|
+
if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
|
|
457
|
+
logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
|
|
458
|
+
connectionId,
|
|
459
|
+
routeName: route.name,
|
|
460
|
+
tlsMode: route.action.tls?.mode || 'none',
|
|
461
|
+
component: 'route-handler'
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
385
466
|
|
|
386
467
|
// Handle the route based on its action type
|
|
387
468
|
switch (route.action.type) {
|
|
@@ -634,6 +715,18 @@ export class RouteConnectionHandler {
|
|
|
634
715
|
// No TLS settings - check if this port should use HttpProxy
|
|
635
716
|
const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
|
|
636
717
|
|
|
718
|
+
// Debug logging
|
|
719
|
+
if (this.settings.enableDetailedLogging) {
|
|
720
|
+
logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.httpProxyBridge.getHttpProxy()}`, {
|
|
721
|
+
connectionId,
|
|
722
|
+
localPort: record.localPort,
|
|
723
|
+
useHttpProxy: this.settings.useHttpProxy,
|
|
724
|
+
isHttpProxyPort,
|
|
725
|
+
hasHttpProxy: !!this.httpProxyBridge.getHttpProxy(),
|
|
726
|
+
component: 'route-handler'
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
637
730
|
if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
|
|
638
731
|
// Forward non-TLS connections to HttpProxy if configured
|
|
639
732
|
if (this.settings.enableDetailedLogging) {
|
|
@@ -211,9 +211,10 @@ export class RouteManager extends plugins.EventEmitter {
|
|
|
211
211
|
|
|
212
212
|
/**
|
|
213
213
|
* Check if a client IP is allowed by a route's security settings
|
|
214
|
+
* @deprecated Security is now checked in route-connection-handler.ts after route matching
|
|
214
215
|
*/
|
|
215
216
|
private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
|
|
216
|
-
const security = route.
|
|
217
|
+
const security = route.security;
|
|
217
218
|
|
|
218
219
|
if (!security) {
|
|
219
220
|
return true; // No security settings means allowed
|
|
@@ -330,8 +331,9 @@ export class RouteManager extends plugins.EventEmitter {
|
|
|
330
331
|
clientIp: string;
|
|
331
332
|
path?: string;
|
|
332
333
|
tlsVersion?: string;
|
|
334
|
+
skipDomainCheck?: boolean;
|
|
333
335
|
}): IRouteMatchResult | null {
|
|
334
|
-
const { port, domain, clientIp, path, tlsVersion } = options;
|
|
336
|
+
const { port, domain, clientIp, path, tlsVersion, skipDomainCheck } = options;
|
|
335
337
|
|
|
336
338
|
// Get all routes for this port
|
|
337
339
|
const routesForPort = this.getRoutesForPort(port);
|
|
@@ -340,7 +342,7 @@ export class RouteManager extends plugins.EventEmitter {
|
|
|
340
342
|
for (const route of routesForPort) {
|
|
341
343
|
// Check domain match
|
|
342
344
|
// If the route has domain restrictions and we have a domain to check
|
|
343
|
-
if (route.match.domains) {
|
|
345
|
+
if (route.match.domains && !skipDomainCheck) {
|
|
344
346
|
// If no domain was provided (non-TLS or no SNI), this route doesn't match
|
|
345
347
|
if (!domain) {
|
|
346
348
|
continue;
|
|
@@ -351,6 +353,7 @@ export class RouteManager extends plugins.EventEmitter {
|
|
|
351
353
|
}
|
|
352
354
|
}
|
|
353
355
|
// If route has no domain restrictions, it matches all domains
|
|
356
|
+
// If skipDomainCheck is true, we skip domain validation for HTTP connections
|
|
354
357
|
|
|
355
358
|
// Check path match if specified in both route and request
|
|
356
359
|
if (path && route.match.path) {
|
|
@@ -371,12 +374,8 @@ export class RouteManager extends plugins.EventEmitter {
|
|
|
371
374
|
continue;
|
|
372
375
|
}
|
|
373
376
|
|
|
374
|
-
// Check security settings
|
|
375
|
-
if (!this.isClientIpAllowed(route, clientIp)) {
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
377
|
// All checks passed, this route matches
|
|
378
|
+
// NOTE: Security is checked AFTER route matching in route-connection-handler.ts
|
|
380
379
|
return { route };
|
|
381
380
|
}
|
|
382
381
|
|
|
@@ -625,14 +625,6 @@ export function createNfTablesRoute(
|
|
|
625
625
|
}
|
|
626
626
|
};
|
|
627
627
|
|
|
628
|
-
// Add security if allowed or blocked IPs are specified
|
|
629
|
-
if (options.ipAllowList?.length || options.ipBlockList?.length) {
|
|
630
|
-
action.security = {
|
|
631
|
-
ipAllowList: options.ipAllowList,
|
|
632
|
-
ipBlockList: options.ipBlockList
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
|
|
636
628
|
// Add TLS options if needed
|
|
637
629
|
if (options.useTls) {
|
|
638
630
|
action.tls = {
|
|
@@ -641,11 +633,21 @@ export function createNfTablesRoute(
|
|
|
641
633
|
}
|
|
642
634
|
|
|
643
635
|
// Create the route config
|
|
644
|
-
|
|
636
|
+
const routeConfig: IRouteConfig = {
|
|
645
637
|
name,
|
|
646
638
|
match,
|
|
647
639
|
action
|
|
648
640
|
};
|
|
641
|
+
|
|
642
|
+
// Add security if allowed or blocked IPs are specified
|
|
643
|
+
if (options.ipAllowList?.length || options.ipBlockList?.length) {
|
|
644
|
+
routeConfig.security = {
|
|
645
|
+
ipAllowList: options.ipAllowList,
|
|
646
|
+
ipBlockList: options.ipBlockList
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return routeConfig;
|
|
649
651
|
}
|
|
650
652
|
|
|
651
653
|
/**
|