Compare commits

..

35 Commits
2.1.0 ... 2.1.1

Author SHA1 Message Date
ansible-middleware-core
ad6021c29a Update changelog for release 2.1.1
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-04-17 08:58:43 +00:00
Guido Grazioli
05ebd90121 Merge pull request #191 from guidograzioli/190_sysconfig_worldreadable
Unrelax configuration file permissions
2024-04-17 10:51:45 +02:00
Guido Grazioli
1229a0b023 Unrelax configuration file permissions 2024-04-17 10:46:23 +02:00
Guido Grazioli
4ba9014edb Merge pull request #187 from roumano/parse_proxy_headers
Permit parse reverse proxy headers
2024-04-17 10:36:50 +02:00
Christian Iuga
ea57f8b689 remove unwanted extra code 2024-04-16 13:41:09 +02:00
Christian Iuga
3fbae4882e move keycloak_quarkus_proxy_headers into keycloak.conf 2024-04-16 13:39:33 +02:00
Christian Iuga
27717d7b4e Avoid cmd-line arguments
Fix https://github.com/ansible-middleware/keycloak/pull/187#discussion_r1565779164
2024-04-15 15:50:55 +02:00
Christian Iuga
4aa862101c Add new variable keycloak_quarkus_proxy_headers into meta/argument_specs.yml
Fix comment https://github.com/ansible-middleware/keycloak/pull/187#discussion_r1565772058
2024-04-15 15:48:02 +02:00
Christian Iuga
8e2f3eb77f Permit parse reverse proxy headers
- Via created a new optional variable : keycloak_quarkus_proxy_headers
- Fix enhancement #183
- see https://www.keycloak.org/server/reverseproxy about the official documentation
2024-04-15 14:41:56 +02:00
Guido Grazioli
10d4cb8db7 Merge pull request #186 from guidograzioli/185_java_heap_options
JVM arguments go in JAVA_OPTS
2024-04-09 17:16:17 +02:00
Guido Grazioli
8f8de33350 JVM arguments go IN JAVA_OPTS 2024-04-08 16:47:49 +02:00
Guido Grazioli
7dceb7f819 Merge pull request #184 from avskor/issue-125
Fix permissions on controller-side downloaded artifacts
2024-04-08 09:15:52 +02:00
avskor
c2e456e1d5 Fix #125. Permission error when the become variable is set to true in the playbook 2024-04-04 11:22:18 +03:00
Guido Grazioli
4421375dd5 Merge pull request #181 from guidograzioli/multi_distro_refactor
Multi distro refactor
2024-03-25 16:42:29 +01:00
Guido Grazioli
2bbf7d9cc4 revert JVM var that cannot be overridden 2024-03-25 16:30:13 +01:00
Guido Grazioli
467cfda0f7 same changes for keycloak-legacy 2024-03-25 16:00:18 +01:00
Guido Grazioli
e17505fe42 update molecule for debian container 2024-03-25 15:37:02 +01:00
Guido Grazioli
0e4df659f4 add test 2024-03-25 14:35:28 +01:00
Guido Grazioli
3400b64b10 add to ci 2024-03-25 14:34:25 +01:00
Guido Grazioli
3b1534d700 refactor 2024-03-25 10:19:28 +01:00
Guido Grazioli
dd6171f024 Add ansible_family based vars loading 2024-03-25 10:19:08 +01:00
Guido Grazioli
c1da6ea38d Merge pull request #180 from guidograzioli/keycloak_realm_default
Use `keycloak_realm` as default for sub-entities
2024-03-25 09:40:30 +01:00
Guido Grazioli
56e4a43cf9 add keycloak_realm default to sub entities 2024-03-25 09:30:25 +01:00
Guido Grazioli
7a0a99a31c Merge pull request #178 from Aeyk/ubuntu
Ubuntu compatibility
2024-03-18 09:09:07 +01:00
aeyk
fdce0bd922 Merge branch 'main' into ubuntu 2024-03-17 05:35:09 -04:00
Malik Kennedy
b9d9874a00 feat: ubuntu compatibility 2024-03-17 09:15:38 +00:00
Guido Grazioli
1cecf51f37 downstream: more updates to custom xml 2024-03-14 11:41:52 +01:00
Guido Grazioli
0cea03dfc0 downstream: simplify overridexml test 2024-03-14 10:37:09 +01:00
Guido Grazioli
0c079740e1 downstream: molecule custom xml that works with rhsso 2024-03-14 10:13:46 +01:00
Guido Grazioli
96804d8086 downstream: rhsso has new patch filename pattern 2024-03-13 17:55:30 +01:00
Guido Grazioli
a875166fe0 Merge pull request #176 from growi/templates_comment_filter
Utilize comment filter for `ansible_managed` annotations
2024-03-13 14:24:19 +01:00
Björn Großewinkelmann
a97c349f41 Utilize comment filter for {{ ansible_maanged }} annotations
Signed-off-by: Björn Großewinkelmann <bgrossew@redhat.com>
2024-03-13 00:19:42 +01:00
Romain Pelisse
a59a1fb8dd Rework Molecule prepare phase to install sudo only if root on target 2024-03-12 12:48:46 +01:00
Guido Grazioli
d74820190f ci: rename keycloak_quarkus infinispan jinja2 template 2024-02-28 17:10:02 +01:00
ansible-middleware-core
6541b5e386 Bump version to 2.1.1 2024-02-28 15:58:47 +00:00
64 changed files with 571 additions and 249 deletions

View File

@@ -15,4 +15,4 @@ jobs:
with:
fqcn: 'middleware_automation/keycloak'
molecule_tests: >-
[ "default", "overridexml", "https_revproxy", "quarkus", "quarkus-devmode" ]
[ "default", "overridexml", "https_revproxy", "quarkus", "quarkus-devmode", "debian" ]

View File

@@ -6,6 +6,24 @@ middleware\_automation.keycloak Release Notes
This changelog describes changes after version 0.2.6.
v2.1.1
======
Minor Changes
-------------
- Add reverse ``proxy_headers`` config, supersedes ``proxy_mode`` `#187 <https://github.com/ansible-middleware/keycloak/pull/187>`_
- Debian/Ubuntu compatibility `#178 <https://github.com/ansible-middleware/keycloak/pull/178>`_
- Use ``keycloak_realm`` as default for sub-entities `#180 <https://github.com/ansible-middleware/keycloak/pull/180>`_
Bugfixes
--------
- Fix permissions on controller-side downloaded artifacts `#184 <https://github.com/ansible-middleware/keycloak/pull/184>`_
- JVM args moved to ``JAVA_OPTS`` envvar (instead of JAVA_OPTS_APPEND) `#186 <https://github.com/ansible-middleware/keycloak/pull/186>`_
- Unrelax configuration file permissions `#191 <https://github.com/ansible-middleware/keycloak/pull/191>`_
- Utilize comment filter for ``ansible_managed`` annotations `#176 <https://github.com/ansible-middleware/keycloak/pull/176>`_
v2.1.0
======

View File

@@ -1,8 +1,9 @@
python3-dev [compile platform:dpkg]
python3-devel [compile platform:rpm]
python39-devel [compile platform:centos-8 platform:rhel-8]
git-lfs [platform:rpm]
python3-netaddr [platform:rpm]
python3-lxml [platform:rpm]
python3-jmespath [platform:rpm]
python3-requests [platform:rpm]
git-lfs [platform:rpm platform:dpkg]
python3-netaddr [platform:rpm platform:dpkg]
python3-lxml [platform:rpm platform:dpkg]
python3-jmespath [platform:rpm platform:dpkg]
python3-requests [platform:rpm platform:dpkg]

View File

@@ -419,3 +419,38 @@ releases:
- 167.yaml
- 171.yaml
release_date: '2024-02-28'
2.1.1:
changes:
bugfixes:
- 'Fix permissions on controller-side downloaded artifacts `#184 <https://github.com/ansible-middleware/keycloak/pull/184>`_
'
- 'JVM args moved to ``JAVA_OPTS`` envvar (instead of JAVA_OPTS_APPEND) `#186
<https://github.com/ansible-middleware/keycloak/pull/186>`_
'
- 'Unrelax configuration file permissions `#191 <https://github.com/ansible-middleware/keycloak/pull/191>`_
'
- 'Utilize comment filter for ``ansible_managed`` annotations `#176 <https://github.com/ansible-middleware/keycloak/pull/176>`_
'
minor_changes:
- 'Add reverse ``proxy_headers`` config, supersedes ``proxy_mode`` `#187 <https://github.com/ansible-middleware/keycloak/pull/187>`_
'
- 'Debian/Ubuntu compatibility `#178 <https://github.com/ansible-middleware/keycloak/pull/178>`_
'
- 'Use ``keycloak_realm`` as default for sub-entities `#180 <https://github.com/ansible-middleware/keycloak/pull/180>`_
'
fragments:
- 176.yaml
- 178.yaml
- 180.yaml
- 184.yaml
- 186.yaml
- 187.yaml
- 191.yaml
release_date: '2024-04-17'

View File

@@ -1,7 +1,7 @@
---
namespace: middleware_automation
name: keycloak
version: "2.1.0"
version: "2.1.1"
readme: README.md
authors:
- Romain Pelisse <rpelisse@redhat.com>

View File

@@ -0,0 +1,41 @@
---
- name: Converge
hosts: all
vars:
keycloak_quarkus_admin_pass: "remembertochangeme"
keycloak_realm: TestRealm
keycloak_quarkus_log: file
keycloak_quarkus_frontend_url: 'http://localhost:8080/'
keycloak_quarkus_start_dev: True
keycloak_quarkus_proxy_mode: none
keycloak_client_default_roles:
- TestRoleAdmin
- TestRoleUser
keycloak_client_users:
- username: TestUser
password: password
client_roles:
- client: TestClient
role: TestRoleUser
- username: TestAdmin
password: password
client_roles:
- client: TestClient
role: TestRoleUser
- client: TestClient
role: TestRoleAdmin
keycloak_clients:
- name: TestClient
roles: "{{ keycloak_client_default_roles }}"
public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_client_users }}"
client_id: TestClient
attributes:
post.logout.redirect.uris: '/public/logout'
roles:
- role: keycloak_quarkus
- role: keycloak_realm
keycloak_realm: TestRealm
keycloak_admin_password: "remembertochangeme"
keycloak_context: ''

View File

@@ -0,0 +1,48 @@
---
driver:
name: docker
platforms:
- name: instance
image: ghcr.io/hspaans/molecule-containers:debian-11
pre_build_image: true
privileged: true
port_bindings:
- "8080/tcp"
- "8443/tcp"
- "8009/tcp"
cgroupns_mode: host
command: "/lib/systemd/systemd"
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
provisioner:
name: ansible
config_options:
defaults:
interpreter_python: auto_silent
ssh_connection:
pipelining: false
playbooks:
prepare: prepare.yml
converge: converge.yml
verify: verify.yml
inventory:
host_vars:
localhost:
ansible_python_interpreter: /usr/bin/python3
env:
ANSIBLE_FORCE_COLOR: "true"
ANSIBLE_REMOTE_TMP: /tmp/.ansible/tmp
verifier:
name: ansible
scenario:
test_sequence:
- cleanup
- destroy
- create
- prepare
- converge
- idempotence
- side_effect
- verify
- cleanup
- destroy

View File

@@ -0,0 +1,11 @@
---
- name: Prepare
hosts: all
gather_facts: yes
tasks:
- name: Install sudo
ansible.builtin.apt:
name:
- sudo
- openjdk-17-jdk-headless
state: present

1
molecule/debian/roles Symbolic link
View File

@@ -0,0 +1 @@
../../roles

View File

@@ -0,0 +1,40 @@
---
- name: Verify
hosts: all
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_jboss_port_offset: 10
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
- name: Check if keycloak service started
ansible.builtin.assert:
that:
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify openid config
block:
- name: Fetch openID config # noqa blocked_modules command-instead-of-module
ansible.builtin.shell: |
set -o pipefail
curl http://localhost:8080/realms/master/.well-known/openid-configuration -k | jq .
args:
executable: /bin/bash
delegate_to: localhost
register: openid_config
changed_when: False
- name: Verify endpoint URLs
ansible.builtin.assert:
that:
- (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'http://localhost:8080/realms/master/protocol/openid-connect/ext/ciba/auth'
- (openid_config.stdout | from_json)['issuer'] == 'http://localhost:8080/realms/master'
- (openid_config.stdout | from_json)['authorization_endpoint'] == 'http://localhost:8080/realms/master/protocol/openid-connect/auth'
- (openid_config.stdout | from_json)['token_endpoint'] == 'http://localhost:8080/realms/master/protocol/openid-connect/token'
delegate_to: localhost
when:
- hera_home is defined
- hera_home | length == 0

View File

@@ -1,7 +1,7 @@
---
- name: Converge
hosts: all
vars:
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_jvm_package: java-11-openjdk-headless
keycloak_modcluster_enabled: True
@@ -52,7 +52,7 @@
pre_tasks:
- name: "Retrieve assets server from env"
ansible.builtin.set_fact:
assets_server: "{{ lookup('env','MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
assets_server: "{{ lookup('env', 'MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:

View File

@@ -18,5 +18,12 @@
name:
- java-1.8.0-openjdk
state: present
when: ansible_facts['os_family'] == "RedHat"
- name: Install JDK8
become: yes
ansible.builtin.apt:
name:
- openjdk-8-jdk
state: present
when: ansible_facts['os_family'] == "Debian"

View File

@@ -1,7 +1,7 @@
---
- name: Converge
hosts: all
vars:
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_config_override_template: custom.xml.j2
keycloak_http_port: 8081
@@ -9,47 +9,3 @@
keycloak_service_runas: True
roles:
- role: keycloak
tasks:
- name: Keycloak Realm Role
ansible.builtin.include_role:
name: keycloak_realm
vars:
keycloak_client_default_roles:
- TestRoleAdmin
- TestRoleUser
keycloak_client_users:
- username: TestUser
password: password
client_roles:
- client: TestClient
role: TestRoleUser
realm: "{{ keycloak_realm }}"
- username: TestAdmin
password: password
client_roles:
- client: TestClient
role: TestRoleUser
realm: "{{ keycloak_realm }}"
- client: TestClient
role: TestRoleAdmin
realm: "{{ keycloak_realm }}"
keycloak_realm: TestRealm
keycloak_clients:
- name: TestClient
roles: "{{ keycloak_client_default_roles }}"
realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_client_users }}"
client_id: TestClient
pre_tasks:
- name: "Retrieve assets server from env"
ansible.builtin.set_fact:
assets_server: "{{ lookup('env','MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:
sso_offline_install: True
when:
- assets_server is defined
- assets_server | length > 0

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
<!-- this is a custom file -->
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
@@ -44,7 +44,7 @@
</audit-log>
<management-interfaces>
<http-interface http-authentication-factory="management-http-authentication">
<http-upgrade enabled="true"/>
<http-upgrade enabled="true" sasl-authentication-factory="management-sasl-authentication"/>
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
@@ -481,8 +481,8 @@
<default-provider>default</default-provider>
<provider name="default" enabled="true">
<properties>
<property name="frontendUrl" value="{{ keycloak_modcluster.frontend_url }}"/>
<property name="forceBackendUrlToFrontendUrl" value="true"/>
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
<property name="forceBackendUrlToFrontendUrl" value="false"/>
</properties>
</provider>
</spi>
@@ -520,7 +520,8 @@
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<buffer-cache name="default"/>
<server name="default-server">
<http-listener name="default" socket-binding="http"/>
<http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
<https-listener name="https" socket-binding="https" ssl-context="applicationSSC" enable-http2="true"/>
<host name="default-host" alias="localhost">
<location name="/" handler="welcome-content"/>
<http-invoker http-authentication-factory="application-http-authentication"/>
@@ -533,20 +534,25 @@
<handlers>
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
</handlers>
<application-security-domains>
<application-security-domain name="other" security-domain="ApplicationDomain"/>
</application-security-domains>
</subsystem>
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
<inet-address value="127.0.0.1"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
<inet-address value="127.0.0.1"/>
</interface>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="http" port="8081"/>
<socket-binding name="https" port="8443"/>
<socket-binding name="management-http" interface="management" port="19990"/>
<socket-binding name="management-https" interface="management" port="19991"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">

