feat: add keycloak provisioning tasks

This commit is contained in:
Bert-Jan Fikse 2026-02-27 11:22:08 +01:00
parent 13eb79803f
commit 3fcaebe1a8
Signed by: bert-jan
GPG key ID: C1E0AB516AC16D1A
4 changed files with 242 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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