Skip to content

Gateway-targeted OIDC SecurityPolicy generates an empty oauth2 filter config → listener rejected ("config must be present for global config") #9656

Description

@pelznikl

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

  1. One Gateway with an HTTPS listener and a few HTTPRoutes.
  2. 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
  1. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions