@@ -54,3 +54,287 @@ keycloak-status: ## Show Keycloak status and connection info
5454.PHONY : keycloak-logs
5555keycloak-logs : # # Tail Keycloak logs
5656 @kubectl logs -n $(KEYCLOAK_NAMESPACE ) -l app=keycloak -f --tail=100
57+
58+ .PHONY : keycloak-setup-realm
59+ keycloak-setup-realm : # # Setup OpenShift realm with token exchange support
60+ @echo " ========================================="
61+ @echo " Setting up OpenShift Realm for Token Exchange"
62+ @echo " ========================================="
63+ @echo " Using Keycloak at http://localhost:8090"
64+ @echo " (Ensure 'make keycloak-forward' is running in another terminal)"
65+ @echo " "
66+ @echo " Getting admin access token..."
67+ @TOKEN=$$(curl -s -X POST "http://localhost:8090/realms/master/protocol/openid-connect/token" \
68+ -H " Content-Type: application/x-www-form-urlencoded" \
69+ -d " username=$( KEYCLOAK_ADMIN_USER) " \
70+ -d " password=$( KEYCLOAK_ADMIN_PASSWORD) " \
71+ -d " grant_type=password" \
72+ -d " client_id=admin-cli" \
73+ 2> /dev/null | jq -r ' .access_token // empty' ); \
74+ if [ -z " $$ TOKEN" ] || [ " $$ TOKEN" = " null" ]; then \
75+ echo " ❌ Failed to get access token. Check if:" ; \
76+ echo " - Keycloak is running (make keycloak-install)" ; \
77+ echo " - Port forwarding is active (make keycloak-forward)" ; \
78+ echo " - Admin credentials are correct: $( KEYCLOAK_ADMIN_USER) /$( KEYCLOAK_ADMIN_PASSWORD) " ; \
79+ exit 1; \
80+ fi ; \
81+ echo " ✅ Successfully obtained access token" ; \
82+ echo " " ; \
83+ echo " Creating OpenShift realm..." ; \
84+ REALM_RESPONSE=$$(curl -s -w "%{http_code}" -X POST "http://localhost:8090/admin/realms" \
85+ -H " Authorization: Bearer $$ TOKEN" \
86+ -H " Content-Type: application/json" \
87+ -d ' {"realm":"openshift","enabled":true}' ); \
88+ REALM_CODE=$$(echo "$$REALM_RESPONSE" | tail -c 4 ) ; \
89+ if [ " $$ REALM_CODE" = " 201" ] || [ " $$ REALM_CODE" = " 409" ]; then \
90+ if [ " $$ REALM_CODE" = " 201" ]; then echo " ✅ OpenShift realm created" ; \
91+ else echo " ✅ OpenShift realm already exists" ; fi ; \
92+ else \
93+ echo " ❌ Failed to create OpenShift realm (HTTP $$ REALM_CODE)" ; \
94+ exit 1; \
95+ fi ; \
96+ echo " " ; \
97+ echo " Configuring realm events..." ; \
98+ EVENT_CONFIG_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X PUT "http://localhost:8090/admin/realms/openshift" \
99+ -H " Authorization: Bearer $$ TOKEN" \
100+ -H " Content-Type: application/json" \
101+ -d ' {"realm":"openshift","enabled":true,"eventsEnabled":true,"eventsListeners":["jboss-logging"],"adminEventsEnabled":true,"adminEventsDetailsEnabled":true}' ); \
102+ EVENT_CONFIG_CODE=$$(echo "$$EVENT_CONFIG_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
103+ if [ " $$ EVENT_CONFIG_CODE" = " 204" ]; then \
104+ echo " ✅ User and admin event logging enabled" ; \
105+ else \
106+ echo " ⚠️ Could not configure event logging (HTTP $$ EVENT_CONFIG_CODE)" ; \
107+ fi ; \
108+ echo " " ; \
109+ echo " Creating mcp:openshift client scope..." ; \
110+ SCOPE_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes" \
111+ -H " Authorization: Bearer $$ TOKEN" \
112+ -H " Content-Type: application/json" \
113+ -d ' {"name":"mcp:openshift","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}' ); \
114+ SCOPE_CODE=$$(echo "$$SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
115+ if [ " $$ SCOPE_CODE" = " 201" ] || [ " $$ SCOPE_CODE" = " 409" ]; then \
116+ if [ " $$ SCOPE_CODE" = " 201" ]; then echo " ✅ mcp:openshift client scope created" ; \
117+ else echo " ✅ mcp:openshift client scope already exists" ; fi ; \
118+ else \
119+ echo " ❌ Failed to create mcp:openshift scope (HTTP $$ SCOPE_CODE)" ; \
120+ exit 1; \
121+ fi ; \
122+ echo " " ; \
123+ echo " Adding audience mapper to mcp:openshift scope..." ; \
124+ SCOPES_LIST=$$(curl -s -X GET "http://localhost:8090/admin/realms/openshift/client-scopes" \
125+ -H " Authorization: Bearer $$ TOKEN" \
126+ -H " Accept: application/json" ); \
127+ SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "mcp:openshift" ) | .id // empty' 2>/dev/null); \
128+ if [ -z " $$ SCOPE_ID" ]; then \
129+ echo " ❌ Failed to find mcp:openshift scope" ; \
130+ exit 1; \
131+ fi ; \
132+ MAPPER_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes/$$SCOPE_ID/protocol-mappers/models" \
133+ -H " Authorization: Bearer $$ TOKEN" \
134+ -H " Content-Type: application/json" \
135+ -d ' {"name":"openshift-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"openshift","id.token.claim":"true","access.token.claim":"true"}}' ); \
136+ MAPPER_CODE=$$(echo "$$MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
137+ if [ " $$ MAPPER_CODE" = " 201" ] || [ " $$ MAPPER_CODE" = " 409" ]; then \
138+ if [ " $$ MAPPER_CODE" = " 201" ]; then echo " ✅ Audience mapper added" ; \
139+ else echo " ✅ Audience mapper already exists" ; fi ; \
140+ else \
141+ echo " ❌ Failed to create audience mapper (HTTP $$ MAPPER_CODE)" ; \
142+ exit 1; \
143+ fi ; \
144+ echo " " ; \
145+ echo " Creating groups client scope..." ; \
146+ GROUPS_SCOPE_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes" \
147+ -H " Authorization: Bearer $$ TOKEN" \
148+ -H " Content-Type: application/json" \
149+ -d ' {"name":"groups","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}' ); \
150+ GROUPS_SCOPE_CODE=$$(echo "$$GROUPS_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
151+ if [ " $$ GROUPS_SCOPE_CODE" = " 201" ] || [ " $$ GROUPS_SCOPE_CODE" = " 409" ]; then \
152+ if [ " $$ GROUPS_SCOPE_CODE" = " 201" ]; then echo " ✅ groups client scope created" ; \
153+ else echo " ✅ groups client scope already exists" ; fi ; \
154+ else \
155+ echo " ❌ Failed to create groups scope (HTTP $$ GROUPS_SCOPE_CODE)" ; \
156+ exit 1; \
157+ fi ; \
158+ echo " " ; \
159+ echo " Adding group membership mapper to groups scope..." ; \
160+ SCOPES_LIST=$$(curl -s -X GET "http://localhost:8090/admin/realms/openshift/client-scopes" \
161+ -H " Authorization: Bearer $$ TOKEN" \
162+ -H " Accept: application/json" ); \
163+ GROUPS_SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "groups" ) | .id // empty' 2>/dev/null); \
164+ if [ -z " $$ GROUPS_SCOPE_ID" ]; then \
165+ echo " ❌ Failed to find groups scope" ; \
166+ exit 1; \
167+ fi ; \
168+ GROUPS_MAPPER_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes/$$GROUPS_SCOPE_ID/protocol-mappers/models" \
169+ -H " Authorization: Bearer $$ TOKEN" \
170+ -H " Content-Type: application/json" \
171+ -d ' {"name":"groups","protocol":"openid-connect","protocolMapper":"oidc-group-membership-mapper","config":{"claim.name":"groups","full.path":"false","id.token.claim":"true","access.token.claim":"true","userinfo.token.claim":"true"}}' ); \
172+ GROUPS_MAPPER_CODE=$$(echo "$$GROUPS_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
173+ if [ " $$ GROUPS_MAPPER_CODE" = " 201" ] || [ " $$ GROUPS_MAPPER_CODE" = " 409" ]; then \
174+ if [ " $$ GROUPS_MAPPER_CODE" = " 201" ]; then echo " ✅ Group membership mapper added" ; \
175+ else echo " ✅ Group membership mapper already exists" ; fi ; \
176+ else \
177+ echo " ❌ Failed to create group mapper (HTTP $$ GROUPS_MAPPER_CODE)" ; \
178+ exit 1; \
179+ fi ; \
180+ echo " " ; \
181+ echo " Creating mcp-server client scope..." ; \
182+ MCP_SERVER_SCOPE_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes" \
183+ -H " Authorization: Bearer $$ TOKEN" \
184+ -H " Content-Type: application/json" \
185+ -d ' {"name":"mcp-server","protocol":"openid-connect","attributes":{"display.on.consent.screen":"false","include.in.token.scope":"true"}}' ); \
186+ MCP_SERVER_SCOPE_CODE=$$(echo "$$MCP_SERVER_SCOPE_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
187+ if [ " $$ MCP_SERVER_SCOPE_CODE" = " 201" ] || [ " $$ MCP_SERVER_SCOPE_CODE" = " 409" ]; then \
188+ if [ " $$ MCP_SERVER_SCOPE_CODE" = " 201" ]; then echo " ✅ mcp-server client scope created" ; \
189+ else echo " ✅ mcp-server client scope already exists" ; fi ; \
190+ else \
191+ echo " ❌ Failed to create mcp-server scope (HTTP $$ MCP_SERVER_SCOPE_CODE)" ; \
192+ exit 1; \
193+ fi ; \
194+ echo " " ; \
195+ echo " Adding audience mapper to mcp-server scope..." ; \
196+ SCOPES_LIST=$$(curl -s -X GET "http://localhost:8090/admin/realms/openshift/client-scopes" \
197+ -H " Authorization: Bearer $$ TOKEN" \
198+ -H " Accept: application/json" ); \
199+ MCP_SERVER_SCOPE_ID=$$(echo "$$SCOPES_LIST" | jq -r '.[] | select(.name == "mcp-server" ) | .id // empty' 2>/dev/null); \
200+ if [ -z " $$ MCP_SERVER_SCOPE_ID" ]; then \
201+ echo " ❌ Failed to find mcp-server scope" ; \
202+ exit 1; \
203+ fi ; \
204+ MCP_SERVER_MAPPER_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/client-scopes/$$MCP_SERVER_SCOPE_ID/protocol-mappers/models" \
205+ -H " Authorization: Bearer $$ TOKEN" \
206+ -H " Content-Type: application/json" \
207+ -d ' {"name":"mcp-server-audience","protocol":"openid-connect","protocolMapper":"oidc-audience-mapper","config":{"included.client.audience":"mcp-server","id.token.claim":"true","access.token.claim":"true"}}' ); \
208+ MCP_SERVER_MAPPER_CODE=$$(echo "$$MCP_SERVER_MAPPER_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
209+ if [ " $$ MCP_SERVER_MAPPER_CODE" = " 201" ] || [ " $$ MCP_SERVER_MAPPER_CODE" = " 409" ]; then \
210+ if [ " $$ MCP_SERVER_MAPPER_CODE" = " 201" ]; then echo " ✅ mcp-server audience mapper added" ; \
211+ else echo " ✅ mcp-server audience mapper already exists" ; fi ; \
212+ else \
213+ echo " ❌ Failed to create mcp-server audience mapper (HTTP $$ MCP_SERVER_MAPPER_CODE)" ; \
214+ exit 1; \
215+ fi ; \
216+ echo " " ; \
217+ echo " Creating openshift service client..." ; \
218+ OPENSHIFT_CLIENT_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/clients" \
219+ -H " Authorization: Bearer $$ TOKEN" \
220+ -H " Content-Type: application/json" \
221+ -d ' {"clientId":"openshift","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["groups"],"optionalClientScopes":[]}' ); \
222+ OPENSHIFT_CLIENT_CODE=$$(echo "$$OPENSHIFT_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
223+ if [ " $$ OPENSHIFT_CLIENT_CODE" = " 201" ] || [ " $$ OPENSHIFT_CLIENT_CODE" = " 409" ]; then \
224+ if [ " $$ OPENSHIFT_CLIENT_CODE" = " 201" ]; then echo " ✅ openshift client created" ; \
225+ else echo " ✅ openshift client already exists" ; fi ; \
226+ else \
227+ echo " ❌ Failed to create openshift client (HTTP $$ OPENSHIFT_CLIENT_CODE)" ; \
228+ exit 1; \
229+ fi ; \
230+ echo " " ; \
231+ echo " Creating mcp-client public client..." ; \
232+ MCP_PUBLIC_CLIENT_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/clients" \
233+ -H " Authorization: Bearer $$ TOKEN" \
234+ -H " Content-Type: application/json" \
235+ -d ' {"clientId":"mcp-client","enabled":true,"publicClient":true,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":false,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":[],"optionalClientScopes":["mcp-server"]}' ); \
236+ MCP_PUBLIC_CLIENT_CODE=$$(echo "$$MCP_PUBLIC_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
237+ if [ " $$ MCP_PUBLIC_CLIENT_CODE" = " 201" ] || [ " $$ MCP_PUBLIC_CLIENT_CODE" = " 409" ]; then \
238+ if [ " $$ MCP_PUBLIC_CLIENT_CODE" = " 201" ]; then echo " ✅ mcp-client public client created" ; \
239+ else echo " ✅ mcp-client public client already exists" ; fi ; \
240+ else \
241+ echo " ❌ Failed to create mcp-client public client (HTTP $$ MCP_PUBLIC_CLIENT_CODE)" ; \
242+ exit 1; \
243+ fi ; \
244+ echo " " ; \
245+ echo " Creating mcp-server client with token exchange..." ; \
246+ MCP_CLIENT_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/clients" \
247+ -H " Authorization: Bearer $$ TOKEN" \
248+ -H " Content-Type: application/json" \
249+ -d ' {"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false"}}' ); \
250+ MCP_CLIENT_CODE=$$(echo "$$MCP_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
251+ if [ " $$ MCP_CLIENT_CODE" = " 201" ] || [ " $$ MCP_CLIENT_CODE" = " 409" ]; then \
252+ if [ " $$ MCP_CLIENT_CODE" = " 201" ]; then echo " ✅ mcp-server client created" ; \
253+ else echo " ✅ mcp-server client already exists" ; fi ; \
254+ else \
255+ echo " ❌ Failed to create mcp-server client (HTTP $$ MCP_CLIENT_CODE)" ; \
256+ exit 1; \
257+ fi ; \
258+ echo " " ; \
259+ echo " Enabling standard token exchange for mcp-server..." ; \
260+ CLIENTS_LIST=$$(curl -s -X GET "http://localhost:8090/admin/realms/openshift/clients" \
261+ -H " Authorization: Bearer $$ TOKEN" \
262+ -H " Accept: application/json" ); \
263+ MCP_CLIENT_ID=$$(echo "$$CLIENTS_LIST" | jq -r '.[] | select(.clientId == "mcp-server" ) | .id // empty' 2>/dev/null); \
264+ if [ -z " $$ MCP_CLIENT_ID" ]; then \
265+ echo " ❌ Failed to find mcp-server client" ; \
266+ exit 1; \
267+ fi ; \
268+ UPDATE_CLIENT_RESPONSE=$$(curl -s -w "HTTPCODE:%{http_code}" -X PUT "http://localhost:8090/admin/realms/openshift/clients/$$MCP_CLIENT_ID" \
269+ -H " Authorization: Bearer $$ TOKEN" \
270+ -H " Content-Type: application/json" \
271+ -d ' {"clientId":"mcp-server","enabled":true,"publicClient":false,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"serviceAccountsEnabled":true,"authorizationServicesEnabled":false,"redirectUris":["*"],"defaultClientScopes":["groups","mcp-server"],"optionalClientScopes":["mcp:openshift"],"attributes":{"oauth2.device.authorization.grant.enabled":"false","oidc.ciba.grant.enabled":"false","backchannel.logout.session.required":"true","backchannel.logout.revoke.offline.tokens":"false","standard.token.exchange.enabled":"true"}}' ); \
272+ UPDATE_CLIENT_CODE=$$(echo "$$UPDATE_CLIENT_RESPONSE" | grep -o "HTTPCODE:[0-9]*" | cut -d: -f2 ) ; \
273+ if [ " $$ UPDATE_CLIENT_CODE" = " 204" ]; then \
274+ echo " ✅ Standard token exchange enabled for mcp-server client" ; \
275+ else \
276+ echo " ⚠️ Could not enable token exchange (HTTP $$ UPDATE_CLIENT_CODE)" ; \
277+ fi ; \
278+ echo " " ; \
279+ echo " Getting mcp-server client secret..." ; \
280+ SECRET_RESPONSE=$$(curl -s -X GET "http://localhost:8090/admin/realms/openshift/clients/$$MCP_CLIENT_ID/client-secret" \
281+ -H " Authorization: Bearer $$ TOKEN" \
282+ -H " Accept: application/json" ); \
283+ CLIENT_SECRET=$$(echo "$$SECRET_RESPONSE" | jq -r '.value // empty' 2>/dev/null ) ; \
284+ if [ -z " $$ CLIENT_SECRET" ]; then \
285+ echo " ❌ Failed to get client secret" ; \
286+ else \
287+ echo " ✅ Client secret retrieved" ; \
288+ fi ; \
289+ echo " " ; \
290+ echo " Creating test user developer/developer..." ; \
291+ USER_RESPONSE=$$(curl -s -w "%{http_code}" -X POST "http://localhost:8090/admin/realms/openshift/users" \
292+ -H " Authorization: Bearer $$ TOKEN" \
293+ -H " Content-Type: application/json" \
294+ -d
' {"username":"developer","email":"[email protected] ","firstName":"Developer","lastName":"User","enabled":true,"emailVerified":true,"credentials":[{"type":"password","value":"developer","temporary":false}]}' )
; \ 295+ USER_CODE=$$(echo "$$USER_RESPONSE" | tail -c 4 ) ; \
296+ if [ " $$ USER_CODE" = " 201" ] || [ " $$ USER_CODE" = " 409" ]; then \
297+ if [ " $$ USER_CODE" = " 201" ]; then echo " ✅ developer user created" ; \
298+ else echo " ✅ developer user already exists" ; fi ; \
299+ else \
300+ echo " ❌ Failed to create developer user (HTTP $$ USER_CODE)" ; \
301+ exit 1; \
302+ fi ; \
303+ echo " " ; \
304+ echo " 🎉 OpenShift realm setup complete!" ; \
305+ echo " " ; \
306+ echo " ========================================" ; \
307+ echo " Configuration Summary" ; \
308+ echo " ========================================" ; \
309+ echo " Realm: openshift" ; \
310+ echo " Authorization URL: http://localhost:8090/realms/openshift" ; \
311+ echo " " ; \
312+ echo " Test User:" ; \
313+ echo " Username: developer" ; \
314+ echo " Password: developer" ; \
315+ echo " Email: [email protected] " ; \ 316+ echo " " ; \
317+ echo " Clients:" ; \
318+ echo " mcp-client (public, for browser-based auth)" ; \
319+ echo " Client ID: mcp-client" ; \
320+ echo " Optional Scopes: mcp-server" ; \
321+ echo " mcp-server (confidential, token exchange enabled)" ; \
322+ echo " Client ID: mcp-server" ; \
323+ echo " Client Secret: $$ CLIENT_SECRET" ; \
324+ echo " openshift (service account)" ; \
325+ echo " Client ID: openshift" ; \
326+ echo " " ; \
327+ echo " Client Scopes:" ; \
328+ echo " mcp-server (default) - Audience: mcp-server" ; \
329+ echo " mcp:openshift (optional) - Audience: openshift" ; \
330+ echo " groups (default) - Group membership mapper" ; \
331+ echo " " ; \
332+ echo " TOML Configuration:" ; \
333+ echo " require_oauth = true" ; \
334+ echo " oauth_audience = \" mcp-server\" " ; \
335+ echo " authorization_url = \" http://localhost:8090/realms/openshift\" " ; \
336+ echo " sts_client_id = \" mcp-server\" " ; \
337+ echo " sts_client_secret = \" $$ CLIENT_SECRET\" " ; \
338+ echo " sts_audience = \" openshift\" " ; \
339+ echo " sts_scopes = [\" mcp:openshift\" ]" ; \
340+ echo " ========================================"
0 commit comments