Context: We run gateway-level OIDC (Keycloak) on v1.6.1 and were preparing an upgrade to v1.8. The setup works on 1.6.1; the upgrade to 1.8.2/1.8.3 is what surfaced this. It currently blocks the upgrade.
Description
When a SecurityPolicy with oidc targets a Gateway (rather than an HTTPRoute), Envoy Gateway 1.8 emits the native envoy.filters.http.oauth2 HTTP filter into the HCM filter chain with an empty config — the filter's typed_config contains only the @type and no config field, and the filter is not marked disabled. Envoy rejects the whole listener:
gRPC config for type.googleapis.com/envoy.config.listener.v3.Listener rejected:
Error adding/updating listener(s) <ns>/<gw>/https: config must be present for global config
Because the listener is never programmed, every route served by that listener becomes unreachable. The SecurityPolicy still reports Accepted: True, and the OIDC token-endpoint cluster and the client/HMAC SDS secrets are generated correctly — only the filter's config field is missing.
Changing the same SecurityPolicy targetRefs to an HTTPRoute produces a fully populated oauth2 config and the listener is programmed. So the translation can produce the config; the Gateway-targeted code path leaves it empty.
Environment
- Envoy Gateway: v1.8.2 and v1.8.3 (both reproduce; not present on v1.6.1)
- Chart:
gateway-helm, provider Kubernetes
- Envoy:
distroless-v1.36.2 (as shipped with EG 1.8.2/1.8.3)
- OIDC provider: Keycloak; single HTTPS listener on :443
Reproduction
- One Gateway with an HTTPS listener and a few HTTPRoutes.
- Attach a gateway-level OIDC
SecurityPolicy:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: sso
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: my-gateway
oidc:
provider:
issuer: https://<host>/realms/<realm>
authorizationEndpoint: https://<host>/realms/<realm>/protocol/openid-connect/auth
tokenEndpoint: https://<host>/realms/<realm>/protocol/openid-connect/token
backendRefs:
- kind: Service
name: idp
port: 80
clientID: my-client
clientSecret:
name: my-client-secret
redirectURL: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/oauth2/callback"
forwardAccessToken: true
refreshToken: true
- Inspect the controller log and the envoy proxy admin
config_dump.
Actual behavior
The HCM envoy.filters.http.oauth2 filter is emitted empty:
// http_filters[0].typed_config on the (rejected) https listener
{ "@type": "type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2" }
disabled is unset. Envoy rejects the listener: config must be present for global config. The listener stays in error_state; a freshly-rolled proxy pod has no last-good config, so :443 serves nothing.
The generated token-endpoint cluster (securitypolicy/<ns>/<name>/oidc/0) and the SDS secrets (oauth2/client_secret/securitypolicy/<ns>/<name>, oauth2/hmac_secret/...) are present — only the filter's config field is missing.
Expected behavior
The HCM oauth2 filter should carry the full OAuth2.config (as it does when the same policy targets an HTTPRoute, and as EG 1.6/1.7 did):
{
"@type": ".../oauth2.v3.OAuth2",
"config": {
"token_endpoint": { "uri": "...", "cluster": "securitypolicy/<ns>/<name>/oidc/0", "timeout": "10s" },
"authorization_endpoint": "...",
"credentials": { "client_id": "...", "token_secret": {}, "hmac_secret": {}, "cookie_names": {} },
"redirect_uri": "...", "redirect_path_matcher": {}, "signout_path": {},
"forward_bearer_token": true, "auth_scopes": ["openid"], "auth_type": "BASIC_AUTH"
}
}
Evidence
Side-by-side config_dump: empty under 1.8.2 & 1.8.3 with targetRefs.kind: Gateway; fully populated under 1.6.1 with the identical SecurityPolicy, and fully populated under 1.8.2 after switching targetRefs.kind to HTTPRoute. Injecting the missing config via an EnvoyPatchPolicy (JSONPatch on http_filters[0].typed_config.config) makes Envoy accept the listener — confirming the only missing piece is the filter config on the Gateway-targeted path.
Related observation: OIDC is listener-wide, per-route exemption ineffective
The native oauth2 filter is installed enabled listener-wide (disabled unset on the HCM filter). Targeting the OIDC SecurityPolicy at a single HTTPRoute still protects every route on that listener (verified: targeting only route A still redirects requests to routes B, C … to the IdP). Attaching an empty SecurityPolicy to a route — the documented way to make a route public — does not disable the filter for that route; the route still redirects, producing an auth loop.
This matters when the IdP is served through the same gateway/listener that enforces OIDC: there appears to be no working way to exempt the IdP's own route in 1.8. If listener-wide is intended, a documented/working per-route disable is needed; otherwise the empty-policy override is regressed.
Possibly related
Description
When a
SecurityPolicywithoidctargets aGateway(rather than anHTTPRoute), Envoy Gateway 1.8 emits the nativeenvoy.filters.http.oauth2HTTP filter into the HCM filter chain with an empty config — the filter'styped_configcontains only the@typeand noconfigfield, and the filter is not markeddisabled. Envoy rejects the whole listener:Because the listener is never programmed, every route served by that listener becomes unreachable. The
SecurityPolicystill reportsAccepted: True, and the OIDC token-endpoint cluster and the client/HMAC SDS secrets are generated correctly — only the filter'sconfigfield is missing.Changing the same
SecurityPolicytargetRefsto anHTTPRouteproduces a fully populatedoauth2config and the listener is programmed. So the translation can produce the config; the Gateway-targeted code path leaves it empty.Environment
gateway-helm, providerKubernetesdistroless-v1.36.2(as shipped with EG 1.8.2/1.8.3)Reproduction
SecurityPolicy:config_dump.Actual behavior
The HCM
envoy.filters.http.oauth2filter is emitted empty:disabledis unset. Envoy rejects the listener:config must be present for global config. The listener stays inerror_state; a freshly-rolled proxy pod has no last-good config, so :443 serves nothing.The generated token-endpoint cluster (
securitypolicy/<ns>/<name>/oidc/0) and the SDS secrets (oauth2/client_secret/securitypolicy/<ns>/<name>,oauth2/hmac_secret/...) are present — only the filter'sconfigfield is missing.Expected behavior
The HCM
oauth2filter should carry the fullOAuth2.config(as it does when the same policy targets an HTTPRoute, and as EG 1.6/1.7 did):{ "@type": ".../oauth2.v3.OAuth2", "config": { "token_endpoint": { "uri": "...", "cluster": "securitypolicy/<ns>/<name>/oidc/0", "timeout": "10s" }, "authorization_endpoint": "...", "credentials": { "client_id": "...", "token_secret": {}, "hmac_secret": {}, "cookie_names": {} }, "redirect_uri": "...", "redirect_path_matcher": {}, "signout_path": {}, "forward_bearer_token": true, "auth_scopes": ["openid"], "auth_type": "BASIC_AUTH" } }Evidence
Side-by-side
config_dump: empty under 1.8.2 & 1.8.3 withtargetRefs.kind: Gateway; fully populated under 1.6.1 with the identical SecurityPolicy, and fully populated under 1.8.2 after switchingtargetRefs.kindtoHTTPRoute. Injecting the missingconfigvia anEnvoyPatchPolicy(JSONPatch onhttp_filters[0].typed_config.config) makes Envoy accept the listener — confirming the only missing piece is the filter config on the Gateway-targeted path.Related observation: OIDC is listener-wide, per-route exemption ineffective
The native
oauth2filter is installed enabled listener-wide (disabledunset on the HCM filter). Targeting the OIDCSecurityPolicyat a singleHTTPRoutestill protects every route on that listener (verified: targeting only route A still redirects requests to routes B, C … to the IdP). Attaching an emptySecurityPolicyto a route — the documented way to make a route public — does not disable the filter for that route; the route still redirects, producing an auth loop.This matters when the IdP is served through the same gateway/listener that enforces OIDC: there appears to be no working way to exempt the IdP's own route in 1.8. If listener-wide is intended, a documented/working per-route disable is needed; otherwise the empty-policy override is regressed.
Possibly related