feat: add 389ds ldap backend to keycloak

Signed-off-by: Bert-Jan Fikse <bert-jan@whatwedo.ch>
This commit is contained in:
Bert-Jan Fikse 2026-03-13 10:58:40 +01:00
parent 59d0174905
commit 12864a13b0
Signed by: bert-jan
GPG key ID: C1E0AB516AC16D1A
8 changed files with 138 additions and 2 deletions

View file

@ -19,8 +19,14 @@ ds389_root_password: "changeme"
# Instance configuration # Instance configuration
ds389_instance_name: "localhost" ds389_instance_name: "localhost"
ds389_hostname: "{{ ds389_service_name }}"
# Network configuration # Network configuration
ds389_backend_network: "backend" ds389_backend_network: "backend"
ds389_ldap_port: 3389 ds389_ldap_port: 3389
ds389_ldaps_port: 3636 ds389_ldaps_port: 3636
# Base OUs to create after container starts
ds389_base_ous:
- users
- groups

View file

@ -30,3 +30,47 @@
community.docker.docker_compose_v2: community.docker.docker_compose_v2:
project_src: "{{ ds389_docker_compose_dir }}" project_src: "{{ ds389_docker_compose_dir }}"
state: present 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

View file

@ -0,0 +1,7 @@
{% for ou in ds389_base_ous %}
dn: ou={{ ou }},{{ ds389_suffix }}
changetype: add
objectClass: organizationalUnit
ou: {{ ou }}
{% endfor %}

View file

@ -1,6 +1,7 @@
services: services:
{{ ds389_service_name }}: {{ ds389_service_name }}:
image: {{ ds389_image }} image: {{ ds389_image }}
hostname: {{ ds389_hostname }}
restart: unless-stopped restart: unless-stopped
environment: environment:
DS_SUFFIX_NAME: {{ ds389_suffix }} DS_SUFFIX_NAME: {{ ds389_suffix }}

View file

@ -34,6 +34,14 @@ keycloak_log_level: "INFO"
keycloak_proxy_mode: "edge" keycloak_proxy_mode: "edge"
keycloak_gzip_enabled: false # Disable GZIP encoding to avoid MIME type issues 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 # Provisioning configuration
keycloak_provisioning_enabled: false keycloak_provisioning_enabled: false
@ -96,3 +104,26 @@ keycloak_removed_clients: []
keycloak_removed_identity_providers: [] keycloak_removed_identity_providers: []
# - old-idp # - 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

View file

@ -13,6 +13,8 @@
path: "{{ keycloak_docker_volume_dir }}/data" path: "{{ keycloak_docker_volume_dir }}/data"
state: directory state: directory
mode: '0755' mode: '0755'
owner: "1000"
group: "1000"
- name: Create postgres data directory - name: Create postgres data directory
file: file:

View file

@ -30,6 +30,20 @@
loop: "{{ keycloak_removed_identity_providers }}" loop: "{{ keycloak_removed_identity_providers }}"
no_log: true 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 # Cleanup: Remove deleted clients
- name: Remove deleted clients - name: Remove deleted clients
community.general.keycloak_client: community.general.keycloak_client:
@ -86,6 +100,25 @@
loop: "{{ keycloak_groups }}" loop: "{{ keycloak_groups }}"
no_log: true 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 # Create local users
- name: Create local users - name: Create local users
community.general.keycloak_user: community.general.keycloak_user:

View file

@ -33,13 +33,25 @@ services:
KC_PROXY: {{ keycloak_proxy_mode }} KC_PROXY: {{ keycloak_proxy_mode }}
KC_HOSTNAME: {{ keycloak_domain }} KC_HOSTNAME: {{ keycloak_domain }}
KC_HEALTH_ENABLED: "true" 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: depends_on:
- postgres - postgres
volumes: volumes:
- {{ keycloak_docker_volume_dir }}/data:/opt/keycloak/data - {{ keycloak_docker_volume_dir }}/data:/opt/keycloak/data
{% for cert in keycloak_truststore_certificates %}
- {{ cert }}:/opt/keycloak/certs/{{ cert | basename }}:ro
{% endfor %}
networks: networks:
- {{ keycloak_backend_network }} - {{ keycloak_backend_network }}
- {{ keycloak_traefik_network }} - {{ keycloak_traefik_network }}
{% if keycloak_extra_hosts | length > 0 %}
extra_hosts:
{% for host in keycloak_extra_hosts %}
- "{{ host }}"
{% endfor %}
{% endif %}
tmpfs: tmpfs:
- /opt/keycloak/data/tmp:size=1024m - /opt/keycloak/data/tmp:size=1024m
labels: labels: