From 3fcaebe1a88a50946117c655a210372b34a8c864 Mon Sep 17 00:00:00 2001 From: Bert-Jan Fikse Date: Fri, 27 Feb 2026 11:22:08 +0100 Subject: [PATCH] feat: add keycloak provisioning tasks --- roles/keycloak/defaults/main.yml | 63 +++++++ roles/keycloak/tasks/main.yml | 22 +++ roles/keycloak/tasks/provisioning.yml | 156 ++++++++++++++++++ .../keycloak/templates/docker-compose.yml.j2 | 1 + 4 files changed, 242 insertions(+) create mode 100644 roles/keycloak/tasks/provisioning.yml diff --git a/roles/keycloak/defaults/main.yml b/roles/keycloak/defaults/main.yml index 66d0a72..c242ea5 100644 --- a/roles/keycloak/defaults/main.yml +++ b/roles/keycloak/defaults/main.yml @@ -33,3 +33,66 @@ keycloak_use_ssl: true keycloak_log_level: "INFO" keycloak_proxy_mode: "edge" keycloak_gzip_enabled: false # Disable GZIP encoding to avoid MIME type issues + +# Provisioning configuration +keycloak_provisioning_enabled: false + +# Realm configuration +keycloak_realm: "default" +keycloak_realm_display_name: "Default Realm" + +# Auth URL for API access (used by provisioning tasks) +keycloak_auth_url: "{{ 'https' if keycloak_use_ssl else 'http' }}://{{ keycloak_domain }}" + +# Groups to provision +keycloak_groups: [] +# - name: admins +# - name: users + +# Local users to provision +keycloak_local_users: [] +# - username: admin +# first_name: "Admin" +# last_name: "User" +# email: "admin@example.com" +# password: "changeme" +# groups: +# - name: admins + +# OIDC clients to provision +keycloak_oidc_clients: [] +# - client_id: nextcloud +# name: "Nextcloud" +# client_secret: "changeme" +# redirect_uris: +# - "https://nextcloud.example.com/apps/user_oidc/code" +# default_client_scopes: +# - openid +# - email +# - profile + +# Identity providers (e.g., Entra ID, Google) +keycloak_identity_providers: [] +# - alias: entra-id +# display_name: "Login with Microsoft" +# provider_id: oidc +# config: +# clientId: "{{ entra_client_id }}" +# clientSecret: "{{ entra_client_secret }}" +# authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" +# tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token" +# defaultScope: "openid profile email" + +# Resources to remove from Keycloak (cleanup) +# Add names/aliases here when removing from the lists above +keycloak_removed_users: [] +# - olduser + +keycloak_removed_groups: [] +# - oldgroup + +keycloak_removed_clients: [] +# - old-client + +keycloak_removed_identity_providers: [] +# - old-idp diff --git a/roles/keycloak/tasks/main.yml b/roles/keycloak/tasks/main.yml index 05db2ef..f8a0f1e 100644 --- a/roles/keycloak/tasks/main.yml +++ b/roles/keycloak/tasks/main.yml @@ -30,3 +30,25 @@ community.docker.docker_compose_v2: project_src: "{{ keycloak_docker_compose_dir }}" state: present + +- name: Wait for Keycloak health endpoint + uri: + url: "{{ keycloak_auth_url }}/health/ready" + method: GET + status_code: 200 + validate_certs: false + register: keycloak_health + until: keycloak_health.status == 200 + retries: 30 + delay: 10 + delegate_to: localhost + become: false + when: keycloak_provisioning_enabled | bool + +- name: Run Keycloak provisioning + ansible.builtin.include_tasks: provisioning.yml + args: + apply: + become: false + delegate_to: localhost + when: keycloak_provisioning_enabled | bool diff --git a/roles/keycloak/tasks/provisioning.yml b/roles/keycloak/tasks/provisioning.yml new file mode 100644 index 0000000..03ad6df --- /dev/null +++ b/roles/keycloak/tasks/provisioning.yml @@ -0,0 +1,156 @@ +#SPDX-License-Identifier: MIT-0 +--- +# Keycloak provisioning tasks +# Create realm (if not master) +- name: Create Keycloak realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + display_name: "{{ keycloak_realm_display_name }}" + enabled: true + state: present + validate_certs: false + no_log: true + when: keycloak_realm != "master" + +# Cleanup: Remove deleted identity providers +- name: Remove deleted identity providers + community.general.keycloak_identity_provider: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + alias: "{{ item }}" + state: absent + validate_certs: false + loop: "{{ keycloak_removed_identity_providers }}" + no_log: true + +# Cleanup: Remove deleted clients +- name: Remove deleted clients + community.general.keycloak_client: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + client_id: "{{ item }}" + state: absent + validate_certs: false + loop: "{{ keycloak_removed_clients }}" + no_log: true + +# Cleanup: Remove deleted users +- name: Remove deleted users + community.general.keycloak_user: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + username: "{{ item }}" + state: absent + validate_certs: false + loop: "{{ keycloak_removed_users }}" + no_log: true + +# Cleanup: Remove deleted groups +- name: Remove deleted groups + community.general.keycloak_group: + 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_groups }}" + no_log: true + +# Create groups +- name: Create groups + community.general.keycloak_group: + 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 }}" + state: present + validate_certs: false + loop: "{{ keycloak_groups }}" + no_log: true + +# Create local users +- name: Create local users + community.general.keycloak_user: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + username: "{{ item.username }}" + first_name: "{{ item.first_name | default(omit) }}" + last_name: "{{ item.last_name | default(omit) }}" + email: "{{ item.email | default(omit) }}" + enabled: "{{ item.enabled | default(true) }}" + email_verified: "{{ item.email_verified | default(true) }}" + credentials: + - type: password + value: "{{ item.password }}" + temporary: false + groups: "{{ item.groups | default([]) }}" + state: present + validate_certs: false + loop: "{{ keycloak_local_users }}" + no_log: true + +# Create OIDC clients +- name: Create OIDC clients + community.general.keycloak_client: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + client_id: "{{ item.client_id }}" + name: "{{ item.name | default(item.client_id) }}" + enabled: true + client_authenticator_type: client-secret + secret: "{{ item.client_secret }}" + redirect_uris: "{{ item.redirect_uris | default([]) }}" + web_origins: "{{ item.web_origins | default(['+']) }}" + standard_flow_enabled: true + implicit_flow_enabled: false + direct_access_grants_enabled: "{{ item.direct_access_grants_enabled | default(false) }}" + protocol: openid-connect + default_client_scopes: "{{ item.default_client_scopes | default(['openid', 'email', 'profile']) }}" + state: present + validate_certs: false + loop: "{{ keycloak_oidc_clients }}" + no_log: true + +# Create identity providers +- name: Create identity providers + community.general.keycloak_identity_provider: + auth_keycloak_url: "{{ keycloak_auth_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + alias: "{{ item.alias }}" + display_name: "{{ item.display_name | default(item.alias) }}" + provider_id: "{{ item.provider_id }}" + enabled: "{{ item.enabled | default(true) }}" + trust_email: "{{ item.trust_email | default(true) }}" + first_broker_login_flow_alias: "{{ item.first_broker_login_flow_alias | default('first broker login') }}" + config: "{{ item.config }}" + state: present + validate_certs: false + loop: "{{ keycloak_identity_providers }}" + no_log: true \ No newline at end of file diff --git a/roles/keycloak/templates/docker-compose.yml.j2 b/roles/keycloak/templates/docker-compose.yml.j2 index a91f746..2708f37 100644 --- a/roles/keycloak/templates/docker-compose.yml.j2 +++ b/roles/keycloak/templates/docker-compose.yml.j2 @@ -32,6 +32,7 @@ services: KC_SPI_RESOURCE_ENCODING_GZIP_CACHE_DIR: /opt/keycloak/data/gzip-cache KC_PROXY: {{ keycloak_proxy_mode }} KC_HOSTNAME: {{ keycloak_domain }} + KC_HEALTH_ENABLED: "true" depends_on: - postgres volumes: