Skip to content

Commit 2090fb7

Browse files
committed
docs: move Keycloak setup to generic access-control page
Address review feedback: the Keycloak realm/client/mapper/role setup is not OpenShift-specific. Move the full setup (with kcadm.sh and REST API tabs) to docs/kubernetes/access-control.mdx under a new "Keycloak setup" section. The OpenShift oidc-keycloak page now references the generic guide and only contains OpenShift-specific steps (Helm upgrade, CLI connection with --oidc-issuer). Signed-off-by: Akram <[email protected]>
1 parent 28caa39 commit 2090fb7

3 files changed

Lines changed: 133 additions & 166 deletions

File tree

‎docs/kubernetes/access-control.mdx‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,131 @@ Both `adminRole` and `userRole` must be set, or both must be empty. Setting only
8282
| Microsoft Entra ID | `roles` |
8383
| Okta | `groups` |
8484

85+
### Keycloak setup
86+
87+
Keycloak public clients do not include `sub`, `aud`, or realm roles in access tokens by default. Without these claims, the gateway rejects tokens with errors like `missing field 'sub'`, audience mismatch, or `role 'openshell-user' required`.
88+
89+
After creating a realm and a public client (with PKCE S256, redirect URIs `http://localhost:*` and `http://127.0.0.1:*`), add these protocol mappers to the client:
90+
91+
| Mapper name | Mapper type | Key config |
92+
|---|---|---|
93+
| `sub` | Subject (sub) | access.token.claim: true |
94+
| `openshell-audience` | Audience | included.client.audience: `openshell-cli` |
95+
| `realm-roles` | User Realm Role | claim.name: `realm_access.roles`, multivalued: true |
96+
97+
Add these via the Keycloak admin console under **Clients → openshell-cli → Client scopes → Dedicated scope → Add mapper**, or via the CLI:
98+
99+
<Tabs>
100+
<Tab title="kcadm.sh">
101+
102+
```shell
103+
KC_ADM="kcadm.sh --config /tmp/kcadm.config"
104+
105+
$KC_ADM config credentials \
106+
--server http://localhost:8080 \
107+
--realm master \
108+
--user <admin-user> \
109+
--password <admin-password>
110+
111+
$KC_ADM create realms \
112+
-s realm=openshell \
113+
-s enabled=true
114+
115+
$KC_ADM create clients -r openshell \
116+
-s clientId=openshell-cli \
117+
-s enabled=true \
118+
-s publicClient=true \
119+
-s directAccessGrantsEnabled=true \
120+
-s standardFlowEnabled=true \
121+
-s 'redirectUris=["http://localhost:*","http://127.0.0.1:*"]' \
122+
-s 'webOrigins=["http://localhost","http://127.0.0.1"]' \
123+
-s 'attributes={"pkce.code.challenge.method":"S256"}'
124+
125+
CLIENT_UUID=$($KC_ADM get clients -r openshell \
126+
-q clientId=openshell-cli --fields id --format csv --noquotes)
127+
128+
$KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
129+
-s name=sub \
130+
-s protocol=openid-connect \
131+
-s protocolMapper=oidc-sub-mapper \
132+
-s 'config={"access.token.claim":"true","id.token.claim":"true"}'
133+
134+
$KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
135+
-s name=openshell-audience \
136+
-s protocol=openid-connect \
137+
-s protocolMapper=oidc-audience-mapper \
138+
-s 'config={"included.client.audience":"openshell-cli","access.token.claim":"true","id.token.claim":"true"}'
139+
140+
$KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
141+
-s name=realm-roles \
142+
-s protocol=openid-connect \
143+
-s protocolMapper=oidc-usermodel-realm-role-mapper \
144+
-s 'config={"claim.name":"realm_access.roles","jsonType.label":"String","multivalued":"true","access.token.claim":"true","id.token.claim":"true","userinfo.token.claim":"true"}'
145+
146+
$KC_ADM create roles -r openshell -s name=openshell-user
147+
$KC_ADM create roles -r openshell -s name=openshell-admin
148+
```
149+
150+
</Tab>
151+
<Tab title="REST API">
152+
153+
```shell
154+
KC_URL=https://keycloak.example.com
155+
156+
TOKEN=$(curl -sk "${KC_URL}/realms/master/protocol/openid-connect/token" \
157+
-d "client_id=admin-cli" \
158+
-d "username=<admin-user>" \
159+
-d "password=<admin-password>" \
160+
-d "grant_type=password" | jq -r .access_token)
161+
162+
curl -sk -X POST "${KC_URL}/admin/realms" \
163+
-H "Authorization: Bearer $TOKEN" \
164+
-H "Content-Type: application/json" \
165+
-d '{"realm":"openshell","enabled":true}'
166+
167+
curl -sk -X POST "${KC_URL}/admin/realms/openshell/clients" \
168+
-H "Authorization: Bearer $TOKEN" \
169+
-H "Content-Type: application/json" \
170+
-d '{
171+
"clientId": "openshell-cli",
172+
"enabled": true,
173+
"publicClient": true,
174+
"directAccessGrantsEnabled": true,
175+
"standardFlowEnabled": true,
176+
"redirectUris": ["http://localhost:*", "http://127.0.0.1:*"],
177+
"webOrigins": ["http://localhost", "http://127.0.0.1"],
178+
"attributes": {"pkce.code.challenge.method": "S256"}
179+
}'
180+
181+
CLIENT_UUID=$(curl -sk "${KC_URL}/admin/realms/openshell/clients?clientId=openshell-cli" \
182+
-H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')
183+
184+
for MAPPER in \
185+
'{"name":"sub","protocol":"openid-connect","protocolMapper":"oidc-sub-mapper","config":{"access.token.claim":"true","id.token.claim":"true"}}' \
186+
'{"name":"openshell-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshell-cli","access.token.claim":"true","id.token.claim":"true"}}' \
187+
'{"name":"realm-roles","protocol":"openid-connect","protocolMapper":"oidc-usermodel-realm-role-mapper","config":{"claim.name":"realm_access.roles","jsonType.label":"String","multivalued":"true","access.token.claim":"true","id.token.claim":"true","userinfo.token.claim":"true"}}'; do
188+
curl -sk -X POST "${KC_URL}/admin/realms/openshell/clients/${CLIENT_UUID}/protocol-mappers/models" \
189+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$MAPPER"
190+
done
191+
192+
curl -sk -X POST "${KC_URL}/admin/realms/openshell/roles" \
193+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
194+
-d '{"name":"openshell-user"}'
195+
196+
curl -sk -X POST "${KC_URL}/admin/realms/openshell/roles" \
197+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
198+
-d '{"name":"openshell-admin"}'
199+
```
200+
201+
</Tab>
202+
</Tabs>
203+
204+
Assign `openshell-user` to users who need sandbox access and `openshell-admin` to administrators.
205+
206+
<Note>
207+
On Kubernetes, run `kcadm.sh` via `kubectl exec` into the Keycloak pod. The `--config /tmp/kcadm.config` flag is required when the container runs as non-root. On OpenShift, see [OIDC on OpenShift](/kubernetes/openshift/oidc-keycloak) for the `oc exec` variant and OpenShift-specific details.
208+
</Note>
209+
85210
## Reverse-Proxy Auth Termination
86211

87212
When an access proxy, such as Cloudflare Access, ngrok, or a corporate SSO gateway, handles authentication in front of the OpenShell gateway, you can explicitly allow unauthenticated user calls at the gateway:

‎docs/kubernetes/openshift/index.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ Install the gateway with TLS enabled, configure the SCC binding, and verify the
2727
Connect the CLI to the gateway using port forwarding, a reencrypt Route, or the Gateway API.
2828
</Card>
2929

30-
<Card title="OIDC with Keycloak" href="/kubernetes/openshift/oidc-keycloak">
30+
<Card title="OIDC on OpenShift" href="/kubernetes/openshift/oidc-keycloak">
3131

32-
Configure Keycloak as the OIDC provider, set up the required protocol mappers, and connect the CLI.
32+
Configure the gateway and CLI for Keycloak OIDC authentication on OpenShift.
3333
</Card>
3434

3535
<Card title="Identity Federation (Optional)" href="/kubernetes/openshift/identity-federation">

‎docs/kubernetes/openshift/oidc-keycloak.mdx‎

Lines changed: 6 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,179 +1,21 @@
11
---
22
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
# SPDX-License-Identifier: Apache-2.0
4-
title: "OIDC with Keycloak"
5-
sidebar-title: "OIDC with Keycloak"
4+
title: "OIDC on OpenShift"
5+
sidebar-title: "OIDC on OpenShift"
66
slug: "kubernetes/openshift/oidc-keycloak"
7-
description: "Configure Keycloak as the OIDC provider for OpenShell on OpenShift, including the required protocol mappers and Helm values."
7+
description: "Connect the OpenShell CLI to a Keycloak-authenticated gateway on OpenShift."
88
keywords: "Generative AI, Cybersecurity, Kubernetes, OpenShift, OIDC, Keycloak, Authentication, JWT"
99
position: 3
1010
---
1111

12-
The generic [Access Control](/kubernetes/access-control) guide covers OIDC configuration for any provider. This page covers Keycloak-specific setup on OpenShift, including the protocol mappers required for the gateway to validate access tokens.
12+
This page covers the OpenShift-specific steps for connecting the CLI to a Keycloak-authenticated gateway. For the full Keycloak setup (realm, client, protocol mappers, roles), refer to the [Keycloak setup](/kubernetes/access-control#keycloak-setup) section of the Access Control guide.
1313

1414
## Prerequisites
1515

1616
- OpenShell [installed on OpenShift](/kubernetes/openshift/install) with TLS enabled
17-
- Keycloak instance accessible from the cluster
18-
- Admin access to Keycloak
19-
20-
## Create the realm and client
21-
22-
Create a dedicated `openshell` realm and a public OIDC client for the CLI.
23-
24-
<Note>
25-
The `kcadm.sh` examples below use `KC_POD` and `KC_ADM` variables defined in the first code block. Set them once in your shell and reuse them for all subsequent commands. The `--config /tmp/kcadm.config` flag is required because the Keycloak container runs as non-root and cannot write to the default config path.
26-
</Note>
27-
28-
<Tabs>
29-
<Tab title="kcadm.sh">
30-
31-
```shell
32-
KC_POD="oc exec keycloak-0 -n keycloak --"
33-
KC_ADM="/opt/keycloak/bin/kcadm.sh --config /tmp/kcadm.config"
34-
35-
$KC_POD $KC_ADM config credentials \
36-
--server http://localhost:8080 \
37-
--realm master \
38-
--user <admin-user> \
39-
--password <admin-password>
40-
41-
$KC_POD $KC_ADM create realms \
42-
-s realm=openshell \
43-
-s enabled=true
44-
45-
$KC_POD $KC_ADM create clients -r openshell \
46-
-s clientId=openshell-cli \
47-
-s enabled=true \
48-
-s publicClient=true \
49-
-s directAccessGrantsEnabled=true \
50-
-s standardFlowEnabled=true \
51-
-s 'redirectUris=["http://localhost:*","http://127.0.0.1:*"]' \
52-
-s 'webOrigins=["http://localhost","http://127.0.0.1"]' \
53-
-s 'attributes={"pkce.code.challenge.method":"S256"}'
54-
```
55-
56-
</Tab>
57-
<Tab title="REST API">
58-
59-
```shell
60-
KC_URL=https://keycloak.example.com
61-
62-
TOKEN=$(curl -sk "${KC_URL}/realms/master/protocol/openid-connect/token" \
63-
-d "client_id=admin-cli" \
64-
-d "username=<admin-user>" \
65-
-d "password=<admin-password>" \
66-
-d "grant_type=password" | jq -r .access_token)
67-
68-
curl -sk -X POST "${KC_URL}/admin/realms" \
69-
-H "Authorization: Bearer $TOKEN" \
70-
-H "Content-Type: application/json" \
71-
-d '{"realm":"openshell","enabled":true}'
72-
73-
curl -sk -X POST "${KC_URL}/admin/realms/openshell/clients" \
74-
-H "Authorization: Bearer $TOKEN" \
75-
-H "Content-Type: application/json" \
76-
-d '{
77-
"clientId": "openshell-cli",
78-
"enabled": true,
79-
"publicClient": true,
80-
"directAccessGrantsEnabled": true,
81-
"standardFlowEnabled": true,
82-
"redirectUris": ["http://localhost:*", "http://127.0.0.1:*"],
83-
"webOrigins": ["http://localhost", "http://127.0.0.1"],
84-
"attributes": {"pkce.code.challenge.method": "S256"}
85-
}'
86-
```
87-
88-
</Tab>
89-
</Tabs>
90-
91-
## Add required protocol mappers
92-
93-
Keycloak does not include `sub`, `aud`, or realm roles in access tokens for public clients by default. Without these claims, the gateway rejects tokens with errors like `missing field 'sub'`, audience mismatch, or `role 'openshell-user' required`.
94-
95-
Add three protocol mappers to the `openshell-cli` client:
96-
97-
| Mapper name | Mapper type | Key config |
98-
|---|---|---|
99-
| `sub` | Subject (sub) | access.token.claim: true |
100-
| `openshell-audience` | Audience | included.client.audience: `openshell-cli` |
101-
| `realm-roles` | User Realm Role | claim.name: `realm_access.roles`, multivalued: true |
102-
103-
Add these via the Keycloak admin console under **Clients → openshell-cli → Client scopes → Dedicated scope → Add mapper**, or via the CLI/API:
104-
105-
<Tabs>
106-
<Tab title="kcadm.sh">
107-
108-
```shell
109-
CLIENT_UUID=$($KC_POD $KC_ADM get clients -r openshell \
110-
-q clientId=openshell-cli --fields id --format csv --noquotes)
111-
112-
$KC_POD $KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
113-
-s name=sub \
114-
-s protocol=openid-connect \
115-
-s protocolMapper=oidc-sub-mapper \
116-
-s 'config={"access.token.claim":"true","id.token.claim":"true"}'
117-
118-
$KC_POD $KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
119-
-s name=openshell-audience \
120-
-s protocol=openid-connect \
121-
-s protocolMapper=oidc-audience-mapper \
122-
-s 'config={"included.client.audience":"openshell-cli","access.token.claim":"true","id.token.claim":"true"}'
123-
124-
$KC_POD $KC_ADM create clients/$CLIENT_UUID/protocol-mappers/models -r openshell \
125-
-s name=realm-roles \
126-
-s protocol=openid-connect \
127-
-s protocolMapper=oidc-usermodel-realm-role-mapper \
128-
-s 'config={"claim.name":"realm_access.roles","jsonType.label":"String","multivalued":"true","access.token.claim":"true","id.token.claim":"true","userinfo.token.claim":"true"}'
129-
```
130-
131-
</Tab>
132-
<Tab title="REST API">
133-
134-
```shell
135-
CLIENT_UUID=$(curl -sk "${KC_URL}/admin/realms/openshell/clients?clientId=openshell-cli" \
136-
-H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')
137-
138-
for MAPPER in \
139-
'{"name":"sub","protocol":"openid-connect","protocolMapper":"oidc-sub-mapper","config":{"access.token.claim":"true","id.token.claim":"true"}}' \
140-
'{"name":"openshell-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshell-cli","access.token.claim":"true","id.token.claim":"true"}}' \
141-
'{"name":"realm-roles","protocol":"openid-connect","protocolMapper":"oidc-usermodel-realm-role-mapper","config":{"claim.name":"realm_access.roles","jsonType.label":"String","multivalued":"true","access.token.claim":"true","id.token.claim":"true","userinfo.token.claim":"true"}}'; do
142-
curl -sk -X POST "${KC_URL}/admin/realms/openshell/clients/${CLIENT_UUID}/protocol-mappers/models" \
143-
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$MAPPER"
144-
done
145-
```
146-
147-
</Tab>
148-
</Tabs>
149-
150-
## Create realm roles
151-
152-
<Tabs>
153-
<Tab title="kcadm.sh">
154-
155-
```shell
156-
$KC_POD $KC_ADM create roles -r openshell -s name=openshell-user
157-
$KC_POD $KC_ADM create roles -r openshell -s name=openshell-admin
158-
```
159-
160-
</Tab>
161-
<Tab title="REST API">
162-
163-
```shell
164-
curl -sk -X POST "${KC_URL}/admin/realms/openshell/roles" \
165-
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
166-
-d '{"name":"openshell-user"}'
167-
168-
curl -sk -X POST "${KC_URL}/admin/realms/openshell/roles" \
169-
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
170-
-d '{"name":"openshell-admin"}'
171-
```
172-
173-
</Tab>
174-
</Tabs>
175-
176-
Assign `openshell-user` to users who need sandbox access and `openshell-admin` to administrators through the Keycloak admin console or API.
17+
- Gateway [exposed externally](/kubernetes/openshift/gateway-connection)
18+
- Keycloak realm and client configured per the [Keycloak setup](/kubernetes/access-control#keycloak-setup) guide
17719

17820
## Configure the gateway
17921

0 commit comments

Comments
 (0)