From 12864a13b09f2f913012fdc8dfa44b1038772382 Mon Sep 17 00:00:00 2001 From: Bert-Jan Fikse Date: Fri, 13 Mar 2026 10:58:40 +0100 Subject: [PATCH] feat: add 389ds ldap backend to keycloak Signed-off-by: Bert-Jan Fikse --- roles/389ds/defaults/main.yml | 8 +++- roles/389ds/tasks/main.yml | 46 ++++++++++++++++++- roles/389ds/templates/base-ous.ldif.j2 | 7 +++ roles/389ds/templates/docker-compose.yml.j2 | 1 + roles/keycloak/defaults/main.yml | 31 +++++++++++++ roles/keycloak/tasks/main.yml | 2 + roles/keycloak/tasks/provisioning.yml | 33 +++++++++++++ .../keycloak/templates/docker-compose.yml.j2 | 12 +++++ 8 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 roles/389ds/templates/base-ous.ldif.j2 diff --git a/roles/389ds/defaults/main.yml b/roles/389ds/defaults/main.yml index 00bf5d1..82890ad 100644 --- a/roles/389ds/defaults/main.yml +++ b/roles/389ds/defaults/main.yml @@ -19,8 +19,14 @@ ds389_root_password: "changeme" # Instance configuration ds389_instance_name: "localhost" +ds389_hostname: "{{ ds389_service_name }}" # Network configuration ds389_backend_network: "backend" ds389_ldap_port: 3389 -ds389_ldaps_port: 3636 \ No newline at end of file +ds389_ldaps_port: 3636 + +# Base OUs to create after container starts +ds389_base_ous: + - users + - groups \ No newline at end of file diff --git a/roles/389ds/tasks/main.yml b/roles/389ds/tasks/main.yml index 277d2dd..117f12b 100644 --- a/roles/389ds/tasks/main.yml +++ b/roles/389ds/tasks/main.yml @@ -29,4 +29,48 @@ - name: Start 389ds container community.docker.docker_compose_v2: project_src: "{{ ds389_docker_compose_dir }}" - state: present \ No newline at end of file + state: present + +- name: Wait for LDAP to be ready + shell: > + docker compose -f {{ ds389_docker_compose_dir }}/docker-compose.yml + exec -T {{ ds389_service_name }} ldapsearch -H ldap://localhost:3389 -x + -D "{{ ds389_root_dn }}" -w "{{ ds389_root_password }}" + -b "" -s base "(objectClass=*)" + register: ds389_ldap_ready + retries: 30 + delay: 2 + until: ds389_ldap_ready.rc == 0 + changed_when: false + no_log: true + +- name: Ensure backend and suffix exist + shell: > + docker compose -f {{ ds389_docker_compose_dir }}/docker-compose.yml + exec -T {{ ds389_service_name }} dsconf localhost backend create + --suffix "{{ ds389_suffix }}" --be-name userroot --create-suffix + register: ds389_backend_result + failed_when: + - ds389_backend_result.rc != 0 + - "'already exists' not in ds389_backend_result.stderr" + - "'suffix exists' not in ds389_backend_result.stderr" + changed_when: ds389_backend_result.rc == 0 + +- name: Template base OUs LDIF + template: + src: base-ous.ldif.j2 + dest: "{{ ds389_docker_volume_dir }}/data/base-ous.ldif" + mode: '0644' + +- name: Apply base OUs LDIF + shell: > + docker compose -f {{ ds389_docker_compose_dir }}/docker-compose.yml + exec -T {{ ds389_service_name }} ldapadd -H ldap://localhost:3389 -x + -D "{{ ds389_root_dn }}" -w "{{ ds389_root_password }}" + -f /data/base-ous.ldif + register: ds389_ldapadd_result + failed_when: + - ds389_ldapadd_result.rc != 0 + - "'Already exists' not in ds389_ldapadd_result.stderr" + changed_when: "'Already exists' not in ds389_ldapadd_result.stderr" + no_log: true \ No newline at end of file diff --git a/roles/389ds/templates/base-ous.ldif.j2 b/roles/389ds/templates/base-ous.ldif.j2 new file mode 100644 index 0000000..8cccaa9 --- /dev/null +++ b/roles/389ds/templates/base-ous.ldif.j2 @@ -0,0 +1,7 @@ +{% for ou in ds389_base_ous %} +dn: ou={{ ou }},{{ ds389_suffix }} +changetype: add +objectClass: organizationalUnit +ou: {{ ou }} + +{% endfor %} diff --git a/roles/389ds/templates/docker-compose.yml.j2 b/roles/389ds/templates/docker-compose.yml.j2 index a8842e7..7e0c7c0 100644 --- a/roles/389ds/templates/docker-compose.yml.j2 +++ b/roles/389ds/templates/docker-compose.yml.j2 @@ -1,6 +1,7 @@ services: {{ ds389_service_name }}: image: {{ ds389_image }} + hostname: {{ ds389_hostname }} restart: unless-stopped environment: DS_SUFFIX_NAME: {{ ds389_suffix }} diff --git a/roles/keycloak/defaults/main.yml b/roles/keycloak/defaults/main.yml index c242ea5..df29e65 100644 --- a/roles/keycloak/defaults/main.yml +++ b/roles/keycloak/defaults/main.yml @@ -34,6 +34,14 @@ keycloak_log_level: "INFO" keycloak_proxy_mode: "edge" keycloak_gzip_enabled: false # Disable GZIP encoding to avoid MIME type issues +# Extra CA certificates to trust (host paths to PEM files) +keycloak_truststore_certificates: [] +# - /srv/data/389ds/data/ssca/ca.crt + +# Extra /etc/hosts entries for the Keycloak container +keycloak_extra_hosts: [] +# - "ldap:192.168.56.11" + # Provisioning configuration keycloak_provisioning_enabled: false @@ -96,3 +104,26 @@ keycloak_removed_clients: [] keycloak_removed_identity_providers: [] # - old-idp + +# LDAP user federations +keycloak_user_federations: [] +# - name: ldap-389ds +# provider_id: ldap +# config: +# editMode: WRITABLE +# syncRegistrations: "true" +# importEnabled: "true" +# vendor: rhds +# connectionUrl: "ldaps://ldap.example.com:636" +# usersDn: "ou=users,dc=example,dc=com" +# bindDn: "cn=Directory Manager" +# bindCredential: "changeme" +# usernameLDAPAttribute: uid +# rdnLDAPAttribute: uid +# uuidLDAPAttribute: nsuniqueid +# userObjectClasses: "inetOrgPerson, organizationalPerson" +# authType: simple +# useTruststoreSpi: never + +keycloak_removed_user_federations: [] +# - old-federation diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index f8a0f1e..33374a5 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -13,6 +13,8 @@ path: "{{ keycloak_docker_volume_dir }}/data" state: directory mode: '0755' + owner: "1000" + group: "1000" - name: Create postgres data directory file: diff --git a/roles/keycloak/tasks/provisioning.yml b/roles/keycloak/tasks/provisioning.yml index 03ad6df..97b53b2 100644 --- a/roles/keycloak/tasks/provisioning.yml +++ b/roles/keycloak/tasks/provisioning.yml @@ -30,6 +30,20 @@ loop: "{{ keycloak_removed_identity_providers }}" no_log: true +# Cleanup: Remove deleted user federations +- name: Remove deleted user federations + community.general.keycloak_user_federation: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + name: "{{ item }}" + state: absent + validate_certs: false + loop: "{{ keycloak_removed_user_federations }}" + no_log: true + # Cleanup: Remove deleted clients - name: Remove deleted clients community.general.keycloak_client: @@ -86,6 +100,25 @@ loop: "{{ keycloak_groups }}" no_log: true +# Create user federations (LDAP) +- name: Create user federations + community.general.keycloak_user_federation: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + name: "{{ item.name }}" + provider_id: "{{ item.provider_id }}" + provider_type: org.keycloak.storage.UserStorageProvider + config: "{{ item.config }}" + mappers: "{{ item.mappers | default(omit) }}" + bind_credential_update_mode: only_indirect + state: present + validate_certs: false + loop: "{{ keycloak_user_federations }}" + no_log: true + # Create local users - name: Create local users community.general.keycloak_user: diff --git a/roles/keycloak/templates/docker-compose.yml.j2 b/roles/keycloak/templates/docker-compose.yml.j2 index 2708f37..e08a2c7 100644 --- a/roles/keycloak/templates/docker-compose.yml.j2 +++ b/roles/keycloak/templates/docker-compose.yml.j2 @@ -33,13 +33,25 @@ services: KC_PROXY: {{ keycloak_proxy_mode }} KC_HOSTNAME: {{ keycloak_domain }} KC_HEALTH_ENABLED: "true" +{% if keycloak_truststore_certificates | length > 0 %} + KC_TRUSTSTORE_PATHS: "{{ keycloak_truststore_certificates | map('regex_replace', '^.*/(.*)$', '/opt/keycloak/certs/\\1') | join(',') }}" +{% endif %} depends_on: - postgres volumes: - {{ keycloak_docker_volume_dir }}/data:/opt/keycloak/data +{% for cert in keycloak_truststore_certificates %} + - {{ cert }}:/opt/keycloak/certs/{{ cert | basename }}:ro +{% endfor %} networks: - {{ keycloak_backend_network }} - {{ keycloak_traefik_network }} +{% if keycloak_extra_hosts | length > 0 %} + extra_hosts: +{% for host in keycloak_extra_hosts %} + - "{{ host }}" +{% endfor %} +{% endif %} tmpfs: - /opt/keycloak/data/tmp:size=1024m labels: