diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index f53c501ea..4df621684 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -4,7 +4,8 @@ import { domains, aiProviders, resourceAiProviders, - siteResources + siteResources, + exitNodes } from "@server/db"; import { and, @@ -22,6 +23,7 @@ import config from "@server/lib/config"; import { resources, sites, Target, targets } from "@server/db"; import createPathRewriteMiddleware from "./middleware"; import { sanitize, encodePath, validatePathRewriteConfig } from "./utils"; +import regionalCache from "@server/lib/cache"; const redirectHttpsMiddlewareName = "redirect-to-https"; const badgerMiddlewareName = "badger"; @@ -55,6 +57,21 @@ export async function getTraefikConfig( browserGatewayUiUrl: string | null = null, // UNUSED BUT USED IN PRIVATE aiGatewayUrl: string | null = null ): Promise { + // Get the exit node but cache it for 5 minutes to avoid hitting the DB too often + const exitNodeCacheKey = `exitNode:${exitNodeId}`; + let exitNode = + await regionalCache.get( + exitNodeCacheKey + ); + if (!exitNode) { + [exitNode] = await db + .select() + .from(exitNodes) + .where(eq(exitNodes.exitNodeId, exitNodeId)) + .limit(1); + await regionalCache.set(exitNodeCacheKey, exitNode, 300); + } + // Get resources with their targets and sites in a single optimized query // Start from sites on this exit node, then join to targets and resources const resourcesWithTargetsAndSites = await db @@ -866,7 +883,7 @@ export async function getTraefikConfig( const srKey = `inference-sr${sr.siteResourceId}`; const routerName = `${srKey}-router`; const serviceName = `${srKey}-service`; - const rule = `Host(\`${alias}\`)`; + const rule = `Host(\`${alias}\`) && ClientIP(${exitNode.address})`; // restrict to coming from the exit node ip range that the client is connected to const domainParts = alias.split("."); const wildCard = diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 47d6b69c8..5cd5549c8 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -58,6 +58,7 @@ import { getValidCertificatesForDomains } from "#private/lib/certificates"; import { build } from "@server/build"; +import regionalCache from "#private/lib/cache"; const redirectHttpsMiddlewareName = "redirect-to-https"; const redirectToRootMiddlewareName = "redirect-to-root"; @@ -94,6 +95,21 @@ export async function getTraefikConfig( browserGatewayUiUrl: string | null = null, aiGatewayUrl: string | null = null ): Promise { + // Get the exit node but cache it for 5 minutes to avoid hitting the DB too often + const exitNodeCacheKey = `exitNode:${exitNodeId}`; + let exitNode = + await regionalCache.get( + exitNodeCacheKey + ); + if (!exitNode) { + [exitNode] = await db + .select() + .from(exitNodes) + .where(eq(exitNodes.exitNodeId, exitNodeId)) + .limit(1); + await regionalCache.set(exitNodeCacheKey, exitNode, 300); + } + // Get resources with their targets and sites in a single optimized query // Start from sites on this exit node, then join to targets and resources const resourcesWithTargetsAndSites = await db @@ -1546,7 +1562,8 @@ export async function getTraefikConfig( // is overridden to a different host than the resource's own. In the // default case, leave the Host header untouched so it's visible on // the other end. - const aiGatewayOverride = config.getRawConfig().server.ai_gateway_override; + const aiGatewayOverride = + config.getRawConfig().server.ai_gateway_override; // Public inference resources: same TLS/cert-resolver handling as // plain http-mode resources, but the service points at the AI @@ -1679,7 +1696,7 @@ export async function getTraefikConfig( const srKey = `inference-sr${sr.siteResourceId}`; const routerName = `${srKey}-router`; const serviceName = `${srKey}-service`; - const rule = `Host(\`${alias}\`)`; + const rule = `Host(\`${alias}\`) && ClientIP(${exitNode.address})`; // restrict to coming from the exit node ip range that the client is connected to let tls: any = {}; if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {