From bddceeb79528e27bf0e7442d237cdd73c6f0d91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Montagne?= Date: Wed, 19 Feb 2025 18:25:07 +0100 Subject: [PATCH 1/4] fix: :bug: Set Keycloak permanent admin user --- roles/keycloak/tasks/main.yml | 206 +++++++++++++++++++++++----------- 1 file changed, 142 insertions(+), 64 deletions(-) diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index 1d0707dc1..5815b23a6 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -116,68 +116,6 @@ namespace: "{{ dsc.keycloak.namespace }}" type: Opaque -- name: Check Keycloak helm release - kubernetes.core.helm_info: - name: keycloak - namespace: "{{ dsc.keycloak.namespace }}" - register: kc_helm_release - -- name: Reset Keycloak admin password - when: > - kc_helm_release.status is defined and - kc_adm_pass_secret.resources | length == 0 - block: - - name: Get Keycloak primary BDD pod - kubernetes.core.k8s_info: - kind: Pod - label_selectors: - - "cnpg.io/cluster=pg-cluster-keycloak" - - "cnpg.io/instanceRole=primary" - register: kc_bdd_pod - - - name: Get Keycloak admin ID from database - kubernetes.core.k8s_exec: - pod: "{{ kc_bdd_pod.resources[0].metadata.name }}" - namespace: "{{ dsc.keycloak.namespace }}" - command: > - psql -U postgres -d keycloak --csv -c "\x" -c "select id from user_entity where username = 'admin';" - register: kc_admin_id - - - name: Set kc_admin_id fact - ansible.builtin.set_fact: - kc_admin_id: "{{ kc_admin_id.stdout | regex_search('^id.*', multiline=True) | regex_search('id,(.+)', '\\1') | first }}" - - - name: Delete Keycloak admin in database - kubernetes.core.k8s_exec: - pod: "{{ kc_bdd_pod.resources[0].metadata.name }}" - namespace: "{{ dsc.keycloak.namespace }}" - command: > - psql -U postgres -d keycloak -c "delete from credential where user_id = '"{{ kc_admin_id }}"';" - -c "delete from user_role_mapping where user_id = '"{{ kc_admin_id }}"';" - -c "delete from user_entity where id = '"{{ kc_admin_id }}"';" - -c "delete from user_required_action where user_id = '"{{ kc_admin_id }}"';" - - - name: Restart Keycloak pods to reset admin password - kubernetes.core.k8s: - kind: Pod - namespace: "{{ dsc.keycloak.namespace }}" - label_selectors: - - "app.kubernetes.io/component=keycloak" - - "app.kubernetes.io/instance=keycloak" - state: absent - - - name: Wait Keycloak URL - ansible.builtin.uri: - url: https://{{ keycloak_domain }} - validate_certs: "{{ dsc.exposedCA.type == 'none' }}" - method: GET - status_code: [200, 202] - return_content: false - register: kc_response - until: kc_response is not failed - retries: 30 - delay: 5 - - name: Add bitnami helm repo kubernetes.core.helm_repository: name: bitnami @@ -223,11 +161,113 @@ name: keycloak register: kc_adm_pass -- name: Set Keycloak admin name fact +- name: Set Keycloak admin facts ansible.builtin.set_fact: keycloak_admin_password: "{{ kc_adm_pass.resources[0].data['admin-password'] | b64decode }}" keycloak_admin: admin +- name: Get Keycloak API token + ansible.builtin.uri: + url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token + method: POST + status_code: [200, 202] + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + return_content: true + body: username={{ keycloak_admin }}&password={{ keycloak_admin_password }}&grant_type=password&client_id=admin-cli + register: kc_token + ignore_errors: true + +- name: Reset Keycloak admin fact + when: kc_token is failed + ansible.builtin.set_fact: + keycloak_admin: dsoadmin + +- name: Get Keycloak API token + ansible.builtin.uri: + url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token + method: POST + status_code: [200, 202] + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + return_content: true + body: username={{ keycloak_admin }}&password={{ keycloak_admin_password }}&grant_type=password&client_id=admin-cli + register: kc_token + +- name: Set kc_access_token fact + ansible.builtin.set_fact: + kc_access_token: "{{ kc_token.json.access_token }}" + +- name: Get keycloak master realm users from API + ansible.builtin.uri: + url: https://{{ keycloak_domain }}/admin/realms/master/users + method: GET + status_code: [200, 202] + return_content: true + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + body_format: json + headers: + Authorization: bearer {{ kc_access_token }} + register: kc_master_users + +- name: Set permanent_admin_present fact + ansible.builtin.set_fact: + permanent_admin_present: false + +- name: Update admin_present fact + when: kc_master_users.json | selectattr('username', 'equalto', 'dsoadmin') + ansible.builtin.set_fact: + permanent_admin_present: true + +- name: Create permanent admin group and user into master realm + when: not permanent_admin_present + block: + - name: Create admin group + community.general.keycloak_group: + auth_client_id: admin-cli + auth_keycloak_url: https://{{ keycloak_domain }} + auth_realm: master + auth_username: "{{ keycloak_admin }}" + auth_password: "{{ keycloak_admin_password }}" + name: admin + realm: master + state: present + + - name: Map admin realm role from admin group + community.general.keycloak_realm_rolemapping: + realm: master + auth_client_id: admin-cli + auth_keycloak_url: https://{{ keycloak_domain }} + auth_realm: master + auth_username: "{{ keycloak_admin }}" + auth_password: "{{ keycloak_admin_password }}" + state: present + group_name: admin + roles: + - name: admin + + - name: Create master realm permanent admin user + community.general.keycloak_user: + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + auth_client_id: admin-cli + auth_keycloak_url: https://{{ keycloak_domain }} + auth_realm: master + auth_username: "{{ keycloak_admin }}" + auth_password: "{{ keycloak_admin_password }}" + state: present + realm: master + credentials: + - temporary: false + type: password + value: "{{ keycloak_admin_password }}" + username: dsoadmin + first_name: Admin + last_name: Admin + email: admin@example.com + enabled: true + email_verified: true + groups: + - name: admin + state: present + - name: Update console inventory kubernetes.core.k8s: kind: Secret @@ -237,7 +277,45 @@ definition: data: KEYCLOAK_ADMIN_PASSWORD: "{{ keycloak_admin_password | b64encode }}" - KEYCLOAK_ADMIN: "{{ keycloak_admin | b64encode }}" + KEYCLOAK_ADMIN: "{{ 'dsoadmin' | b64encode }}" + +- name: Set temporary_admin_present fact + ansible.builtin.set_fact: + temporary_admin_present: false + +- name: Update temporary_admin_present fact + when: kc_master_users.json | selectattr('username', 'equalto', 'admin') + ansible.builtin.set_fact: + temporary_admin_present: true + +- name: Remove temporary admin from master realm + when: temporary_admin_present + community.general.keycloak_user: + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + auth_client_id: admin-cli + auth_keycloak_url: https://{{ keycloak_domain }} + auth_realm: master + auth_username: "{{ keycloak_admin }}" + auth_password: "{{ keycloak_admin_password }}" + state: absent + realm: master + username: admin + +- name: Get Keycloak API token + ansible.builtin.uri: + url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token + method: POST + status_code: [200, 202] + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + return_content: true + body: username={{ keycloak_admin }}&password={{ keycloak_admin_password }}&grant_type=password&client_id=admin-cli + register: kc_token + ignore_errors: true + +- name: Reset Keycloak admin fact + when: kc_token is failed + ansible.builtin.set_fact: + keycloak_admin: dsoadmin - name: Get Keycloak API token ansible.builtin.uri: From 0a8b7b107b20407fbc02b6f4c641ec8f8d8f2c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Montagne?= Date: Thu, 20 Feb 2025 10:36:50 +0100 Subject: [PATCH 2/4] refactor: :art: Refactor and comments --- roles/keycloak/tasks/main.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index 5815b23a6..83c38f85e 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -6,6 +6,8 @@ kind: Namespace state: present +# Setup CNPG s3 secret + - name: CNPG s3 CA (secret) when: > dsc.global.backup.cnpg.enabled and @@ -34,7 +36,10 @@ data: ca.pem: "{{ cnpg_s3_ca_pem }}" +# Setup CNPG backup + - name: Set cnpg backup secret + when: dsc.global.backup.cnpg.enabled kubernetes.core.k8s: name: "{{ dsc.global.backup.s3.credentials.name }}" namespace: "{{ dsc.keycloak.namespace }}" @@ -44,7 +49,6 @@ data: accessKeyId: "{{ dsc.global.backup.s3.credentials.accessKeyId.value | b64encode }}" secretAccessKey: "{{ dsc.global.backup.s3.credentials.secretAccessKey.value | b64encode }}" - when: dsc.global.backup.cnpg.enabled - name: Remove cnpg scheduled backup kubernetes.core.k8s: @@ -55,7 +59,9 @@ state: absent when: not dsc.global.backup.cnpg.enabled -- name: Create PostgreSQL cluster and keycloak database +# Create CNPG cluster and Keycloak database + +- name: Create PostgreSQL cluster and Keycloak database kubernetes.core.k8s: template: "{{ item }}" with_items: @@ -95,6 +101,8 @@ retries: 30 delay: 5 +# Set Keycloak admin password + - name: Get Keycloak admin password secret kubernetes.core.k8s_info: namespace: "{{ dsc.keycloak.namespace }}" @@ -116,6 +124,8 @@ namespace: "{{ dsc.keycloak.namespace }}" type: Opaque +# Deploy Keycloak + - name: Add bitnami helm repo kubernetes.core.helm_repository: name: bitnami @@ -154,6 +164,8 @@ retries: 30 delay: 5 +# Set admin facts and check access to Keycloak API + - name: Get Keycloak admin password kubernetes.core.k8s_info: namespace: "{{ dsc.keycloak.namespace }}" @@ -196,6 +208,8 @@ ansible.builtin.set_fact: kc_access_token: "{{ kc_token.json.access_token }}" +# Create permanent Keycloak admin and update DSO Console inventory + - name: Get keycloak master realm users from API ansible.builtin.uri: url: https://{{ keycloak_domain }}/admin/realms/master/users @@ -279,6 +293,8 @@ KEYCLOAK_ADMIN_PASSWORD: "{{ keycloak_admin_password | b64encode }}" KEYCLOAK_ADMIN: "{{ 'dsoadmin' | b64encode }}" +# Remove Keycloak temporary admin + - name: Set temporary_admin_present fact ansible.builtin.set_fact: temporary_admin_present: false @@ -301,6 +317,8 @@ realm: master username: admin +# Ensure we will use permanent admin for subsequent tasks + - name: Get Keycloak API token ansible.builtin.uri: url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token @@ -331,6 +349,8 @@ ansible.builtin.set_fact: kc_access_token: "{{ kc_token.json.access_token }}" +# Create and setup dso realm + - name: Create dso realm community.general.keycloak_realm: validate_certs: "{{ dsc.exposedCA.type == 'none' }}" @@ -511,6 +531,8 @@ realm: dso otp_policy_algorithm: SHA256 +# Patch some metrics resources + - name: Patch serviceMonitors when: > dsc.global.metrics.enabled and From 62099031fe41fd5d71fb4a73e0bf8f289b3fc95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Montagne?= Date: Thu, 20 Feb 2025 14:29:14 +0100 Subject: [PATCH 3/4] perf: :art: Add when condition --- roles/keycloak/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index 83c38f85e..c09a84f90 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -195,6 +195,7 @@ keycloak_admin: dsoadmin - name: Get Keycloak API token + when: kc_token is failed ansible.builtin.uri: url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token method: POST From 3137c69b0cfc4e2fed1d127be5371fbf7610a2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Montagne?= Date: Thu, 20 Feb 2025 16:12:51 +0100 Subject: [PATCH 4/4] fix: :bug: Reset Keycloak admin fact and API token --- roles/keycloak/tasks/main.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index c09a84f90..f30314a7e 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -189,21 +189,22 @@ register: kc_token ignore_errors: true -- name: Reset Keycloak admin fact +- name: Reset Keycloak admin fact and API token when: kc_token is failed - ansible.builtin.set_fact: - keycloak_admin: dsoadmin + block: + - name: Reset Keycloak admin fact + ansible.builtin.set_fact: + keycloak_admin: dsoadmin -- name: Get Keycloak API token - when: kc_token is failed - ansible.builtin.uri: - url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token - method: POST - status_code: [200, 202] - validate_certs: "{{ dsc.exposedCA.type == 'none' }}" - return_content: true - body: username={{ keycloak_admin }}&password={{ keycloak_admin_password }}&grant_type=password&client_id=admin-cli - register: kc_token + - name: Get Keycloak API token + ansible.builtin.uri: + url: https://{{ keycloak_domain }}/realms/master/protocol/openid-connect/token + method: POST + status_code: [200, 202] + validate_certs: "{{ dsc.exposedCA.type == 'none' }}" + return_content: true + body: username={{ keycloak_admin }}&password={{ keycloak_admin_password }}&grant_type=password&client_id=admin-cli + register: kc_token - name: Set kc_access_token fact ansible.builtin.set_fact: