From 234819f8f22ccb7ecd5df39ecd0d5a2e0e02d0fb Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 14 Aug 2026 11:53:10 -0400 Subject: [PATCH] Dont load private resource inference without a exit node loaded --- server/lib/traefik/getTraefikConfig.ts | 172 +++++++------- .../private/lib/traefik/getTraefikConfig.ts | 212 +++++++++--------- 2 files changed, 196 insertions(+), 188 deletions(-) diff --git a/server/lib/traefik/getTraefikConfig.ts b/server/lib/traefik/getTraefikConfig.ts index d0d553910..da9fb0810 100644 --- a/server/lib/traefik/getTraefikConfig.ts +++ b/server/lib/traefik/getTraefikConfig.ts @@ -767,15 +767,14 @@ export async function getTraefikConfig( if (!config_output.http.middlewares) { config_output.http.middlewares = {}; } - config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = - { - headers: { - customRequestHeaders: { - [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), - [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource" - } + config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = { + headers: { + customRequestHeaders: { + [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), + [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource" } - }; + } + }; config_output.http.middlewares[ aiGatewayTrustMiddlewareNameSiteResource ] = { @@ -798,15 +797,14 @@ export async function getTraefikConfig( const enableAiGatewayClientIpHeader = config.getRawConfig().server.enable_ai_gateway_client_ip_header; if (enableAiGatewayClientIpHeader) { - config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = - { - plugin: { - badger: { - disableForwardAuth: true, - realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER - } + config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = { + plugin: { + badger: { + disableForwardAuth: true, + realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER } - }; + } + }; } // Public inference resources: same TLS/cert-resolver handling as @@ -935,90 +933,94 @@ export async function getTraefikConfig( ) ); - for (const sr of siteResourcesInference) { - if (!sr.enabled || !sr.fullDomain) continue; + if (exitNode) { + for (const sr of siteResourcesInference) { + if (!sr.enabled || !sr.fullDomain) continue; - if (!config_output.http.routers) config_output.http.routers = {}; - if (!config_output.http.services) config_output.http.services = {}; + if (!config_output.http.routers) + config_output.http.routers = {}; + if (!config_output.http.services) + config_output.http.services = {}; - const fullDomain = sr.fullDomain; - const srKey = `inference-sr${sr.siteResourceId}`; - const routerName = `${srKey}-router`; - const serviceName = `${srKey}-service`; - const rule = `Host(\`${fullDomain}\`) && ClientIP(${exitNode.address})`; // restrict to coming from the exit node ip range that the client is connected to + const fullDomain = sr.fullDomain; + const srKey = `inference-sr${sr.siteResourceId}`; + const routerName = `${srKey}-router`; + const serviceName = `${srKey}-service`; + const rule = `Host(\`${fullDomain}\`) && ClientIP(${exitNode.address})`; // restrict to coming from the exit node ip range that the client is connected to - const domainParts = fullDomain.split("."); - const wildCard = - domainParts.length <= 2 - ? `*.${domainParts.join(".")}` - : `*.${domainParts.slice(1).join(".")}`; + const domainParts = fullDomain.split("."); + const wildCard = + domainParts.length <= 2 + ? `*.${domainParts.join(".")}` + : `*.${domainParts.slice(1).join(".")}`; - const globalDefaultResolver = - config.getRawConfig().traefik.cert_resolver; - const globalDefaultPreferWildcard = - config.getRawConfig().traefik.prefer_wildcard_cert; + const globalDefaultResolver = + config.getRawConfig().traefik.cert_resolver; + const globalDefaultPreferWildcard = + config.getRawConfig().traefik.prefer_wildcard_cert; - const tls = { - certResolver: globalDefaultResolver, - ...(globalDefaultPreferWildcard - ? { domains: [{ main: wildCard }] } - : {}) - }; + const tls = { + certResolver: globalDefaultResolver, + ...(globalDefaultPreferWildcard + ? { domains: [{ main: wildCard }] } + : {}) + }; - const srHeadersMiddlewareName = `${srKey}-headers-middleware`; - if (!config_output.http.middlewares) { - config_output.http.middlewares = {}; - } - config_output.http.middlewares[srHeadersMiddlewareName] = { - headers: { - customRequestHeaders: { - ...(aiGatewayHost ? { Host: aiGatewayHost } : {}), - "p-host": fullDomain - } + const srHeadersMiddlewareName = `${srKey}-headers-middleware`; + if (!config_output.http.middlewares) { + config_output.http.middlewares = {}; } - }; + config_output.http.middlewares[srHeadersMiddlewareName] = { + headers: { + customRequestHeaders: { + ...(aiGatewayHost ? { Host: aiGatewayHost } : {}), + "p-host": fullDomain + } + } + }; - const additionalMiddlewares = - config.getRawConfig().traefik.additional_middlewares || []; - const routerMiddlewares = [ - ...(enableAiGatewayClientIpHeader - ? [aiGatewayClientIpMiddlewareName] - : []), - aiGatewayTrustMiddlewareNameSiteResource, - srHeadersMiddlewareName, - ...additionalMiddlewares - ]; + const additionalMiddlewares = + config.getRawConfig().traefik.additional_middlewares || []; + const routerMiddlewares = [ + ...(enableAiGatewayClientIpHeader + ? [aiGatewayClientIpMiddlewareName] + : []), + aiGatewayTrustMiddlewareNameSiteResource, + srHeadersMiddlewareName, + ...additionalMiddlewares + ]; - if (sr.ssl) { - config_output.http.routers[routerName + "-redirect"] = { + if (sr.ssl) { + config_output.http.routers[routerName + "-redirect"] = { + entryPoints: [ + config.getRawConfig().traefik.http_entrypoint + ], + middlewares: [redirectHttpsMiddlewareName], + service: serviceName, + rule, + priority: 200 // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + }; + } + + config_output.http.routers[routerName] = { entryPoints: [ - config.getRawConfig().traefik.http_entrypoint + sr.ssl + ? config.getRawConfig().traefik.https_entrypoint + : config.getRawConfig().traefik.http_entrypoint ], - middlewares: [redirectHttpsMiddlewareName], + middlewares: routerMiddlewares, service: serviceName, rule, - priority: 200 // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + priority: 200, // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + ...(sr.ssl ? { tls } : {}) + }; + + config_output.http.services[serviceName] = { + loadBalancer: { + servers: [{ url: aiGatewayUrl }] + } }; } - - config_output.http.routers[routerName] = { - entryPoints: [ - sr.ssl - ? config.getRawConfig().traefik.https_entrypoint - : config.getRawConfig().traefik.http_entrypoint - ], - middlewares: routerMiddlewares, - service: serviceName, - rule, - priority: 200, // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. - ...(sr.ssl ? { tls } : {}) - }; - - config_output.http.services[serviceName] = { - loadBalancer: { - servers: [{ url: aiGatewayUrl }] - } - }; } } diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index f90305bd1..cc841fc1e 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -1581,15 +1581,14 @@ export async function getTraefikConfig( "ai-gateway-trust-headers-resource"; const aiGatewayTrustMiddlewareNameSiteResource = "ai-gateway-trust-headers-site-resource"; - config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = - { - headers: { - customRequestHeaders: { - [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), - [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource" - } + config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = { + headers: { + customRequestHeaders: { + [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), + [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource" } - }; + } + }; config_output.http.middlewares[ aiGatewayTrustMiddlewareNameSiteResource ] = { @@ -1612,15 +1611,14 @@ export async function getTraefikConfig( const enableAiGatewayClientIpHeader = config.getRawConfig().server.enable_ai_gateway_client_ip_header; if (enableAiGatewayClientIpHeader) { - config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = - { - plugin: { - badger: { - disableForwardAuth: true, - realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER - } + config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = { + plugin: { + badger: { + disableForwardAuth: true, + realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER } - }; + } + }; } // Public inference resources: same TLS/cert-resolver handling as @@ -1742,108 +1740,116 @@ export async function getTraefikConfig( }; } - // Private (siteResource) inference resources: routed by their alias - // instead of a public fullDomain, and deliberately WITHOUT the - // badger middleware - no per-user auth/policy stack exists for - // siteResources today (see plan doc), so gating here is - // reachability-only for now. - for (const sr of siteResourcesInference) { - if (!sr.enabled || !sr.fullDomain) continue; + if (exitNode) { + // Private (siteResource) inference resources: routed by their alias + // instead of a public fullDomain, and deliberately WITHOUT the + // badger middleware - no per-user auth/policy stack exists for + // siteResources today (see plan doc), so gating here is + // reachability-only for now. + for (const sr of siteResourcesInference) { + if (!sr.enabled || !sr.fullDomain) continue; - if (!config_output.http.routers) config_output.http.routers = {}; - if (!config_output.http.services) config_output.http.services = {}; + if (!config_output.http.routers) + config_output.http.routers = {}; + if (!config_output.http.services) + config_output.http.services = {}; - const fullDomain = sr.fullDomain; - const srKey = `inference-sr${sr.siteResourceId}`; - const routerName = `${srKey}-router`; - const serviceName = `${srKey}-service`; - const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to + const fullDomain = sr.fullDomain; + const srKey = `inference-sr${sr.siteResourceId}`; + const routerName = `${srKey}-router`; + const serviceName = `${srKey}-service`; + const rule = `Host(\`${fullDomain}\`) && 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) { - const domainParts = fullDomain.split("."); - const wildCard = - domainParts.length <= 2 - ? `*.${domainParts.join(".")}` - : `*.${domainParts.slice(1).join(".")}`; + let tls: any = {}; + if ( + !privateConfig.getRawPrivateConfig().flags.use_pangolin_dns + ) { + const domainParts = fullDomain.split("."); + const wildCard = + domainParts.length <= 2 + ? `*.${domainParts.join(".")}` + : `*.${domainParts.slice(1).join(".")}`; - const globalDefaultResolver = - config.getRawConfig().traefik.cert_resolver; - const globalDefaultPreferWildcard = - config.getRawConfig().traefik.prefer_wildcard_cert; + const globalDefaultResolver = + config.getRawConfig().traefik.cert_resolver; + const globalDefaultPreferWildcard = + config.getRawConfig().traefik.prefer_wildcard_cert; - tls = { - certResolver: globalDefaultResolver, - ...(globalDefaultPreferWildcard - ? { domains: [{ main: wildCard }] } - : {}) - }; - } else { - const matchingCert = validCerts.find( - (cert) => cert.queriedDomain === fullDomain - ); - if (!matchingCert) { - logger.debug( - `No matching certificate found for inference siteResource fullDomain: ${fullDomain}` + tls = { + certResolver: globalDefaultResolver, + ...(globalDefaultPreferWildcard + ? { domains: [{ main: wildCard }] } + : {}) + }; + } else { + const matchingCert = validCerts.find( + (cert) => cert.queriedDomain === fullDomain ); - continue; - } - } - - const additionalMiddlewares = - config.getRawConfig().traefik.additional_middlewares || []; - const routerMiddlewares: string[] = [ - ...(enableAiGatewayClientIpHeader - ? [aiGatewayClientIpMiddlewareName] - : []), - aiGatewayTrustMiddlewareNameSiteResource - ]; - - if (aiGatewayOverride) { - const srHeadersMiddlewareName = `${srKey}-headers-middleware`; - config_output.http.middlewares[srHeadersMiddlewareName] = { - headers: { - customRequestHeaders: { - ...(aiGatewayHost ? { Host: aiGatewayHost } : {}), - "p-host": fullDomain - } + if (!matchingCert) { + logger.debug( + `No matching certificate found for inference siteResource fullDomain: ${fullDomain}` + ); + continue; } - }; - routerMiddlewares.push(srHeadersMiddlewareName); - } + } - routerMiddlewares.push(...additionalMiddlewares); + const additionalMiddlewares = + config.getRawConfig().traefik.additional_middlewares || []; + const routerMiddlewares: string[] = [ + ...(enableAiGatewayClientIpHeader + ? [aiGatewayClientIpMiddlewareName] + : []), + aiGatewayTrustMiddlewareNameSiteResource + ]; - if (sr.ssl) { - config_output.http.routers[routerName + "-redirect"] = { + if (aiGatewayOverride) { + const srHeadersMiddlewareName = `${srKey}-headers-middleware`; + config_output.http.middlewares[srHeadersMiddlewareName] = { + headers: { + customRequestHeaders: { + ...(aiGatewayHost + ? { Host: aiGatewayHost } + : {}), + "p-host": fullDomain + } + } + }; + routerMiddlewares.push(srHeadersMiddlewareName); + } + + routerMiddlewares.push(...additionalMiddlewares); + + if (sr.ssl) { + config_output.http.routers[routerName + "-redirect"] = { + entryPoints: [ + config.getRawConfig().traefik.http_entrypoint + ], + middlewares: [redirectHttpsMiddlewareName], + service: serviceName, + rule, + priority: 200 // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + }; + } + + config_output.http.routers[routerName] = { entryPoints: [ - config.getRawConfig().traefik.http_entrypoint + sr.ssl + ? config.getRawConfig().traefik.https_entrypoint + : config.getRawConfig().traefik.http_entrypoint ], - middlewares: [redirectHttpsMiddlewareName], + middlewares: routerMiddlewares, service: serviceName, rule, - priority: 200 // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + priority: 200, // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. + ...(sr.ssl ? { tls } : {}) + }; + + config_output.http.services[serviceName] = { + loadBalancer: { + servers: [{ url: aiGatewayUrl }] + } }; } - - config_output.http.routers[routerName] = { - entryPoints: [ - sr.ssl - ? config.getRawConfig().traefik.https_entrypoint - : config.getRawConfig().traefik.http_entrypoint - ], - middlewares: routerMiddlewares, - service: serviceName, - rule, - priority: 200, // we want to match on the site resource first because the clientIP rule is more specific than the public inference resource rule, which is just the exit node IP range. so we give it a higher priority to ensure it matches first. - ...(sr.ssl ? { tls } : {}) - }; - - config_output.http.services[serviceName] = { - loadBalancer: { - servers: [{ url: aiGatewayUrl }] - } - }; } }