View File

@@ -1,6 +1,10 @@
---
- name: Verify
hosts: all
vars:
keycloak_uri: "http://localhost:8081"
keycloak_management_port: "http://localhost:19990"
keycloak_admin_password: "remembertochangeme"
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
@@ -9,3 +13,20 @@
that:
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify we are running on requested jvm # noqa blocked_modules command-instead-of-module
ansible.builtin.shell: |
set -o pipefail
ps -ef | grep '/etc/alternatives/jre_1.8.0/' | grep -v grep
args:
executable: /bin/bash
changed_when: no
- name: Verify token api call
ansible.builtin.uri:
url: "{{ keycloak_uri }}/auth/realms/master/protocol/openid-connect/token"
method: POST
body: "client_id=admin-cli&username=admin&password={{ keycloak_admin_password }}&grant_type=password"
validate_certs: no
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 2
delay: 2

View File

@@ -3,28 +3,31 @@
ansible.builtin.debug:
msg: "Ansible version is {{ ansible_version.full }}"
- name: "Set package name for sudo"
ansible.builtin.set_fact:
sudo_pkg_name: sudo
- name: "Ensure {{ sudo_pkg_name }} is installed (if user is root)."
ansible.builtin.yum:
name: "{{ sudo_pkg_name }}"
state: present
when:
- ansible_user_id == 'root'
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: "Check if {{ sudo_pkg_name }} is installed."
- name: "Check if sudo is installed."
ansible.builtin.assert:
that:
- sudo_pkg_name in ansible_facts.packages
fail_msg: "sudo is not installed on target system"
- name: Install sudo
- name: "Install iproute"
become: yes
ansible.builtin.yum:
name:
- sudo
- iproute
state: present
@@ -36,6 +39,8 @@
when:
- assets_server is defined
- assets_server | length > 0
- assets is defined
- assets | length > 0
block:
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:

View File

@@ -3,18 +3,46 @@
hosts: all
tasks:
- name: Install sudo
ansible.builtin.yum:
ansible.builtin.apt:
name:
- sudo
- openjdk-17-jdk-headless
state: present
when:
- ansible_facts.os_family == 'Debian'
- name: "Ensure common prepare phase are set."
ansible.builtin.include_tasks: ../prepare.yml
- name: Install JDK17
become: yes
ansible.builtin.yum:
name:
- java-17-openjdk-headless
state: present
when:
- ansible_facts.os_family == 'RedHat'
- name: Link default logs directory
become: yes
ansible.builtin.file:
state: link
src: "{{ item }}"
dest: /opt/openjdk
force: true
with_fileglob:
- /usr/lib/jvm/java-17-openjdk*
when:
- ansible_facts.os_family == "Debian"
- name: Link default logs directory
ansible.builtin.file:
state: link
src: /usr/lib/jvm/jre-17-openjdk
dest: /opt/openjdk
force: true
when:
- ansible_facts.os_family == "RedHat"
- name: "Display hera_home if defined."
ansible.builtin.set_fact:

View File

@@ -3,13 +3,16 @@
hosts: all
tasks:
- name: Install sudo
ansible.builtin.yum:
ansible.builtin.package:
name: sudo
state: present
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"
- name: "Ensure common prepare phase are set."
ansible.builtin.include_tasks: ../prepare.yml
- name: Create certificate request
ansible.builtin.command: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=instance'
@@ -17,12 +20,14 @@
changed_when: False
- name: Create conf directory # risky-file-permissions in test user account does not exist yet
become: yes
ansible.builtin.file:
state: directory
path: "/opt/keycloak/certs/"
mode: 0755
- name: Copy certificates
become: yes
ansible.builtin.copy:
src: "{{ item }}"
dest: "/opt/keycloak/certs/{{ item }}"

View File

@@ -49,8 +49,9 @@
- keycloak_log_folder.stat.exists
- not keycloak_log_folder.stat.isdir
- keycloak_log_folder.stat.islnk
- name: Check log file
become: yes
ansible.builtin.stat:
path: "/tmp/keycloak/keycloak.log"
register: keycloak_log_file
@@ -62,6 +63,7 @@
- not keycloak_log_file.stat.isdir
- name: Check default log folder
become: yes
ansible.builtin.stat:
path: "/var/log/keycloak"
register: keycloak_default_log_folder

View File

@@ -5,7 +5,7 @@ collections:
- name: community.general
- name: ansible.posix
- name: community.docker
version: ">=1.9.1"
version: ">=3.8.0"
roles:
- name: elan.simple_nginx_reverse_proxy

View File

@@ -10,6 +10,7 @@ Requirements
This role requires the `python3-netaddr` library installed on the controller node.
* to install via yum/dnf: `dnf install python3-netaddr`
* to install via apt: `apt install python3-netaddr`
* or via pip: `pip install netaddr==0.8.0`
* or via the collection: `pip install -r requirements.txt`

View File

@@ -8,7 +8,6 @@ keycloak_installdir: "{{ keycloak_dest }}/keycloak-{{ keycloak_version }}"
keycloak_offline_install: false
### Install location and service settings
keycloak_jvm_package: java-1.8.0-openjdk-headless
keycloak_java_home:
keycloak_dest: /opt/keycloak
keycloak_jboss_home: "{{ keycloak_installdir }}"
@@ -33,6 +32,7 @@ keycloak_service_startlimitburst: "5"
keycloak_service_restartsec: "10s"
keycloak_configure_firewalld: false
keycloak_configure_iptables: false
### administrator console password
keycloak_admin_password: ''

View File

@@ -2,42 +2,38 @@ argument_specs:
main:
options:
keycloak_version:
# line 3 of keycloak/defaults/main.yml
default: "18.0.2"
description: "keycloak.org package version"
type: "str"
keycloak_archive:
# line 4 of keycloak/defaults/main.yml
default: "keycloak-legacy-{{ keycloak_version }}.zip"
description: "keycloak install archive filename"
type: "str"
keycloak_configure_iptables:
default: false
description: "Ensure iptables is running and configure keycloak ports"
type: "bool"
keycloak_configure_firewalld:
# line 33 of keycloak/defaults/main.yml
default: false
description: "Ensure firewalld is running and configure keycloak ports"
type: "bool"
keycloak_download_url:
# line 5 of keycloak/defaults/main.yml
default: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_version }}/{{ keycloak_archive }}"
description: "Download URL for keycloak"
type: "str"
keycloak_download_url_9x:
# line 6 of keycloak/defaults/main.yml
default: "https://downloads.jboss.org/keycloak/{{ keycloak_version }}/{{ keycloak_archive }}"
description: "Download URL for keycloak (deprecated)"
type: "str"
keycloak_installdir:
# line 7 of keycloak/defaults/main.yml
default: "{{ keycloak_dest }}/keycloak-{{ keycloak_version }}"
description: "Installation path"
type: "str"
keycloak_offline_install:
# line 20 of keycloak/defaults/main.yml
default: false
description: "Perform an offline install"
type: "bool"
keycloak_jvm_package:
# line 23 of keycloak/defaults/main.yml
default: "java-1.8.0-openjdk-headless"
description: "RHEL java package runtime rpm"
type: "str"
@@ -45,12 +41,10 @@ argument_specs:
description: "JAVA_HOME of installed JRE, leave empty for using specified keycloak_jvm_package RPM path"
type: "str"
keycloak_dest:
# line 24 of keycloak/defaults/main.yml
default: "/opt/keycloak"
description: "Root installation directory"
type: "str"
keycloak_jboss_home:
# line 25 of keycloak/defaults/main.yml
default: "{{ keycloak_installdir }}"
description: "Installation work directory"
type: "str"
@@ -59,52 +53,42 @@ argument_specs:
description: "Port offset for the JBoss socket binding"
type: "int"
keycloak_config_dir:
# line 26 of keycloak/defaults/main.yml
default: "{{ keycloak_jboss_home }}/standalone/configuration"
description: "Path for configuration"
type: "str"
keycloak_config_standalone_xml:
# line 27 of keycloak/defaults/main.yml
default: "keycloak.xml"
description: "Service configuration filename"
type: "str"
keycloak_config_path_to_standalone_xml:
# line 28 of keycloak/defaults/main.yml
default: "{{ keycloak_jboss_home }}/standalone/configuration/{{ keycloak_config_standalone_xml }}"
description: "Custom path for configuration"
type: "str"
keycloak_config_override_template:
# line 30 of keycloak/defaults/main.yml
default: ""
description: "Path to custom template for standalone.xml configuration"
type: "str"
keycloak_service_runas:
# line 20 of keycloak/defaults/main.yml
keycloak_service_runas:
default: false
description: "Enable execution of service as `keycloak_service_user`"
type: "bool"
keycloak_service_user:
# line 29 of keycloak/defaults/main.yml
default: "keycloak"
description: "posix account username"
type: "str"
keycloak_service_group:
# line 30 of keycloak/defaults/main.yml
default: "keycloak"
description: "posix account group"
type: "str"
keycloak_service_pidfile:
# line 31 of keycloak/defaults/main.yml
default: "/run/keycloak/keycloak.pid"
description: "PID file path for service"
type: "str"
keycloak_features:
# line 17 of keycloak/defaults/main.yml
default: "[]"
description: "List of `name`/`status` pairs of features (also known as profiles on RH-SSO) to `enable` or `disable`, example: `[ { name: 'docker', status: 'enabled' } ]`"
type: "list"
keycloak_bind_address:
# line 34 of keycloak/defaults/main.yml
default: "0.0.0.0"
description: "Address for binding service ports"
type: "str"
@@ -113,52 +97,42 @@ argument_specs:
description: "Address for binding the management ports"
type: "str"
keycloak_host:
# line 35 of keycloak/defaults/main.yml
default: "localhost"
description: "Hostname for service"
type: "str"
keycloak_http_port:
# line 36 of keycloak/defaults/main.yml
default: 8080
description: "Listening HTTP port"
type: "int"
keycloak_https_port:
# line 37 of keycloak/defaults/main.yml
default: 8443
description: "Listening HTTPS port"
type: "int"
keycloak_ajp_port:
# line 38 of keycloak/defaults/main.yml
default: 8009
description: "Listening AJP port"
type: "int"
keycloak_jgroups_port:
# line 39 of keycloak/defaults/main.yml
default: 7600
description: "jgroups cluster tcp port"
type: "int"
keycloak_management_http_port:
# line 40 of keycloak/defaults/main.yml
default: 9990
description: "Management port (http)"
type: "int"
keycloak_management_https_port:
# line 41 of keycloak/defaults/main.yml
default: 9993
description: "Management port (https)"
type: "int"
keycloak_java_opts:
# line 42 of keycloak/defaults/main.yml
default: "-Xms1024m -Xmx2048m"
description: "Additional JVM options"
type: "str"
keycloak_prefer_ipv4:
# line 43 of keycloak/defaults/main.yml
default: true
description: "Prefer IPv4 stack and addresses for port binding"
type: "bool"
keycloak_ha_enabled:
# line 46 of keycloak/defaults/main.yml
default: false
description: "Enable auto configuration for database backend, clustering and remote caches on infinispan"
type: "bool"
@@ -167,27 +141,22 @@ argument_specs:
description: "Discovery protocol for HA cluster members"
type: "str"
keycloak_db_enabled:
# line 48 of keycloak/defaults/main.yml
default: "{{ True if keycloak_ha_enabled else False }}"
description: "Enable auto configuration for database backend"
type: "bool"
keycloak_admin_user:
# line 51 of keycloak/defaults/main.yml
default: "admin"
description: "Administration console user account"
type: "str"
keycloak_auth_realm:
# line 52 of keycloak/defaults/main.yml
default: "master"
description: "Name for rest authentication realm"
type: "str"
keycloak_auth_client:
# line 53 of keycloak/defaults/main.yml
default: "admin-cli"
description: "Authentication client for configuration REST calls"
type: "str"
keycloak_force_install:
# line 55 of keycloak/defaults/main.yml
default: false
description: "Remove pre-existing versions of service"
type: "bool"
@@ -196,7 +165,6 @@ argument_specs:
description: "Enable configuration for modcluster subsystem"
type: "bool"
keycloak_modcluster_url:
# line 58 of keycloak/defaults/main.yml
default: "localhost"
description: "URL for the modcluster reverse proxy"
type: "str"
@@ -209,7 +177,6 @@ argument_specs:
description: "List of modproxy node URLs in the format { host, port } for the modcluster reverse proxy"
type: "list"
keycloak_frontend_url:
# line 59 of keycloak/defaults/main.yml
default: "http://localhost"
description: "Frontend URL for keycloak endpoints when a reverse proxy is used"
type: "str"
@@ -218,77 +185,62 @@ argument_specs:
description: "Force backend requests to use the frontend URL"
type: "bool"
keycloak_infinispan_user:
# line 62 of keycloak/defaults/main.yml
default: "supervisor"
description: "Username for connecting to infinispan"
type: "str"
keycloak_infinispan_pass:
# line 63 of keycloak/defaults/main.yml
default: "supervisor"
description: "Password for connecting to infinispan"
type: "str"
keycloak_infinispan_url:
# line 64 of keycloak/defaults/main.yml
default: "localhost"
description: "URL for the infinispan remote-cache server"
type: "str"
keycloak_infinispan_sasl_mechanism:
# line 65 of keycloak/defaults/main.yml
default: "SCRAM-SHA-512"
description: "Authentication type to infinispan server"
type: "str"
keycloak_infinispan_use_ssl:
# line 66 of keycloak/defaults/main.yml
default: false
description: "Enable hotrod client TLS communication"
type: "bool"
keycloak_infinispan_trust_store_path:
# line 68 of keycloak/defaults/main.yml
default: "/etc/pki/java/cacerts"
description: "TODO document argument"
type: "str"
keycloak_infinispan_trust_store_password:
# line 69 of keycloak/defaults/main.yml
default: "changeit"
description: "Path to truststore containing infinispan server certificate"
type: "str"
keycloak_jdbc_engine:
# line 72 of keycloak/defaults/main.yml
default: "postgres"
description: "Backend database flavour when db is enabled: [ postgres, mariadb, sqlserver ]"
type: "str"
keycloak_db_user:
# line 74 of keycloak/defaults/main.yml
default: "keycloak-user"
description: "Username for connecting to database"
type: "str"
keycloak_db_pass:
# line 75 of keycloak/defaults/main.yml
default: "keycloak-pass"
description: "Password for connecting to database"
type: "str"
keycloak_jdbc_url:
# line 76 of keycloak/defaults/main.yml
default: "{{ keycloak_default_jdbc[keycloak_jdbc_engine].url }}"
description: "URL for connecting to backend database"
type: "str"
keycloak_jdbc_driver_version:
# line 77 of keycloak/defaults/main.yml
default: "{{ keycloak_default_jdbc[keycloak_jdbc_engine].version }}"
description: "Version for the JDBC driver to download"
type: "str"
keycloak_admin_password:
# line 4 of keycloak/vars/main.yml
required: true
description: "Password for the administration console user account"
type: "str"
keycloak_url:
# line 12 of keycloak/vars/main.yml
default: "http://{{ keycloak_host }}:{{ keycloak_http_port + keycloak_jboss_port_offset }}"
description: "URL for configuration rest calls"
type: "str"
keycloak_management_url:
# line 13 of keycloak/vars/main.yml
default: "http://{{ keycloak_host }}:{{ keycloak_management_http_port + keycloak_jboss_port_offset }}"
description: "URL for management console rest calls"
type: "str"

View File

@@ -0,0 +1,6 @@
---
- name: Include firewall config tasks
ansible.builtin.include_tasks: iptables.yml
when: keycloak_configure_iptables
tags:
- firewall

View File

@@ -4,14 +4,27 @@
register: 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:
packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_to_install }}"
become: true
ansible.builtin.yum:
name: "{{ packages_to_install }}"
state: present
when: packages_to_install | default([]) | length > 0
when:
- packages_to_install | default([]) | length > 0
- ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}"
become: 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,23 @@
---
- 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: 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_http_port }}"
- "{{ keycloak_https_port }}"
- "{{ keycloak_management_http_port }}"
- "{{ keycloak_management_https_port }}"
- "{{ keycloak_jgroups_port }}"
- "{{ keycloak_ajp_port }}"

View File

@@ -5,11 +5,10 @@
tags:
- prereqs
- name: Include firewall config tasks
ansible.builtin.include_tasks: firewalld.yml
when: keycloak_configure_firewalld
- name: Distro specific tasks
ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml"
tags:
- firewall
- unbound
- name: Include install tasks
ansible.builtin.include_tasks: install.yml
@@ -26,6 +25,7 @@
when:
- sso_apply_patches is defined and sso_apply_patches
- sso_enable is defined and sso_enable
- ansible_facts.os_family == "RedHat"
tags:
- install
- patch

View File

@@ -36,12 +36,20 @@
success_msg: "Configuring JDBC persistence using {{ keycloak_jdbc_engine }} database"
when: keycloak_db_enabled
- 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_jvm_package }}"
- unzip
- procps-ng
- initscripts
- tzdata-java
packages_list: "{{ keycloak_prereq_package_list }}"

View File

@@ -0,0 +1,6 @@
---
- name: Include firewall config tasks
ansible.builtin.include_tasks: firewalld.yml
when: keycloak_configure_firewalld
tags:
- firewall

View File

@@ -36,7 +36,9 @@
- name: Determine patch versions list
ansible.builtin.set_fact:
filtered_versions: "{{ rhn_products.results | map(attribute='file_path') | select('match', '^[^/]*/rh-sso-.*[0-9]*[.][0-9]*[.][0-9]*.*$') | map('regex_replace', '[^/]*/rh-sso-([0-9]*[.][0-9]*[.][0-9]*)-.*', '\\1') | list | unique }}"
filtered_versions: "{{ rhn_products.results | map(attribute='file_path') | \
select('match', '^[^/]*/rh-sso-.*[0-9]*[.][0-9]*[.][0-9]*.*$') | \
map('regex_replace', '[^/]*/rh-sso-([0-9]*[.][0-9]*[.][0-9]*(-[0-9])?)-.*', '\\1') | list | unique }}"
when: sso_patch_version is not defined or sso_patch_version | length == 0
delegate_to: localhost
run_once: true
@@ -70,7 +72,7 @@
middleware_automation.common.product_download: # noqa risky-file-permissions delegated, uses controller host user
client_id: "{{ rhn_username }}"
client_secret: "{{ rhn_password }}"
product_id: "{{ (rhn_filtered_products | first).id }}"
product_id: "{{ (rhn_filtered_products | sort | last).id }}"
dest: "{{ local_path.stat.path }}/{{ patch_bundle }}"
no_log: "{{ omit_rhn_output | default(true) }}"
delegate_to: localhost
@@ -114,7 +116,7 @@
when:
- cli_result is defined
- cli_result.stdout is defined
- patch_version not in cli_result.stdout
- patch_version | regex_replace('-[0-9]$', '') not in cli_result.stdout
block:
- name: "Apply patch {{ patch_version }} to server"
ansible.builtin.include_tasks: rhsso_cli.yml

View File

@@ -10,20 +10,14 @@
notify:
- restart keycloak
- name: Determine JAVA_HOME for selected JVM RPM
ansible.builtin.set_fact:
rpm_java_home: "/etc/alternatives/jre_{{ keycloak_jvm_package | regex_search('(?<=java-)[0-9.]+') }}"
- name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: true
ansible.builtin.template:
src: keycloak-sysconfig.j2
dest: /etc/sysconfig/keycloak
dest: "{{ keycloak_sysconf_file }}"
owner: root
group: root
mode: 0644
vars:
keycloak_rpm_java_home: "{{ rpm_java_home }}"
notify:
- restart keycloak

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>

View File

@@ -1,5 +1,5 @@
#!/bin/bash -eu
# {{ ansible_managed }}
{{ ansible_managed | comment }}
set +u -o pipefail

View File

@@ -1,6 +1,6 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
JAVA_OPTS='{{ keycloak_java_opts }}'
JAVA_HOME={{ keycloak_java_home | default(keycloak_rpm_java_home, true) }}
JAVA_HOME={{ keycloak_java_home | default(keycloak_pkg_java_home, true) }}
JBOSS_HOME={{ keycloak.home }}
KEYCLOAK_BIND_ADDRESS={{ keycloak_bind_address }}
KEYCLOAK_HTTP_PORT={{ keycloak_http_port }}

View File

@@ -1,4 +1,4 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
[Unit]
Description={{ keycloak.service_name }} Server
After=network.target
@@ -11,7 +11,7 @@ StartLimitBurst={{ keycloak_service_startlimitburst }}
User={{ keycloak_service_user }}
Group={{ keycloak_service_group }}
{% endif -%}
EnvironmentFile=-/etc/sysconfig/keycloak
EnvironmentFile=-{{ keycloak_sysconf_file }}
PIDFile={{ keycloak_service_pidfile }}
ExecStart={{ keycloak.home }}/bin/standalone.sh $WILDFLY_OPTS
WorkingDirectory={{ keycloak.home }}

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
@@ -539,7 +539,7 @@
</mail-session>
</subsystem>
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:jboss}"/>
{% if keycloak_modcluster.enabled %}
{% if keycloak_modcluster.enabled %}
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
<proxy name="default" advertise="false" listener="ajp" proxies="{{ ['proxy_'] | product(keycloak_modcluster.reverse_proxy_urls | map(attribute='host')) | map('join') | list | join(' ') }}">
<dynamic-load-provider>
@@ -547,7 +547,7 @@
</dynamic-load-provider>
</proxy>
</subsystem>
{% endif %}
{% endif %}
<subsystem xmlns="urn:jboss:domain:naming:2.0">
<remote-naming/>
</subsystem>
@@ -621,6 +621,6 @@
<remote-destination host="{{ modcluster.host }}" port="{{ modcluster.port }}"/>
</outbound-socket-binding>
{% endfor %}
{% endif %}
{% endif %}
</socket-binding-group>
</server>

View File

