feat(keycloak): implement SPOT with Realm

Replace 01_import.yml with 01_initialize.yml (KEYCLOAK_HOST_IMPORT_DIR)
Add generic 02_update.yml (kcadm updater for clients/components)
- Resolve ID → read current → merge (kc_merge_path optional)
- Preserve immutable fields; support kc_force_attrs
Update tasks/main.yml:
- Readiness via KEYCLOAK_MASTER_REALM_URL; kcadm login
- Merge LDAP component config from Realm when KEYCLOAK_LDAP_ENABLED
- Update client settings incl. frontchannel.logout.url
realm.json.j2: include ldap.json in UserStorageProvider
ldap.json.j2: use KEYCLOAK_LDAP_* vars for bindDn/credential/connectionUrl
vars/main.yml: add KEYCLOAK_* URLs/dirs and KEYCLOAK_DICTIONARY_REALM(_RAW)
docker-compose.yml.j2: mount KEYCLOAK_HOST_IMPORT_DIR
Cleanup: remove 02_update_client_redirects.yml, 03_update-ldap-bind.yml, 04_ssh_public_key.yml; drop obsolete config flag; formatting

Note: redirectUris/webOrigins ordering may still cause changed=true; consider sorting for stability in a follow-up.
This commit is contained in:
Kevin Veen-Birkenbach 2025-08-17 14:27:33 +02:00
parent 20c8d46f54
commit 7d0502ebc5
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
14 changed files with 182 additions and 327 deletions

View File

@ -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

View File

@ -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) }}"

View File

@ -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) }}"

View File

@ -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

View File

@ -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 }}"

View File

@ -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 }}"

View File

@ -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 }}"

View File

@ -1,3 +1,3 @@
# Todos
- Include 03_update-ldap-bind.yml
- Include 03_update-ldap.yml
- Include 04_ssh_public_key.yml

View File

@ -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
- 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

View File

@ -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 %}

View File

@ -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,

View File

@ -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" ],

View File

@ -1654,7 +1654,7 @@
}
],
"org.keycloak.storage.UserStorageProvider": [
{% include "client.json.j2" %}
{% include "ldap.json.j2" %}
],
"org.keycloak.keys.KeyProvider": [
{

View File

@ -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
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)
}}