Front a console with Envoy Gateway OIDC¶
Put an Envoy Gateway SecurityPolicy in front of a console. The gateway runs the sign-in and the console runs no OpenID flow. To choose this shape first, read choosing native or gateway OIDC.
Before you start¶
-
A
SecurityPolicythat does not attach leaves the console open. Run the check under Verify after every change. -
There is no Back-Channel Logout receiver. A revoke reaches the console at its next refresh, bounded by the client's
ttl_cap. -
A very short
ttl_capmultiplies the refresh race in Traps. -
Field names are checked against Envoy Gateway v1.9. Check them against your release.
Steps¶
1. Declare the client¶
clients:
myconsole.example.internal:
kind: confidential
secret: myconsole-oidc-client
display_name: My Console
redirects: [https://myconsole.example.internal/oauth2/callback]
signed_out: [https://myconsole.example.internal/]
requires: [all:myconsole:operator, all:myconsole:viewer]
ttl_cap: 15m
The filter holds the secret, so the client is confidential. requires is checked at sign-in and at every refresh. Every client field is in policy clients.
2. Give the console its own HTTPRoute¶
Attach the policy to that route. A policy on a Gateway or shared ListenerSet fronts everything routed through it.
3. Attach the SecurityPolicy¶
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: myconsole-oidc
namespace: myconsole
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: myconsole
oidc:
provider:
issuer: https://issuer.example.internal
authorizationEndpoint: https://issuer.example.internal/authorize
tokenEndpoint: https://issuer.example.internal/token
endSessionEndpoint: https://issuer.example.internal/end_session
clientID: myconsole.example.internal
clientSecret:
group: ""
kind: Secret
name: myconsole-oidc-client
redirectURL: https://myconsole.example.internal/oauth2/callback
logoutPath: /oauth2/sign_out
refreshToken: true
forwardAccessToken: true
forwardIDToken:
header: x-id-token
jwt:
providers:
- name: myconsole-idtoken
issuer: https://issuer.example.internal
remoteJWKS:
uri: https://issuer.example.internal/keys
cacheDuration: 5m
extractFrom:
headers:
- name: x-id-token
claimToHeaders:
- header: x-groups
claim: groups
authorization:
defaultAction: Deny
rules:
- name: operators-and-viewers
action: Allow
principal:
jwt:
provider: myconsole-idtoken
claims:
- name: groups
valueType: StringArray
values: [all:myconsole:operator, all:myconsole:viewer]
Four fields bite:
-
endSessionEndpointis discovered only whenauthorizationEndpointortokenEndpointis unset. State all three, or sign-out never ends the issuer session. -
valueType: StringArrayis required. The defaultStringnever matches the arraygroupsclaim, so the rule never allows. -
extractFromnamesx-id-token, becauseAuthorizationalready carries the forwarded access token.forwardIDToken.headermay not beAuthorizationwhileforwardAccessTokenistrue. -
Write the defaulted fields (
group,kind,cacheDuration). A GitOps controller reads a field the API server fills in as permanent drift.
The backend reads the forwarded access token with the identity verifier (connect a console).
4. Sign-out¶
The filter serves logoutPath (default /logout). It clears the filter cookies, then redirects to endSessionEndpoint, which ends the issuer session. This filter serves no /oauth2/start: loading a protected page starts the sign-in.
Traps¶
| Symptom | Cause | Fix |
|---|---|---|
| Sign-in loop or 401 under load | Each gateway replica refreshes per request. A spent refresh token replayed more than 30 seconds later ends the session | Keep ttl_cap above a few minutes |
A backendSettings change on one side does nothing |
The token endpoint and remoteJWKS share a host and port, so one Envoy cluster serves both |
Give the JWKS side its own backendRefs |
| Cookie rejected | A large groups claim nears the 4 KB cookie limit |
Set groupsScoping: enforce so the token carries only the client's requires pairs (groups override) |
Pin the JWKS cluster like this:
remoteJWKS:
uri: https://issuer.example.internal/keys
backendRefs:
- name: sluis
port: 443
Verify¶
curl -sD - -o /dev/null https://myconsole.example.internal/
A 302 to authorizationEndpoint means the policy is attached. A 200 means it is not. Then sign in once, and open the logout path: the issuer lists no session for the client.
Roll back¶
Deleting the SecurityPolicy alone leaves the console open. Remove the route, then delete the policy.