@@ -0,0 +1,11 @@
---
keycloak_varjvm_package: "{{ keycloak_jvm_package | default('openjdk-11-jdk-headless') }}"
keycloak_prereq_package_list:
- "{{ keycloak_varjvm_package }}"
- unzip
- procps
- apt
- tzdata
keycloak_configure_iptables: True
keycloak_sysconf_file: /etc/default/keycloak
keycloak_pkg_java_home: "/usr/lib/jvm/java-{{ keycloak_varjvm_package | regex_search('(?!:openjdk-)[0-9.]+') }}-openjdk-{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"

View File

@@ -0,0 +1,10 @@
---
keycloak_varjvm_package: "{{ keycloak_jvm_package | default('java-1.8.0-openjdk-headless') }}"
keycloak_prereq_package_list:
- "{{ keycloak_varjvm_package }}"
- unzip
- procps-ng
- initscripts
- tzdata-java
keycloak_sysconf_file: /etc/sysconfig/keycloak
keycloak_pkg_java_home: "/etc/alternatives/jre_{{ keycloak_varjvm_package | regex_search('(?<=java-)[0-9.]+') }}"

View File

@@ -38,7 +38,9 @@ Role Defaults
|`keycloak_quarkus_service_pidfile`| Pid file path for service | `/run/keycloak.pid` |
|`keycloak_quarkus_jvm_package`| RHEL java package runtime | `java-17-openjdk-headless` |
|`keycloak_quarkus_java_home`| JAVA_HOME of installed JRE, leave empty for using specified keycloak_quarkus_jvm_package RPM path | `None` |
|`keycloak_quarkus_java_opts`| Additional JVM options | `-Xms1024m -Xmx2048m` |
|`keycloak_quarkus_java_opts`| Heap memory JVM setting | `-Xms1024m -Xmx2048m` |
|`keycloak_quarkus_java_jvm_opts`| Other JVM settings | same as keycloak |
|`keycloak_quarkus_java_opts`| JVM arguments; if overriden, it takes precedence over `keycloak_quarkus_java_*` | `{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}` |
|`keycloak_quarkus_frontend_url`| Set the base URL for frontend URLs, including scheme, host, port and path | |
|`keycloak_quarkus_admin_url`| Set the base URL for accessing the administration console, including scheme, host, port and path | |
|`keycloak_quarkus_http_relative_path` | Set the path relative to / for serving resources. The path must start with a / | `/` |
@@ -52,7 +54,7 @@ Role Defaults
|`keycloak_quarkus_https_trust_store_enabled`| Enalbe confiugration of a trust store | `False` |
|`keycloak_quarkus_trust_store_file`| The file pat to the trust store | `{{ keycloak.home }}/conf/trust_store.p12` |
|`keycloak_quarkus_trust_store_password`| Password for the trust store | `""` |
|`keycloak_quarkus_proxy_headers`| Parse reverse proxy headers (`forwarded` or `xforwardedPassword`) | `""` |
* Hostname configuration

View File

@@ -9,7 +9,6 @@ keycloak_quarkus_installdir: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_q
keycloak_quarkus_offline_install: false
### Install location and service settings
keycloak_quarkus_jvm_package: java-17-openjdk-headless
keycloak_quarkus_java_home:
keycloak_quarkus_dest: /opt/keycloak
keycloak_quarkus_home: "{{ keycloak_quarkus_installdir }}"
@@ -18,11 +17,13 @@ keycloak_quarkus_start_dev: false
keycloak_quarkus_service_user: keycloak
keycloak_quarkus_service_group: keycloak
keycloak_quarkus_service_pidfile: "/run/keycloak/keycloak.pid"
keycloak_quarkus_configure_firewalld: false
keycloak_quarkus_service_restart_always: false
keycloak_quarkus_service_restart_on_failure: false
keycloak_quarkus_service_restartsec: "10s"
keycloak_quarkus_configure_firewalld: false
keycloak_quarkus_configure_iptables: false
### administrator console password
keycloak_quarkus_admin_user: admin
keycloak_quarkus_admin_pass:
@@ -38,7 +39,12 @@ keycloak_quarkus_http_port: 8080
keycloak_quarkus_https_port: 8443
keycloak_quarkus_ajp_port: 8009
keycloak_quarkus_jgroups_port: 7800
keycloak_quarkus_java_opts: "-Xms1024m -Xmx2048m"
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx2048m"
keycloak_quarkus_java_jvm_opts: "-XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8
-Dsun.err.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -XX:+ExitOnOutOfMemoryError
-Djava.security.egd=file:/dev/urandom -XX:+UseParallelGC -XX:GCTimeRatio=4
-XX:AdaptiveSizePolicyWeight=90 -XX:FlightRecorderOptions=stackdepth=512"
keycloak_quarkus_java_opts: "{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}"
### TLS/HTTPS configuration
keycloak_quarkus_https_key_file_enabled: false

View File

