Dont load private resource inference without a exit node loaded

This commit is contained in:
Owen
2026-08-14 11:53:10 -04:00
parent b34a8d116b
commit 234819f8f2
2 changed files with 196 additions and 188 deletions
+87 -85
View File
@@ -767,15 +767,14 @@ export async function getTraefikConfig(
if (!config_output.http.middlewares) { if (!config_output.http.middlewares) {
config_output.http.middlewares = {}; config_output.http.middlewares = {};
} }
config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = {
{ headers: {
headers: { customRequestHeaders: {
customRequestHeaders: { [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(),
[AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource"
[AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource"
}
} }
}; }
};
config_output.http.middlewares[ config_output.http.middlewares[
aiGatewayTrustMiddlewareNameSiteResource aiGatewayTrustMiddlewareNameSiteResource
] = { ] = {
@@ -798,15 +797,14 @@ export async function getTraefikConfig(
const enableAiGatewayClientIpHeader = const enableAiGatewayClientIpHeader =
config.getRawConfig().server.enable_ai_gateway_client_ip_header; config.getRawConfig().server.enable_ai_gateway_client_ip_header;
if (enableAiGatewayClientIpHeader) { if (enableAiGatewayClientIpHeader) {
config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = {
{ plugin: {
plugin: { badger: {
badger: { disableForwardAuth: true,
disableForwardAuth: true, realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER
realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER
}
} }
}; }
};
} }
// Public inference resources: same TLS/cert-resolver handling as // Public inference resources: same TLS/cert-resolver handling as
@@ -935,90 +933,94 @@ export async function getTraefikConfig(
) )
); );
for (const sr of siteResourcesInference) { if (exitNode) {
if (!sr.enabled || !sr.fullDomain) continue; for (const sr of siteResourcesInference) {
if (!sr.enabled || !sr.fullDomain) continue;
if (!config_output.http.routers) config_output.http.routers = {}; if (!config_output.http.routers)
if (!config_output.http.services) config_output.http.services = {}; config_output.http.routers = {};
if (!config_output.http.services)
config_output.http.services = {};
const fullDomain = sr.fullDomain; const fullDomain = sr.fullDomain;
const srKey = `inference-sr${sr.siteResourceId}`; const srKey = `inference-sr${sr.siteResourceId}`;
const routerName = `${srKey}-router`; const routerName = `${srKey}-router`;
const serviceName = `${srKey}-service`; 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 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 domainParts = fullDomain.split(".");
const wildCard = const wildCard =
domainParts.length <= 2 domainParts.length <= 2
? `*.${domainParts.join(".")}` ? `*.${domainParts.join(".")}`
: `*.${domainParts.slice(1).join(".")}`; : `*.${domainParts.slice(1).join(".")}`;
const globalDefaultResolver = const globalDefaultResolver =
config.getRawConfig().traefik.cert_resolver; config.getRawConfig().traefik.cert_resolver;
const globalDefaultPreferWildcard = const globalDefaultPreferWildcard =
config.getRawConfig().traefik.prefer_wildcard_cert; config.getRawConfig().traefik.prefer_wildcard_cert;
const tls = { const tls = {
certResolver: globalDefaultResolver, certResolver: globalDefaultResolver,
...(globalDefaultPreferWildcard ...(globalDefaultPreferWildcard
? { domains: [{ main: wildCard }] } ? { domains: [{ main: wildCard }] }
: {}) : {})
}; };
const srHeadersMiddlewareName = `${srKey}-headers-middleware`; const srHeadersMiddlewareName = `${srKey}-headers-middleware`;
if (!config_output.http.middlewares) { if (!config_output.http.middlewares) {
config_output.http.middlewares = {}; config_output.http.middlewares = {};
}
config_output.http.middlewares[srHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": fullDomain
}
} }
}; config_output.http.middlewares[srHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
...(aiGatewayHost ? { Host: aiGatewayHost } : {}),
"p-host": fullDomain
}
}
};
const additionalMiddlewares = const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || []; config.getRawConfig().traefik.additional_middlewares || [];
const routerMiddlewares = [ const routerMiddlewares = [
...(enableAiGatewayClientIpHeader ...(enableAiGatewayClientIpHeader
? [aiGatewayClientIpMiddlewareName] ? [aiGatewayClientIpMiddlewareName]
: []), : []),
aiGatewayTrustMiddlewareNameSiteResource, aiGatewayTrustMiddlewareNameSiteResource,
srHeadersMiddlewareName, srHeadersMiddlewareName,
...additionalMiddlewares ...additionalMiddlewares
]; ];
if (sr.ssl) { if (sr.ssl) {
config_output.http.routers[routerName + "-redirect"] = { 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: [ entryPoints: [
config.getRawConfig().traefik.http_entrypoint sr.ssl
? config.getRawConfig().traefik.https_entrypoint
: config.getRawConfig().traefik.http_entrypoint
], ],
middlewares: [redirectHttpsMiddlewareName], middlewares: routerMiddlewares,
service: serviceName, service: serviceName,
rule, 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 }]
}
};
} }
} }
+109 -103
View File
@@ -1581,15 +1581,14 @@ export async function getTraefikConfig(
"ai-gateway-trust-headers-resource"; "ai-gateway-trust-headers-resource";
const aiGatewayTrustMiddlewareNameSiteResource = const aiGatewayTrustMiddlewareNameSiteResource =
"ai-gateway-trust-headers-site-resource"; "ai-gateway-trust-headers-site-resource";
config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = config_output.http.middlewares[aiGatewayTrustMiddlewareNameResource] = {
{ headers: {
headers: { customRequestHeaders: {
customRequestHeaders: { [AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(),
[AI_GATEWAY_TRUST_HEADER]: getAiGatewayTrustToken(), [AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource"
[AI_GATEWAY_RESOURCE_TYPE_HEADER]: "resource"
}
} }
}; }
};
config_output.http.middlewares[ config_output.http.middlewares[
aiGatewayTrustMiddlewareNameSiteResource aiGatewayTrustMiddlewareNameSiteResource
] = { ] = {
@@ -1612,15 +1611,14 @@ export async function getTraefikConfig(
const enableAiGatewayClientIpHeader = const enableAiGatewayClientIpHeader =
config.getRawConfig().server.enable_ai_gateway_client_ip_header; config.getRawConfig().server.enable_ai_gateway_client_ip_header;
if (enableAiGatewayClientIpHeader) { if (enableAiGatewayClientIpHeader) {
config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = config_output.http.middlewares[aiGatewayClientIpMiddlewareName] = {
{ plugin: {
plugin: { badger: {
badger: { disableForwardAuth: true,
disableForwardAuth: true, realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER
realIpHeader: AI_GATEWAY_CLIENT_IP_HEADER
}
} }
}; }
};
} }
// Public inference resources: same TLS/cert-resolver handling as // 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 if (exitNode) {
// instead of a public fullDomain, and deliberately WITHOUT the // Private (siteResource) inference resources: routed by their alias
// badger middleware - no per-user auth/policy stack exists for // instead of a public fullDomain, and deliberately WITHOUT the
// siteResources today (see plan doc), so gating here is // badger middleware - no per-user auth/policy stack exists for
// reachability-only for now. // siteResources today (see plan doc), so gating here is
for (const sr of siteResourcesInference) { // reachability-only for now.
if (!sr.enabled || !sr.fullDomain) continue; for (const sr of siteResourcesInference) {
if (!sr.enabled || !sr.fullDomain) continue;
if (!config_output.http.routers) config_output.http.routers = {}; if (!config_output.http.routers)
if (!config_output.http.services) config_output.http.services = {}; config_output.http.routers = {};
if (!config_output.http.services)
config_output.http.services = {};
const fullDomain = sr.fullDomain; const fullDomain = sr.fullDomain;
const srKey = `inference-sr${sr.siteResourceId}`; const srKey = `inference-sr${sr.siteResourceId}`;
const routerName = `${srKey}-router`; const routerName = `${srKey}-router`;
const serviceName = `${srKey}-service`; 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 rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
let tls: any = {}; let tls: any = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) { if (
const domainParts = fullDomain.split("."); !privateConfig.getRawPrivateConfig().flags.use_pangolin_dns
const wildCard = ) {
domainParts.length <= 2 const domainParts = fullDomain.split(".");
? `*.${domainParts.join(".")}` const wildCard =
: `*.${domainParts.slice(1).join(".")}`; domainParts.length <= 2
? `*.${domainParts.join(".")}`
: `*.${domainParts.slice(1).join(".")}`;
const globalDefaultResolver = const globalDefaultResolver =
config.getRawConfig().traefik.cert_resolver; config.getRawConfig().traefik.cert_resolver;
const globalDefaultPreferWildcard = const globalDefaultPreferWildcard =
config.getRawConfig().traefik.prefer_wildcard_cert; config.getRawConfig().traefik.prefer_wildcard_cert;
tls = { tls = {
certResolver: globalDefaultResolver, certResolver: globalDefaultResolver,
...(globalDefaultPreferWildcard ...(globalDefaultPreferWildcard
? { domains: [{ main: wildCard }] } ? { domains: [{ main: wildCard }] }
: {}) : {})
}; };
} else { } else {
const matchingCert = validCerts.find( const matchingCert = validCerts.find(
(cert) => cert.queriedDomain === fullDomain (cert) => cert.queriedDomain === fullDomain
);
if (!matchingCert) {
logger.debug(
`No matching certificate found for inference siteResource fullDomain: ${fullDomain}`
); );
continue; if (!matchingCert) {
} logger.debug(
} `No matching certificate found for inference siteResource fullDomain: ${fullDomain}`
);
const additionalMiddlewares = continue;
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
}
} }
}; }
routerMiddlewares.push(srHeadersMiddlewareName);
}
routerMiddlewares.push(...additionalMiddlewares); const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || [];
const routerMiddlewares: string[] = [
...(enableAiGatewayClientIpHeader
? [aiGatewayClientIpMiddlewareName]
: []),
aiGatewayTrustMiddlewareNameSiteResource
];
if (sr.ssl) { if (aiGatewayOverride) {
config_output.http.routers[routerName + "-redirect"] = { 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: [ entryPoints: [
config.getRawConfig().traefik.http_entrypoint sr.ssl
? config.getRawConfig().traefik.https_entrypoint
: config.getRawConfig().traefik.http_entrypoint
], ],
middlewares: [redirectHttpsMiddlewareName], middlewares: routerMiddlewares,
service: serviceName, service: serviceName,
rule, 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 }]
}
};
} }
} }