From d0aaf64aad2f1ee8bab741919cb55b0a73371a97 Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 5 Aug 2026 12:00:24 +0000 Subject: [PATCH 1/2] Route /v1/models to ai-gateway-discovery for per-key filtering Envoy AI Gateway's built-in /v1/models returns every model to any valid key. Add an exact-path GET /v1/models HTTPRoute on the ai-gateway that routes the call to ai-gateway-discovery's models handler, which returns only the models the caller's key can use. A more-specific HTTPRoute overrides the gateway's built-in handling; the call stays authenticated by the existing gateway-scoped default-deny + ext_authz. Includes the cross-namespace ReferenceGrant for the discovery Service backendRef. Both render only under aiGateway.enabled + routeHostname, so they ship and retract atomically with the rest of the AI gateway. --- .../httproute-ai-gateway-models.yaml | 40 +++++++++++++++++++ .../reference-grant-ai-gateway-models.yaml | 21 ++++++++++ 2 files changed, 61 insertions(+) create mode 100644 sources/envoy-gateway-config/templates/httproute-ai-gateway-models.yaml create mode 100644 sources/envoy-gateway-config/templates/reference-grant-ai-gateway-models.yaml diff --git a/sources/envoy-gateway-config/templates/httproute-ai-gateway-models.yaml b/sources/envoy-gateway-config/templates/httproute-ai-gateway-models.yaml new file mode 100644 index 00000000..f4171c7f --- /dev/null +++ b/sources/envoy-gateway-config/templates/httproute-ai-gateway-models.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.aiGateway.enabled .Values.aiGateway.routeHostname }} +# Per-key /v1/models: route the OpenAI model-list call to ai-gateway-discovery's models handler +# instead of Envoy AI Gateway's built-in aggregator, so the returned list is filtered to the +# models the calling API key can actually use (deployed ∩ authorized) rather than the whole fleet. +# +# The exact-path match overrides the gateway's built-in /v1/models handling (a more-specific +# HTTPRoute wins). The call is still authenticated by the gateway-scoped ai-gateway-default-deny +# SecurityPolicy + body-aware ext_authz (which target the whole ai-gateway Gateway), so the handler +# always sees a valid key. The backendRef is cross-namespace (Gateway here, discovery Service in +# discoveryNamespace) — see reference-grant-ai-gateway-models.yaml. +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ai-gateway-models + namespace: envoy-gateway-system # same namespace as the ai-gateway Gateway +spec: + hostnames: + - "{{ .Values.aiGateway.routeHostname }}" + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: ai-gateway + namespace: envoy-gateway-system + sectionName: https + rules: + - matches: + - path: + type: Exact + value: /v1/models + method: GET + backendRefs: + - group: "" + kind: Service + name: ai-gateway-discovery + # discovery's per-key models handler port. Hardcoded like the ext_authz port (8083) in + # security-policy-ai-gateway-default-deny.yaml; must match the ai-gateway-discovery + # chart's controller.modelsPort. + namespace: {{ .Values.aiGateway.discoveryNamespace }} + port: 8082 +{{- end }} diff --git a/sources/envoy-gateway-config/templates/reference-grant-ai-gateway-models.yaml b/sources/envoy-gateway-config/templates/reference-grant-ai-gateway-models.yaml new file mode 100644 index 00000000..5422ea40 --- /dev/null +++ b/sources/envoy-gateway-config/templates/reference-grant-ai-gateway-models.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.aiGateway.enabled .Values.aiGateway.routeHostname }} +# Cross-namespace grant for the /v1/models HTTPRoute backendRef: the ai-gateway-models HTTPRoute +# lives in envoy-gateway-system (with the ai-gateway Gateway), but the ai-gateway-discovery Service +# it routes to lives in the discovery app's namespace. Gateway API requires a ReferenceGrant in the +# target (Service) namespace, which tracks .Values.aiGateway.discoveryNamespace — mirrors +# reference-grant-ai-gateway-body-authz.yaml, but from an HTTPRoute instead of a SecurityPolicy. +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: ai-gateway-models-grant + namespace: {{ .Values.aiGateway.discoveryNamespace }} # ai-gateway-discovery Service namespace +spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: envoy-gateway-system # HTTPRoute namespace + to: + - group: "" + kind: Service + name: ai-gateway-discovery # per-key models handler Service (discovery models port) +{{- end }} From c52e21466841eb4aeea07f8ad8a3ddd7472fb7ef Mon Sep 17 00:00:00 2001 From: John Lybeck Date: Wed, 5 Aug 2026 12:44:17 +0000 Subject: [PATCH 2/2] Add route-scoped SecurityPolicy for the /v1/models route The gateway-scoped ai-gateway-default-deny applies both extAuth and an authorization RBAC deny to every route on the ai-gateway. The static /v1/models HTTPRoute has no per-model SecurityPolicy to override it, so a valid key passed extAuth but was then refused by the RBAC deny (HTTP 403, verified on app-dev). This route-scoped policy overrides the gateway one for /v1/models: it keeps extAuth (missing/invalid key still 401'd, valid key forwarded with x-api-key-id) but carries no authorization block, so the request reaches the models handler. Verified end-to-end on app-dev: a key bound to one of two deployed models now gets a 200 filtered to that model. --- .../security-policy-ai-gateway-models.yaml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 sources/envoy-gateway-config/templates/security-policy-ai-gateway-models.yaml diff --git a/sources/envoy-gateway-config/templates/security-policy-ai-gateway-models.yaml b/sources/envoy-gateway-config/templates/security-policy-ai-gateway-models.yaml new file mode 100644 index 00000000..bde14e48 --- /dev/null +++ b/sources/envoy-gateway-config/templates/security-policy-ai-gateway-models.yaml @@ -0,0 +1,39 @@ +{{- if and .Values.aiGateway.enabled .Values.aiGateway.routeHostname }} +# Route-scoped SecurityPolicy for the /v1/models HTTPRoute. +# +# The gateway-scoped ai-gateway-default-deny SecurityPolicy applies BOTH extAuth and +# authorization.defaultAction: Deny to every route on the ai-gateway Gateway. Per-model inference +# routes escape the deny because ai-gateway-discovery stamps a per-model apiKeyAuth SecurityPolicy +# that overrides it. The static /v1/models HTTPRoute has no such per-model policy, so without this +# it authenticates via extAuth but is then refused by the gateway-level RBAC deny (HTTP 403). +# +# This route-scoped policy overrides the gateway-level one for /v1/models (Envoy Gateway resolves +# by specificity, no merge): it keeps extAuth — so a missing/invalid key is still rejected at the +# gateway (401) and a valid bound key is forwarded with x-api-key-id — but carries NO authorization +# block, so RBAC no longer denies. The request then reaches the models handler, which returns the +# list filtered to the caller's key. The extAuth backendRef reuses the cross-namespace grant from +# reference-grant-ai-gateway-body-authz.yaml (SecurityPolicy → ai-gateway-discovery Service). +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: ai-gateway-models + namespace: envoy-gateway-system # must match the ai-gateway Gateway / HTTPRoute namespace +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: ai-gateway-models + extAuth: + failOpen: false + headersToExtAuth: + - authorization + http: + backendRefs: + - group: '' + kind: Service + name: ai-gateway-discovery + namespace: {{ .Values.aiGateway.discoveryNamespace }} + port: 8083 + headersToBackend: + - x-api-key-id +{{- end }}