@@ -2,32 +2,26 @@ argument_specs:
main:
options:
keycloak_quarkus_version:
# line 3 of defaults/main.yml
default: "17.0.1"
default: "23.0.7"
description: "keycloak.org package version"
type: "str"
keycloak_quarkus_archive:
# line 4 of defaults/main.yml
default: "keycloak-{{ keycloak_quarkus_version }}.zip"
description: "keycloak install archive filename"
type: "str"
keycloak_quarkus_download_url:
# line 5 of defaults/main.yml
default: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}"
description: "Download URL for keycloak"
type: "str"
keycloak_quarkus_installdir:
# line 6 of defaults/main.yml
default: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}"
description: "Installation path"
type: "str"
keycloak_quarkus_offline_install:
# line 9 of defaults/main.yml
default: false
description: "Perform an offline install"
type: "bool"
keycloak_quarkus_jvm_package:
# line 12 of defaults/main.yml
default: "java-11-openjdk-headless"
description: "RHEL java package runtime"
type: "str"
@@ -35,37 +29,34 @@ argument_specs:
description: "JAVA_HOME of installed JRE, leave empty for using specified keycloak_jvm_package RPM path"
type: "str"
keycloak_quarkus_dest:
# line 13 of defaults/main.yml
default: "/opt/keycloak"
description: "Installation root path"
type: "str"
keycloak_quarkus_home:
# line 14 of defaults/main.yml
default: "{{ keycloak_quarkus_installdir }}"
description: "Installation work directory"
type: "str"
keycloak_quarkus_config_dir:
# line 15 of defaults/main.yml
default: "{{ keycloak_quarkus_home }}/conf"
description: "Path for configuration"
type: "str"
keycloak_quarkus_service_user:
# line 16 of defaults/main.yml
default: "keycloak"
description: "Posix account username"
type: "str"
keycloak_quarkus_service_group:
# line 17 of defaults/main.yml
default: "keycloak"
description: "Posix account group"
type: "str"
keycloak_quarkus_service_pidfile:
# line 18 of defaults/main.yml
default: "/run/keycloak/keycloak.pid"
description: "Pid file path for service"
type: "str"
keycloak_quarkus_configure_firewalld:
# line 19 of defaults/main.yml
default: false
description: "Ensure firewalld is running and configure keycloak ports"
type: "bool"
keycloak_quarkus_configure_iptables:
default: false
description: "Ensure firewalld is running and configure keycloak ports"
type: "bool"
@@ -90,12 +81,10 @@ argument_specs:
description: "Password of console admin account"
type: "str"
keycloak_quarkus_master_realm:
# line 24 of defaults/main.yml
default: "master"
description: "Name for rest authentication realm"
type: "str"
keycloak_quarkus_bind_address:
# line 27 of defaults/main.yml
default: "0.0.0.0"
description: "Address for binding service ports"
type: "str"
@@ -116,7 +105,6 @@ argument_specs:
description: "Enable listener on HTTP port"
type: "bool"
keycloak_quarkus_http_port:
# line 29 of defaults/main.yml
default: 8080
description: "HTTP port"
type: "int"
@@ -157,27 +145,30 @@ argument_specs:
description: "Password for the trust store"
type: "str"
keycloak_quarkus_https_port:
# line 30 of defaults/main.yml
default: 8443
description: "HTTPS port"
type: "int"
keycloak_quarkus_ajp_port:
# line 31 of defaults/main.yml
default: 8009
description: "AJP port"
type: "int"
keycloak_quarkus_jgroups_port:
# line 32 of defaults/main.yml
default: 7800
description: "jgroups cluster tcp port"
type: "int"
keycloak_quarkus_java_opts:
# line 33 of defaults/main.yml
keycloak_quarkus_java_heap_opts:
default: "-Xms1024m -Xmx2048m"
description: "Additional JVM options"
description: "Heap memory JVM setting"
type: "str"
keycloak_quarkus_java_jvm_opts:
default: "-XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.err.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -XX:+ExitOnOutOfMemoryError -Djava.security.egd=file:/dev/urandom -XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:FlightRecorderOptions=stackdepth=512"
description: "Other JVM settings"
type: "str"
keycloak_quarkus_java_opts:
default: "{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}"
description: "JVM arguments, by default heap_opts + jvm_opts, if overriden it takes precedence over them"
type: "str"
keycloak_quarkus_ha_enabled:
# line 36 of defaults/main.yml
default: false
description: "Enable auto configuration for database backend, clustering and remote caches on infinispan"
type: "bool"
@@ -186,7 +177,6 @@ argument_specs:
description: "Discovery protocol for HA cluster members"
type: "str"
keycloak_quarkus_db_enabled:
# line 38 of defaults/main.yml
default: "{{ True if keycloak_quarkus_ha_enabled else False }}"
description: "Enable auto configuration for database backend"
type: "str"
@@ -204,7 +194,6 @@ argument_specs:
description: "Service URL for the admin console"
type: "str"
keycloak_quarkus_metrics_enabled:
# line 43 of defaults/main.yml
default: false
description: "Whether to enable metrics"
type: "bool"
@@ -213,62 +202,50 @@ argument_specs:
description: "If the server should expose health check endpoints"
type: "bool"
keycloak_quarkus_ispn_user:
# line 46 of defaults/main.yml
default: "supervisor"
description: "Username for connecting to infinispan"
type: "str"
keycloak_quarkus_ispn_pass:
# line 47 of defaults/main.yml
default: "supervisor"
description: "Password for connecting to infinispan"
type: "str"
keycloak_quarkus_ispn_hosts:
# line 48 of defaults/main.yml
default: "localhost:11222"
description: "host name/port for connecting to infinispan, eg. host1:11222;host2:11222"
type: "str"
keycloak_quarkus_ispn_sasl_mechanism:
# line 49 of defaults/main.yml
default: "SCRAM-SHA-512"
description: "Infinispan auth mechanism"
type: "str"
keycloak_quarkus_ispn_use_ssl:
# line 50 of defaults/main.yml
default: false
description: "Whether infinispan uses TLS connection"
type: "bool"
keycloak_quarkus_ispn_trust_store_path:
# line 52 of defaults/main.yml
default: "/etc/pki/java/cacerts"
description: "Path to infinispan server trust certificate"
type: "str"
keycloak_quarkus_ispn_trust_store_password:
# line 53 of defaults/main.yml
default: "changeit"
description: "Password for infinispan certificate keystore"
type: "str"
keycloak_quarkus_jdbc_engine:
# line 56 of defaults/main.yml
default: "postgres"
description: "Database engine [mariadb,postres,mssql]"
type: "str"
keycloak_quarkus_db_user:
# line 58 of defaults/main.yml
default: "keycloak-user"
description: "User for database connection"
type: "str"
keycloak_quarkus_db_pass:
# line 59 of defaults/main.yml
default: "keycloak-pass"
description: "Password for database connection"
type: "str"
keycloak_quarkus_jdbc_url:
# line 60 of defaults/main.yml
default: "{{ keycloak_quarkus_default_jdbc[keycloak_quarkus_jdbc_engine].url }}"
description: "JDBC URL for connecting to database"
type: "str"
keycloak_quarkus_jdbc_driver_version:
# line 61 of defaults/main.yml
default: "{{ keycloak_quarkus_default_jdbc[keycloak_quarkus_jdbc_engine].version }}"
description: "Version for JDBC driver"
type: "str"
@@ -308,6 +285,10 @@ argument_specs:
default: 'edge'
type: "str"
description: "The proxy address forwarding mode if the server is behind a reverse proxy. Set to 'none' if not using a proxy"
keycloak_quarkus_proxy_headers:
default: ""
type: "str"
description: "Parse reverse proxy headers (`forwarded` or `xforwardedPassword`), overrides the deprecated keycloak_quarkus_proxy_mode argument"
keycloak_quarkus_start_dev:
default: false
type: "bool"

View File

@@ -14,6 +14,11 @@ galaxy_info:
- name: EL
versions:
- "8"
- "9"
- name: Fedora
- name: Debian
- name: Ubuntu
galaxy_tags:
- keycloak
@@ -25,3 +30,4 @@ galaxy_info:
- identity
- security
- rhbk
- debian

View File

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

View File

@@ -4,14 +4,27 @@
register: 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:
packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_to_install }}"
become: true
ansible.builtin.yum:
ansible.builtin.dnf:
name: "{{ packages_to_install }}"
state: present
when: packages_to_install | default([]) | length > 0
when:
- packages_to_install | default([]) | length > 0
- ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}"
become: true
ansible.builtin.package:
name: "{{ packages_list }}"
state: present
when:
- packages_list | default([]) | length > 0
- ansible_facts.os_family == "Debian"

View File

@@ -31,7 +31,7 @@
state: directory
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0750
mode: '0750'
## check remote archive
- name: Set download archive path
@@ -50,13 +50,15 @@
path: "{{ lookup('env', 'PWD') }}"
register: local_path
delegate_to: localhost
become: false
- name: Download keycloak archive
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_quarkus_download_url }}"
dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: 0640
mode: '0640'
delegate_to: localhost
become: false
run_once: true
when:
- archive_path is defined
@@ -116,7 +118,7 @@
dest: "{{ archive }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0640
mode: '0640'
register: new_version_downloaded
when:
- not archive_path.stat.exists

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

@@ -6,7 +6,7 @@
dest: "{{ keycloak.home }}/providers"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0640
mode: '0640'
become: true
notify:
- restart keycloak

View File

@@ -4,12 +4,12 @@
ansible.builtin.include_tasks: prereqs.yml
tags:
- prereqs
- always
- name: Include firewall config tasks
ansible.builtin.include_tasks: firewalld.yml
when: keycloak_quarkus_configure_firewalld
- name: Distro specific tasks
ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml"
tags:
- firewall
- unbound
- name: Include install tasks
ansible.builtin.include_tasks: install.yml
@@ -27,7 +27,7 @@
dest: "{{ keycloak.home }}/conf/keycloak.conf"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0644
mode: '0640'
become: true
notify:
- rebuild keycloak config
@@ -39,7 +39,7 @@
dest: "{{ keycloak.home }}/conf/quarkus.properties"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0644
mode: '0640'
become: true
notify:
- restart keycloak
@@ -60,11 +60,11 @@
- name: "Configure infinispan config for keycloak service"
ansible.builtin.template:
src: cache-ispn.xml
src: cache-ispn.xml.j2
dest: "{{ keycloak.home }}/conf/cache-ispn.xml"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0644
mode: '0640'
become: true
notify:
- rebuild keycloak config
@@ -76,7 +76,7 @@
path: "{{ keycloak.log.file | dirname }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: 0775
mode: '0775'
become: true
- name: Flush pending handlers

View File

@@ -6,7 +6,7 @@
quiet: true
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_admin_pass variable to a 12+ char long string"
success_msg: "{{ 'Console administrator password OK' }}"
- name: Validate relative path
ansible.builtin.assert:
that:
@@ -23,12 +23,20 @@
fail_msg: "Cannot install HA setup without 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_jvm_package }}"
- unzip
- procps-ng
- initscripts
- tzdata-java
packages_list: "{{ keycloak_quarkus_prereq_package_list }}"

View File

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

View File

@@ -1,18 +1,14 @@
---
- name: Determine JAVA_HOME for selected JVM RPM
ansible.builtin.set_fact:
rpm_java_home: "/etc/alternatives/jre_{{ keycloak_quarkus_jvm_package | regex_search('(?<=java-)[0-9.]+') }}"
- name: "Configure sysconfig file for keycloak service"
- name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: true
ansible.builtin.template:
src: keycloak-sysconfig.j2
dest: /etc/sysconfig/keycloak
dest: "{{ keycloak_quarkus_sysconf_file }}"
owner: root
group: root
mode: 0644
mode: '0640'
vars:
keycloak_rpm_java_home: "{{ rpm_java_home }}"
keycloak_pkg_java_home: "{{ keycloak_quarkus_pkg_java_home }}"
notify:
- restart keycloak
@@ -22,7 +18,7 @@
dest: /etc/systemd/system/keycloak.service
owner: root
group: root
mode: 0644
mode: '0644'
become: true
register: systemdunit
notify:

View File

@@ -1,4 +1,4 @@
<!-- {{ ansible_managed }} -->
{{ ansible_managed | comment('xml') }}
<!--
~ Copyright 2019 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.

View File

@@ -1,6 +1,6 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
KEYCLOAK_ADMIN={{ keycloak_quarkus_admin_user }}
KEYCLOAK_ADMIN_PASSWORD='{{ keycloak_quarkus_admin_pass }}'
PATH={{ keycloak_quarkus_java_home | default(keycloak_rpm_java_home, true) }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JAVA_HOME={{ keycloak_quarkus_java_home | default(keycloak_rpm_java_home, true) }}
JAVA_OPTS_APPEND={{ keycloak_quarkus_java_opts }}
PATH={{ keycloak_quarkus_java_home | default(keycloak_pkg_java_home, true) }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JAVA_HOME={{ keycloak_quarkus_java_home | default(keycloak_pkg_java_home, true) }}
JAVA_OPTS={{ keycloak_quarkus_java_opts }}

View File

@@ -1,4 +1,4 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
{% if keycloak_quarkus_db_enabled %}
# Database
@@ -54,9 +54,14 @@ cache-config-file=cache-ispn.xml
{% endif %}
{% if keycloak_quarkus_proxy_mode is defined and keycloak_quarkus_proxy_mode != "none" %}
# Proxy
# Deprecated Proxy configuration
proxy={{ keycloak_quarkus_proxy_mode }}
{% endif %}
{% if keycloak_quarkus_proxy_headers is defined and keycloak_quarkus_proxy_headers != "none" %}
# Proxy
proxy-headers={{ keycloak_quarkus_proxy_headers }}
{% endif %}
spi-sticky-session-encoder-infinispan-should-attach-route={{ keycloak_quarkus_spi_sticky_session_encoder_infinispan_should_attach_route | d(true) | lower }}
# Transaction

View File

@@ -1,11 +1,11 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
[Unit]
Description=Keycloak Server
After=network.target
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/keycloak
EnvironmentFile=-{{ keycloak_quarkus_sysconf_file }}
PIDFile={{ keycloak_quarkus_service_pidfile }}
{% if keycloak_quarkus_start_dev %}
ExecStart={{ keycloak.home }}/bin/kc.sh start-dev

View File

@@ -1,4 +1,4 @@
# {{ ansible_managed }}
{{ ansible_managed | comment }}
{% if keycloak_quarkus_ha_enabled %}
{% if not rhbk_enable or keycloak_quarkus_version.split('.')[0]|int < 22 %}
quarkus.infinispan-client.server-list={{ keycloak_quarkus_ispn_hosts }}

View File

@@ -0,0 +1,10 @@
---
keycloak_quarkus_varjvm_package: "{{ keycloak_quarkus_jvm_package | default('openjdk-17-jdk-headless') }}"
keycloak_quarkus_prereq_package_list:
- "{{ keycloak_quarkus_varjvm_package }}"
- unzip
- procps
- apt
- tzdata
keycloak_quarkus_sysconf_file: /etc/default/keycloak
keycloak_quarkus_pkg_java_home: "/usr/lib/jvm/java-{{ keycloak_quarkus_varjvm_package | regex_search('(?!:openjdk-)[0-9.]+') }}-openjdk-{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}"

View File

@@ -0,0 +1,10 @@
---
keycloak_quarkus_varjvm_package: "{{ keycloak_quarkus_jvm_package | default('java-17-openjdk-headless') }}"
keycloak_quarkus_prereq_package_list:
- "{{ keycloak_quarkus_varjvm_package }}"
- unzip
- procps-ng
- initscripts
- tzdata-java
keycloak_quarkus_sysconf_file: /etc/sysconfig/keycloak
keycloak_quarkus_pkg_java_home: "/etc/alternatives/jre_{{ keycloak_quarkus_varjvm_package | regex_search('(?<=java-)[0-9.]+') }}"

View File

@@ -41,11 +41,11 @@
auth_realm: "{{ keycloak_auth_realm }}"
auth_username: "{{ keycloak_admin_user }}"
auth_password: "{{ keycloak_admin_password }}"
realm: "{{ item.realm }}"
realm: "{{ item.realm | default(keycloak_realm) }}"
name: "{{ item.name }}"
state: present
provider_id: "{{ item.provider_id }}"
provider_type: "{{ item.provider_type | default(org.keycloak.storage.UserStorageProvider) }}"
provider_type: "{{ item.provider_type | default(org.keycloak.storage.UserStorageProvider) }}"
config: "{{ item.config }}"
mappers: "{{ item.mappers | default(omit) }}"
no_log: "{{ keycloak_no_log | default('True') }}"
@@ -71,7 +71,7 @@
auth_realm: "{{ keycloak_auth_realm }}"
auth_username: "{{ keycloak_admin_user }}"
auth_password: "{{ keycloak_admin_password }}"
realm: "{{ item.realm }}"
realm: "{{ item.realm | default(keycloak_realm) }}"
default_roles: "{{ item.roles | default(omit) }}"
client_id: "{{ item.client_id | default(omit) }}"
id: "{{ item.id | default(omit) }}"

View File

@@ -1,7 +1,7 @@
- name: Create client roles
middleware_automation.keycloak.keycloak_role:
name: "{{ item }}"
realm: "{{ client.realm }}"
realm: "{{ client.realm | default(keycloak_realm) }}"
client_id: "{{ client.name }}"
auth_client_id: "{{ keycloak_auth_client }}"
auth_keycloak_url: "{{ keycloak_url }}{{ keycloak_context }}"

View File

@@ -1,7 +1,7 @@
---
- name: "Get Realm for role"
ansible.builtin.uri:
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm }}"
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm | default(keycloak_realm) }}"
method: GET
status_code:
- 200
@@ -12,7 +12,7 @@
- name: Check if Mapping is available
ansible.builtin.uri:
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}/available"
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm | default(keycloak_realm) }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}/available"
method: GET
status_code:
- 200
@@ -23,7 +23,7 @@
- name: "Create Role Mapping"
ansible.builtin.uri:
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}"
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm | default(keycloak_realm) }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}"
method: POST
body:
- id: "{{ item.id }}"