envoyproxy / envoyproxy/gateway

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

Open
#9,656 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
3k
Forks
864
Avg merge
1d 22h
Merged PRs (30d)
148

Description

> **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) //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`:

```yaml
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:///realms/
authorizationEndpoint: https:///realms//protocol/openid-connect/auth
tokenEndpoint: https:///realms//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
```

3. Inspect the controller log and the envoy proxy admin `config_dump`.

## Actual behavior

The HCM `envoy.filters.http.oauth2` filter is emitted empty:

```json
// 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///oidc/0`) and the **SDS secrets** (`oauth2/client_secret/securitypolicy//`, `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):

```json
{
"@type": ".../oauth2.v3.OAuth2",
"config": {
"token_endpoint": { "uri": "...", "cluster": "securitypolicy///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

- envoyproxy/gateway#8649 (gateway-level + route-level OIDC interaction)
- envoyproxy/gateway#7459 (authorizationEndpoint ineffective without tokenEndpoint)
- envoyproxy/envoy#40735 (oauth2 "unknown cluster" — different symptom, already fixed)

Contributor guide

No contributing guide indexed for this repository

Research direction

Trace the OIDC SecurityPolicy translation entry points for Gateway and HTTPRoute targets, comparing how each constructs the Envoy oauth2 filter. Add a regression test for the Gateway-targeted path and verify that the generated filter includes its config and that the listener is accepted; also check the documented route exemption behavior described in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, kubernetes
Domain
api, authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.