Files
computer-playbook/roles/web-app-keycloak/tasks/05a_service_account.yml
Kevin Veen-Birkenbach 6fcf6a1ab6 feat(keycloak): add automation service account client support
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
2025-10-03 21:02:16 +02:00

63 lines
2.6 KiB
YAML

# Creates a confidential client with service account, fetches the secret,
# and grants realm-management/realm-admin to its service-account user.
- name: "Ensure automation client exists (confidential + service accounts)"
shell: |
{{ KEYCLOAK_EXEC_KCADM }} create clients -r {{ KEYCLOAK_REALM }} \
-s clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }} \
-s protocol=openid-connect \
-s publicClient=false \
-s serviceAccountsEnabled=true \
-s directAccessGrantsEnabled=false
register: create_client
changed_when: create_client.rc == 0
failed_when: create_client.rc != 0 and ('already exists' not in (create_client.stderr | lower))
- name: "Resolve automation client id"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get clients -r {{ KEYCLOAK_REALM }}
--query 'clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }}' --fields id --format json | jq -r '.[0].id'
register: auto_client_id_cmd
changed_when: false
- name: "Fail if client id could not be resolved"
assert:
that:
- "(auto_client_id_cmd.stdout | trim) is match('^[0-9a-f-]+$')"
fail_msg: "Automation client id could not be resolved."
- name: "Read client secret"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} get clients/{{ auto_client_id_cmd.stdout | trim }}/client-secret
-r {{ KEYCLOAK_REALM }} --format json | jq -r .value
register: auto_client_secret_cmd
changed_when: false
- name: "Expose client secret as a fact"
set_fact:
KEYCLOAK_AUTOMATION_CLIENT_SECRET: "{{ auto_client_secret_cmd.stdout | trim }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Grant {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }} to service account"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} add-roles -r {{ KEYCLOAK_REALM }}
--uusername service-account-{{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
--cclientid realm-management
--rolename {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }}
register: grant_role
changed_when: grant_role.rc == 0
failed_when: grant_role.rc != 0 and ('already exists' not in (grant_role.stderr | lower))
- name: "Verify client-credentials login works"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
shell: >
{{ KEYCLOAK_EXEC_KCADM }} config credentials
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
--realm {{ KEYCLOAK_REALM }}
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }} &&
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
register: verify_cc
changed_when: false
failed_when: (verify_cc.rc != 0) or ((verify_cc.stdout | trim) != (KEYCLOAK_REALM | trim))