This commit is contained in:
2025-05-29 12:15:53 +00:00
parent b0beeae19e
commit ab1ea95070
12 changed files with 855 additions and 38 deletions

View File

@ -146,18 +146,42 @@ export class RouteConnectionHandler {
);
}
// Start TLS SNI handling
this.handleTlsConnection(socket, record);
// Handle the connection - wait for initial data to determine if it's TLS
this.handleInitialData(socket, record);
}
/**
* Handle a connection and wait for TLS handshake for SNI extraction if needed
* Handle initial data from a connection to determine routing
*/
private handleTlsConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
const connectionId = record.id;
const localPort = record.localPort;
let initialDataReceived = false;
// Check if any routes on this port require TLS handling
const allRoutes = this.routeManager.getAllRoutes();
const needsTlsHandling = allRoutes.some(route => {
// Check if route matches this port
const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
return matchesPort &&
route.action.type === 'forward' &&
route.action.tls &&
(route.action.tls.mode === 'terminate' ||
route.action.tls.mode === 'passthrough');
});
// If no routes require TLS handling and it's not port 443, route immediately
if (!needsTlsHandling && localPort !== 443) {
// Set up error handler
socket.on('error', this.connectionManager.handleError('incoming', record));
// Route immediately for non-TLS connections
this.routeConnection(socket, record, '', undefined);
return;
}
// Otherwise, wait for initial data to check if it's TLS
// Set an initial timeout for handshake data
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
if (!initialDataReceived) {
@ -382,6 +406,56 @@ export class RouteConnectionHandler {
});
}
// Apply route-specific security checks
if (route.security) {
// Check IP allow/block lists
if (route.security.ipAllowList || route.security.ipBlockList) {
const isIPAllowed = this.securityManager.isIPAuthorized(
remoteIP,
route.security.ipAllowList || [],
route.security.ipBlockList || []
);
if (!isIPAllowed) {
logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
connectionId,
remoteIP,
routeName: route.name || 'unnamed',
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
return;
}
}
// Check max connections per route
if (route.security.maxConnections !== undefined) {
// TODO: Implement per-route connection tracking
// For now, log that this feature is not yet implemented
if (this.settings.enableDetailedLogging) {
logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
connectionId,
routeName: route.name,
component: 'route-handler'
});
}
}
// Check authentication requirements
if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
// Authentication checks would typically happen at the HTTP layer
// For non-HTTP connections or passthrough, we can't enforce authentication
if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
connectionId,
routeName: route.name,
tlsMode: route.action.tls?.mode || 'none',
component: 'route-handler'
});
}
}
}
// Handle the route based on its action type
switch (route.action.type) {