mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-10-31 18:29:21 +00:00 
			
		
		
		
	Introduce a confidential service-account client (Option A) to replace user-based kcadm sessions. The client is created automatically, granted realm-admin role, and used for all subsequent Keycloak updates. Includes improved error handling for HTTP 401 responses. Discussion: https://chatgpt.com/share/68e01da3-39fc-800f-81be-2d0c8efd81a1
		
			
				
	
	
		
			77 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			77 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
| # --- Ensure RBAC client scope exists (idempotent) ---
 | |
| - name: Ensure RBAC client scope exists
 | |
|   shell: |
 | |
|     cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} create client-scopes -r {{ KEYCLOAK_REALM }} -f -
 | |
|     {{
 | |
|       (
 | |
|         KEYCLOAK_DICTIONARY_REALM.clientScopes
 | |
|         | selectattr('name','equalto', KEYCLOAK_RBAC_GROUP_CLAIM)
 | |
|         | list | first
 | |
|       ) | to_json
 | |
|     }}
 | |
|     JSON
 | |
|   register: create_rbac_scope
 | |
|   changed_when: create_rbac_scope.rc == 0
 | |
|   failed_when: create_rbac_scope.rc != 0 and
 | |
|                ('already exists' not in (create_rbac_scope.stderr | lower))
 | |
|   no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
 | |
| 
 | |
| # --- Get the scope id we will attach to the client ---
 | |
| - name: Get all client scopes
 | |
|   shell: "{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json"
 | |
|   register: all_scopes
 | |
|   changed_when: false
 | |
|   failed_when: "'HTTP 401' in (all_scopes.stderr | default(''))"
 | |
| 
 | |
| - name: Extract RBAC scope id
 | |
|   set_fact:
 | |
|     scope_id_rbac: >-
 | |
|       {{ (
 | |
|           all_scopes.stdout | from_json
 | |
|           | selectattr('name','equalto', KEYCLOAK_RBAC_GROUP_CLAIM)
 | |
|           | list | first | default({})
 | |
|         ).id | default('') }}
 | |
| 
 | |
| - name: Resolve application client id
 | |
|   shell: >
 | |
|     {{ KEYCLOAK_EXEC_KCADM }} get clients
 | |
|     -r {{ KEYCLOAK_REALM }}
 | |
|     --query 'clientId={{ KEYCLOAK_CLIENT_ID }}'
 | |
|     --fields id --format json | jq -r '.[0].id'
 | |
|   register: app_client_id_cmd
 | |
|   changed_when: false
 | |
| 
 | |
| - name: Sanity check IDs
 | |
|   assert:
 | |
|     that:
 | |
|       - scope_id_rbac | length > 0
 | |
|       - (app_client_id_cmd.stdout | trim) is match('^[0-9a-f-]+$')
 | |
|     fail_msg: "Could not determine client or scope ID."
 | |
|   when: MODE_ASSERT | bool
 | |
| 
 | |
| - name: Get current optional client scopes
 | |
|   shell: >
 | |
|     {{ KEYCLOAK_EXEC_KCADM }} get
 | |
|     clients/{{ app_client_id_cmd.stdout | trim }}/optional-client-scopes
 | |
|     -r {{ KEYCLOAK_REALM }} --format json
 | |
|   register: opt_scopes
 | |
|   changed_when: false
 | |
| 
 | |
| - name: Decide if RBAC scope already assigned
 | |
|   set_fact:
 | |
|     has_rbac_optional: >-
 | |
|       {{ (opt_scopes.stdout | from_json
 | |
|           | selectattr('id','equalto', scope_id_rbac) | list | length) > 0 }}
 | |
| 
 | |
| - name: Ensure RBAC scope assigned as optional (only if missing)
 | |
|   when: not has_rbac_optional
 | |
|   shell: >
 | |
|     {{ KEYCLOAK_EXEC_KCADM }} update
 | |
|     clients/{{ app_client_id_cmd.stdout | trim }}/optional-client-scopes/{{ scope_id_rbac }}
 | |
|     -r {{ KEYCLOAK_REALM }}
 | |
|   register: add_opt
 | |
|   changed_when: true
 | |
|   failed_when: add_opt.rc != 0
 | |
|   async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
 | |
|   poll:  "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
 |