mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-14 08:19:51 +02:00
optionally stamp ip header for downstream use
This commit is contained in:
@@ -12,6 +12,15 @@ export const AI_GATEWAY_RESOURCE_TYPE_HEADER =
|
|||||||
|
|
||||||
export type AiGatewayResourceType = "resource" | "site-resource";
|
export type AiGatewayResourceType = "resource" | "site-resource";
|
||||||
|
|
||||||
|
// Opt-in (server.enable_ai_gateway_client_ip_header): carries the client IP
|
||||||
|
// that Badger resolved at the Traefik hop, so it survives an intermediary
|
||||||
|
// proxy between Traefik and the AI gateway that overwrites
|
||||||
|
// X-Forwarded-For/X-Real-Ip instead of appending to them. Set by a
|
||||||
|
// disableForwardAuth Badger middleware instance (see getTraefikConfig.ts)
|
||||||
|
// on the site-resource inference router only, since that's the sole path
|
||||||
|
// that resolves request identity from the client IP.
|
||||||
|
export const AI_GATEWAY_CLIENT_IP_HEADER = "X-Pangolin-Client-Ip";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derive a Traefik-injected trust token from the server secret.
|
* Derive a Traefik-injected trust token from the server secret.
|
||||||
* Traefik overwrites this header on inference routes so the AI gateway can
|
* Traefik overwrites this header on inference routes so the AI gateway can
|
||||||
|
|||||||
@@ -153,6 +153,25 @@ export const configSchema = z
|
|||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
trust_proxy: z.int().gte(0).optional().default(1),
|
trust_proxy: z.int().gte(0).optional().default(1),
|
||||||
|
// Opt-in: have Traefik/Badger stamp the resolved client IP
|
||||||
|
// into a dedicated header (X-Pangolin-Client-Ip) on the
|
||||||
|
// site-resource AI gateway route, so it survives an
|
||||||
|
// intermediary proxy between Traefik and the gateway that
|
||||||
|
// overwrites X-Forwarded-For/X-Real-Ip instead of appending
|
||||||
|
// to them. Off by default since it requires a Badger
|
||||||
|
// version that supports realIpHeader.
|
||||||
|
enable_ai_gateway_client_ip_header: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(false)
|
||||||
|
.transform((val) =>
|
||||||
|
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !==
|
||||||
|
undefined
|
||||||
|
? process.env
|
||||||
|
.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER ===
|
||||||
|
"true"
|
||||||
|
: val
|
||||||
|
),
|
||||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
secret: z.string().pipe(z.string().min(8)).optional(),
|
||||||
maxmind_db_path: z.string().optional(),
|
maxmind_db_path: z.string().optional(),
|
||||||
maxmind_asn_path: z.string().optional()
|
maxmind_asn_path: z.string().optional()
|
||||||
@@ -183,7 +202,8 @@ export const configSchema = z
|
|||||||
"resource_session_request_param",
|
"resource_session_request_param",
|
||||||
dashboard_session_length_hours: 720,
|
dashboard_session_length_hours: 720,
|
||||||
resource_session_length_hours: 720,
|
resource_session_length_hours: 720,
|
||||||
trust_proxy: 1
|
trust_proxy: 1,
|
||||||
|
enable_ai_gateway_client_ip_header: false
|
||||||
}),
|
}),
|
||||||
postgres: z
|
postgres: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import regionalCache from "@server/lib/cache";
|
|||||||
import {
|
import {
|
||||||
AI_GATEWAY_TRUST_HEADER,
|
AI_GATEWAY_TRUST_HEADER,
|
||||||
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
||||||
|
AI_GATEWAY_CLIENT_IP_HEADER,
|
||||||
getAiGatewayTrustToken
|
getAiGatewayTrustToken
|
||||||
} from "@server/lib/aiGatewayTrust";
|
} from "@server/lib/aiGatewayTrust";
|
||||||
|
|
||||||
@@ -786,6 +787,28 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Opt-in: a Badger instance with forward auth disabled, used only
|
||||||
|
// to stamp the resolved client IP into a dedicated header before
|
||||||
|
// the request reaches whatever sits between Traefik and the AI
|
||||||
|
// gateway. Only the site-resource router below needs this - it's
|
||||||
|
// the only path that resolves request identity from the client IP
|
||||||
|
// (see resolveRequestUser in aiGateway/pipeline.ts) - and it's the
|
||||||
|
// only inference router that doesn't already run Badger.
|
||||||
|
const aiGatewayClientIpMiddlewareName = "ai-gateway-client-ip";
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Public inference resources: same TLS/cert-resolver handling as
|
// Public inference resources: same TLS/cert-resolver handling as
|
||||||
// plain http-mode resources, but the service points at the AI
|
// plain http-mode resources, but the service points at the AI
|
||||||
// gateway instead of any real backend targets.
|
// gateway instead of any real backend targets.
|
||||||
@@ -958,6 +981,9 @@ export async function getTraefikConfig(
|
|||||||
const additionalMiddlewares =
|
const additionalMiddlewares =
|
||||||
config.getRawConfig().traefik.additional_middlewares || [];
|
config.getRawConfig().traefik.additional_middlewares || [];
|
||||||
const routerMiddlewares = [
|
const routerMiddlewares = [
|
||||||
|
...(enableAiGatewayClientIpHeader
|
||||||
|
? [aiGatewayClientIpMiddlewareName]
|
||||||
|
: []),
|
||||||
aiGatewayTrustMiddlewareNameSiteResource,
|
aiGatewayTrustMiddlewareNameSiteResource,
|
||||||
srHeadersMiddlewareName,
|
srHeadersMiddlewareName,
|
||||||
...additionalMiddlewares
|
...additionalMiddlewares
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import regionalCache from "#private/lib/cache";
|
|||||||
import {
|
import {
|
||||||
AI_GATEWAY_TRUST_HEADER,
|
AI_GATEWAY_TRUST_HEADER,
|
||||||
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
||||||
|
AI_GATEWAY_CLIENT_IP_HEADER,
|
||||||
getAiGatewayTrustToken
|
getAiGatewayTrustToken
|
||||||
} from "@server/lib/aiGatewayTrust";
|
} from "@server/lib/aiGatewayTrust";
|
||||||
|
|
||||||
@@ -1600,6 +1601,28 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Opt-in: a Badger instance with forward auth disabled, used only
|
||||||
|
// to stamp the resolved client IP into a dedicated header before
|
||||||
|
// the request reaches whatever sits between Traefik and the AI
|
||||||
|
// gateway. Only the site-resource router below needs this - it's
|
||||||
|
// the only path that resolves request identity from the client IP
|
||||||
|
// (see resolveRequestUser in aiGateway/pipeline.ts) - and it's the
|
||||||
|
// only inference router that doesn't already run Badger.
|
||||||
|
const aiGatewayClientIpMiddlewareName = "ai-gateway-client-ip";
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Public inference resources: same TLS/cert-resolver handling as
|
// Public inference resources: same TLS/cert-resolver handling as
|
||||||
// plain http-mode resources, but the service points at the AI
|
// plain http-mode resources, but the service points at the AI
|
||||||
// gateway instead of any real backend targets.
|
// gateway instead of any real backend targets.
|
||||||
@@ -1770,6 +1793,9 @@ export async function getTraefikConfig(
|
|||||||
const additionalMiddlewares =
|
const additionalMiddlewares =
|
||||||
config.getRawConfig().traefik.additional_middlewares || [];
|
config.getRawConfig().traefik.additional_middlewares || [];
|
||||||
const routerMiddlewares: string[] = [
|
const routerMiddlewares: string[] = [
|
||||||
|
...(enableAiGatewayClientIpHeader
|
||||||
|
? [aiGatewayClientIpMiddlewareName]
|
||||||
|
: []),
|
||||||
aiGatewayTrustMiddlewareNameSiteResource
|
aiGatewayTrustMiddlewareNameSiteResource
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { localCache } from "@server/lib/cache";
|
|||||||
import {
|
import {
|
||||||
AI_GATEWAY_TRUST_HEADER,
|
AI_GATEWAY_TRUST_HEADER,
|
||||||
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
AI_GATEWAY_RESOURCE_TYPE_HEADER,
|
||||||
|
AI_GATEWAY_CLIENT_IP_HEADER,
|
||||||
isAiGatewayTrustHeaderValid,
|
isAiGatewayTrustHeaderValid,
|
||||||
getAiGatewayResourceType,
|
getAiGatewayResourceType,
|
||||||
type AiGatewayResourceType
|
type AiGatewayResourceType
|
||||||
@@ -303,7 +304,12 @@ async function resolveRequestUser(
|
|||||||
return { user: null, virtualApiKeyId: null };
|
return { user: null, virtualApiKeyId: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const ip = req.ip;
|
// Prefer the dedicated header Badger stamps at the Traefik hop (opt-in
|
||||||
|
// via server.enable_ai_gateway_client_ip_header) over req.ip, since an
|
||||||
|
// intermediary proxy between Traefik and this gateway may overwrite
|
||||||
|
// X-Forwarded-For/X-Real-Ip rather than appending to them, corrupting
|
||||||
|
// what req.ip resolves to.
|
||||||
|
const ip = getRequestHeader(req, AI_GATEWAY_CLIENT_IP_HEADER) || req.ip;
|
||||||
if (!ip) {
|
if (!ip) {
|
||||||
return { user: null, virtualApiKeyId: null };
|
return { user: null, virtualApiKeyId: null };
|
||||||
}
|
}
|
||||||
@@ -965,7 +971,8 @@ export async function handleAiGatewayProxy(
|
|||||||
"content-length",
|
"content-length",
|
||||||
"accept-encoding",
|
"accept-encoding",
|
||||||
AI_GATEWAY_TRUST_HEADER.toLowerCase(),
|
AI_GATEWAY_TRUST_HEADER.toLowerCase(),
|
||||||
AI_GATEWAY_RESOURCE_TYPE_HEADER.toLowerCase()
|
AI_GATEWAY_RESOURCE_TYPE_HEADER.toLowerCase(),
|
||||||
|
AI_GATEWAY_CLIENT_IP_HEADER.toLowerCase()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
|
|||||||
Reference in New Issue
Block a user