diff --git a/roles/web-app-keycloak/config/main.yml b/roles/web-app-keycloak/config/main.yml index d8436658..3a05c492 100644 --- a/roles/web-app-keycloak/config/main.yml +++ b/roles/web-app-keycloak/config/main.yml @@ -1,10 +1,9 @@ actions: import_realm: True # Import REALM - update_ldap_bind: True # Updates LDAP binds features: matomo: true css: true - desktop: true + desktop: true ldap: true central_database: true recaptcha: true diff --git a/roles/web-app-keycloak/tasks/01_import.yml b/roles/web-app-keycloak/tasks/01_import.yml deleted file mode 100644 index 85e791b9..00000000 --- a/roles/web-app-keycloak/tasks/01_import.yml +++ /dev/null @@ -1,19 +0,0 @@ -- name: "load variables from {{ DOCKER_VARS_FILE }}" - include_vars: "{{ DOCKER_VARS_FILE }}" - -- name: Set the directory to which keycloak import files will be copied on host - set_fact: - keycloak_host_import_directory: "{{ docker_compose.directories.volumes }}import/" - -- name: "create directory {{ keycloak_host_import_directory }}" - file: - path: "{{ keycloak_host_import_directory }}" - state: directory - mode: "0755" - -- name: "Copy import files to {{ keycloak_host_import_directory }}" - template: - src: "{{ item }}" - dest: "{{ keycloak_host_import_directory }}/{{ item | basename | regex_replace('\\.j2$', '') }}" - mode: "0770" - loop: "{{ lookup('fileglob', role_path ~ '/templates/import/*.j2', wantlist=True) }}" \ No newline at end of file diff --git a/roles/web-app-keycloak/tasks/01_initialize.yml b/roles/web-app-keycloak/tasks/01_initialize.yml new file mode 100644 index 00000000..69d324cd --- /dev/null +++ b/roles/web-app-keycloak/tasks/01_initialize.yml @@ -0,0 +1,15 @@ +- name: "load variables from {{ DOCKER_VARS_FILE }}" + include_vars: "{{ DOCKER_VARS_FILE }}" + +- name: "create directory {{ KEYCLOAK_HOST_IMPORT_DIR }}" + file: + path: "{{ KEYCLOAK_HOST_IMPORT_DIR }}" + state: directory + mode: "0755" + +- name: "Copy import files to {{ KEYCLOAK_HOST_IMPORT_DIR }}" + template: + src: "{{ item }}" + dest: "{{ KEYCLOAK_HOST_IMPORT_DIR }}/{{ item | basename | regex_replace('\\.j2$', '') }}" + mode: "0770" + loop: "{{ lookup('fileglob', role_path ~ '/templates/import/*.j2', wantlist=True) }}" \ No newline at end of file diff --git a/roles/web-app-keycloak/tasks/02_update.yml b/roles/web-app-keycloak/tasks/02_update.yml new file mode 100644 index 00000000..0f165446 --- /dev/null +++ b/roles/web-app-keycloak/tasks/02_update.yml @@ -0,0 +1,107 @@ +# Generic updater for Keycloak client/component via kcadm. +# Flow: resolve ID → read current object → merge with desired → preserve immutable fields → update via stdin. +# +# Required vars (pass via include): +# - kc_object_kind: "client" | "component" +# - kc_lookup_value: e.g., KEYCLOAK_CLIENT_ID or KEYCLOAK_LDAP_CMP_NAME +# - kc_desired: dict, e.g., KEYCLOAK_DICTIONARY_CLIENT or KEYCLOAK_DICTIONARY_LDAP +# +# Optional: +# - kc_lookup_field: override lookup field (defaults: clientId for client, name for component) +# - kc_merge_path: if set (e.g. "config"), only that subkey is merged +# - kc_force_attrs: dict to force on the final payload (merged last) + +- name: Assert required vars + assert: + that: + - kc_object_kind in ['client','component'] + - kc_lookup_value is defined + - kc_desired is defined + fail_msg: "kc_object_kind, kc_lookup_value, kc_desired are required." + +- name: Derive API endpoint and lookup field + set_fact: + kc_api: "{{ 'clients' if kc_object_kind == 'client' else 'components' }}" + kc_lookup_field_eff: "{{ 'clientId' if kc_object_kind == 'client' else (kc_lookup_field | default('name')) }}" + +- name: Resolve object id + shell: > + {{ KEYCLOAK_EXEC_KCADM }} get {{ kc_api }} + -r {{ KEYCLOAK_REALM }} + --query '{{ kc_lookup_field_eff }}={{ kc_lookup_value }}' + --fields id --format json | jq -r '.[0].id' + register: kc_obj_id + changed_when: false + +- name: Fail if object not found + assert: + that: + - (kc_obj_id.stdout | trim) != '' + - (kc_obj_id.stdout | trim) != 'null' + fail_msg: "{{ kc_object_kind | capitalize }} '{{ kc_lookup_value }}' not found." + +- name: Read current object + shell: > + {{ KEYCLOAK_EXEC_KCADM }} get {{ kc_api }}/{{ kc_obj_id.stdout }} + -r {{ KEYCLOAK_REALM }} --format json + register: kc_cur + changed_when: false + +# ── Build merge payload safely (avoid evaluating kc_desired[kc_merge_path] when undefined) ───────── + +- name: Parse current object + set_fact: + cur_obj: "{{ kc_cur.stdout | from_json }}" + +- name: Prepare merge payload (subpath) + when: kc_merge_path is defined and (kc_merge_path | length) > 0 + set_fact: + merge_payload: "{{ { (kc_merge_path): (kc_desired[kc_merge_path] | default({}, true)) } }}" + +- name: Prepare merge payload (full object) + when: kc_merge_path is not defined or (kc_merge_path | length) == 0 + set_fact: + merge_payload: "{{ kc_desired }}" + +- name: Build desired object (base merge) + set_fact: + desired_obj: "{{ cur_obj | combine(merge_payload, recursive=True) }}" + +# Preserve immutable fields +- name: Preserve immutable fields for client + when: kc_object_kind == 'client' + set_fact: + desired_obj: >- + {{ + desired_obj + | combine({ + 'id': cur_obj.id, + 'clientId': cur_obj.clientId + }, recursive=True) + }} + +- name: Preserve immutable fields for component + when: kc_object_kind == 'component' + set_fact: + desired_obj: >- + {{ + desired_obj + | combine({ + 'id': cur_obj.id, + 'providerId': cur_obj.providerId, + 'providerType': cur_obj.providerType, + 'parentId': cur_obj.parentId + }, recursive=True) + }} + +# Optional forced attributes (e.g., frontchannelLogout) +- name: Apply forced attributes (optional) + when: kc_force_attrs is defined + set_fact: + desired_obj: "{{ desired_obj | combine(kc_force_attrs, recursive=True) }}" + +- name: Update object via stdin + shell: | + cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} update {{ kc_api }}/{{ kc_obj_id.stdout }} -r {{ KEYCLOAK_REALM }} -f - + {{ desired_obj | to_json }} + JSON diff --git a/roles/web-app-keycloak/tasks/02_update_client_redirects.yml b/roles/web-app-keycloak/tasks/02_update_client_redirects.yml deleted file mode 100644 index e395adcd..00000000 --- a/roles/web-app-keycloak/tasks/02_update_client_redirects.yml +++ /dev/null @@ -1,108 +0,0 @@ ---- -# Update redirectUris/webOrigins per kcadm.sh — no defaults used. - -# ── REQUIRED VARS (must be provided by caller) ─────────────────────────────── -# - WEB_PROTOCOL e.g. "https" -# - KEYCLOAK_REALM target realm name -# - KEYCLOAK_SERVER_HOST_URL e.g. "http://127.0.0.1:8080" -# - KEYCLOAK_SERVER_INTERNAL_URL e.g. "http://127.0.0.1:8080" -# - KEYCLOAK_EXEC_KCADM e.g. "docker exec -i keycloak /opt/keycloak/bin/kcadm.sh" -# - KEYCLOAK_MASTER_API_USER_NAME -# - KEYCLOAK_MASTER_API_USER_PASSWORD -# - KEYCLOAK_CLIENT_ID clientId to update (e.g. same as realm or an app client) -# - domains your domain map -# - applications your applications map - -- name: "Assert required variables are present (no defaults allowed)" - assert: - that: - - WEB_PROTOCOL is defined - - KEYCLOAK_REALM is defined - - KEYCLOAK_SERVER_HOST_URL is defined - - KEYCLOAK_SERVER_INTERNAL_URL is defined - - KEYCLOAK_EXEC_KCADM is defined - - KEYCLOAK_MASTER_API_USER_NAME is defined - - KEYCLOAK_MASTER_API_USER_PASSWORD is defined - - KEYCLOAK_CLIENT_ID is defined - - KEYCLOAK_REDIRECT_FEATURES is defined - - domains is defined - - applications is defined - fail_msg: "Missing required variable(s). Provide all vars listed at the top of 02_update_client_redirects.yml." - -- name: "kcadm login" - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - shell: > - {{ KEYCLOAK_EXEC_KCADM }} config credentials - --server {{ KEYCLOAK_SERVER_INTERNAL_URL }} - --realm master - --user {{ KEYCLOAK_MASTER_API_USER_NAME }} - --password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }} - changed_when: false - -- name: "Resolve client internal id for {{ KEYCLOAK_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: kc_client - changed_when: false - -- name: "Fail if client not found" - assert: - that: kc_client.stdout is match('^[0-9a-f-]+$') - fail_msg: "Client '{{ KEYCLOAK_CLIENT_ID }}' not found in realm '{{ KEYCLOAK_REALM }}'." - -- name: "Read current client configuration" - shell: > - {{ KEYCLOAK_EXEC_KCADM }} get clients/{{ kc_client.stdout }} - -r {{ KEYCLOAK_REALM }} --format json - register: kc_client_obj - changed_when: false - -- name: "Normalize current vs desired for comparison" - set_fact: - kc_current_redirect_uris: "{{ (kc_client_obj.stdout | from_json).redirectUris | sort }}" - kc_current_web_origins: "{{ (kc_client_obj.stdout | from_json).webOrigins | sort }}" - kc_current_logout_uris: >- - {{ - ( - (kc_client_obj.stdout | from_json).attributes['post.logout.redirect.uris'] - if 'post.logout.redirect.uris' in (kc_client_obj.stdout | from_json).attributes - else '' - ) - | regex_replace('\r','') - | split('\n') - | reject('equalto','') - | list | sort - }} - kc_desired_redirect_uris: "{{ KEYCLOAK_REDIRECT_URIS | sort }}" - kc_desired_web_origins: "{{ KEYCLOAK_WEB_ORIGINS | sort }}" - KEYCLOAK_POST_LOGOUT_URIS_list: >- - {{ "+" | split('\n') | reject('equalto','') | list | sort }} - -- name: "Extract current frontchannel logout url" - set_fact: - kc_current_frontchannel_logout_url: >- - {{ - ( - (kc_client_obj.stdout | from_json).attributes['frontchannel.logout.url'] - if 'frontchannel.logout.url' in (kc_client_obj.stdout | from_json).attributes - else '' - ) - }} - -- name: "Update client with redirectUris, webOrigins, frontchannelLogout" - shell: > - {{ KEYCLOAK_EXEC_KCADM }} update clients/{{ kc_client.stdout }} - -r {{ KEYCLOAK_REALM }} - -s 'redirectUris={{ KEYCLOAK_REDIRECT_URIS | to_json }}' - -s 'webOrigins={{ KEYCLOAK_WEB_ORIGINS | to_json }}' - -s 'frontchannelLogout=true' - -s 'attributes."frontchannel.logout.url"={{ KEYCLOAK_FRONTCHANNEL_LOGOUT_URL | to_json }}' - when: kc_current_redirect_uris != kc_desired_redirect_uris - or kc_current_web_origins != kc_desired_web_origins - or kc_current_frontchannel_logout_url != KEYCLOAK_FRONTCHANNEL_LOGOUT_URL - async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" - poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" - diff --git a/roles/web-app-keycloak/tasks/03_update-ldap-bind.yml b/roles/web-app-keycloak/tasks/03_update-ldap-bind.yml deleted file mode 100644 index 226a4e32..00000000 --- a/roles/web-app-keycloak/tasks/03_update-ldap-bind.yml +++ /dev/null @@ -1,105 +0,0 @@ ---- -# Idempotent update of Keycloak LDAP provider: -# - bindDn -# - bindCredential -# - connectionUrl -# -# STRICT: Uses ONLY values from ldap.* (no computed defaults) -# - ldap.dn.administrator.data -# - ldap.bind_credential -# - ldap.server.uri - -- name: "Assert required vars exist (strict: use ldap.* only, no defaults)" - assert: - that: - - KEYCLOAK_REALM is defined - - KEYCLOAK_CONTAINER is defined - - KEYCLOAK_SERVER_INTERNAL_URL is defined - - KEYCLOAK_MASTER_API_USER_NAME is defined - - KEYCLOAK_MASTER_API_USER_PASSWORD is defined - - KEYCLOAK_LDAP_CMP_NAME is defined - - ldap is defined - - ldap.dn.administrator is defined - - ldap.dn.administrator.data is defined - - ldap.bind_credential is defined - - ldap.server is defined - - ldap.server.uri is defined - fail_msg: >- - Missing required Keycloak/LDAP variables. Ensure ldap.dn.administrator.data, - ldap.bind_credential, and ldap.server.uri are defined. - -# Build a base argv for kcadm to avoid fragile shell quoting -- name: "Build kcadm argv base" - set_fact: - kcadm_argv_base: - - docker - - exec - - -i - - "{{ KEYCLOAK_CONTAINER }}" - - /opt/keycloak/bin/kcadm.sh - -- name: "kcadm login (master)" - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - command: - argv: "{{ kcadm_argv_base - + ['config', 'credentials', - '--server', KEYCLOAK_SERVER_INTERNAL_URL, - '--realm', 'master', - '--user', KEYCLOAK_MASTER_API_USER_NAME, - '--password', KEYCLOAK_MASTER_API_USER_PASSWORD] }}" - changed_when: false - -# Resolve the LDAP component *by name* to avoid picking the wrong one. -- name: "Resolve LDAP component id by name '{{ KEYCLOAK_LDAP_CMP_NAME }}'" - command: - argv: "{{ kcadm_argv_base - + ['get', 'components', - '-r', KEYCLOAK_REALM, - '--query', 'name=' ~ KEYCLOAK_LDAP_CMP_NAME, - '--fields', 'id,name,providerId,config', - '--format', 'json'] }}" - register: kc_ldap_list - changed_when: false - -- name: "Validate that exactly one LDAP component matched" - vars: - parsed: "{{ kc_ldap_list.stdout | from_json }}" - assert: - that: - - (parsed | length) == 1 - fail_msg: >- - Expected exactly one LDAP component named '{{ KEYCLOAK_LDAP_CMP_NAME }}', - found {{ (kc_ldap_list.stdout | from_json) | length }}. - -- name: "Extract current LDAP component values" - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - set_fact: - kc_ldap_component_id: "{{ (kc_ldap_list.stdout | from_json)[0].id }}" - kc_ldap_current_bind_dn: "{{ ((kc_ldap_list.stdout | from_json)[0].config['bindDn'] | default(['']))[0] }}" - kc_ldap_current_bind_pw: "{{ ((kc_ldap_list.stdout | from_json)[0].config['bindCredential'] | default(['']))[0] }}" - kc_ldap_current_connection_url: "{{ ((kc_ldap_list.stdout | from_json)[0].config['connectionUrl'] | default(['']))[0] }}" - -- name: "Determine if update is required" - set_fact: - kc_needs_update: >- - {{ - (kc_ldap_current_bind_dn != KEYCLOAK_LDAP_BIND_DN) - or (kc_ldap_current_bind_pw != KEYCLOAK_LDAP_BIND_PW) - or (kc_ldap_current_connection_url != KEYCLOAK_LDAP_URL) - }} - -# Pass each -s as a single argv token with valid JSON (arrays), zero shell quoting issues. -- name: "Update LDAP bindDn / bindCredential / connectionUrl (strict, argv)" - no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" - command: - argv: "{{ kcadm_argv_base - + ['update', 'components/' ~ kc_ldap_component_id, - '-r', KEYCLOAK_REALM, - '-s', 'config.bindDn=' ~ ([KEYCLOAK_LDAP_BIND_DN] | to_json), - '-s', 'config.bindCredential=' ~ ([KEYCLOAK_LDAP_BIND_PW] | to_json), - '-s', 'config.connectionUrl=' ~ ([KEYCLOAK_LDAP_URL] | to_json) - ] }}" - when: kc_needs_update | bool - register: kc_bind_update - async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" - poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" diff --git a/roles/web-app-keycloak/tasks/04_ssh_public_key.yml b/roles/web-app-keycloak/tasks/04_ssh_public_key.yml deleted file mode 100644 index 5af7b19e..00000000 --- a/roles/web-app-keycloak/tasks/04_ssh_public_key.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Configure Credentials -- name: Ensure Keycloak CLI credentials are configured - shell: | - {{ KEYCLOAK_EXEC_KCADM }} config credentials \ - --server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \ - --realm master \ - --user {{ KEYCLOAK_MASTER_API_USER_NAME }} \ - --password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }} - -# LDAP Source -- name: Get ID of LDAP storage provider - shell: | - {{ KEYCLOAK_EXEC_KCADM }} get components \ - -r {{ KEYCLOAK_REALM }} \ - --query 'providerId=ldap' \ - --fields id,name \ - --format json - register: ldap_components - -- name: Extract LDAP component ID - set_fact: - ldap_component_id: "{{ (ldap_components.stdout | from_json)[0].id }}" - -- name: Ensure {{ ldap.user.attributes.ssh_public_key }} LDAP Mapper exists - shell: | - docker exec -i keycloak_application bash -c ' - /opt/keycloak/bin/kcadm.sh get components -r {{ KEYCLOAK_REALM }} \ - | grep -q "\"name\" : \"{{ ldap.user.attributes.ssh_public_key }}\"" \ - || printf "%s\n" "{ - \"name\": \"{{ ldap.user.attributes.ssh_public_key }}\", - \"parentId\": \"{{ ldap_component_id }}\", - \"providerId\": \"user-attribute-ldap-mapper\", - \"providerType\": \"org.keycloak.storage.ldap.mappers.LDAPStorageMapper\", - \"config\": { - \"user.model.attribute\": [\"{{ ldap.user.attributes.ssh_public_key }}\"], - \"ldap.attribute\": [\"{{ ldap.user.attributes.ssh_public_key }}\"], - \"read.only\": [\"false\"], - \"write.only\": [\"true\"], - \"always.read.value.from.ldap\": [\"false\"], - \"multivalued\": [\"true\"] - } - }" | /opt/keycloak/bin/kcadm.sh create components -r {{ KEYCLOAK_REALM }} -f -' - register: mapper_create - changed_when: mapper_create.rc == 0 and mapper_create.stdout != "" - -# GUI - -- name: Enable user profile in realm - shell: > - {{ KEYCLOAK_EXEC_KCADM }} update realms/{{ KEYCLOAK_REALM }} - -s 'attributes.userProfileEnabled=true' - -- name: Re-authenticate to Keycloak after enabling user profile - shell: | - {{ KEYCLOAK_EXEC_KCADM }} config credentials \ - --server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \ - --realm master \ - --user {{ KEYCLOAK_MASTER_API_USER_NAME }} \ - --password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }} - -- name: Render user-profile JSON for SSH key - template: - src: import/user-profile.json.j2 - dest: "{{ keycloak_host_import_directory }}/user-profile.json" - mode: '0644' - notify: docker compose up - -- name: Apply SSH Public Key to user-profile via kcadm - shell: | - docker exec -i {{ KEYCLOAK_CONTAINER }} \ - /opt/keycloak/bin/kcadm.sh update realms/{{ KEYCLOAK_REALM }} -f {{ KEYCLOAK_DOCKER_IMPORT_DIR }}user-profile.json - async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}" - poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}" \ No newline at end of file diff --git a/roles/web-app-keycloak/tasks/Todo.md b/roles/web-app-keycloak/tasks/Todo.md index 92bd2fc9..5f8678fa 100644 --- a/roles/web-app-keycloak/tasks/Todo.md +++ b/roles/web-app-keycloak/tasks/Todo.md @@ -1,3 +1,3 @@ # Todos -- Include 03_update-ldap-bind.yml +- Include 03_update-ldap.yml - Include 04_ssh_public_key.yml \ No newline at end of file diff --git a/roles/web-app-keycloak/tasks/main.yml b/roles/web-app-keycloak/tasks/main.yml index 451cfe21..d71e4d53 100644 --- a/roles/web-app-keycloak/tasks/main.yml +++ b/roles/web-app-keycloak/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: "create import files for {{ application_id }}" - include_tasks: 01_import.yml + include_tasks: 01_initialize.yml - name: "load required 'web-svc-logout' for {{ application_id }}" include_role: @@ -15,7 +15,7 @@ - name: "Wait until Keycloak is reachable at {{ KEYCLOAK_SERVER_HOST_URL }}" uri: - url: "{{ KEYCLOAK_SERVER_HOST_URL }}/realms/master" + url: "{{ KEYCLOAK_MASTER_REALM_URL }}" method: GET status_code: 200 validate_certs: false @@ -24,13 +24,41 @@ delay: 5 until: kc_up.status == 200 -- name: "Apply client redirects without realm import" - include_tasks: 02_update_client_redirects.yml +- name: kcadm login (master) + no_log: true + shell: > + {{ KEYCLOAK_EXEC_KCADM }} config credentials + --server {{ KEYCLOAK_SERVER_INTERNAL_URL }} + --realm master + --user {{ KEYCLOAK_MASTER_API_USER_NAME }} + --password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }} + changed_when: false -- name: "Update LDAP bind credentials from ldap.*" - when: KEYCLOAK_UPDATE_LDAP_BIND | bool - include_tasks: 03_update-ldap-bind.yml +- name: "Update REALM settings" + include_tasks: 02_update.yml + vars: + kc_object_kind: "component" + kc_lookup_value: "{{ KEYCLOAK_LDAP_CMP_NAME }}" + kc_desired: >- + {{ + KEYCLOAK_DICTIONARY_REALM.components['org.keycloak.storage.UserStorageProvider'] + | selectattr('providerId','equalto','ldap') + | list | first }} + kc_merge_path: "config" + when: KEYCLOAK_LDAP_ENABLED | bool -# Deactivated temporary. Import now via realm.yml -#- name: Implement SSH Public Key Attribut -# include_tasks: 03_ssh_public_key.yml \ No newline at end of file +- name: "Update Client settings" + vars: + kc_object_kind: "client" + kc_lookup_value: "{{ KEYCLOAK_CLIENT_ID }}" + kc_desired: >- + {{ + KEYCLOAK_DICTIONARY_REALM.clients + | selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID) + | list | first + }} + kc_force_attrs: + frontchannelLogout: true + attributes: "{{ (KEYCLOAK_DICTIONARY_CLIENT.attributes | default({})) + | combine({'frontchannel.logout.url': KEYCLOAK_FRONTCHANNEL_LOGOUT_URL}, recursive=True) }}" + include_tasks: 02_update.yml diff --git a/roles/web-app-keycloak/templates/docker-compose.yml.j2 b/roles/web-app-keycloak/templates/docker-compose.yml.j2 index 77498306..e94691ee 100644 --- a/roles/web-app-keycloak/templates/docker-compose.yml.j2 +++ b/roles/web-app-keycloak/templates/docker-compose.yml.j2 @@ -8,7 +8,7 @@ ports: - "{{ KEYCLOAK_SERVER_HOST }}:8080" volumes: - - "{{ keycloak_host_import_directory }}:{{KEYCLOAK_DOCKER_IMPORT_DIR}}" + - "{{ KEYCLOAK_HOST_IMPORT_DIR }}:{{KEYCLOAK_DOCKER_IMPORT_DIR}}" {% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %} {% include 'roles/docker-container/templates/networks.yml.j2' %} {% set container_port = 9000 %} diff --git a/roles/web-app-keycloak/templates/import/client.json.j2 b/roles/web-app-keycloak/templates/import/client.json.j2 index 026f225e..15f5ce30 100644 --- a/roles/web-app-keycloak/templates/import/client.json.j2 +++ b/roles/web-app-keycloak/templates/import/client.json.j2 @@ -10,7 +10,6 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "{{ OIDC.CLIENT.SECRET }}", - {# The following line should be covered by 02_update_client_redirects.yml #} "redirectUris": {{ KEYCLOAK_REDIRECT_URIS | to_json }}, "webOrigins": {{ KEYCLOAK_WEB_ORIGINS | to_json }}, "notBefore": 0, diff --git a/roles/web-app-keycloak/templates/import/ldap.json.j2 b/roles/web-app-keycloak/templates/import/ldap.json.j2 index d8c92c88..470221f7 100644 --- a/roles/web-app-keycloak/templates/import/ldap.json.j2 +++ b/roles/web-app-keycloak/templates/import/ldap.json.j2 @@ -196,14 +196,14 @@ "useKerberosForPasswordAuthentication": [ "false" ], "importEnabled": [ "true" ], "enabled": [ "true" ], - "bindCredential": [ "{{ ldap.bind_credential }}" ], + "bindCredential": [ "{{ KEYCLOAK_LDAP_BIND_PW }}" ], "changedSyncPeriod": [ "-1" ], "usernameLDAPAttribute": [ "{{ ldap.user.attributes.id }}" ], - "bindDn": [ "{{ ldap.dn.administrator.data }}" ], + "bindDn": [ "{{ KEYCLOAK_LDAP_BIND_DN }}" ], "vendor": [ "other" ], "uuidLDAPAttribute": [ "{{ ldap.user.attributes.id }}" ], "allowKerberosAuthentication": [ "false" ], - "connectionUrl": [ "{{ ldap.server.uri }}" ], + "connectionUrl": [ "{{ KEYCLOAK_LDAP_URL }}" ], "syncRegistrations": [ "true" ], "authType": [ "simple" ], "krbPrincipalAttribute": [ "krb5PrincipalName" ], diff --git a/roles/web-app-keycloak/templates/import/realm.json.j2 b/roles/web-app-keycloak/templates/import/realm.json.j2 index 9bda52a6..1dc4d83c 100644 --- a/roles/web-app-keycloak/templates/import/realm.json.j2 +++ b/roles/web-app-keycloak/templates/import/realm.json.j2 @@ -1654,7 +1654,7 @@ } ], "org.keycloak.storage.UserStorageProvider": [ - {% include "client.json.j2" %} + {% include "ldap.json.j2" %} ], "org.keycloak.keys.KeyProvider": [ { diff --git a/roles/web-app-keycloak/vars/main.yml b/roles/web-app-keycloak/vars/main.yml index 18b03e9d..3c2a8947 100644 --- a/roles/web-app-keycloak/vars/main.yml +++ b/roles/web-app-keycloak/vars/main.yml @@ -10,6 +10,9 @@ KEYCLOAK_REALM: "{{ OIDC.CLIENT.REALM }}" # This is the name KEYCLOAK_REALM_URL: "{{ WEB_PROTOCOL }}://{{ KEYCLOAK_REALM }}" KEYCLOAK_DEBUG_ENABLED: "{{ MODE_DEBUG }}" KEYCLOAK_CLIENT_ID: "{{ OIDC.CLIENT.ID }}" +KEYCLOAK_MASTER_REALM_URL: "{{ KEYCLOAK_SERVER_HOST_URL }}/realms/master" +KEYCLOAK_HOST_IMPORT_DIR: "{{ docker_compose.directories.volumes }}import/" +KEYCLOAK_SERVER_INTERNAL_URL: "http://127.0.0.1:8080" # Credentials KEYCLOAK_ADMIN: "{{ applications | get_app_conf(application_id, 'users.administrator.username') }}" @@ -23,14 +26,12 @@ KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_ KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version ## Server -KEYCLOAK_SERVER_INTERNAL_URL: "http://127.0.0.1:8080" KEYCLOAK_SERVER_HOST: "127.0.0.1:{{ ports.localhost.http[application_id] }}" KEYCLOAK_SERVER_HOST_URL: "http://{{ KEYCLOAK_SERVER_HOST }}" ## Update KEYCLOAK_REDIRECT_FEATURES: ["features.oauth2","features.oidc"] KEYCLOAK_IMPORT_REALM_ENABLED: "{{ applications | get_app_conf(application_id, 'actions.import_realm') }}" # Activate realm import -KEYCLOAK_UPDATE_LDAP_BIND: "{{ applications | get_app_conf(application_id, 'actions.update_ldap_bind') }}" # Toggle the LDAP bind update step KEYCLOAK_FRONTCHANNEL_LOGOUT_URL: "{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}/" KEYCLOAK_REDIRECT_URIS: "{{ domains | redirect_uris(applications, WEB_PROTOCOL, '/*', KEYCLOAK_REDIRECT_FEATURES) }}" KEYCLOAK_WEB_ORIGINS: >- @@ -42,6 +43,7 @@ KEYCLOAK_WEB_ORIGINS: >- KEYCLOAK_POST_LOGOUT_URIS: "+" ## LDAP +KEYCLOAK_LDAP_ENABLED: "{{ applications | get_app_conf(application_id, 'features.ldap', False) }}" KEYCLOAK_LDAP_CMP_NAME: "{{ ldap.server.domain }}" # Name of the LDAP User Federation component in Keycloak (as shown in UI) KEYCLOAK_LDAP_BIND_DN: "{{ ldap.dn.administrator.data }}" KEYCLOAK_LDAP_BIND_PW: "{{ ldap.bind_credential }}" @@ -50,4 +52,14 @@ KEYCLOAK_LDAP_URL: "{{ ldap.server.uri }}" ## API KEYCLOAK_MASTER_API_USER: "{{ applications | get_app_conf(application_id, 'users.administrator') }}" # Master Administrator KEYCLOAK_MASTER_API_USER_NAME: "{{ KEYCLOAK_MASTER_API_USER.username }}" # Master Administrator Username -KEYCLOAK_MASTER_API_USER_PASSWORD: "{{ KEYCLOAK_MASTER_API_USER.password }}" # Master Administrator Password \ No newline at end of file +KEYCLOAK_MASTER_API_USER_PASSWORD: "{{ KEYCLOAK_MASTER_API_USER.password }}" # Master Administrator Password + + +# Dictionaries +KEYCLOAK_DICTIONARY_REALM_RAW: "{{ lookup('template', 'import/realm.json.j2') }}" +KEYCLOAK_DICTIONARY_REALM: >- + {{ + KEYCLOAK_DICTIONARY_REALM_RAW + if (KEYCLOAK_DICTIONARY_REALM_RAW is mapping) + else (KEYCLOAK_DICTIONARY_REALM_RAW | from_json) + }}