From 2fba279d6153a3857310938e6ddf24bc758f3b35 Mon Sep 17 00:00:00 2001 From: MIKE-4-prog Date: Wed, 29 Jul 2026 19:55:18 +0100 Subject: [PATCH 1/2] docs: reorganize Keycloak guide for better clarity and structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorganized into: Install → Configure → Configure Policy → Verify → Cleanup - Updated realm screenshot to show 'myrealm' - Added Access Token Validation option for API clients - Converted field notes to table format - Removed duplicate Keycloak card from landing page - Fixed language: removed 'you will', replaced 'e.g.' with 'such as' - Fixed fragmented sentences - Screenshots now correctly placed at each step Signed-off-by: MIKE-4-prog --- assets/img/keycloak/realm-creation.png | Bin 33597 -> 17363 bytes .../pages/security/oauth2-keycloak.md | 331 ++++++++++++------ .../envoy/latest/security/oauth2/_index.md | 4 - 3 files changed, 221 insertions(+), 114 deletions(-) diff --git a/assets/img/keycloak/realm-creation.png b/assets/img/keycloak/realm-creation.png index 6e99f9717539b0839be544daed27b1950f5a7149..4f818caf9dcd83419c374fc344a1d076f0cee2c8 100644 GIT binary patch literal 17363 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 literal 33597 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 V(a3+e9exM+Q}} {{< reuse-image-dark srcDark="img/keycloak/keycloak-login.png" >}} -3. Create a new realm: - - Click **Add realm** from the realm dropdown (top-left) - - Enter a realm name (e.g., `myrealm`) - - Click **Create** +Create a new realm. + +1. Click **Add realm** from the realm dropdown (top-left). +2. Enter a realm name (such as, `myrealm`). +3. Click **Create**. {{< reuse-image src="img/keycloak/realm-creation.png" >}} {{< reuse-image-dark srcDark="img/keycloak/realm-creation.png" >}} -4. Create a client: - - Click **Clients** in the left sidebar - - Click **Create client** - - Set **Client ID** (e.g., `kgateway-client`) +Create a client. + +1. Click **Clients** in the left sidebar. +2. Click **Create client**. +3. Set **Client ID** (such as, `kgateway-client`). +4. Enable **Client authentication**. {{< reuse-image src="img/keycloak/client-creation.png" >}} {{< reuse-image-dark srcDark="img/keycloak/client-creation.png" >}} - - Enable **Client authentication** - - Click **Next** - - In **Valid redirect URIs**, add `https://GATEWAY_HOST/oauth2/redirect` +5. Click **Next**. +6. In **Valid redirect URIs**, add: + +```text +https://GATEWAY_HOST/oauth2/redirect +``` {{< reuse-image src="img/keycloak/client-redirect-uri.png" >}} {{< reuse-image-dark srcDark="img/keycloak/client-redirect-uri.png" >}} - - Click **Save** +7. Click **Save**. -5. Note the client secret: - - Go to the **Credentials** tab of your client - - Copy the **Client secret** — you will need it for the `oauth2-client-secret` in the next section +Note the client secret. + +1. Go to the **Credentials** tab of your client. +2. Copy the **Client secret** — you need it for the `oauth2-client-secret` in the next section. {{< reuse-image src="img/keycloak/client-secret.png" >}} {{< reuse-image-dark srcDark="img/keycloak/client-secret.png" >}} -6. Create a test user: - - Click **Users** in the left sidebar - - Click **Add user** - - Set **Username** (e.g., `testuser`) - - Click **Create** - - Go to the **Credentials** tab - - Set a password (e.g., `password`) - - Turn **Temporary** off - - Click **Set Password** +Create a test user. -{{< reuse-image src="img/keycloak/user-password.png" >}} -{{< reuse-image-dark srcDark="img/keycloak/user-password.png" >}} +1. Click **Users** in the left sidebar. +2. Click **Add user**. +3. Set **Username** (such as, `testuser`). +4. Click **Create**. {{< reuse-image src="img/keycloak/user-created.png" >}} {{< reuse-image-dark srcDark="img/keycloak/user-created.png" >}} +5. Go to the **Credentials** tab. +6. Set a password (such as, `password`). +7. Turn **Temporary** off. +8. Click **Set Password**. + +{{< reuse-image src="img/keycloak/user-password.png" >}} +{{< reuse-image-dark srcDark="img/keycloak/user-password.png" >}} + + {{< callout type="info" >}} For production, use a dedicated Keycloak instance with proper TLS and a real realm. The steps above use the `myrealm` realm for testing only. {{< /callout >}} -## Store the client secret {#store-credentials} +## Configure the OAuth Policy + +### Store the client secret {#store-credentials} Create a Kubernetes Secret with the Keycloak client secret. kgateway reads the value from the `client-secret` key specifically, so the key name matters. -```shell +```sh kubectl create secret generic keycloak-client-secret \ --from-literal=client-secret=YOUR_CLIENT_SECRET \ -n {{< reuse "kgw-docs/snippets/namespace.md" >}} ``` -Grab `YOUR_CLIENT_SECRET` from the **Credentials** tab of your Keycloak client. +Replace `YOUR_CLIENT_SECRET` with the value you copied from the Credentials tab of your Keycloak client in the previous section. -## Configure TLS for Keycloak {#configure-tls} +### Configure TLS for Keycloak {#configure-tls} -Since Keycloak uses a self-signed certificate for HTTPS, you need to configure kgateway to skip TLS verification when communicating with the Keycloak backend. +Since Keycloak uses a self-signed certificate for HTTPS, configure kgateway to skip TLS verification when communicating with the Keycloak backend. Create a `BackendConfigPolicy` that skips TLS verification for the Keycloak Backend: @@ -209,7 +223,7 @@ spec: EOF ``` -## Create a Backend for Keycloak {#create-backend} +### Create a Backend for Keycloak {#create-backend} Create a `Backend` resource that defines how kgateway reaches your Keycloak instance. This Backend uses the `Static` type with the host and port configured for Keycloak. @@ -229,11 +243,11 @@ spec: EOF ``` -Replace **keycloak.example.com** with your Keycloak hostname. The port must be **443** because kgateway communicates with Keycloak over HTTPS. +Replace `keycloak.example.com` with your Keycloak hostname (such as, `keycloak.keycloak.svc.cluster.local`). The port must be `443` because kgateway communicates with Keycloak over HTTPS. -## Create the GatewayExtension {#create-extension} +### Create the GatewayExtension {#create-extension} -The `GatewayExtension` holds everything the gateway needs to talk to Keycloak. The `GatewayExtension` is independent of routing, so you can reuse the same extension across multiple `TrafficPolicy` resources. +The `GatewayExtension` holds everything the gateway needs to talk to Keycloak. The GatewayExtension is independent of routing, so you can reuse the same extension across multiple `TrafficPolicy` resources. ```yaml kubectl apply -f- <}} -The OAuth2 filter does not protect against CSRF attacks on routes with cached authentication cookies. Pair the OAuth2 filter with a `CSRFPolicy` on the same route, especially for browser-facing apps. +The OAuth2 filter does not protect against CSRF attacks on routes with cached authentication cookies. Pair the OAuth2 filter with a CSRFPolicy on the same route, especially for browser-facing apps. {{< /callout >}} ```yaml @@ -301,11 +321,11 @@ spec: EOF ``` -`targetRefs` can point to a specific `HTTPRoute` or all routes in a `Gateway`. This example locks down a single route named `httpbin`. +Note: `targetRefs` can point to a specific HTTPRoute or all routes in a Gateway. This example locks down a single route named `httpbin`. -## Configure cookie settings {#cookie-config} +### Configure cookie settings {#cookie-config} -kgateway stores the access and ID tokens in session cookies. The default `SameSite` policy is `Lax`. If you need custom cookie names (for example, to read them in downstream services or share across subdomains), set them explicitly under `cookies` on the `GatewayExtension`. +kgateway stores the access and ID tokens in session cookies. The default SameSite policy is `Lax`. If you need custom cookie names (for example, to read them in downstream services or share across subdomains), set them explicitly under `cookies` on the GatewayExtension. ```yaml spec: @@ -318,13 +338,13 @@ spec: idToken: kgw-id ``` -`Strict` means the browser won't send cookies on any cross-site request, including top-level navigations. Use `Lax`, which is the default, if users arrive at your app through links from other origins, like an email link. `None` requires HTTPS and should only be used when you explicitly need cross-site cookie sharing. +`Strict` means the browser does not send cookies on any cross-site request, including top-level navigations. Use `Lax`, which is the default, if users arrive at your app through links from other origins, like an email link. `None` requires HTTPS and should only be used when you explicitly need cross-site cookie sharing. -Add this block to the `GatewayExtension` manifest from the previous step and re-apply. +Add this block to the GatewayExtension manifest from the previous step and re-apply. -## Stop redirecting API clients {#deny-redirect} +### Stop redirecting API clients {#deny-redirect} -By default, any unauthenticated request gets a `302` redirect to the Keycloak login page. That response is what you want for a browser, but not for API clients. `curl`, mobile apps, and AJAX calls that hit an unauthenticated route will silently follow the redirect, land on the Keycloak login HTML, and fail. +By default, any unauthenticated request gets a `302` redirect to the Keycloak login page. That response works for a browser, but not for API clients. `curl`, mobile apps, and AJAX calls that hit an unauthenticated route silently follow the redirect, land on the Keycloak login HTML, and fail. The `denyRedirect` field on `OAuth2Provider` lets you match specific requests and return `401` instead of redirecting them. It takes a list of `HTTPHeaderMatch` entries, and a request matches if it satisfies all of them. @@ -341,27 +361,27 @@ spec: value: application/json ``` -For requests that might send `Accept: application/json; charset=utf-8` or similar variations, you can use `RegularExpression`: +For requests that might send `Accept: application/json; charset=utf-8` or similar variations, use `RegularExpression`: ```yaml - denyRedirect: - headers: - - name: Accept - type: RegularExpression - value: "application/json.*" +denyRedirect: + headers: + - name: Accept + type: RegularExpression + value: "application/json.*" ``` For AJAX requests from browser JavaScript: ```yaml - denyRedirect: - headers: - - name: X-Requested-With - type: Exact - value: XMLHttpRequest +denyRedirect: + headers: + - name: X-Requested-With + type: Exact + value: XMLHttpRequest ``` -The full `GatewayExtension` with `denyRedirect` included: +The full GatewayExtension with `denyRedirect` included: ```yaml kubectl apply -f- <}} - {{% tab tabName="Cloud Provider LoadBalancer" %}} - ```sh - curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" -H "host: www.example.com" - ``` - {{% /tab %}} - {{% tab tabName="Port-forward for local testing" %}} - ```sh - curl -vi "http://localhost:8080/headers" -H "host: www.example.com" - ``` - {{% /tab %}} - {{< /tabs >}} - - Example output: - ``` - < HTTP/1.1 302 Found - < location: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth?client_id=kgateway-client&... - ``` - -2. Send the same request with `Accept: application/json`. Because `denyRedirect` matches on this header, the gateway returns `401` directly instead of redirecting. - - {{< tabs tabTotal="2" items="Cloud Provider LoadBalancer,Port-forward for local testing" >}} - {{% tab tabName="Cloud Provider LoadBalancer" %}} - ```sh - curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" \ - -H "host: www.example.com" \ - -H "Accept: application/json" - ``` - {{% /tab %}} - {{% tab tabName="Port-forward for local testing" %}} - ```sh - curl -vi "http://localhost:8080/headers" \ - -H "host: www.example.com" \ - -H "Accept: application/json" - ``` - {{% /tab %}} - {{< /tabs >}} - - Example output: - ``` - < HTTP/1.1 401 Unauthorized - ``` +### Option A: Authorization Code Flow (Browser) + +Send a request without a session cookie. The gateway redirects to Keycloak. + +{{< tabs tabTotal="2" items="Cloud Provider LoadBalancer,Port-forward for local testing" >}} +{{% tab tabName="Cloud Provider LoadBalancer" %}} + +```sh +curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" -H "host: www.example.com" +``` + +{{% /tab %}} +{{% tab tabName="Port-forward for local testing" %}} + +```sh +curl -vi "http://localhost:8080/headers" -H "host: www.example.com" +``` + +{{% /tab %}} +{{< /tabs >}} + +Example output: + +```text +< HTTP/1.1 302 Found +< location: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth?client_id=kgateway-client&... +``` + +Send the same request with `Accept: application/json`. Because `denyRedirect` matches on this header, the gateway returns `401` directly instead of redirecting. + +{{< tabs tabTotal="2" items="Cloud Provider LoadBalancer,Port-forward for local testing" >}} +{{% tab tabName="Cloud Provider LoadBalancer" %}} + +```sh +curl -vi "http://${INGRESS_GW_ADDRESS}:8080/headers" \ + -H "host: www.example.com" \ + -H "Accept: application/json" +``` + +{{% /tab %}} +{{% tab tabName="Port-forward for local testing" %}} + +```sh +curl -vi "http://localhost:8080/headers" \ + -H "host: www.example.com" \ + -H "Accept: application/json" +``` + +{{% /tab %}} +{{< /tabs >}} + +Example output: + +```text +< HTTP/1.1 401 Unauthorized +``` + +### Option B: Access Token Validation (API) + +For API clients, you can validate access tokens directly without the browser redirect flow. + +Get the JWKS URI from Keycloak: + +1. In the Keycloak admin console, go to **Realm Settings → General** tab. +2. Scroll down to the **Endpoints** section. +3. Open the **OpenID Endpoint Configuration** link in a new tab. +4. In the OpenID configuration, search for the `jwks_uri` field. +5. Copy the value, for example: + +```text +https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs +``` + +Create a GatewayExtension for JWT validation: + +```yaml +kubectl apply -f- <}} +kind: GatewayExtension +metadata: + name: keycloak-jwt + namespace: {{< reuse "kgw-docs/snippets/namespace.md" >}} +spec: + jwt: + providers: + - issuer: https://keycloak.example.com/realms/myrealm + jwks: + remote: + url: https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs + audiences: + - kgateway-client +EOF +``` + +Replace the following values: + +* `keycloak.example.com` with your Keycloak hostname. +* `myrealm` with your realm name. +* `kgateway-client` with your Client ID. + +Attach the JWT policy: + +```yaml +kubectl apply -f- <}} +kind: {{< reuse "kgw-docs/snippets/trafficpolicy.md" >}} +metadata: + name: keycloak-jwt-policy + namespace: {{< reuse "kgw-docs/snippets/namespace.md" >}} +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httpbin + jwtAuth: + extensionRef: + name: keycloak-jwt + namespace: {{< reuse "kgw-docs/snippets/namespace.md" >}} +EOF +``` + +Get a token from Keycloak and test: + +```bash +export TOKEN=$(curl -d "client_id=kgateway-client" \ + -d "client_secret=YOUR_CLIENT_SECRET" \ + -d "grant_type=client_credentials" \ + "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" \ + | jq -r .access_token) + +curl -v "http://localhost:8080/headers" \ + -H "Authorization: Bearer $TOKEN" +``` + +A successful response shows the headers from the upstream service. ## Cleanup {#cleanup} diff --git a/content/docs/envoy/latest/security/oauth2/_index.md b/content/docs/envoy/latest/security/oauth2/_index.md index 3641be4b4..462c93842 100644 --- a/content/docs/envoy/latest/security/oauth2/_index.md +++ b/content/docs/envoy/latest/security/oauth2/_index.md @@ -5,7 +5,3 @@ description: Protect routes with browser-based OAuth2/OIDC login flows backed by --- kgateway has built-in support for the OAuth2 authorization code flow. Point a `GatewayExtension` at your provider, attach it to a route via `TrafficPolicy`, and the gateway handles the rest - login redirects, token exchange, and session cookies - without touching your upstream service. - -{{< cards >}} - {{< card link="keycloak" title="Keycloak" >}} -{{< /cards >}} From e74b325ae91e3d7b2e2061e76514dd24d18807af Mon Sep 17 00:00:00 2001 From: MIKE-4-prog Date: Wed, 29 Jul 2026 20:14:29 +0100 Subject: [PATCH 2/2] revert: remove landing page fix from this PR (moved to separate PR) Signed-off-by: MIKE-4-prog --- content/docs/envoy/latest/security/oauth2/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/docs/envoy/latest/security/oauth2/_index.md b/content/docs/envoy/latest/security/oauth2/_index.md index 462c93842..3641be4b4 100644 --- a/content/docs/envoy/latest/security/oauth2/_index.md +++ b/content/docs/envoy/latest/security/oauth2/_index.md @@ -5,3 +5,7 @@ description: Protect routes with browser-based OAuth2/OIDC login flows backed by --- kgateway has built-in support for the OAuth2 authorization code flow. Point a `GatewayExtension` at your provider, attach it to a route via `TrafficPolicy`, and the gateway handles the rest - login redirects, token exchange, and session cookies - without touching your upstream service. + +{{< cards >}} + {{< card link="keycloak" title="Keycloak" >}} +{{< /cards >}}