Skip to main content

AIGateway

AIGateway is the top-level resource for the Stacklok AI Gateway. One resource configures the whole gateway: its listeners, the model providers it can reach, the routes that map logical model names onto them, authentication, content screening, audit and journaling, and its resilience and timeout behavior. Attach access lists and MCP policy with AIPolicy.

API: ai-gateway.stacklok.dev/v1alpha1 · Scope: Namespaced · Short names: aigw

Example

aigateway.yaml
apiVersion: ai-gateway.stacklok.dev/v1alpha1
kind: AIGateway
metadata:
name: my-aigateway
namespace: default
spec:
auth: {}
gateway:
listeners:
- port: 0
providers:
- credentials:
type: APIKey
endpoint:
hostname: <string>
name: <string>
schema: OpenAI
routes:
- backendRefs:
- provider: <string>
name: <string>

Schema

spec

AIGatewaySpec defines the desired state of the AI Gateway.

FieldTypeDescription
auditobject

Audit configures audit event emission. When nil or Enabled=false, no audit events are emitted.

authrequiredobject

Auth defines authentication and authorization configuration.

gatewayrequiredobject

Gateway defines listener configuration for the Envoy Gateway.

guardrailsobject

Guardrails configures cloud prompt-injection screening of inbound prompts. Engine-discriminated and config-driven; the operator synthesizes a webhook from this block that screens prompts via a cloud provider and blocks (Mode=Enforce) or only records (Mode=Monitor) per policy. Sits alongside spec.webhooks. NOTE: distinct from the historical "guardrails" wording on spec.processor (ProcessorConfig) — that refers to the in-cluster PII/PCI ext-proc and its "guardrails" Deployment. This top-level field is the new cloud prompt-injection guardrail and is unrelated to PII scanning.

journalingobject

Journaling configures capture of prompt and response payloads for compliance and forensics. Distinct from Audit: audit records control- plane and policy decisions, while journaling records the request and response bodies themselves.

monitoringobject

Monitoring configures observability features.

networkPoliciesobject

NetworkPolicies configures NetworkPolicy generation for all components.

policiesobject

Policies enables AIPolicy CRD enforcement for group-based access control.

processorobject

Processor defines PII processor configuration.

providersrequiredobject[]

Providers is the list of LLM provider backends.

resilienceobject

Resilience configures retry, circuit breaker, passive health checking, and upstream timeouts for all routes managed by this gateway. When nil, upstream errors pass through to the client unchanged (no retries).

routesrequiredobject[]

Routes defines model routing rules. Maximum 120 entries — the hard ceiling of 15 collapsed rules times 8 matches per rule (see below); the practical ceiling is lower and depends on how many routes share a backend. The operator collapses routes that share the same backendRefs and timeout into ONE AIGatewayRoute rule with one match per model, chunked at 8 matches per rule — e.g. 40 model routes split across 3 providers of <=8 models each become 3 rules; a single backend of 20 models becomes 3 rules (8+8+4). GeminiNative (path-matched) routes and the no-match default route are never collapsed; each keeps its own rule. Gateway API caps HTTPRoute.spec.rules at 16, and the upstream AI Gateway controller always appends a route-not-found rule (returning 404 for unmatched traffic) as the final entry, consuming one slot — so the collapsed rule count must stay at or below 15, and each rule holds at most 8 matches, giving the 15*8=120 model ceiling. The reconciler enforces this at reconcile time (see the RoutesValid status condition) since the grouping is not CEL-expressible; a spec with too many distinct backend/timeout combinations sets RoutesValid=False and the AIGatewayRoute is not updated (retains last-known-good) rather than failing admission. Inserting or removing a route that changes which routes share a group shifts every subsequent group's rule index (rule/i), and therefore its RLS generic_key route scope — the same class of budget-counter reset that already applies to reordering routes today, just now scoped to the group rather than the individual route.

versionstring

Version is the AI Gateway version. The operator derives the compatible Envoy Gateway version from a built-in compatibility matrix.


default "v0.5.0" · pattern ^v\d+\.\d+\.\d+$
webhooksobject[]

Webhooks registers external in-cluster receivers that validate request traffic (Validating) and observe responses (Observation). Each entry names a same-namespace Service destination authenticated with a projected, audience-bound Kubernetes ServiceAccount token. Sits alongside spec.audit and spec.journaling; the operator and main processor wire these in (see docs/plan/webhooks.md).

spec.audit

Audit configures audit event emission. When nil or Enabled=false, no audit events are emitted.

FieldTypeDescription
enabledboolean

Enabled turns audit event emission on. Off by default.


default false
webhookSamplingstring

WebhookSampling is the fraction [0,1] of webhook invocation audit events to emit, written as a decimal string ("0", "0.1", "1", "1.0"). It downsamples only aigw.processor.webhook.invocation; every other webhook event (denied, error, drop, circuit_*) emits at full rate. A string (rather than a float, which Kubernetes discourages in CRDs) and a pointer so "unset" (emit all, the default applied at render time) is distinguishable from an explicit "0" (emit none). Has no effect unless audit.enabled is true and spec.webhooks is configured.


pattern ^(0(\.[0-9]+)?|1(\.0+)?)$

spec.auth

Auth defines authentication and authorization configuration.

FieldTypeDescription
authzobject

Authz configures platform-owned role definitions consumed by AIPolicy enforcement and admin-surface authorization checks.

oidcobject

OIDC configures OpenID Connect authentication.

virtualAPIKeysobject

VirtualAPIKeys configures the optional API key service.

workloadIssuersobject[]

WorkloadIssuers configures additional internal JWT issuers that mint short-lived tokens carrying an end-user identity on behalf of a platform service (e.g. Atrium Front Door TxTokens, EdDSA-signed). Tokens validate against the same jwt_authn filter as the user IdP and resolve to the SAME x-user-id identity spine; they flow through the SAME claim-based AIPolicy authorization. Distinct from spec.auth.oidc (the user IdP). Configuring a workload issuer makes the gateway's identity headers authoritative (ingress-stripped) and requires spec.policies enforcement to be enabled.

spec.auth.authz

Authz configures platform-owned role definitions consumed by AIPolicy enforcement and admin-surface authorization checks.

FieldTypeDescription
rolesrequiredmap<string, array>

Roles maps a role name to the list of principal matchers that grant that role. A principal gets the role if any matcher matches.

spec.auth.oidc

OIDC configures OpenID Connect authentication.

FieldTypeDescription
audiencerequiredstring

Audience is the expected aud claim value in OIDC tokens. Must match the audience/client_id registered in your OIDC provider. Also used as the audience in JWTs synthesized by the virtual API key service.


minLength 1
claimToHeadersobject[]

ClaimToHeaders maps JWT claims to request headers.

issuerrequiredstring

Issuer is the OIDC issuer URL. Must start with https://.


pattern ^https://
remoteJWKSrequiredobject

RemoteJWKS defines the remote JWKS endpoint for JWT validation.

spec.auth.oidc.claimToHeaders[]

ClaimToHeaders maps JWT claims to request headers.

FieldTypeDescription
claimrequiredstring

Claim is the JWT claim name to extract.

headerrequiredstring

Header is the request header name to set.

spec.auth.oidc.remoteJWKS

RemoteJWKS defines the remote JWKS endpoint for JWT validation.

FieldTypeDescription
cacheDurationstring

CacheDuration is how long Envoy caches the fetched JWKS before refetch. Maps to Envoy Gateway SecurityPolicy remoteJWKS.cacheDuration (upstream default 300s when omitted). When unset the operator omits the key so existing SecurityPolicies render byte-identical. NOTE: this is also the de-facto revocation latency for the issuer's signing keys — a rotated/ revoked key stays trusted for up to this duration. Use the GEP-2257 duration subset (Envoy's pattern), e.g. "300s", "5m", "1h30m".


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
urirequiredstring

URI is the JWKS endpoint URL. Must use http or https scheme.


pattern ^https?:// · minLength 1
spec.auth.virtualAPIKeys

VirtualAPIKeys configures the optional API key service.

FieldTypeDescription
defaultTTLstring

DefaultTTL is the default time-to-live for newly created keys (e.g., "2160h" for 90 days). Deprecated: no longer consumed by the api-key-service. Virtual-key TTL is now the directory module's concern (key storage and lifecycle moved into the directory), and the pod no longer reads a DEFAULT_TTL env var. Retained to avoid a breaking schema change for existing manifests; a future cleanup PR may remove it.


default "2160h"
directoryobject

Directory configures the gRPC endpoint of the toolhive-enterprise directory module that the api-key-service validates virtual keys against. Required in practice when Enabled is true — the api-key-service has no other source of truth for virtual keys since virtual-key storage and validation moved into the directory module. A nil Directory when the service is enabled surfaces at runtime as a dial error in the api-key-service (it has no endpoint to reach), not at admission.

enabledrequiredboolean

Enabled deploys the api-key-service. The service currently hosts two roles: (1) it issues and validates virtual API keys (long-lived keys exchanged for a short-lived JWT carrying the issuing user's identity), and (2) it serves the management API (/v1/policies, /v1/me, /v1/budgets, /v1/models, /v1/mcp-servers) used by the Stacklok Enterprise UI. Set to true to enable either capability; operators who only want the management API still need this flag, because there is no separate gate for it today. The management-API role is co-located on this service as an interim hosting model and may move to its own service in a future release, pending an RFC on the management API's responsibilities; the deployment gate will be revisited if and when that happens.


default false
imagestring

Image is the container image for the api-key-service. When empty the operator falls back to its built-in default (set by the operator Helm chart). An explicit empty string is rejected at admission so a typo combined with a chart that's missing the env doesn't surface as an unhelpful runtime reconcile error.


minLength 1
imagePullPolicystring

ImagePullPolicy defines the pull policy for the api-key-service container image.


default "IfNotPresent" · enum: Always | Never | IfNotPresent
maxReplicasinteger

MaxReplicas is the upper bound for HPA scaling. When set, the operator creates a HorizontalPodAutoscaler targeting the api-key-service Deployment.


format int32 · min 1
maxTTLstring

MaxTTL is the maximum allowable TTL for any key (e.g., "8760h" for 365 days). Deprecated: no longer consumed by the api-key-service. Virtual-key TTL is now the directory module's concern (key storage and lifecycle moved into the directory), and the pod no longer reads a MAX_TTL env var. Retained to avoid a breaking schema change for existing manifests; a future cleanup PR may remove it.


default "8760h"
messageTimeoutstring

MessageTimeout is the ext-proc message timeout for virtual key validation.


default "2s"
replicasinteger

Replicas is the number of api-key-service pods.


default 2 · format int32 · min 1
resourcesobject

Resources defines compute resource requirements for the api-key-service pods.

selfServiceboolean

SelfService allows authenticated users to create their own keys.


default true
targetCPUUtilizationinteger

TargetCPUUtilization is the CPU utilization target for HPA scaling (percentage).


default 70 · format int32 · min 1 · max 100
tlsobject

TLS provisions HTTPS for the api-key-service management API. When set the operator creates a cert-manager Certificate and the pod additionally serves HTTPS on port 8443 alongside HTTP on port 8080. The HTTP endpoint on port 8080 is kept because Envoy Gateway's SecurityPolicy remoteJWKS fetch uses an internal cluster not governed by BackendTLSPolicy; a follow-up EnvoyPatchPolicy is required to configure Envoy's internal JWT fetch cluster before the JWKS URI can switch to HTTPS. Requires cert-manager to be installed in the cluster.

spec.auth.virtualAPIKeys.directory

Directory configures the gRPC endpoint of the toolhive-enterprise directory module that the api-key-service validates virtual keys against. Required in practice when Enabled is true — the api-key-service has no other source of truth for virtual keys since virtual-key storage and validation moved into the directory module. A nil Directory when the service is enabled surfaces at runtime as a dial error in the api-key-service (it has no endpoint to reach), not at admission.

FieldTypeDescription
endpointrequiredstring

Endpoint is the directory's gRPC address, e.g. "enterprise-manager.<namespace>.svc:9091".


minLength 1
insecureboolean

Insecure skips the per-RPC ServiceAccount-token credential the api-key-service otherwise presents to the directory's caller-auth gate. Only for local/e2e clusters running the directory with ENTERPRISE_MANAGER_GRPC_INSECURE=true — never set true in production.


default false
spec.auth.virtualAPIKeys.resources

Resources defines compute resource requirements for the api-key-service pods.

FieldTypeDescription
limitsobject

Limits defines the maximum resources allowed.

requestsobject

Requests defines the minimum resources required.

spec.auth.virtualAPIKeys.resources.limits

Limits defines the maximum resources allowed.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.auth.virtualAPIKeys.resources.requests

Requests defines the minimum resources required.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.auth.virtualAPIKeys.tls

TLS provisions HTTPS for the api-key-service management API. When set the operator creates a cert-manager Certificate and the pod additionally serves HTTPS on port 8443 alongside HTTP on port 8080. The HTTP endpoint on port 8080 is kept because Envoy Gateway's SecurityPolicy remoteJWKS fetch uses an internal cluster not governed by BackendTLSPolicy; a follow-up EnvoyPatchPolicy is required to configure Envoy's internal JWT fetch cluster before the JWKS URI can switch to HTTPS. Requires cert-manager to be installed in the cluster.

FieldTypeDescription
issuerRefrequiredobject

IssuerRef references a cert-manager Issuer or ClusterIssuer used to provision the api-key-service TLS certificate.

spec.auth.virtualAPIKeys.tls.issuerRef

IssuerRef references a cert-manager Issuer or ClusterIssuer used to provision the api-key-service TLS certificate.

FieldTypeDescription
kindstring

Kind is either "Issuer" or "ClusterIssuer".


default "ClusterIssuer" · enum: Issuer | ClusterIssuer
namerequiredstring

Name of the Issuer or ClusterIssuer.

spec.auth.workloadIssuers[]

WorkloadIssuers configures additional internal JWT issuers that mint short-lived tokens carrying an end-user identity on behalf of a platform service (e.g. Atrium Front Door TxTokens, EdDSA-signed). Tokens validate against the same jwt_authn filter as the user IdP and resolve to the SAME x-user-id identity spine; they flow through the SAME claim-based AIPolicy authorization. Distinct from spec.auth.oidc (the user IdP). Configuring a workload issuer makes the gateway's identity headers authoritative (ingress-stripped) and requires spec.policies enforcement to be enabled.

FieldTypeDescription
audiencesrequiredstring[]

Audiences is the set of acceptable aud values for this issuer's tokens. MUST be disjoint from spec.auth.oidc.audience (a shared audience would let an Okta token validate on this path and vice-versa). MaxItems=8 mirrors Envoy Gateway and is load-bearing for the audience-distinctness CEL cost.

issuerrequiredstring

Issuer is the expected iss claim. Internal issuers use non-resolvable URIs (e.g. https://frontdoor.atrium.internal); jwt_authn matches iss as an opaque string, so https:// is NOT required here (unlike spec.auth.oidc). Must differ from spec.auth.oidc.issuer and from every other workload issuer (enforced cross-field — see reconciler validation).


minLength 1 · maxLength 253
namerequiredstring

Name is the jwt_authn provider name for this issuer and the principal.jwt .provider in generated authorization rules. Unique across workloadIssuers (enforced by listMapKey). Must not collide with the reserved provider names "okta" (user IdP) or "api-key-service" (virtual-key issuer), which are emitted by the controller and invisible to list-scoped CEL.


pattern ^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$ · maxLength 63
remoteJWKSrequiredobject

RemoteJWKS is the JWKS endpoint used to verify this issuer's signatures (EdDSA/Ed25519 or any alg the JWKS advertises — alg cannot be pinned at the Envoy Gateway layer, so the JWKS MUST advertise only trusted-alg keys).

subjectClaimstring

SubjectClaim names the claim carrying the end-user identity, mapped (and OVERWRITTEN) onto x-user-id so this issuer shares the identity spine with the user IdP. The CRD default is authoritative for admitted objects; the controller's empty-string fallback to "sub" only covers non-admission paths (e.g. unit-test structs).


default "sub" · minLength 1 · maxLength 128
spec.auth.workloadIssuers.remoteJWKS

RemoteJWKS is the JWKS endpoint used to verify this issuer's signatures (EdDSA/Ed25519 or any alg the JWKS advertises — alg cannot be pinned at the Envoy Gateway layer, so the JWKS MUST advertise only trusted-alg keys).

FieldTypeDescription
cacheDurationstring

CacheDuration is how long Envoy caches the fetched JWKS before refetch. Maps to Envoy Gateway SecurityPolicy remoteJWKS.cacheDuration (upstream default 300s when omitted). When unset the operator omits the key so existing SecurityPolicies render byte-identical. NOTE: this is also the de-facto revocation latency for the issuer's signing keys — a rotated/ revoked key stays trusted for up to this duration. Use the GEP-2257 duration subset (Envoy's pattern), e.g. "300s", "5m", "1h30m".


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
urirequiredstring

URI is the JWKS endpoint URL. Must use http or https scheme.


pattern ^https?:// · minLength 1

spec.gateway

Gateway defines listener configuration for the Envoy Gateway.

FieldTypeDescription
bufferLimitstring

BufferLimit is the connection buffer limit for LLM request bodies. Raised from 10Mi to 50Mi to accommodate large request bodies (e.g. gemini-2.5-pro requests that embed large system prompts and MCP tool definitions) that exceed the previous default. Clusters using the CRD default previously received 413 Payload Too Large for such requests while clusters with an explicit 50Mi setting succeeded — this aligns the two. Memory impact: worst case is bufferLimit × connectionLimit per proxy pod (e.g. 50Mi × 500 = ~25 GiB); size proxy pod memory limits accordingly.


default "50Mi"
connectionLimitinteger

ConnectionLimit is the max concurrent client connections per proxy pod.


default 500 · format int32
controllerNamestring

ControllerName is the controller name for the GatewayClass resource. This must match the controllerName configured in the Envoy Gateway installation that should reconcile this GatewayClass. When running alongside an existing Envoy Gateway installation, set this to a unique value to avoid conflicts. Must be a domain-prefixed path (e.g., "gateway.envoyproxy.io/gatewayclass-controller").


default "gateway.envoyproxy.io/gatewayclass-controller" · pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/.+$ · minLength 1
gatewayClassNamestring

GatewayClassName is the name of the GatewayClass resource.


default "eg"
listenersrequiredobject[]

Listeners defines the Gateway listener port and protocol. Exactly one listener must be specified; multi-listener gateways are not yet supported.

proxyobject

Proxy configures the Envoy proxy Deployment created by Envoy Gateway.

streamTimeoutsobject

StreamTimeouts bounds streaming traffic without imposing a hard total request cap. It is the recommended way to protect the data path: the idle timeout reaps stalled connections (and resets on every byte, so it never cuts a healthy stream), while the max-stream-duration provides an absolute ceiling. Applied gateway-wide (the underlying ClientTrafficPolicy and BackendTrafficPolicy are one-per-gateway). When nil, the operator applies its defaults (streamIdleTimeout=5m, maxStreamDuration=15m).

stripHeadersstring[]

StripHeaders lists identity headers to strip from inbound requests before JWT processing to prevent spoofing.

timeoutsobject

Timeouts defines an optional total request deadline applied to all routes when no per-route timeout is set. This is an opt-in absolute cap on the entire request (including the streamed response body): once set, Envoy 504s any request — healthy or not — that outlives it. It is DISABLED by default so long-running streaming turns (chat completions with stream=true, long-context Claude requests) are never cut mid-stream. To bound streaming traffic without a hard total cap, use StreamTimeouts (idle + max-stream-duration) instead. Only set Timeouts when you deliberately want every request to fail past a fixed wall-clock budget. Note: the operator also applies a default maxStreamDuration ceiling (15m); a requestTimeout LONGER than the effective maxStreamDuration is pre-empted by that ceiling (the stream is cut first), so to allow longer requests you must raise BOTH this value and spec.gateway.streamTimeouts.maxStreamDuration. The operator surfaces a TimeoutConfigValid=False condition + Warning event when this happens.

tlsobject

GatewayTLS configures the gateway-level TLS certificate (cert-manager).

spec.gateway.listeners[]

Listeners defines the Gateway listener port and protocol. Exactly one listener must be specified; multi-listener gateways are not yet supported.

FieldTypeDescription
portrequiredinteger

Port is the listener port number.


format int32 · min 1 · max 65535
protocolstring

Protocol is the listener protocol.


default "HTTPS" · enum: HTTPS | HTTP
tlsobject

TLS configures TLS for this listener.

spec.gateway.listeners.tls

TLS configures TLS for this listener.

FieldTypeDescription
certificateRefrequiredobject

CertificateRef references a Kubernetes Secret containing the TLS cert.

spec.gateway.listeners.tls.certificateRef

CertificateRef references a Kubernetes Secret containing the TLS cert.

FieldTypeDescription
namerequiredstring

Name of the referenced resource.

spec.gateway.proxy

Proxy configures the Envoy proxy Deployment created by Envoy Gateway.

FieldTypeDescription
imagestring

Image is the full container image reference, including tag, for the Envoy data-plane proxy pod that Envoy Gateway creates from the EnvoyProxy resource. When empty, Envoy Gateway's own compiled-in default applies. Setting an explicit empty string is rejected at admission so a typo doesn't surface as a confusing reconcile error. A tag or digest is REQUIRED. The operator chart already hard-fails a dataPlaneProxy.image.repository set without a tag, because an untagged reference resolves to :latest and lands the proxy in the ImagePullBackOff this field exists to escape. Without this rule the CR path would be laxer than the chart path for the same setting, so the guarantee could be bypassed through the very field that provides it. The check finds the ':' AFTER the last '/', so a registry port (registry.internal:5000/envoy) is not mistaken for a tag.


minLength 1 · maxLength 512
replicasinteger

Replicas is the number of proxy pods. Passed to the EnvoyProxy resource; the proxy Deployment is owned by Envoy Gateway, not this operator.


default 2 · format int32
resourcesobject

Resources defines compute resource requirements for the proxy pods.

servicePortsobject[]

ServicePorts defines NodePort mappings for the proxy service. Only used when ServiceType is NodePort.

serviceTypestring

ServiceType is the Kubernetes Service type for the proxy.


default "ClusterIP" · enum: ClusterIP | NodePort | LoadBalancer
shutdownobject

Shutdown tunes how the Envoy proxy pod drains in-flight connections on a rollout. It maps to the EnvoyProxy spec.shutdown block. When unset, the operator DERIVES the drain timeout from the effective maxStreamDuration ceiling plus a teardown margin, so a rolling update never severs a still-running stream. Envoy Gateway in turn sizes the proxy pod's terminationGracePeriodSeconds to drainTimeout + 5m, so only drainTimeout needs to be set.

spec.gateway.proxy.resources

Resources defines compute resource requirements for the proxy pods.

FieldTypeDescription
limitsobject

Limits defines the maximum resources allowed.

requestsobject

Requests defines the minimum resources required.

spec.gateway.proxy.resources.limits

Limits defines the maximum resources allowed.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.gateway.proxy.resources.requests

Requests defines the minimum resources required.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.gateway.proxy.servicePorts[]

ServicePorts defines NodePort mappings for the proxy service. Only used when ServiceType is NodePort.

FieldTypeDescription
namerequiredstring

Name of the port.

nodePortinteger

NodePort is the static NodePort number.


format int32
portrequiredinteger

Port is the service port number.


format int32
protocolstring

Protocol is the port protocol.


default "TCP"
targetPortinteger

TargetPort is the container port.


format int32
spec.gateway.proxy.shutdown

Shutdown tunes how the Envoy proxy pod drains in-flight connections on a rollout. It maps to the EnvoyProxy spec.shutdown block. When unset, the operator DERIVES the drain timeout from the effective maxStreamDuration ceiling plus a teardown margin, so a rolling update never severs a still-running stream. Envoy Gateway in turn sizes the proxy pod's terminationGracePeriodSeconds to drainTimeout + 5m, so only drainTimeout needs to be set.

FieldTypeDescription
drainTimeoutstring

DrainTimeout is how long Envoy waits for open connections to drain before it force-closes them on shutdown. To avoid cutting an in-flight LLM stream during a rollout it must cover a full stream, so when unset the operator derives it from the effective maxStreamDuration ceiling (spec.gateway.streamTimeouts.maxStreamDuration) plus a fixed teardown margin; when the ceiling is unbounded ("0s") it falls back to a fixed default. Set an explicit value only to override that derivation. WARNING: an explicit value below maxStreamDuration re-introduces the mid-stream sever this derivation exists to prevent — Envoy force-closes the connection before a full-length stream finishes. The operator flags this via the advisory TimeoutConfigValid status condition (reason DrainTimeoutBelowMaxStreamDuration) but does not reject it. Keep any override at or above the ceiling, or leave it unset. Accepts the Envoy Gateway Duration form (e.g. "16m", "960s"); no fractional values or us/ns units.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
minDrainDurationstring

MinDrainDuration is the minimum drain time Envoy observes even when all connections close early, allowing endpoint deprogramming to complete. When unset, Envoy Gateway's default (10s) applies. Accepts the Envoy Gateway Duration form (e.g. "10s", "30s"); no fractional values or us/ns units.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
spec.gateway.streamTimeouts

StreamTimeouts bounds streaming traffic without imposing a hard total request cap. It is the recommended way to protect the data path: the idle timeout reaps stalled connections (and resets on every byte, so it never cuts a healthy stream), while the max-stream-duration provides an absolute ceiling. Applied gateway-wide (the underlying ClientTrafficPolicy and BackendTrafficPolicy are one-per-gateway). When nil, the operator applies its defaults (streamIdleTimeout=5m, maxStreamDuration=15m).

FieldTypeDescription
maxStreamDurationstring

MaxStreamDuration is the absolute ceiling on a single stream's lifetime, enforced regardless of activity. Unlike StreamIdleTimeout it does not reset on activity. Accepts the Envoy Gateway Duration form (e.g. "15m", "30m"); no fractional values or us/ns units. When unset, the operator applies 15m. "0s" makes the stream unbounded (only the idle timeout and any explicit RequestTimeout then apply).


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
streamIdleTimeoutstring

StreamIdleTimeout is the maximum time a stream may go without any upstream or downstream activity before Envoy terminates it. It resets on every byte, so it never cuts a healthy stream — it only reaps stalled connections. Accepts the Envoy Gateway Duration form (e.g. "5m", "90s"); no fractional values or us/ns units. When unset, the operator applies 5m. "0s" disables idle reaping (not recommended: a hung upstream then leaks the connection).


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
spec.gateway.timeouts

Timeouts defines an optional total request deadline applied to all routes when no per-route timeout is set. This is an opt-in absolute cap on the entire request (including the streamed response body): once set, Envoy 504s any request — healthy or not — that outlives it. It is DISABLED by default so long-running streaming turns (chat completions with stream=true, long-context Claude requests) are never cut mid-stream. To bound streaming traffic without a hard total cap, use StreamTimeouts (idle + max-stream-duration) instead. Only set Timeouts when you deliberately want every request to fail past a fixed wall-clock budget. Note: the operator also applies a default maxStreamDuration ceiling (15m); a requestTimeout LONGER than the effective maxStreamDuration is pre-empted by that ceiling (the stream is cut first), so to allow longer requests you must raise BOTH this value and spec.gateway.streamTimeouts.maxStreamDuration. The operator surfaces a TimeoutConfigValid=False condition + Warning event when this happens.

FieldTypeDescription
requestTimeoutstring

RequestTimeout is the maximum total duration allowed for a single request, including the streamed response body. This is an absolute cap: the request is terminated when it elapses even if data is still flowing. Accepts the Envoy Gateway Duration form (e.g. "5m"). "0s" disables the cap.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
spec.gateway.tls

GatewayTLS configures the gateway-level TLS certificate (cert-manager).

FieldTypeDescription
certificateRefobject

CertificateRef references an existing cert-manager Certificate.

dnsNamesstring[]

DNSNames is the list of DNS names for the certificate.

durationstring

Duration is the certificate duration (e.g., "2160h" for 90 days).


default "2160h"
issuerRefobject

IssuerRef configures the cert-manager issuer. When set, the operator creates a cert-manager Certificate resource.

renewBeforestring

RenewBefore is how long before expiry to renew (e.g., "720h" for 30 days).


default "720h"
spec.gateway.tls.certificateRef

CertificateRef references an existing cert-manager Certificate.

FieldTypeDescription
namerequiredstring

Name of the referenced resource.

spec.gateway.tls.issuerRef

IssuerRef configures the cert-manager issuer. When set, the operator creates a cert-manager Certificate resource.

FieldTypeDescription
kindstring

Kind is either "Issuer" or "ClusterIssuer".


default "ClusterIssuer" · enum: Issuer | ClusterIssuer
namerequiredstring

Name of the Issuer or ClusterIssuer.

spec.guardrails

Guardrails configures cloud prompt-injection screening of inbound prompts. Engine-discriminated and config-driven; the operator synthesizes a webhook from this block that screens prompts via a cloud provider and blocks (Mode=Enforce) or only records (Mode=Monitor) per policy. Sits alongside spec.webhooks. NOTE: distinct from the historical "guardrails" wording on spec.processor (ProcessorConfig) — that refers to the in-cluster PII/PCI ext-proc and its "guardrails" Deployment. This top-level field is the new cloud prompt-injection guardrail and is unrelated to PII scanning.

FieldTypeDescription
bedrockobject

Bedrock configures the AWS Bedrock Guardrails provider. Required when engine is BedrockGuardrails (enforced by the CEL rules above). The AWS principal the adapter authenticates as (the IRSA-projected role by default, or the static credentials in credentialsSecretRef) must be granted the bedrock:ApplyGuardrail action on the guardrail ARN.

enabledboolean

Enabled turns prompt-injection screening on. Off by default.


default false
enginerequiredstring

Engine selects the cloud guardrail provider. v1: BedrockGuardrails.


enum: BedrockGuardrails
failurePolicystring

FailurePolicy decides the request outcome when the guardrail call fails. Fail (default): deny the request (fail-closed); Ignore: let it proceed (fail-open).


default "Fail" · enum: Fail | Ignore
maxReplicasinteger

MaxReplicas is the upper bound for HPA scaling. When set, the operator creates a HorizontalPodAutoscaler targeting the guardrails-adapter Deployment.


format int32 · min 1
modestring

Mode decides whether a flagged request is blocked (Enforce) or only observed (Monitor). Maps onto the synthesized webhook Type.


default "Enforce" · enum: Enforce | Monitor
phasesrequiredstring[]

Phases lists the request-lifecycle points at which screening runs. v1 supports only Request.

replicasinteger

Replicas is the number of guardrails-adapter pods. It is also the HPA floor (minReplicas) when MaxReplicas is set.


default 1 · format int32 · min 1
resourcesobject

Resources defines compute resource requests/limits for the guardrails-adapter container. When unset, the operator uses modest built-in defaults (requests: 50m/64Mi, limits: 500m/256Mi).

targetCPUUtilizationinteger

TargetCPUUtilization is the CPU utilization target for HPA scaling (percentage). The operator applies a runtime fallback of 75 when this field is nil and MaxReplicas is set. No kubebuilder default is emitted because the paired CEL rule above (`has(targetCPUUtilization) -> has(maxReplicas)`) needs `nil` to be observable as "user did not set this", which a default-everything-to-75 would erase.


format int32 · min 1 · max 100
timeoutSecondsinteger

TimeoutSeconds bounds a single guardrail screening call.


default 5 · format int32 · min 1 · max 120
unscreenableContentPolicystring

UnscreenableContentPolicy decides the request outcome when user content is present but cannot be extracted into screenable text (e.g. an image-only or multimodal message, or an unrecognized content shape). Deny (default): block it (fail-closed); Admit: let it through unscreened. This is separate from FailurePolicy so that unscreenable-but-benign content can be admitted while genuine guardrail call failures still fail closed. The decision is recorded in the audit trail either way.


default "Deny" · enum: Admit | Deny
spec.guardrails.bedrock

Bedrock configures the AWS Bedrock Guardrails provider. Required when engine is BedrockGuardrails (enforced by the CEL rules above). The AWS principal the adapter authenticates as (the IRSA-projected role by default, or the static credentials in credentialsSecretRef) must be granted the bedrock:ApplyGuardrail action on the guardrail ARN.

FieldTypeDescription
credentialsSecretRefobject

CredentialsSecretRef optionally references a Secret of static AWS credentials for the non-IRSA fallback. When omitted, the adapter uses the pod's IRSA-projected role. The Secret must contain a single key "credentials" whose value is an AWS credentials file in INI format with a [default] profile (aws_access_key_id, aws_secret_access_key, optional aws_session_token) — the same format used by the Envoy AI Gateway AWS rotator. Name-only reference, resolved in the AIGateway namespace.

guardrailIdrequiredstring

GuardrailID is the Bedrock guardrail identifier.


minLength 1 · maxLength 64
guardrailVersionrequiredstring

GuardrailVersion is the Bedrock guardrail version: either the string "DRAFT" or a positive integer string (e.g. "1", "12"). The Bedrock API rejects any other value; rejecting it at admission gives a cleaner error.


pattern ^(DRAFT|[1-9][0-9]*)$ · maxLength 12
regionrequiredstring

Region is the AWS region hosting the guardrail.


pattern ^[a-z][a-z0-9-]*[a-z0-9]$ · maxLength 63
spec.guardrails.bedrock.credentialsSecretRef

CredentialsSecretRef optionally references a Secret of static AWS credentials for the non-IRSA fallback. When omitted, the adapter uses the pod's IRSA-projected role. The Secret must contain a single key "credentials" whose value is an AWS credentials file in INI format with a [default] profile (aws_access_key_id, aws_secret_access_key, optional aws_session_token) — the same format used by the Envoy AI Gateway AWS rotator. Name-only reference, resolved in the AIGateway namespace.

FieldTypeDescription
namerequiredstring

Name of the referenced resource.

spec.guardrails.resources

Resources defines compute resource requests/limits for the guardrails-adapter container. When unset, the operator uses modest built-in defaults (requests: 50m/64Mi, limits: 500m/256Mi).

FieldTypeDescription
limitsobject

Limits defines the maximum resources allowed.

requestsobject

Requests defines the minimum resources required.

spec.guardrails.resources.limits

Limits defines the maximum resources allowed.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.guardrails.resources.requests

Requests defines the minimum resources required.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.journaling

Journaling configures capture of prompt and response payloads for compliance and forensics. Distinct from Audit: audit records control- plane and policy decisions, while journaling records the request and response bodies themselves.

FieldTypeDescription
bufferobject

Buffer tunes the in-memory dispatch channel and per-record capture caps. When unset, defaults are applied.

enabledboolean

Enabled turns journaling on. Off by default.


default false
includeResponsesboolean

IncludeResponses journals response bodies in addition to requests. Off by default — responses may include bearer tokens, session state, or regenerated PII and must be an explicit opt-in.


default false
spec.journaling.buffer

Buffer tunes the in-memory dispatch channel and per-record capture caps. When unset, defaults are applied.

FieldTypeDescription
maxInFlightinteger

MaxInFlight bounds records in the in-memory dispatch channel. When full, records are dropped; each drop emits an audit event with a monotonic sequence number so SIEM can detect gaps.


default 1024 · format int32 · min 1 · max 100000
maxResponseCapturestring

MaxResponseCapture caps aggregated streaming-response bytes per record. Over-cap streams are flagged truncated=true. Accepts Kubernetes resource-quantity notation (e.g. "2Mi", "512Ki").


default "2Mi" · pattern ^[0-9]+(Ki|Mi|Gi|K|M|G)?$

spec.monitoring

Monitoring configures observability features.

FieldTypeDescription
grafanaDashboardsobject

GrafanaDashboards configures Grafana dashboard ConfigMap generation.

otelCollectorobject

OTelCollector configures OpenTelemetry collector settings for guardrails.

tokenMetricsobject

TokenMetrics configures token usage metric emission from the guardrails ext-proc.

spec.monitoring.grafanaDashboards

GrafanaDashboards configures Grafana dashboard ConfigMap generation.

FieldTypeDescription
enabledrequiredboolean

Enabled toggles Grafana dashboard ConfigMap generation.


default false
spec.monitoring.otelCollector

OTelCollector configures OpenTelemetry collector settings for guardrails.

FieldTypeDescription
egressCIDRsstring[]

EgressCIDRs optionally allowlists IP ranges for the main-processor's NetworkPolicy egress to this collector, in addition to the always-present namespaceSelector rule. A namespaceSelector cannot match an off-cluster endpoint or a bare hostname/IP, so a collector reachable only that way needs an explicit CIDR to receive traffic under NetworkPolicy enforcement. Each entry must be a valid IPv4 or IPv6 CIDR (e.g. "10.0.0.0/8", "203.0.113.5/32"). Leave empty to rely solely on the namespaceSelector rule. This regex is defense-in-depth only, not the enforcement: it rejects obviously malformed shapes and bounds octet/prefix ranges, but RE2 (no lookahead) cannot fully validate every case (e.g. IPv6 double "::" compression rules). filterValidCIDRs (internal/controller/resources.go), via netip.ParsePrefix, is the real gate before anything reaches the K8s API — see ConditionOTelCollectorEgressCIDRsInvalid.

endpointstring

Endpoint is the OTLP gRPC endpoint for traces and metrics.

protocolstring

Protocol selects the OTLP exporter protocol the main-processor and api-key-service pods use to reach Endpoint. Defaults to "grpc" when unset, matching the operator's own OTLP exporter default. Set to "http/protobuf" to target an HTTP-only collector receiver.


default "grpc" · enum: grpc | http/protobuf
spec.monitoring.tokenMetrics

TokenMetrics configures token usage metric emission from the guardrails ext-proc.

FieldTypeDescription
enabledrequiredboolean

Enabled toggles token usage metric emission from the main processor ext-proc.


default false
labelsobject[]

Labels maps request header names to metric attribute names. Only listed headers are extracted and attached to the aigw.token.usage metric. Use only low-cardinality headers (model, department, team) to avoid metric cardinality explosion.

prometheusAddrstring

PrometheusAddr is the address to serve Prometheus /metrics on (e.g., ":9090"). When empty, the Prometheus HTTP endpoint is disabled.

spec.monitoring.tokenMetrics.labels[]

Labels maps request header names to metric attribute names. Only listed headers are extracted and attached to the aigw.token.usage metric. Use only low-cardinality headers (model, department, team) to avoid metric cardinality explosion.

FieldTypeDescription
attributerequiredstring

Attribute is the metric attribute name (e.g., "model").


minLength 1
headerrequiredstring

Header is the lowercase HTTP request header name (e.g., "x-ai-eg-model").


minLength 1

spec.networkPolicies

NetworkPolicies configures NetworkPolicy generation for all components.

FieldTypeDescription
enabledrequiredboolean

Enabled toggles NetworkPolicy creation for all components.


default false

spec.policies

Policies enables AIPolicy CRD enforcement for group-based access control.

FieldTypeDescription
defaultActionstring

DefaultAction is applied when no AIPolicy matches the request principal. 'Deny' is the default and recommended posture: rule generation today emits Allow-rules exclusively, so combining 'Allow' default with the current PR's ruleset produces an effectively open gateway. 'Allow' is accepted so that a later phase introducing Deny-rules (Allow + Deny-exceptions composition) does not require a CRD migration — operators opting into 'Allow' today must do so deliberately with that future ruleset in mind.


default "Deny" · enum: Allow | Deny
enabledboolean

Enabled toggles AIPolicy enforcement. When unset, defaults to true so that `spec.policies: {}` opts into enforcement rather than silently disabling it. Use a pointer so an explicit `enabled: false` survives round-tripping without being overwritten by the default.


default true

spec.processor

Processor defines PII processor configuration.

FieldTypeDescription
diagnosticsobject

Diagnostics tunes Debug-level diagnostic log emission from the processor. Every field is Debug-only: nothing here changes runtime behavior outside the log stream, and nothing emits unless the main-processor pod runs with LOG_LEVEL=DEBUG. Default values are chosen so an operator enabling DEBUG for unrelated diagnostics does not inherit PII emission as a side effect. See ProcessorDiagnostics for the per-field threat model.

extractionFailureActionstring

ExtractionFailureAction controls behaviour when extracting text from the request body fails (e.g. malformed JSON). "Block" (default, fail-closed) blocks the request with a 403; "Continue" (fail-open) lets it through unscanned. Applies to the processor's extraction stage and is independent of the detection engine.


default "Block" · enum: Block | Continue
imagestring

Image is the container image for the main processor. When empty, the operator falls back to its built-in default (`MAIN_PROCESSOR_IMAGE` env var, set by the operator Helm chart), so most installs should leave this unset and let the chart pin the version. Setting an explicit empty string is rejected at admission so a typo combined with a chart that's missing the env doesn't surface as an unhelpful runtime reconcile error.


minLength 1
imagePullPolicystring

ImagePullPolicy defines the pull policy for the guardrails container image.


default "IfNotPresent" · enum: Always | Never | IfNotPresent
maxReplicasinteger

MaxReplicas is the upper bound for HPA scaling. When set, the operator creates a HorizontalPodAutoscaler targeting the guardrails Deployment.


format int32 · min 1
messageTimeoutstring

MessageTimeout is the ext-proc message timeout: how long Envoy waits for the main-processor to ack each request/response body chunk. The budget must outlast every downstream call made while the processor holds the chunk — most importantly the NER /analyze RTT — so it is bounded below by NERProviderConfig.Timeout. The default (60s) sits comfortably above the 5s NER default and covers the 200-500 KB request bodies typical of agent CLI traffic (Claude Code, Gemini CLI). The CEL rule on ProcessorConfig enforces messageTimeout > nerProvider.timeout at admission; an operator who lowers this value should lower nerProvider.timeout in lock-step.


default "60s"
mutationFailureActionstring

MutationFailureAction controls behaviour when redacting (mutating) the body fails. "Block" (default, fail-closed) blocks the request with a 403; "Continue" (fail-open) lets it through with no redaction applied. Applies to the processor's mutation stage and is independent of the detection engine.


default "Block" · enum: Block | Continue
nerProviderobject

NERProvider configures the ML/external NER backend that performs PII detection. The Type discriminator selects the provider implementation and the matching sub-block carries provider-specific settings. When nil, no detection runs and the processor passes traffic through; the operator gates the EnvoyExtensionPolicy on this field, so an unconfigured processor receives no traffic.

placeholderstring

Placeholder is the redaction string substituted for matched bytes when the active action is Redact. Empty falls back to a default derived from the entity type (e.g. "[REDACTED-EMAIL_ADDRESS]").

replicasinteger

Replicas is the number of guardrail ext-proc pods.


default 2 · format int32 · min 1
requestActionstring

RequestAction is the action the processor takes when the NER provider reports a PII match on the request path. "Block" (default) returns 403, "Redact" replaces the matched bytes with Placeholder, "LogOnly" emits telemetry and lets the request through unchanged.


default "Block" · enum: Block | Redact | LogOnly
resourcesobject

Resources defines compute resource requirements for the guardrails pods.

responseActionstring

ResponseAction is the action the processor takes when the NER provider reports a PII match on the response path. Same enum as RequestAction; when empty, the processor falls back to RequestAction.


enum: Block | Redact | LogOnly
targetCPUUtilizationinteger

TargetCPUUtilization is the CPU utilization target for HPA scaling (percentage).


default 75 · format int32 · min 1 · max 100
terminationGracePeriodSecondsinteger

TerminationGracePeriodSeconds is how long the kubelet waits after sending SIGTERM before it SIGKILLs a main-processor pod. On SIGTERM the processor calls grpcServer.GracefulStop(), which keeps every in-flight ext-proc stream (i.e. an in-progress streaming LLM completion) open until it finishes; the kubelet then kills the pod once this period elapses. The Kubernetes default (30s) is far shorter than a long agentic generation, so a rollout that lands mid-stream severs the upstream connection and the caller sees an unexpected EOF with no response. When unset, the operator DERIVES this from the effective maxStreamDuration ceiling (spec.gateway.streamTimeouts.maxStreamDuration) plus a fixed teardown margin, so the grace always covers a full-length stream and moves in lock-step with the ceiling instead of drifting from it. Set an explicit value only to pin a tighter or wider known bound; when the ceiling is unbounded ("0s") the operator falls back to a fixed default.


format int64 · min 1
tlsobject

TLS overrides the mTLS settings for the Envoy-to-main-processor hop. Setting it also opts this gateway into that mTLS regardless of the operator's default, which is how a CR asked for mTLS before the operator carried a chart-level switch (certManager.processorTLS.enabled). Prefer leaving this unset and enabling mTLS on the operator chart, so the issuer is named once for the whole platform: omitting it means "no per-gateway override", NOT "mTLS off". Do not add +nullable here. Every field below is optional, so an author opting in with a fully empty object can end up submitting a JSON null instead of `{}` — that is how the staging manifests broke. Marking null valid stores it, and a stored null unmarshals back to a nil pointer that reads as "no override" while the author meant "opt in": plaintext on a hop carrying full request and response bodies. Non-nullable makes a null a rejection on server-side apply (Flux, `kubectl apply --server-side`) and a prune-to-absent on a client-side create or update, so it is never stored as a silent off. Pinned by test-integration/processor_tls_validation_test.go.

spec.processor.diagnostics

Diagnostics tunes Debug-level diagnostic log emission from the processor. Every field is Debug-only: nothing here changes runtime behavior outside the log stream, and nothing emits unless the main-processor pod runs with LOG_LEVEL=DEBUG. Default values are chosen so an operator enabling DEBUG for unrelated diagnostics does not inherit PII emission as a side effect. See ProcessorDiagnostics for the per-field threat model.

FieldTypeDescription
includePIIInDebugLogsboolean

IncludePIIInDebugLogs, when true, includes the matched substring (the raw bytes the PII detector flagged) in the "redact match diagnostic" Debug record. Default false. SECURITY: enabling this writes raw user PII to whatever log sink the main-processor pod forwards to at Debug level. Two gates must both be open for the substring to land in a log: this flag AND LOG_LEVEL=DEBUG on the pod. The Info-level absence contract pinned by the processor unit tests holds regardless of this flag. Intended for incident response only — leave false in production unless an active investigation requires the matched substring to be diagnosable from a support bundle. Note on the wire representation: this field combines a kubebuilder default of false with a JSON omitempty modifier. On a bool that pairing means "absent" and "explicitly false" are indistinguishable on Get — both round-trip as the default. That is intentional and aligned with the safety property (absent === safe default). Do not promote this to a *bool tri-state unless a future feature genuinely needs to distinguish unset from false.


default false
spec.processor.nerProvider

NERProvider configures the ML/external NER backend that performs PII detection. The Type discriminator selects the provider implementation and the matching sub-block carries provider-specific settings. When nil, no detection runs and the processor passes traffic through; the operator gates the EnvoyExtensionPolicy on this field, so an unconfigured processor receives no traffic.

FieldTypeDescription
circuitBreakerobject

CircuitBreaker overrides the failure threshold and reset timeout for the provider call path.

concurrencyinteger

Concurrency caps the number of Presidio /analyze calls the processor fans out in parallel per request (one goroutine per text node). Higher values reduce latency on multi-node payloads at the cost of more simultaneous connections to the Presidio backend. Takes effect on the processor rollout triggered by this config change.


default 8 · format int32 · min 1 · max 128
failureActionstring

FailureAction controls behaviour when the provider circuit breaker is open or the provider returns an error. "fail-closed" (default) blocks the request with a 403 guardrail_circuit_open response; "fail-open" logs a warning and allows the request to proceed with no matches.


default "fail-closed" · enum: fail-closed | fail-open
presidioobject

Presidio carries Presidio-specific settings. Required when type=presidio.

resultCacheobject

ResultCache opts this gateway's NER scan results into the shared Redis/Valkey cache. Provider-agnostic (it caches whatever the selected provider returned for a given text), which is why it sits at this level rather than inside the presidio sub-block.

timeoutstring

Timeout is the per-request timeout applied to calls into the NER provider. Uses Go duration format (e.g., "500ms", "1s", "5s"). The default (5s) is sized for Microsoft Presidio's spaCy engine — the only provider this CRD wires today — running on the request-body sizes the gateway sees in practice: 100-500ms per /analyze on a warm replica with dedicated CPU, 200-3000ms when the pod shares a CPU-bound node, and several seconds on the 200-500 KB bodies typical of agent CLI traffic (Claude Code, Gemini CLI). 5s sits above warm-replica p99 and the contended/large-body case while staying short enough that the gateway fail-closes in seconds when Presidio is stuck. Operators with strict latency SLOs can lower this: a tighter timeout trips the breaker sooner, which is the right behaviour when the SLO is the dominant pressure. ProcessorConfig.MessageTimeout must remain strictly greater than this value or Envoy aborts the ext-proc stream before NER returns; the CEL rule on ProcessorConfig enforces that ordering at admission.


default "5s" · pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$
typerequiredstring

Type selects the provider implementation. Currently only `presidio` is supported; the enum is narrow on purpose so unsupported values fail at admission instead of producing a silently-empty processor config.


enum: presidio
spec.processor.nerProvider.circuitBreaker

CircuitBreaker overrides the failure threshold and reset timeout for the provider call path.

FieldTypeDescription
failureThresholdinteger

FailureThreshold is the consecutive failure count that trips the breaker.


format int32 · min 1
resetTimeoutstring

ResetTimeout is how long the breaker stays open before allowing a probe request. Uses Go duration format (e.g., "30s", "1m").


pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$
spec.processor.nerProvider.presidio

Presidio carries Presidio-specific settings. Required when type=presidio.

FieldTypeDescription
allowliststring[]

Allowlist defines text strings that should be ignored by Presidio detection. Useful for reducing false positives on known company names or product names. Honoured server-side by Presidio's AllowListRecognizer at analysis time; allowlisted strings never appear in match results and do not bias scoring of nearby tokens.

allowlistMatchstring

AllowlistMatch selects how Presidio compares Allowlist entries to detected text. "Exact" (default) is case-insensitive equality on the matched value. "Regex" treats each Allowlist entry as a regular expression evaluated by Presidio against the matched value, so patterns like `acme-[a-z0-9]+` skip the per-variant enumeration that exact mode forces. Inert when Allowlist is empty.


default "Exact" · enum: Exact | Regex
autoscalingobject

Autoscaling selects how the Presidio Deployment is scaled. When nil/Operator (the default), the operator creates an HPA targeting the Deployment when MaxReplicas is set. When `External`, the operator skips HPA creation and stops re-emitting `replicas` on subsequent reconciles so an external autoscaler (Keda, KPA, vendor) can write to the Deployment without being reverted. When `None`, no HPA is created and `replicas` is owned by the operator (manual scaling via the CRD).

entitiesstring[]

Entities is the list of Presidio entity types to detect. The default covers the common structured-PII entities (CREDIT_CARD, US_SSN, PHONE_NUMBER, EMAIL_ADDRESS, ABA_ROUTING_NUMBER, IBAN_CODE, IP_ADDRESS, US_ITIN, IN_AADHAAR, AU_TFN, UK_NHS) alongside the NER-model entities (PERSON, LOCATION, DATE_TIME). Extend via manifest to add additional Presidio built-ins or custom recognizers. See https://microsoft.github.io/presidio/supported_entities/ for the full list of supported built-in and custom entity types.


default ["PERSON","LOCATION","DATE_TIME","CREDIT_CARD","US_SSN","PHONE_NUMBER","EMAIL_ADDRESS","ABA_ROUTING_NUMBER","IBAN_CODE","IP_ADDRESS","US_ITIN","IN_AADHAAR","AU_TFN","UK_NHS"]
entityActionsobject[]

EntityActions overrides the default action / placeholder / mode for a specific Presidio entity type (e.g. "US_SSN", "EMAIL_ADDRESS", "CREDIT_CARD"). Entries are keyed by entity name and layered on top of processor.defaults; entities without an entry use the global default. Entity names are not constrained to a closed set so custom Presidio recognizers are supported. Per-entity overrides are Presidio-vocabulary-specific, so this list lives in the provider sub-block rather than at the processor level.

imagestring

Image is the container image for the Presidio Analyzer deployment. When empty, the operator uses its `PRESIDIO_DEFAULT_IMAGE` env var (the operator Helm chart wires this from values.yaml with a digest pin), so most installs should leave this unset and let the chart pin the version. Override for air-gapped clusters or to consume a newer Presidio release without bumping the operator. If both this field and the operator's PRESIDIO_DEFAULT_IMAGE are empty the operator fails the Deployment reconcile with a clear error rather than silently using a mutable tag.


minLength 1
imagePullPolicystring

ImagePullPolicy defines the pull policy for the Presidio Analyzer image.


default "IfNotPresent" · enum: Always | Never | IfNotPresent
languagestring

Language is the ISO 639-1 two-letter language code passed to Presidio's /analyze endpoint. Currently restricted to "en" because the vendored default_recognizers.yaml only declares supported_languages: [en], so a different language stamped onto a custom recognizer would either be silently dropped by Presidio's loader or wedge the rollout. The field is kept (rather than removed) so non-en support can be enabled by widening the Enum once the vendored defaults grow corresponding language coverage and we have e2e tests for that path.


default "en" · enum: en
maxReplicasinteger

MaxReplicas is the upper bound for HPA scaling of the Presidio Analyzer deployment. When set, the operator creates a HorizontalPodAutoscaler targeting the Presidio Deployment.


format int32 · min 1
recognizersobject[]

Recognizers declares user-defined Presidio Pattern recognizers that the operator materializes into a recognizers YAML and mounts into the Presidio Analyzer pod at startup. Each entry mirrors Presidio's PatternRecognizer shape (name, supportedEntity, patterns, optional contextWords and denyList). The ConfigMap is rendered alongside the upstream-vendored default recognizer set so built-ins continue to work; setting this field never disables predefined recognizers. Recognizers are Presidio-specific (numeric per-pattern scores, implicit-proximity context words, deny-lists) and do not generalize across DLP providers, which is why they live in the provider sub-block rather than at the processor level. The operator auto-unions every supportedEntity into Entities at reconcile time, so an entity declared on a recognizer does not also need to be added to spec.processor.nerProvider.presidio.entities.

replicasinteger

Replicas is the number of Presidio Analyzer pods. When `autoscaling.mode` is `Operator` (default) or `None`, the operator owns this field and writes it on every reconcile. When `autoscaling.mode` is `External`, the operator does not write `spec.replicas` to the Deployment at all — an external scaler (Keda, KPA, vendor) owns the field, and this CRD value is silently ignored after the Deployment's first creation. Set this only as a manual scaling target in `Operator`/`None` modes.


default 2 · format int32 · min 1
resourcesobject

Resources defines compute resource requirements for the Presidio Analyzer pods. The operator applies a runtime fallback of 500m/2 CPU + 1Gi/2Gi memory when this field is nil; the fallback accommodates the spaCy en_core_web_md model (~800MB resident). The defaults are not surfaced in the CRD schema (so kubectl explain does not show them) because they live in the controller, not in admission — set this field explicitly to pin observable values.

scoreThresholdinteger

ScoreThreshold is the minimum confidence score as a percentage (0-100) for a Presidio detection to be acted upon. The operator converts this to a 0.0-1.0 float when calling the Presidio API (e.g., 50 becomes 0.5). Higher values reduce false positives.


default 50 · format int32 · min 0 · max 100
targetCPUUtilizationinteger

TargetCPUUtilization is the CPU utilization target for HPA scaling (percentage) of the Presidio Analyzer deployment. The operator applies a runtime fallback of 75 when this field is nil and MaxReplicas is set. No kubebuilder default is emitted because the paired CEL rule above (`has(targetCPUUtilization) -> has(maxReplicas)`) needs `nil` to be observable as "user did not set this", which a default-everything-to-75 would erase.


format int32 · min 1 · max 100
spec.processor.nerProvider.presidio.autoscaling

Autoscaling selects how the Presidio Deployment is scaled. When nil/Operator (the default), the operator creates an HPA targeting the Deployment when MaxReplicas is set. When `External`, the operator skips HPA creation and stops re-emitting `replicas` on subsequent reconciles so an external autoscaler (Keda, KPA, vendor) can write to the Deployment without being reverted. When `None`, no HPA is created and `replicas` is owned by the operator (manual scaling via the CRD).

FieldTypeDescription
modestring

Mode selects who owns replicas. Operator (default) means the operator creates an HPA when MaxReplicas is set and hands replica ownership to that HPA; with MaxReplicas unset it re-emits `replicas` on every reconcile. External means the operator stays out of replicas entirely so an external autoscaler (e.g. Keda ScaledObject) can drive the Deployment. None disables operator-managed HPA but keeps `replicas` pinned to the CRD value (manual scaling).


default "Operator" · enum: Operator | External | None
spec.processor.nerProvider.presidio.entityActions[]

EntityActions overrides the default action / placeholder / mode for a specific Presidio entity type (e.g. "US_SSN", "EMAIL_ADDRESS", "CREDIT_CARD"). Entries are keyed by entity name and layered on top of processor.defaults; entities without an entry use the global default. Entity names are not constrained to a closed set so custom Presidio recognizers are supported. Per-entity overrides are Presidio-vocabulary-specific, so this list lives in the provider sub-block rather than at the processor level.

FieldTypeDescription
entityrequiredstring

Entity is the NER provider entity name to override (e.g. "US_SSN", "EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD", or a custom recognizer's supportedEntity). Matched case-sensitively against the provider's reported entity type.


minLength 1 · maxLength 128
modestring

Mode controls enforcement for this entity. "enforce" (default) applies the configured action; "shadow" detects and emits telemetry/logging but never blocks or redacts.


default "enforce" · enum: enforce | shadow
placeholderstring

Placeholder overrides the redaction placeholder for matches of this entity. Empty falls back to ProcessorConfig.Placeholder.


maxLength 64
requestActionstring

RequestAction overrides the default action on the request path.


enum: Block | Redact | LogOnly
responseActionstring

ResponseAction overrides the default action on the response path.


enum: Block | Redact | LogOnly
spec.processor.nerProvider.presidio.recognizers[]

Recognizers declares user-defined Presidio Pattern recognizers that the operator materializes into a recognizers YAML and mounts into the Presidio Analyzer pod at startup. Each entry mirrors Presidio's PatternRecognizer shape (name, supportedEntity, patterns, optional contextWords and denyList). The ConfigMap is rendered alongside the upstream-vendored default recognizer set so built-ins continue to work; setting this field never disables predefined recognizers. Recognizers are Presidio-specific (numeric per-pattern scores, implicit-proximity context words, deny-lists) and do not generalize across DLP providers, which is why they live in the provider sub-block rather than at the processor level. The operator auto-unions every supportedEntity into Entities at reconcile time, so an entity declared on a recognizer does not also need to be added to spec.processor.nerProvider.presidio.entities.

FieldTypeDescription
contextWordsstring[]

ContextWords are tokens whose presence near a match boosts the match's confidence (Presidio's implicit-proximity context-word enhancement). Useful for relaxing a low-score pattern that would otherwise fall below scoreThreshold while still suppressing matches in unrelated text.

denyListstring[]

DenyList is a vocabulary of terms that match as the SupportedEntity even when no pattern fires. Each entry is regex-escaped by Presidio, joined into a single alternation, and evaluated under `re.DOTALL | re.MULTILINE | re.IGNORECASE` with word-boundary anchors `(?:^|(?<=\W))(...)(?:(?=\W)|$)`. So matches are case-insensitive, word-boundary substring matches anywhere in the input — NOT exact-string equality: - `"PROJ-1"` fires on `"PROJ-1"`, `"proj-1"`, `" PROJ-1."`. - `"PROJ-1"` does NOT fire on `"PROJ-12"` (`1` is followed by `2`, not `\W`). Useful for fixed vocabularies (titles, internal product code names) where regex would be overkill. Storage warning: the rendered recognizers YAML lives in a ConfigMap (`{cr-name}-presidio-recognizers`). Anyone with `get configmap` in the namespace, including most read-only RBAC roles, can read every entry verbatim. Avoid putting sensitive content in DenyList[]: internal product code names, employee names, or any vocabulary whose disclosure carries the same risk as disclosing the matched data itself.

namerequiredstring

Name identifies the recognizer in Presidio's logs and metrics. Must be unique within the recognizers list. The operator does not impose the upstream PascalCase recognizer-name convention because Presidio accepts any unique string for custom recognizers. The leading character is constrained to alphanumeric so the name remains safe to use as a CLI flag value, filename component, or label value (a leading `-` breaks flag parsing; a leading `.` fails kube admission on metadata.name; a leading `_` is rejected by some label validators).


pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$ · minLength 1 · maxLength 128
patternsobject[]

Patterns is the list of regex patterns this recognizer matches on. Each pattern carries its own confidence score; matches are surfaced at the highest pattern score, optionally boosted by ContextWords.

supportedEntityrequiredstring

SupportedEntity is the entity label this recognizer emits on a match (e.g. "INTERNAL_ACCOUNT_ID"). Convention is upper snake case to match Presidio's built-in entity vocabulary. The operator auto-unions every SupportedEntity into the Entities list at reconcile time, so the same name does not also need to be declared there. Custom recognizers may NOT target an entity emitted by one of Presidio's built-in recognizers (e.g. EMAIL_ADDRESS, US_SSN). The upstream registry has no name-dedup or replace-by-name path — declaring a custom recognizer with a built-in supportedEntity does not shadow the built-in; both fire independently. Presidio's analyze-time dedup collapses identical-span same-entity hits but keeps partial overlaps as separate `RecognizerResult` rows, so stacking on a built-in produces unpredictable results. The rejection list mirrors `internal/guardrails/provider/presidio`'s BuiltinEntities; a drift test in that package fails CI when the two views disagree. Pick a unique entity name, or wait for upstream to ship `replace_recognizer` semantics.


pattern ^[A-Z][A-Z0-9_]*$ · minLength 1 · maxLength 128
spec.processor.nerProvider.presidio.recognizers.patterns[]

Patterns is the list of regex patterns this recognizer matches on. Each pattern carries its own confidence score; matches are surfaced at the highest pattern score, optionally boosted by ContextWords.

FieldTypeDescription
namerequiredstring

Name identifies the pattern in Presidio's logs and metrics. Must be unique within the parent recognizer's patterns list.


minLength 1 · maxLength 128
regexrequiredstring

Regex is the pattern Presidio evaluates against incoming text. The operator pre-compiles each pattern with Go's RE2 engine before applying the recognizers ConfigMap, so non-compiling patterns fail reconcile rather than wedging the Presidio rollout. Presidio itself runs the third-party Python `regex` package (imported as `re`), whose syntax is a superset of RE2 — patterns using lookaround or backreferences are accepted by the engine but rejected by the operator's pre-compile because the YAML loader can't express them anyway; use ContextWords for proximity matching instead. RE2 acceptance does NOT imply linear-time evaluation in Presidio: `regex` is a backtracking engine, so polynomial patterns like `(a+)+x` compile under RE2 syntax but can blow up on crafted inputs. The operator's actual ReDoS guard is the per-call `REGEX_TIMEOUT_SECONDS` env var on the Presidio container, which the operator pins to a tight default (5s) so a bad pattern caps a single request rather than the worker. The 1024-character bound matches the project's regex length limit on other custom-pattern surfaces.


minLength 1 · maxLength 1024
scoreinteger

Score is the pattern's confidence as a percentage (0-100). The operator converts this to the 0.0-1.0 float Presidio expects when rendering the recognizers YAML. Combines with ContextWords boosting and PresidioConfig.ScoreThreshold filtering.


default 50 · format int32 · min 0 · max 100
spec.processor.nerProvider.presidio.resources

Resources defines compute resource requirements for the Presidio Analyzer pods. The operator applies a runtime fallback of 500m/2 CPU + 1Gi/2Gi memory when this field is nil; the fallback accommodates the spaCy en_core_web_md model (~800MB resident). The defaults are not surfaced in the CRD schema (so kubectl explain does not show them) because they live in the controller, not in admission — set this field explicitly to pin observable values.

FieldTypeDescription
limitsobject

Limits defines the maximum resources allowed.

requestsobject

Requests defines the minimum resources required.

spec.processor.nerProvider.presidio.resources.limits

Limits defines the maximum resources allowed.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.processor.nerProvider.presidio.resources.requests

Requests defines the minimum resources required.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.processor.nerProvider.resultCache

ResultCache opts this gateway's NER scan results into the shared Redis/Valkey cache. Provider-agnostic (it caches whatever the selected provider returned for a given text), which is why it sits at this level rather than inside the presidio sub-block.

FieldTypeDescription
enabledboolean

Enabled turns the cache on. Off by default: the cache is a latency optimization, and an install that never opts in must keep the main-processor pod template it had before this field existed (a stamped backend address would otherwise force a rolling restart on upgrade).


default false
ttlstring

TTL is how long a cached scan result lives. Defaults to 24h when unset. Entries cannot go stale: the cache key is content- and config-addressed, so anything that would change the answer changes the key. This is therefore purely a memory and disclosure-window knob, never a correctness one — which is what makes a long default defensible, since agentic sessions routinely outlive a shorter window and a session resumed after a break should still be warm. It rides the watched scanning ConfigMap rather than pod env, and the operator deliberately keeps it out of the main-processor's checksum/config annotation, so retuning it hot-reloads: it rolls no pod, rebuilds no NER provider, and resets no circuit-breaker state. That last point is the reason for the care — a retune during a Presidio outage must not reopen the breaker and re-herd a down backend into 403s under the default fail-closed action. Footprint, for sizing: an entry holds entity types and byte offsets and never the scanned text, so entry size is independent of how large the scanned turn was. Budget roughly 250 bytes per entry with Redis overhead, times the number of DISTINCT turns in the window.


pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$ · maxLength 32
spec.processor.resources

Resources defines compute resource requirements for the guardrails pods.

FieldTypeDescription
limitsobject

Limits defines the maximum resources allowed.

requestsobject

Requests defines the minimum resources required.

spec.processor.resources.limits

Limits defines the maximum resources allowed.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.processor.resources.requests

Requests defines the minimum resources required.

FieldTypeDescription
cpustring

CPU is the CPU resource quantity (e.g., "500m", "2").

memorystring

Memory is the memory resource quantity (e.g., "512Mi", "2Gi").

spec.processor.tls

TLS overrides the mTLS settings for the Envoy-to-main-processor hop. Setting it also opts this gateway into that mTLS regardless of the operator's default, which is how a CR asked for mTLS before the operator carried a chart-level switch (certManager.processorTLS.enabled). Prefer leaving this unset and enabling mTLS on the operator chart, so the issuer is named once for the whole platform: omitting it means "no per-gateway override", NOT "mTLS off". Do not add +nullable here. Every field below is optional, so an author opting in with a fully empty object can end up submitting a JSON null instead of `{}` — that is how the staging manifests broke. Marking null valid stores it, and a stored null unmarshals back to a nil pointer that reads as "no override" while the author meant "opt in": plaintext on a hop carrying full request and response bodies. Non-nullable makes a null a rejection on server-side apply (Flux, `kubectl apply --server-side`) and a prune-to-absent on a client-side create or update, so it is never stored as a silent off. Pinned by test-integration/processor_tls_validation_test.go.

FieldTypeDescription
issuerRefobject

IssuerRef configures the cert-manager issuer for the main-processor and guardrails-adapter serving certs. When omitted, the operator falls back to its chart-configured default issuer (certManager.defaultIssuer on the operator chart). Reconciliation fails closed if neither the CR nor the operator default supplies an issuer name.

spec.processor.tls.issuerRef

IssuerRef configures the cert-manager issuer for the main-processor and guardrails-adapter serving certs. When omitted, the operator falls back to its chart-configured default issuer (certManager.defaultIssuer on the operator chart). Reconciliation fails closed if neither the CR nor the operator default supplies an issuer name.

FieldTypeDescription
kindstring

Kind is either "Issuer" or "ClusterIssuer".


default "ClusterIssuer" · enum: Issuer | ClusterIssuer
namerequiredstring

Name of the Issuer or ClusterIssuer.

spec.providers[]

Providers is the list of LLM provider backends.

FieldTypeDescription
credentialsrequiredobject

Credentials defines how to authenticate to the upstream provider.

endpointrequiredobject

Endpoint defines the upstream provider endpoint.

namerequiredstring

Name is the unique identifier for this provider.


pattern ^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$ · maxLength 63
pathPrefixstring

PathPrefix customizes the upstream request path for an OpenAI-compatible backend; e.g. OpenRouter uses "/api/v1". Only valid when schema is OpenAI — the upstream prefix mechanism (AIServiceBackend schema.prefix) only applies to the OpenAI translator. Registry-owned aliases (e.g. GeminiAIStudio) already carry their own prefix and must not set this field.


pattern ^/?[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$ · maxLength 128
schemarequiredstring

Schema is the provider API schema. Mirrors the upstream Envoy AI Gateway AIServiceBackend schema enum (aigateway.envoyproxy.io/v1alpha1) verbatim except for two operator translations: GeminiAIStudio → {name: OpenAI, prefix: /v1beta/openai} for Google AI Studio's OpenAI-compatibility endpoint, and GoogleGenerativeLanguage targets the public Gemini API at generativelanguage.googleapis.com (v1beta/models/{model}:generateContent). GCPVertexAI talks to a regional Vertex AI aiplatform endpoint. AWSAnthropic serves Anthropic Claude models on AWS Bedrock via the InvokeModel API (native Anthropic Messages wire shape), distinct from AWSBedrock's Converse API: the InvokeModel path returns the Anthropic-native usage block the processor already prices, so Bedrock Claude traffic is billed under the `awsanthropic` pricing family. The exact upstream fork version this mirrors is pinned in FORK-VERSION; it is deliberately not restated here so this comment does not go stale on every fork bump.


enum: OpenAI | Anthropic | AWSBedrock | AWSAnthropic | AzureOpenAI | GCPVertexAI | GeminiAIStudio | GoogleGenerativeLanguage
spec.providers.credentials

Credentials defines how to authenticate to the upstream provider.

FieldTypeDescription
projectNamestring

ProjectName is the GCP project ID slug (the unique identifier used in API calls — e.g. "my-prod-7a3b" — not the human-readable display name). Required when Type=GCPCredentials, otherwise unused. Together with Region, this scopes Vertex requests to a project/region pair on the upstream BackendSecurityPolicy. The pattern matches GCP's documented project ID shape (lowercase letter followed by 5-29 chars of [a-z0-9-], ending in [a-z0-9]).


pattern ^[a-z][a-z0-9-]{4,28}[a-z0-9]$ · maxLength 30
regionstring

Region is the cloud provider region (for AWSCredentials, AzureCredentials, GCPCredentials). The pattern accepts lowercase canonical region tokens such as us-east-1, us-central1, europe-west4, eastus2 — and rejects whitespace, uppercase, and trailing newlines that would propagate into the upstream BackendSecurityPolicy.


pattern ^[a-z][a-z0-9-]*[a-z0-9]$ · maxLength 63
secretRefobject

SecretRef references the Kubernetes Secret containing the credentials.

typerequiredstring

Type is the authentication mechanism.


enum: APIKey | AnthropicAPIKey | AWSCredentials | AzureAPIKey | AzureCredentials | GCPCredentials
spec.providers.credentials.secretRef

SecretRef references the Kubernetes Secret containing the credentials.

FieldTypeDescription
keystring

Key within the Secret data.


default "apiKey"
namerequiredstring

Name of the Secret.

spec.providers.endpoint

Endpoint defines the upstream provider endpoint.

FieldTypeDescription
hostnamerequiredstring

Hostname of the upstream provider API.


minLength 1
portinteger

Port of the upstream provider API.


default 443 · format int32

spec.resilience

Resilience configures retry, circuit breaker, passive health checking, and upstream timeouts for all routes managed by this gateway. When nil, upstream errors pass through to the client unchanged (no retries).

FieldTypeDescription
circuitBreakerobject

CircuitBreaker bounds connection and request concurrency to each upstream cluster.

passiveHealthCheckobject

PassiveHealthCheck configures Envoy outlier detection so that hosts returning consecutive errors are ejected from the load balancing set.

retryobject

Retry configures request retries on the upstream data path.

upstreamConnectionobject

UpstreamConnection tunes how Envoy recycles pooled connections to each upstream provider. Recycling Envoy's own connections on a shorter clock than the provider's edge prevents the provider from closing a connection mid-response, which otherwise surfaces to clients as an abrupt socket close.

spec.resilience.circuitBreaker

CircuitBreaker bounds connection and request concurrency to each upstream cluster.

FieldTypeDescription
maxConnectionsinteger

MaxConnections caps concurrent TCP connections to the upstream cluster.


format int32 · min 1
maxParallelRequestsinteger

MaxParallelRequests caps in-flight requests to the upstream cluster.


format int32 · min 1
maxParallelRetriesinteger

MaxParallelRetries caps in-flight retry attempts to the upstream cluster so a retry storm cannot exhaust upstream capacity.


format int32 · min 1
maxPendingRequestsinteger

MaxPendingRequests caps requests waiting for a ready connection.


format int32 · min 1
spec.resilience.passiveHealthCheck

PassiveHealthCheck configures Envoy outlier detection so that hosts returning consecutive errors are ejected from the load balancing set.

FieldTypeDescription
baseEjectionTimestring

BaseEjectionTime is how long an ejected host stays out of the load balancing set before rejoining. Accepts Go duration syntax.


pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$
consecutive5xxErrorsinteger

Consecutive5xxErrors is the number of consecutive 5xx responses that cause a host to be ejected.


format int32 · min 1
consecutiveGatewayErrorsinteger

ConsecutiveGatewayErrors is the number of consecutive gateway errors (502, 503, 504) that cause a host to be ejected.


format int32 · min 1
intervalstring

Interval is the scan cadence for outlier detection. Accepts Go duration syntax (e.g. "10s", "1m").


pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$
maxEjectionPercentinteger

MaxEjectionPercent is the upper bound on the percentage of hosts that outlier detection may eject at once, preventing mass ejection from collapsing the cluster.


format int32 · min 1 · max 100
spec.resilience.retry

Retry configures request retries on the upstream data path.

FieldTypeDescription
numAttemptsPerPriorityinteger

NumAttemptsPerPriority is the number of attempts made against hosts at the same priority before failing over to the next priority group. Defaults to 1 (switch to the next priority on every retry).


format int32 · min 1 · max 10
numRetriesinteger

NumRetries is the maximum retry attempts per request.


format int32 · min 0 · max 10
perRetryTimeoutstring

PerRetryTimeout bounds each individual retry attempt. To bound the whole request lifetime across all attempts use spec.gateway.timeouts.requestTimeout or spec.routes[].timeouts.requestTimeout.


pattern ^([0-9]+([.][0-9]+)?(ns|us|ms|s|m|h))+$
retryOnobject

RetryOn selects the upstream conditions that trigger a retry. At least one trigger or HTTP status code must be declared whenever NumRetries is greater than zero.

spec.resilience.retry.retryOn

RetryOn selects the upstream conditions that trigger a retry. At least one trigger or HTTP status code must be declared whenever NumRetries is greater than zero.

FieldTypeDescription
httpStatusCodesinteger[]

HTTPStatusCodes pairs with the retriable-status-codes trigger. The trigger has no effect unless at least one status code is listed here.

triggersstring[]

Triggers is the list of retry conditions. Combined with HTTPStatusCodes by OR; a request is retried when any trigger matches.

spec.resilience.upstreamConnection

UpstreamConnection tunes how Envoy recycles pooled connections to each upstream provider. Recycling Envoy's own connections on a shorter clock than the provider's edge prevents the provider from closing a connection mid-response, which otherwise surfaces to clients as an abrupt socket close.

FieldTypeDescription
connectionIdleTimeoutstring

ConnectionIdleTimeout is how long a pooled upstream connection may sit with no active requests before Envoy closes it. Closing idle connections locally avoids reusing one the provider's edge has already reaped. Maps to spec.timeout.http.connectionIdleTimeout on the BackendTrafficPolicy. Accepts the Envoy Gateway Duration form (e.g. "55s", "5m"); no fractional values or us/ns units. When unset, Envoy's 1h default applies.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$
maxConnectionDurationstring

MaxConnectionDuration is the absolute lifetime of an upstream connection regardless of activity. On reaching it Envoy drains the connection gracefully (HTTP/2 GOAWAY: in-flight streams finish, no new streams open), so setting it below the provider's own max connection age prevents the provider from closing a connection mid-response. Maps to spec.timeout.http.maxConnectionDuration on the BackendTrafficPolicy. Accepts the Envoy Gateway Duration form (e.g. "10m", "30m"); no fractional values or us/ns units. When unset, Envoy leaves connection lifetime unbounded.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$

spec.routes[]

Routes defines model routing rules. Maximum 120 entries — the hard ceiling of 15 collapsed rules times 8 matches per rule (see below); the practical ceiling is lower and depends on how many routes share a backend. The operator collapses routes that share the same backendRefs and timeout into ONE AIGatewayRoute rule with one match per model, chunked at 8 matches per rule — e.g. 40 model routes split across 3 providers of <=8 models each become 3 rules; a single backend of 20 models becomes 3 rules (8+8+4). GeminiNative (path-matched) routes and the no-match default route are never collapsed; each keeps its own rule. Gateway API caps HTTPRoute.spec.rules at 16, and the upstream AI Gateway controller always appends a route-not-found rule (returning 404 for unmatched traffic) as the final entry, consuming one slot — so the collapsed rule count must stay at or below 15, and each rule holds at most 8 matches, giving the 15*8=120 model ceiling. The reconciler enforces this at reconcile time (see the RoutesValid status condition) since the grouping is not CEL-expressible; a spec with too many distinct backend/timeout combinations sets RoutesValid=False and the AIGatewayRoute is not updated (retains last-known-good) rather than failing admission. Inserting or removing a route that changes which routes share a group shifts every subsequent group's rule index (rule/i), and therefore its RLS generic_key route scope — the same class of budget-counter reset that already applies to reordering routes today, just now scoped to the group rather than the individual route.

FieldTypeDescription
backendRefsrequiredobject[]

BackendRefs defines which providers handle matched requests.

displayNamestring

DisplayName is the human-readable label surfaced to model-discovery clients (e.g. Claude Code's model picker) via GET /v1/models `display_name`. When empty, the operator falls back to the route's Model id.


maxLength 253
inputSchemastring

InputSchema declares the wire-format shape this route accepts from clients. When omitted, the route accepts OpenAI-shaped requests at /v1/chat/completions, /v1/embeddings, etc. — backwards-compatible with every existing AIGateway. Set to "GeminiNative" to accept Gemini-shaped requests at /v1beta/models/{model}:generateContent and :streamGenerateContent (e.g., from Gemini CLI in proxy mode). The gateway translates the request to the backend provider's schema; any provider schema is permitted as a backend.


default "OpenAI" · enum: OpenAI | GeminiNative
matchobject

Match defines when this route applies. If omitted, this is the default route.

namerequiredstring

Name is a unique identifier for this route.


maxLength 63
timeoutsobject

Timeouts overrides the gateway-level total request cap for this specific route. Takes precedence over spec.gateway.timeouts. Like the gateway-level field, this is an opt-in absolute deadline on the entire request (including the streamed response) and is disabled by default — set it only when you want this route's requests to fail past a fixed wall-clock budget. Streaming bounds (idle + max-stream-duration) are configured gateway-wide via spec.gateway.streamTimeouts, not per route. Note: the operator also applies a default maxStreamDuration ceiling (15m); a per-route requestTimeout LONGER than the effective maxStreamDuration is pre-empted by that ceiling (the stream is cut first), so to allow longer requests you must raise BOTH this value and spec.gateway.streamTimeouts.maxStreamDuration. The operator surfaces a TimeoutConfigValid=False condition + Warning event when this happens.

spec.routes.backendRefs[]

BackendRefs defines which providers handle matched requests.

FieldTypeDescription
priorityinteger

Priority for failover. Lower values are preferred.


default 0 · format int32
providerrequiredstring

Provider must match the name of a defined provider.

weightinteger

Weight for weighted load balancing.


default 1 · format int32
spec.routes.match

Match defines when this route applies. If omitted, this is the default route.

FieldTypeDescription
modelrequiredstring

Model is the model name to match (matched against x-ai-eg-model header).

spec.routes.timeouts

Timeouts overrides the gateway-level total request cap for this specific route. Takes precedence over spec.gateway.timeouts. Like the gateway-level field, this is an opt-in absolute deadline on the entire request (including the streamed response) and is disabled by default — set it only when you want this route's requests to fail past a fixed wall-clock budget. Streaming bounds (idle + max-stream-duration) are configured gateway-wide via spec.gateway.streamTimeouts, not per route. Note: the operator also applies a default maxStreamDuration ceiling (15m); a per-route requestTimeout LONGER than the effective maxStreamDuration is pre-empted by that ceiling (the stream is cut first), so to allow longer requests you must raise BOTH this value and spec.gateway.streamTimeouts.maxStreamDuration. The operator surfaces a TimeoutConfigValid=False condition + Warning event when this happens.

FieldTypeDescription
requestTimeoutstring

RequestTimeout is the maximum total duration allowed for a single request, including the streamed response body. This is an absolute cap: the request is terminated when it elapses even if data is still flowing. Accepts the Envoy Gateway Duration form (e.g. "5m"). "0s" disables the cap.


pattern ^([0-9]{1,5}(h|m|s|ms)){1,4}$

spec.webhooks[]

Webhooks registers external in-cluster receivers that validate request traffic (Validating) and observe responses (Observation). Each entry names a same-namespace Service destination authenticated with a projected, audience-bound Kubernetes ServiceAccount token. Sits alongside spec.audit and spec.journaling; the operator and main processor wire these in (see docs/plan/webhooks.md).

FieldTypeDescription
authrequiredobject

Auth configures how the gateway authenticates to the receiver.

eventsstring[]

Events optionally filters which catalog events this webhook receives. When empty, the receiver gets the phase-default events. Receivers ignore events they do not recognize.

failurePolicystring

FailurePolicy decides the request outcome when the webhook call fails.


default "Fail" · enum: Fail | Ignore
includeRequestHeadersstring[]

IncludeRequestHeaders is a glob allowlist of request headers projected onto the envelope. A fixed reserved-header denylist always strips credential and identity carriers (and any X-Stacklok-* header) regardless of this allowlist.

includeResponseBodyboolean

IncludeResponseBody opts this response-phase webhook into receiving the assembled response body on the response.completed event. The body rides in result.body as structured JSON, always a JSON array: a streamed SSE response is the array of its data: event payloads, each kept verbatim in its provider-native shape (no reassembly into a logical completion); a non-streamed response is its single JSON object wrapped as a one-element array. Any Content-Encoding is decoded before assembly. Off by default: response bodies are large and carry un-redacted model output, so capture is an explicit per-webhook opt-in. The aggregate byte cap is a deployment-level knob (AIGW_WEBHOOK_MAX_RESPONSE_BODY_CAPTURE on the main-processor, set via the operator chart), not a CRD field. Has no effect on request-phase webhooks.


default false
includeResponseHeadersstring[]

IncludeResponseHeaders is a glob allowlist of response headers projected onto response-phase envelopes. The same reserved-header denylist applies.

includeSubjectClaimsstring[]

IncludeSubjectClaims is an allowlist of the caller's validated OIDC claim names projected onto subject.claims, on request- and response-phase envelopes alike. Only scalar string claims are eligible (the wire type is an object of strings); array, object, and numeric claims are never projected. Empty — the default — omits the claims block entirely. Entries are EXACT claim names, matched case-sensitively; the single wildcard "*" projects every eligible claim. Unlike the header allowlists above this is not glob-matched, because JWT claim names are case-sensitive and may contain '/' (namespaced claims such as `https://example.com/roles`) — see ProjectClaims in internal/webhooks. Off by default because a claim set is caller identity: it routinely carries email, name, and preferred_username, which a receiver has no business seeing unless it was registered to act on them. Name the specific claims a receiver needs (`iss`, `sub`, whichever claim its identity lookup keys on) rather than reaching for "*".

namerequiredstring

Name uniquely identifies this webhook within the AIGateway. Uniqueness is enforced by the API server (listType=map keyed on name). Keep this literal in sync with GuardrailsWebhookName, BudgetsAdmissionWebhookName, BudgetsUsageWebhookName, and BudgetsCaptureWebhookName (CEL markers cannot reference Go identifiers).


pattern ^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$ · minLength 1 · maxLength 63
phaserequiredstring

Phase is the request-lifecycle point at which this webhook fires.


enum: Request | Response
servicerequiredobject

Service is the in-cluster receiver destination, in the same namespace as the AIGateway. Modeled on admissionregistration.k8s.io/v1.ServiceReference.

timeoutSecondsinteger

TimeoutSeconds bounds a single webhook call.


default 5 · format int32 · min 1 · max 120
typerequiredstring

Type is the contract this webhook participates in.


enum: Validating | Observation
spec.webhooks.auth

Auth configures how the gateway authenticates to the receiver.

FieldTypeDescription
serviceAccountTokenobject

ServiceAccountToken configures projected, audience-bound ServiceAccount token authentication. Required when Type is ServiceAccountToken.

typerequiredstring

Type selects the authentication mode.


default "ServiceAccountToken" · enum: ServiceAccountToken
spec.webhooks.auth.serviceAccountToken

ServiceAccountToken configures projected, audience-bound ServiceAccount token authentication. Required when Type is ServiceAccountToken.

FieldTypeDescription
audiencerequiredstring

Audience the receiver expects in the projected token. Must be non-empty.


minLength 1 · maxLength 253
expirationSecondsinteger

ExpirationSeconds is the requested token lifetime. The kubelet clamps to a cluster minimum (commonly 600s); omitting it takes the kubelet default.


format int64 · min 600
spec.webhooks.service

Service is the in-cluster receiver destination, in the same namespace as the AIGateway. Modeled on admissionregistration.k8s.io/v1.ServiceReference.

FieldTypeDescription
namerequiredstring

Name of the receiver Service.


minLength 1 · maxLength 253
pathstring

Path is the HTTP path the envelope is POSTed to.


maxLength 511
portinteger

Port is the Service port the receiver serves HTTPS on. Defaults to 443 when omitted, matching the convention on the provider endpoint port.


default 443 · format int32 · min 1 · max 65535

status

AIGatewayStatus defines the observed state of the AI Gateway.

FieldTypeDescription
conditionsobject[]

Conditions represent the latest available observations of the AIGateway's state.

endpointstring

Endpoint is the gateway's externally-reachable endpoint URL.

observedGenerationinteger

ObservedGeneration is the most recent generation observed by the controller.


format int64
readyProvidersinteger

ReadyProviders is the count of providers in a ready state.


format int32
totalProvidersinteger

TotalProviders is the total number of configured providers.


format int32
webhooksobject[]

Webhooks reports the per-webhook provisioning-ping outcome. The operator fires a webhook.ping on registration and on Service-ref, audience, or token-expiration change, then records the result here so an operator can see, per webhook, whether the receiver accepted the audience-bound token. The dispatcher reads this to decide whether real traffic defers to the per-webhook failurePolicy while a probe is failing. The aggregate WebhooksReady condition summarizes these for humans.

status.conditions[]

Conditions represent the latest available observations of the AIGateway's state.

FieldTypeDescription
lastTransitionTimerequiredstring

lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.


format date-time
messagerequiredstring

message is a human readable message indicating details about the transition. This may be an empty string.


maxLength 32768
observedGenerationinteger

observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.


format int64 · min 0
reasonrequiredstring

reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.


pattern ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ · minLength 1 · maxLength 1024
statusrequiredstring

status of the condition, one of True, False, Unknown.


enum: True | False | Unknown
typerequiredstring

type of condition in CamelCase or in foo.example.com/CamelCase.


pattern ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ · maxLength 316

status.webhooks[]

Webhooks reports the per-webhook provisioning-ping outcome. The operator fires a webhook.ping on registration and on Service-ref, audience, or token-expiration change, then records the result here so an operator can see, per webhook, whether the receiver accepted the audience-bound token. The dispatcher reads this to decide whether real traffic defers to the per-webhook failurePolicy while a probe is failing. The aggregate WebhooksReady condition summarizes these for humans.

FieldTypeDescription
lastProbeTimestring

LastProbeTime is when the operator last fired a ping for this webhook.


format date-time
messagestring

Message is a human-readable explanation of the last ping outcome.

namerequiredstring

Name is the webhook this status entry describes (matches spec.webhooks[].name).

observedAudiencestring

ObservedAudience is the auth.serviceAccountToken.audience the last ping was bound to, so an audience change is visible as a status diff.

probeSucceededrequiredboolean

ProbeSucceeded is true when the most recent webhook.ping returned a 2xx. False means the last ping returned non-2xx, a transport error, or a token the receiver rejected; while false, real traffic defers to failurePolicy.

reasonstring

Reason is a one-word CamelCase summary (ProbeSucceeded / ProbeFailed).

Referenced by: