Vendor middleware_automation.keycloak collection

Needed for keycloak_authentication_v2, to manage Keycloak
authentication flows (e.g. the CCCHH browser flow) as YAML.
Chosen over community.general's own copy of the module (deprecated in
favor of this collection, and limited to 4 sub-flow levels there vs.
10 here).
This commit is contained in:
Stefan Bethke 2026-09-02 10:46:57 +02:00
commit 4832e5da5d
159 changed files with 34399 additions and 0 deletions

View file

@ -0,0 +1,16 @@
---
- name: Save ansible custom facts
become: "{{ keycloak_quarkus_bootstrapped_require_privilege_escalation | default(true) }}"
ansible.builtin.template:
src: keycloak.fact.j2
dest: /etc/ansible/facts.d/keycloak.fact
mode: '0644'
vars:
bootstrapped: true
- name: Refresh custom facts
ansible.builtin.setup:
filter: ansible_local
- name: Ensure that `KEYCLOAK_ADMIN[_PASSWORD]` get purged
ansible.builtin.include_tasks: systemd.yml

View file

@ -0,0 +1,52 @@
---
- name: "Initialize configuration key store variables to be written"
ansible.builtin.set_fact:
keycloak_quarkus_store_items:
- key: "kc.db-password"
value: "{{ keycloak_quarkus_db_pass }}"
- name: "Initialize empty configuration key store"
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation | default(true) }}"
# keytool doesn't allow creating an empty key store, so this is a hacky way around it
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
set -o nounset # abort on unbound variable
set -o pipefail # do not hide errors within pipes
set -o errexit # abort on nonzero exit status
echo dummy | keytool -noprompt -importpass -alias dummy -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }} -storetype PKCS12
keytool -delete -alias dummy -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
args:
creates: "{{ keycloak_quarkus_config_key_store_file }}"
- name: "Set configuration key store using keytool"
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
set -o nounset # abort on unbound variable
set -o pipefail # do not hide errors within pipes
keytool -list -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
retVal=$?
set -o errexit # abort on nonzero exit status
if [ $retVal -eq 0 ]; then
# value is already in keystore, but keytool has no replace function: delete and re-create instead
# note that we can not read whether the value has changed either[^1], so we need to override it
# [^1]: https://stackoverflow.com/a/37491400
keytool -delete -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
fi
echo {{ item.value | quote }} | keytool -noprompt -importpass -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }} -storetype PKCS12
loop: "{{ keycloak_quarkus_store_items }}"
no_log: true
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation | default(true) }}"
changed_when: true
notify:
- restart keycloak
- name: "Set owner of configuration key store {{ keycloak_quarkus_config_key_store_file }}"
ansible.builtin.file:
path: "{{ keycloak_quarkus_config_key_store_file }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0400'
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation | default(true) }}"

View file

@ -0,0 +1,10 @@
---
- name: Include firewall config tasks
ansible.builtin.include_tasks:
file: iptables.yml
apply:
tags:
- firewall
when: keycloak_quarkus_configure_iptables
tags:
- firewall

View file

@ -0,0 +1,162 @@
---
- name: Check deprecation keycloak_quarkus_key_store -> keycloak_quarkus_http_key_store
delegate_to: localhost
run_once: true
when:
- keycloak_quarkus_https_key_store_enabled
block:
- name: Ensure backward compatibility for `keycloak_quarkus_key_store_file`, superseded by `keycloak_quarkus_https_key_store_file`
when:
- keycloak_quarkus_key_store_file is defined
- keycloak_quarkus_key_store_file != ''
- keycloak_quarkus_https_key_store_file == keycloak.home + "/conf/key_store.p12" # default value
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_https_key_store_file: "{{ keycloak_quarkus_key_store_file }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_key_store_file" # read in deprecation handler
notify:
- print deprecation warning
- name: Flush handlers
ansible.builtin.meta: flush_handlers
- name: Ensure backward compatibility for `keycloak_quarkus_key_store_password`, superseded by `keycloak_quarkus_https_key_store_password`
when:
- keycloak_quarkus_key_store_password is defined
- keycloak_quarkus_key_store_password != ''
- keycloak_quarkus_https_key_store_password == "" # default value
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_https_key_store_password: "{{ keycloak_quarkus_key_store_password }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_key_store_password" # read in deprecation handler
notify:
- print deprecation warning
- name: Flush handlers
ansible.builtin.meta: flush_handlers
# https://access.redhat.com/documentation/en-us/red_hat_build_of_keycloak/24.0/html-single/upgrading_guide/index#deprecated_literal_proxy_literal_option
- name: Check deprecation of keycloak_quarkus_proxy_mode
when:
- keycloak_quarkus_proxy_mode is defined
- keycloak_quarkus_proxy_headers is defined and keycloak_quarkus_proxy_headers | length == 0
- keycloak_quarkus_version.split('.') | first | int >= 24
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_proxy_mode" # read in deprecation handler
notify:
- print deprecation warning
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
- name: Check deprecation of keycloak_quarkus_frontend_url -> keycloak_quarkus_hostname
when:
- keycloak_quarkus_hostname is not defined
- keycloak_quarkus_frontend_url is defined
- keycloak_quarkus_frontend_url != ''
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_hostname: "{{ keycloak_quarkus_frontend_url }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_frontend_url" # read in deprecation handler
notify:
- print deprecation warning
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
- name: Check deprecation of keycloak_quarkus_hostname_strict_https + keycloak_quarkus_host + keycloak_quarkus_port + keycloak_quarkus_path -> keycloak_quarkus_hostname
when:
- keycloak_quarkus_hostname is not defined
- keycloak_quarkus_hostname_strict_https is defined or keycloak_quarkus_frontend_url is defined or keycloak_quarkus_port is defined or keycloak_quarkus_path is defined
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_hostname: >-
{% set protocol = '' %}
{% if keycloak_quarkus_hostname_strict_https %}
{% set protocol = 'https://' %}
{% elif keycloak_quarkus_hostname_strict_https is defined and keycloak_quarkus_hostname_strict_https is False %}
{% set protocol = 'http://' %}
{% endif %}
{{ protocol }}{{ keycloak_quarkus_host }}:{{ keycloak_quarkus_port }}/{{ keycloak_quarkus_path }}
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_hostname_strict_https or keycloak_quarkus_frontend_url or keycloak_quarkus_frontend_url or keycloak_quarkus_hostname" # read in deprecation handler
notify:
- print deprecation warning
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
- name: Check deprecation of keycloak_quarkus_admin_url -> keycloak_quarkus_hostname_admin
when:
- keycloak_quarkus_hostname_admin is not defined
- keycloak_quarkus_admin_url is defined
- keycloak_quarkus_admin_url != ''
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_hostname_admin: "{{ keycloak_quarkus_admin_url }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_url" # read in deprecation handler
notify:
- print deprecation warning
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
- name: Check deprecation of keycloak_quarkus_hostname_strict_backchannel -> keycloak_quarkus_hostname_backchannel_dynamic
when:
- keycloak_quarkus_hostname_backchannel_dynamic is not defined
- keycloak_quarkus_hostname_strict_backchannel is defined
- keycloak_quarkus_hostname_strict_backchannel != ''
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_hostname_backchannel_dynamic: "{{ keycloak_quarkus_hostname_strict_backchannel == False }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_hostname_backchannel_dynamic" # read in deprecation handler
notify:
- print deprecation warning
# https://github.com/keycloak/keycloak/issues/30009
- name: Check deprecation of keycloak_quarkus_admin_user -> keycloak_quarkus_bootstrap_admin_user
when:
- keycloak_quarkus_bootstrap_admin_user is not defined
- keycloak_quarkus_admin_user is defined
- keycloak_quarkus_admin_user != ''
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_user }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_user" # read in deprecation handler
notify:
- print deprecation warning
# https://github.com/keycloak/keycloak/issues/30009
- name: Check deprecation of keycloak_quarkus_admin_pass -> keycloak_quarkus_bootstrap_admin_password
when:
- keycloak_quarkus_bootstrap_admin_password is not defined
- keycloak_quarkus_admin_pass is defined
- keycloak_quarkus_admin_pass != ''
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_pass }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_admin_pass" # read in deprecation handler
notify:
- print deprecation warning
- name: Check deprecation of keycloak_quarkus_bind_address -> keycloak_quarkus_http_host
when:
- keycloak_quarkus_bind_address is defined
- keycloak_quarkus_bind_address != '0.0.0.0'
delegate_to: localhost
run_once: true
changed_when: keycloak_quarkus_show_deprecation_warnings
ansible.builtin.set_fact:
keycloak_quarkus_http_host: "{{ keycloak_quarkus_bind_address }}"
keycloak_quarkus_deprecated_variable: "keycloak_quarkus_bind_address" # read in deprecation handler
notify:
- print deprecation warning
- name: Flush handlers
ansible.builtin.meta: flush_handlers

View file

@ -0,0 +1,31 @@
---
- name: "Check if packages are already installed" # noqa command-instead-of-module this runs faster
ansible.builtin.command: "rpm -q {{ packages_list | join(' ') }}"
register: keycloak_quarkus_rpm_info
changed_when: false
failed_when: false
when: ansible_facts.os_family == "RedHat"
- name: "Add missing packages to the yum install list"
ansible.builtin.set_fact:
keycloak_quarkus_packages_to_install: "{{ keycloak_quarkus_packages_to_install | default([]) + keycloak_quarkus_rpm_info.stdout_lines | \
map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ keycloak_quarkus_packages_to_install }}"
become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation | default(true) }}"
ansible.builtin.dnf:
name: "{{ keycloak_quarkus_packages_to_install }}"
state: present
when:
- keycloak_quarkus_packages_to_install | default([]) | length > 0
- ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}"
become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation | default(true) }}"
ansible.builtin.package:
name: "{{ packages_list }}"
state: present
when:
- packages_list | default([]) | length > 0
- ansible_facts.os_family == "Debian"

View file

@ -0,0 +1,36 @@
---
- name: Ensure required package firewalld are installed
ansible.builtin.include_tasks: fastpackages.yml
vars:
packages_list:
- firewalld
- name: Enable and start the firewalld service
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}"
ansible.builtin.systemd:
name: firewalld
enabled: true
state: started
- name: "Configure firewall for http port: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- "{{ keycloak_quarkus_http_port }}/tcp"
when: keycloak_quarkus_http_enabled | bool
- name: "Configure firewall for ports: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation | default(true) }}"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- "{{ keycloak_quarkus_https_port }}/tcp"
- "{{ keycloak_quarkus_http_management_port }}/tcp"
- "{{ keycloak_quarkus_jgroups_port }}/tcp"

View file

@ -0,0 +1,319 @@
---
- name: Validate parameters
ansible.builtin.assert:
that:
- keycloak.home is defined
- keycloak_quarkus_service_user is defined
- keycloak_quarkus_dest is defined
- keycloak_quarkus_archive is defined
- keycloak_quarkus_download_url is defined
- keycloak_quarkus_version is defined
- keycloak_quarkus_local_path is defined
quiet: true
- name: Check for an existing deployment
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat:
path: "{{ keycloak.home }}"
register: keycloak_quarkus_existing_deploy
- name: Stop and restart if existing deployment exists and install forced
when: keycloak_quarkus_existing_deploy.stat.exists and keycloak_quarkus_force_install | bool
block:
- name: "Stop the old service: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
failed_when: false
ansible.builtin.systemd:
name: keycloak
state: stopped
- name: "Remove the old deployment: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file:
path: "{{ keycloak_quarkus_home }}"
state: absent
- name: Check for an existing deployment after possible forced removal
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat:
path: "{{ keycloak_quarkus_home }}"
register: keycloak_quarkus_existing_deploy
- name: "Create service user/group: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.user:
name: "{{ keycloak.service_user }}"
home: /opt/keycloak
system: true
create_home: false
- name: "Create install location: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file:
dest: "{{ keycloak_quarkus_dest }}"
state: directory
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0750'
- name: Create directory for ansible custom facts
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.file:
state: directory
recurse: true
path: /etc/ansible/facts.d
## check remote archive
- name: Set download archive path
ansible.builtin.set_fact:
keycloak_quarkus_archive_full_path: "{{ keycloak_quarkus_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
ansible.builtin.stat:
path: "{{ keycloak_quarkus_archive_full_path }}"
register: keycloak_quarkus_archive_path
## download to controller
- name: Download keycloak archive
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_quarkus_download_url }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640'
url_username: "{{ keycloak_quarkus_binary_download_user | default(omit) }}"
url_password: "{{ keycloak_quarkus_binary_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
delegate_to: localhost
become: false
run_once: true
when:
- keycloak_quarkus_archive_path is defined
- keycloak_quarkus_archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists
- not keycloak.offline_install
- not rhbk_enable is defined or not rhbk_enable
- name: Perform download from RHN using the Unified Downloads API
delegate_to: localhost
run_once: true
when:
- keycloak_quarkus_archive_path is defined
- keycloak_quarkus_archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists
- rhbk_enable is defined and rhbk_enable
- not keycloak.offline_install
- keycloak_quarkus_alternate_download_url is undefined
block:
- name: Retrieve product download using Unified Downloads API
middleware_automation.common.product_search:
client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}"
product_type: DISTRIBUTION
product_version: "{{ rhbk_version }}"
product_category: "{{ rhbk_product_category }}"
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
register: keycloak_quarkus_rhn_products
no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost
run_once: true
- name: Determine install zipfile from search results
ansible.builtin.set_fact:
keycloak_quarkus_rhn_filtered_products: "{{ keycloak_quarkus_rhn_products.results | selectattr('file_name', 'match', '[^/]*/' + rhbk_archive + '$') }}"
delegate_to: localhost
run_once: true
- name: Download Red Hat Build of Keycloak
middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user
client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}"
product_id: "{{ (keycloak_quarkus_rhn_filtered_products | first).id }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640'
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost
run_once: true
become: false
- name: Perform download of RHBK from alternate download location
delegate_to: localhost
run_once: true
become: false
when:
- keycloak_quarkus_archive_path is defined
- keycloak_quarkus_archive_path.stat is defined
- not keycloak_quarkus_archive_path.stat.exists
- rhbk_enable is defined and rhbk_enable
- not keycloak.offline_install
- keycloak_quarkus_alternate_download_url is defined
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_quarkus_alternate_download_url }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0640'
url_username: "{{ keycloak_quarkus_download_user | default(omit) }}"
url_password: "{{ keycloak_quarkus_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
- name: Check downloaded archive
ansible.builtin.stat:
path: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
register: keycloak_quarkus_local_archive_path
delegate_to: localhost
become: false
run_once: true
## copy and unpack
- name: Copy archive to target nodes
ansible.builtin.copy:
src: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
dest: "{{ keycloak_quarkus_archive_full_path }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
register: keycloak_quarkus_new_version_downloaded
when:
- not keycloak_quarkus_archive_path.stat.exists
- keycloak_quarkus_local_archive_path.stat is defined
- keycloak_quarkus_local_archive_path.stat.exists
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
- name: "Check target directory /bin/: {{ keycloak.home }}"
ansible.builtin.stat:
path: "{{ keycloak.home }}/bin/"
register: keycloak_quarkus_path_to_workdir
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
- name: "Extract Keycloak archive on target" # noqa no-handler need to run this here
ansible.builtin.unarchive:
remote_src: true
src: "{{ keycloak_quarkus_archive_full_path }}"
dest: "{{ keycloak_quarkus_dest }}"
creates: "{{ keycloak.home }}/bin/"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
when:
- (not keycloak_quarkus_path_to_workdir.stat.exists) or keycloak_quarkus_new_version_downloaded.changed
notify:
- restart keycloak
- name: Inform decompression was not executed
ansible.builtin.debug:
msg: "{{ keycloak.home }} already exists and version unchanged, skipping decompression"
when:
- (not keycloak_quarkus_new_version_downloaded.changed) and keycloak_quarkus_path_to_workdir.stat.exists
- name: "Copy private key to target"
ansible.builtin.copy:
content: "{{ keycloak_quarkus_key_content }}"
dest: "{{ keycloak_quarkus_key_file }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
when:
- keycloak_quarkus_https_key_file_enabled is defined and keycloak_quarkus_https_key_file_enabled
- keycloak_quarkus_key_file_copy_enabled is defined and keycloak_quarkus_key_file_copy_enabled
- keycloak_quarkus_key_content | length > 0
- name: "Copy certificate to target"
ansible.builtin.copy:
src: "{{ keycloak_quarkus_cert_file_src }}"
dest: "{{ keycloak_quarkus_cert_file }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0644'
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
when:
- keycloak_quarkus_https_key_file_enabled is defined and keycloak_quarkus_https_key_file_enabled
- keycloak_quarkus_cert_file_copy_enabled is defined and keycloak_quarkus_cert_file_copy_enabled
- keycloak_quarkus_cert_file_src | length > 0
- name: "Install JDBC driver: {{ keycloak_quarkus_db_engine }}"
ansible.builtin.include_tasks: jdbc_driver.yml
when:
- rhbk_enable is defined and rhbk_enable
- keycloak_quarkus_default_jdbc[keycloak_quarkus_db_engine].driver_jar_url is defined
- name: "Download custom providers via http"
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
checksum: "{{ item.checksum | default(omit) }}"
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.url is defined and item.url | length > 0
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
# this requires the `lxml` package to be installed; we redirect this step to localhost such that we do need to install it on the remote hosts
- name: "Download custom providers to localhost using maven"
middleware_automation.common.maven_artifact:
repository_url: "{{ item.maven.repository_url }}"
group_id: "{{ item.maven.group_id }}"
artifact_id: "{{ item.maven.artifact_id }}"
version: "{{ item.maven.version | default(omit) }}"
username: "{{ item.maven.username | default(omit) }}"
password: "{{ item.maven.password | default(omit) }}"
dest: "{{ keycloak_quarkus_local_path.stat.path }}/{{ item.id }}.jar"
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
delegate_to: "localhost"
run_once: true
loop: "{{ keycloak_quarkus_providers }}"
when: item.maven is defined
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
- name: "Copy maven providers"
ansible.builtin.copy:
src: "{{ keycloak_quarkus_local_path.stat.path }}/{{ item.id }}.jar"
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
checksum: "{{ item.checksum | default(omit) }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.maven is defined
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
- name: "Copy local providers"
ansible.builtin.copy:
src: "{{ item.local_path }}"
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
remote_src: "{{ item.remote | default(false) }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.local_path is defined
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
- name: Ensure required folder structure for policies exists
ansible.builtin.file:
path: "{{ keycloak.home }}/data/{{ item | lower }}"
state: directory
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0750'
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_supported_policy_types }}"
- name: "Install custom policies"
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "{{ keycloak.home }}/data/{{ item.type | default(keycloak_quarkus_supported_policy_types | first) | lower }}/{{ item.name }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
validate_certs: "{{ keycloak_quarkus_download_validate_certs | default(omit) }}"
become: "{{ keycloak_quarkus_install_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_policies }}"
when: item.url is defined and item.url | length > 0
notify: "restart keycloak"

View file

@ -0,0 +1,11 @@
---
# From https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/24.0/html/server_developer_guide/themes#creating_a_theme:
# If you want to manually delete the content of the themes cache,
# you can do so by deleting the data/tmp/kc-gzip-cache directory of the server distribution
# It can be useful for instance if you redeployed custom providers or custom themes without
# disabling themes caching in the previous server executions.
- name: "Delete theme cache directory: {{ keycloak.service_name }}"
ansible.builtin.file:
path: "{{ keycloak.home }}/data/tmp/kc-gzip-cache"
state: absent
become: "{{ keycloak_quarkus_invalidate_theme_cache_require_privilege_escalation | default(true) }}"

View file

@ -0,0 +1,20 @@
---
- name: Ensure required package iptables are installed
ansible.builtin.include_tasks: fastpackages.yml
vars:
packages_list:
- iptables
- name: "Configure firewall ports for {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_iptables_require_privilege_escalation | default(true) }}"
ansible.builtin.iptables:
destination_port: "{{ item }}"
action: "insert"
rule_num: 6 # magic number I forget why
chain: "INPUT"
policy: "ACCEPT"
protocol: tcp
loop:
- "{{ keycloak_quarkus_http_port }}"
- "{{ keycloak_quarkus_https_port }}"
- "{{ keycloak_quarkus_jgroups_port }}"

View file

@ -0,0 +1,22 @@
---
- name: "Verify valid parameters for download credentials when specified"
ansible.builtin.fail:
msg: >-
When JDBC driver download credentials are set, both the username and the password MUST be set
when: >
(keycloak_quarkus_jdbc_download_user is undefined and keycloak_quarkus_jdbc_download_pass is not undefined) or
(keycloak_quarkus_jdbc_download_pass is undefined and keycloak_quarkus_jdbc_download_user is not undefined)
- name: "Retrieve JDBC Driver from {{ keycloak_jdbc_download_url | default(keycloak_quarkus_default_jdbc[keycloak_quarkus_db_engine].driver_jar_url) }}"
ansible.builtin.get_url:
url: "{{ keycloak_quarkus_jdbc_download_url | default(keycloak_quarkus_default_jdbc[keycloak_quarkus_db_engine].driver_jar_url) }}"
dest: "{{ keycloak.home }}/providers"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
url_username: "{{ keycloak_quarkus_jdbc_download_user | default(omit) }}"
url_password: "{{ keycloak_quarkus_jdbc_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_quarkus_jdbc_download_validate_certs | default(omit) }}"
mode: '0640'
become: "{{ keycloak_quarkus_jdbc_driver_require_privilege_escalation | default(true) }}"
notify:
- restart keycloak

View file

@ -0,0 +1,139 @@
---
# tasks file for keycloak
- name: Check prerequisites
ansible.builtin.include_tasks:
file: prereqs.yml
apply:
tags:
- prereqs
tags:
- prereqs
- always
- name: Check for deprecations
ansible.builtin.include_tasks:
file: deprecations.yml
apply:
tags:
- always
tags:
- always
- name: Distro specific tasks
ansible.builtin.include_tasks:
file: "{{ ansible_os_family | lower }}.yml"
apply:
tags:
- unbound
tags:
- unbound
- name: Include install tasks
ansible.builtin.include_tasks:
file: install.yml
apply:
tags:
- install
tags:
- install
- name: Include systemd tasks
ansible.builtin.include_tasks:
file: systemd.yml
apply:
tags:
- systemd
tags:
- systemd
- name: Include configuration key store tasks
ansible.builtin.include_tasks:
file: config_store.yml
apply:
tags:
- install
when: keycloak.config_key_store_enabled
tags:
- install
- name: Create tcpping cluster node list
ansible.builtin.set_fact:
keycloak_quarkus_cluster_nodes: >
{{ keycloak_quarkus_cluster_nodes | default([]) + [
{
"name": item,
"address": 'jgroups-' + item,
"inventory_host": hostvars[item].keycloak_quarkus_jgroups_ip | default(item) + '[' + (keycloak_quarkus_jgroups_port | string) + ']',
"value": hostvars[item].keycloak_quarkus_jgroups_ip | default(item)
}
] }}
loop: "{{ ansible_play_batch }}"
when: keycloak_quarkus_ha_enabled and keycloak_quarkus_ha_discovery == 'TCPPING'
- name: Determine the config files
ansible.builtin.set_fact:
keycloak_quarkus_config_files: "{{ ['keycloak.conf', 'quarkus.properties'] + (keycloak_quarkus_cache_managed_infinispan_config | ternary([keycloak_quarkus_cache_infinispan_template], [])) }}"
- name: "Configure config files for keycloak service"
ansible.builtin.template:
src: "{{ item }}.j2"
dest: "{{ keycloak.home }}/conf/{{ item }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
become: "{{ keycloak_quarkus_require_privilege_escalation | default(true) }}"
loop: "{{ keycloak_quarkus_config_files }}"
notify:
- rebuild keycloak config
- restart keycloak
- name: Ensure logdirectory exists
ansible.builtin.file:
state: directory
path: "{{ keycloak.log.file | dirname }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0775'
become: "{{ keycloak_quarkus_require_privilege_escalation | default(true) }}"
- name: Ensure tmp-directory exists
ansible.builtin.file:
state: directory
path: "{{ keycloak.home }}/data/tmp"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0755'
become: "{{ keycloak_quarkus_require_privilege_escalation | default(true) }}"
- name: Flush pending handlers
ansible.builtin.meta: flush_handlers
- name: "Start and wait for keycloak service"
ansible.builtin.include_tasks: start.yml
- name: Link default logs directory
ansible.builtin.file:
state: link
src: "{{ keycloak.log.file | dirname }}"
dest: "{{ keycloak_quarkus_log_target }}"
force: true
become: "{{ keycloak_quarkus_require_privilege_escalation | default(true) }}"
- name: Check service status
ansible.builtin.systemd_service:
name: "{{ keycloak.service_name }}"
register: keycloak_quarkus_service_status
changed_when: false
- name: "Notify to remove `keycloak_quarkus_bootstrap_admin_user[_password]` env vars"
when:
- not ansible_local.keycloak.general.bootstrapped | default(false) | bool # it was not bootstrapped prior to the current role's execution
- keycloak_quarkus_service_status.status.ActiveState == "active" # but it is now
ansible.builtin.assert: { that: true, quiet: true }
changed_when: true
notify:
- bootstrapped
- name: Flush pending handlers
ansible.builtin.meta: flush_handlers

View file

@ -0,0 +1,154 @@
---
- name: Validate admin console password
ansible.builtin.assert:
that:
- keycloak_quarkus_bootstrap_admin_password is defined
- keycloak_quarkus_bootstrap_admin_password is not none
- keycloak_quarkus_bootstrap_admin_password | length > 12
quiet: true
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_bootstrap_admin_password to a 12+ char long string"
success_msg: "{{ 'Console administrator password OK' }}"
- name: Validate http_relative_path
ansible.builtin.assert:
that:
- keycloak_quarkus_http_relative_path is regex('^/.*')
quiet: true
fail_msg: "The relative path for keycloak_quarkus_http_relative_path must begin with /"
success_msg: "{{ 'Relative path OK' }}"
- name: Validate http_management_relative_path
ansible.builtin.assert:
that:
- keycloak_quarkus_http_management_relative_path is regex('^/.*')
quiet: true
fail_msg: "The relative path for keycloak_quarkus_http_management_relative_path must begin with /"
success_msg: "{{ 'Relative mgmt path OK' }}"
when: keycloak_quarkus_http_management_relative_path is defined
- name: Validate configuration
ansible.builtin.assert:
that:
- (keycloak_quarkus_ha_enabled and keycloak_quarkus_db_enabled) or
(not keycloak_quarkus_ha_enabled and keycloak_quarkus_db_enabled) or
(not keycloak_quarkus_ha_enabled and not keycloak_quarkus_db_enabled)
quiet: true
fail_msg: "HA setup requires a backend database service. Check keycloak_quarkus_ha_enabled and keycloak_quarkus_db_enabled"
success_msg: "{{ 'Configuring HA' if keycloak_quarkus_ha_enabled else 'Configuring standalone' }}"
- name: Validate OS family
ansible.builtin.assert:
that:
- ansible_os_family in ["RedHat", "Debian"]
quiet: true
fail_msg: "Can only install on RedHat or Debian OS families; found {{ ansible_os_family }}"
success_msg: "Installing on {{ ansible_os_family }}"
- name: Load OS specific variables
ansible.builtin.include_vars: "vars/{{ ansible_os_family | lower }}.yml"
tags:
- always
- name: Ensure required packages are installed
ansible.builtin.include_tasks: fastpackages.yml
vars:
packages_list: "{{ keycloak_quarkus_prereq_package_list }}"
- name: Check local download archive path
ansible.builtin.stat:
path: "{{ keycloak_quarkus_download_path }}"
register: keycloak_quarkus_local_path
delegate_to: localhost
run_once: true
become: false
- name: Validate local download path
ansible.builtin.assert:
that:
- keycloak_quarkus_local_path.stat.exists
- keycloak_quarkus_local_path.stat.readable
- keycloak_quarkus_offline_install or keycloak_quarkus_local_path.stat.writeable
quiet: true
fail_msg: "Defined controller path for downloading resources is incorrect or unreadable: {{ keycloak_quarkus_download_path }}"
success_msg: "Will download resource to controller path: {{ keycloak_quarkus_download_path }}"
delegate_to: localhost
run_once: true
- name: Check downloaded archive if offline
ansible.builtin.stat:
path: "{{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
when: keycloak_quarkus_offline_install
register: keycloak_quarkus_local_archive_path_check
delegate_to: localhost
run_once: true
- name: Validate local downloaded archive if offline
ansible.builtin.assert:
that:
- keycloak_quarkus_local_archive_path_check.stat.exists
- keycloak_quarkus_local_archive_path_check.stat.readable
quiet: true
fail_msg: "Configured for offline install but install archive not found at: {{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
success_msg: "Will install offline with expected archive: {{ keycloak_quarkus_local_path.stat.path }}/{{ keycloak.bundle }}"
when: keycloak_quarkus_offline_install
delegate_to: localhost
run_once: true
- name: "Validate keytool"
when: keycloak_quarkus_config_key_store_password | length > 0
block:
- name: "Check run keytool"
changed_when: false
ansible.builtin.command: keytool -help
register: keycloak_quarkus_keytool_check
ignore_errors: true
- name: "Fail when no keytool found"
when: keycloak_quarkus_keytool_check.rc != 0
ansible.builtin.fail:
msg: "keytool NOT found in the PATH, but is required for setting up the configuration key store"
- name: "Validate providers"
ansible.builtin.assert:
that: >
item.id is defined and item.id | length > 0 and
( (item.spi is defined and item.spi | length > 0) or
(item.url is defined and item.url | length > 0) or
( item.maven is defined and item.maven.repository_url is defined and item.maven.repository_url | length > 0 and
item.maven.group_id is defined and item.maven.group_id | length > 0 and
item.maven.artifact_id is defined and item.maven.artifact_id | length > 0) or
(item.local_path is defined and item.local_path | length > 0)
)
quiet: true
fail_msg: >
Providers definition incorrect; `id` and one of `spi`, `url`, `local_path`, or `maven` are mandatory. `key` and `value` are mandatory for each property
loop: "{{ keycloak_quarkus_providers }}"
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
- name: "Validate policies"
ansible.builtin.assert:
that:
- item.name is defined and item.name | length > 0
- item.url is defined and item.url | length > 0
- item.type is not defined or item.type | lower in keycloak_quarkus_supported_policy_types
quiet: true
fail_msg: >
Policy definition is incorrect: `name` and one of `url` are mandatory, `type` needs to be left empty or one of {{ keycloak_quarkus_supported_policy_types }}.
loop: "{{ keycloak_quarkus_policies }}"
- name: "Validate additional env variables"
ansible.builtin.assert:
that:
- item.key is defined and item.key | length > 0
- item.value is defined and item.value | length > 0
quiet: true
fail_msg: "Additional env variable definition is incorrect: `key` and `value` are mandatory."
no_log: true
loop: "{{ keycloak_quarkus_additional_env_vars }}"
- name: "Validate proxy-headers"
ansible.builtin.assert:
that:
- keycloak_quarkus_proxy_headers | lower in ['', 'forwarded', 'xforwarded']
quiet: true
fail_msg: "keycloak_quarkus_proxy_headers must be either '', 'forwarded' or 'xforwarded'"

View file

@ -0,0 +1,7 @@
---
# cf. https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup
- name: "Rebuild config: {{ keycloak.service_name }}"
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
env -i bash -c "set -a ; source {{ keycloak_quarkus_sysconf_file }} ; {{ keycloak.home }}/bin/kc.sh build "
become: "{{ keycloak_quarkus_rebuild_config_require_privilege_escalation | default(true) }}"
changed_when: true

View file

@ -0,0 +1,10 @@
---
- name: Include firewall config tasks
ansible.builtin.include_tasks:
file: firewalld.yml
apply:
tags:
- firewall
when: keycloak_quarkus_configure_firewalld
tags:
- firewall

View file

@ -0,0 +1,24 @@
---
- name: "Restart and enable service: {{ keycloak.service_name }}"
ansible.builtin.systemd:
name: "{{ keycloak.service_name }}"
enabled: true
state: restarted
daemon_reload: true
become: "{{ keycloak_quarkus_restart_require_privilege_escalation | default(true) }}"
- name: "Wait until service becomes active: {{ keycloak.service_name }}"
ansible.builtin.uri:
url: "{{ keycloak.health_url }}"
validate_certs: "{{ keycloak_quarkus_validate_certs }}"
register: keycloak_quarkus_status
until: keycloak_quarkus_status.status == 200
retries: "{{ keycloak_quarkus_restart_health_check_retries }}"
delay: "{{ keycloak_quarkus_restart_health_check_delay }}"
when: internal_force_health_check | default(keycloak_quarkus_restart_health_check)
- name: Wait to give distributed ispn caches time to (re-)replicate back onto first host
ansible.builtin.pause:
seconds: "{{ keycloak_quarkus_restart_pause }}"
when:
- keycloak_quarkus_ha_enabled

View file

@ -0,0 +1,4 @@
---
- name: "Display message"
ansible.builtin.debug:
msg: "keycloak_quarkus_restart_strategy is none, skipping restart"

View file

@ -0,0 +1,11 @@
---
- name: "Restart services in serial, with optional healtch check (keycloak_quarkus_restart_health_check)"
throttle: 1
block:
- name: "Restart and enable service: {{ keycloak.service_name }}"
ansible.builtin.include_tasks:
file: restart.yml
apply:
delegate_to: "{{ item }}"
run_once: true
loop: "{{ ansible_play_hosts }}"

View file

@ -0,0 +1,20 @@
---
- name: Verify first restarted service with health URL, then rest restart in parallel
block:
- name: "Restart and enable service on initial host: {{ keycloak.service_name }}"
ansible.builtin.include_tasks:
file: restart.yml
apply:
delegate_to: "{{ ansible_play_hosts | first }}"
run_once: true
vars:
internal_force_health_check: true
- name: "Restart and enable service on other hosts: {{ keycloak.service_name }}"
ansible.builtin.systemd:
name: "{{ keycloak.service_name }}"
enabled: true
state: restarted
daemon_reload: true
become: "{{ keycloak_quarkus_restart_require_privilege_escalation | default(true) }}"
when: inventory_hostname != ansible_play_hosts | first

View file

@ -0,0 +1,18 @@
---
- name: "Start service: {{ keycloak.service_name }}"
ansible.builtin.systemd:
name: keycloak
enabled: true
state: started
daemon_reload: true
become: "{{ keycloak_quarkus_start_require_privilege_escalation | default(true) }}"
- name: "Wait until service becomes active: {{ keycloak.service_name }}"
ansible.builtin.uri:
url: "{{ keycloak.health_url }}"
validate_certs: "{{ keycloak_quarkus_validate_certs }}"
register: keycloak_quarkus_status
until: keycloak_quarkus_status.status == 200
retries: 25
delay: 10
when: internal_force_health_check | default(keycloak_quarkus_restart_health_check)

View file

@ -0,0 +1,27 @@
---
- name: "Configure sysconfig file for service: {{ keycloak.service_name }}"
become: "{{ keycloak_quarkus_systemd_require_privilege_escalation | default(true) }}"
ansible.builtin.template:
src: keycloak-sysconfig.j2
dest: "{{ keycloak_quarkus_sysconf_file }}"
owner: root
group: root
mode: '0640'
vars:
keycloak_sys_pkg_java_home: "{{ keycloak_quarkus_pkg_java_home }}"
notify:
- rebuild keycloak config
- restart keycloak
- name: "Configure systemd unit file for keycloak service"
ansible.builtin.template:
src: keycloak.service.j2
dest: /etc/systemd/system/keycloak.service
owner: root
group: root
mode: '0644'
become: "{{ keycloak_quarkus_systemd_require_privilege_escalation | default(true) }}"
register: keycloak_quarkus_systemdunit
notify:
- rebuild keycloak config
- restart keycloak