Compare commits

...

34 Commits
3.0.3 ... 3.0.4

Author SHA1 Message Date
ansible-middleware-core
c6e3337778 Update changelog for release 3.0.4
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2026-05-20 13:38:01 +00:00
Harsha Cherukuri
d1b295f551 Merge pull request #332 from cihlamar/AMW-522
Fix certification requirements for Keycloak
2026-05-20 09:36:11 -04:00
Martin Cihlar
5e13f4ea50 Fix certification requirements for Keycloak
- Add .ansible-lint, .DS_Store to build_ignore in galaxy.yml
- Add Release and Upgrade Notes section to README

[AMW-522](https://redhat.atlassian.net/browse/AMW-522)
2026-05-20 11:00:58 +02:00
Harsha Cherukuri
06cf664b08 Merge pull request #331 from RanabirChakraborty/SET-1341
SET-1341 Without ansible-core tag tests are failing in keycloak
2026-04-30 03:33:48 -04:00
Ranabir Chakraborty
e5690d7513 SET-1341 Without ansible-core tag tests are failing in keycloak 2026-04-28 22:15:14 +05:30
Ranabir Chakraborty
fb76736441 Merge pull request #330 from nwintering/main
Check that `data/tmp` directory has correct ownership
2026-04-28 18:24:19 +05:30
nwintering
6d00dcff48 check that tmp directory has correct permissions 2026-04-27 15:35:03 +02:00
Harsha Cherukuri
eaf9964aab Fix docs pipeline 2026-04-24 10:50:10 -04:00
Harsha Cherukuri
180f075a9f Merge pull request #324 from RanabirChakraborty/AMW-518
AMW-518 Validating arguments against arg spec 'main' fails unexpectedly.
2026-04-24 10:34:55 -04:00
Harsha Cherukuri
1013a05f8c Merge pull request #328 from hcherukuri/main
Fix sanity and molecule tests
2026-04-24 10:26:26 -04:00
Harsha Cherukuri
22f1ce516d Fix sanity and molecule tests 2026-04-24 10:07:24 -04:00
Harsha Cherukuri
7be872cc48 Merge pull request #326 from paulomenon/add/example-playbooks-client-scope-auth-flow
Add/example playbooks client scope auth flow
2026-04-24 08:29:16 -04:00
Harsha Cherukuri
55248de9ae Merge pull request #325 from paulomenon/fix/keycloak-context-default
Fix keycloak_context default from /auth to empty string for Quarkus-based Keycloak
2026-04-24 08:29:00 -04:00
Harsha Cherukuri
c6d4dfb8bb Merge pull request #327 from hcherukuri/main
Fix CI
2026-04-24 08:16:25 -04:00
Harsha Cherukuri
c8f4065eb5 Fix CI 2026-04-24 08:00:45 -04:00
pamenon
06e096ac50 Add module documentation to collection and role READMEs
Document all six modules (including the two new ones) in the main
collection README under a new 'Included modules' section. Add the
three new example playbooks to the Config Playbooks section. Update
the keycloak_realm role README with a 'Related Modules' table and
inline examples for keycloak_client_scope and
keycloak_authentication_flow usage.

Made-with: Cursor
2026-04-23 12:54:22 +01:00
pamenon
c6189bfc51 Add keycloak_client_scope and keycloak_authentication_flow modules with example playbooks
The collection was missing modules for managing client scopes and
authentication flows, forcing users to write raw uri calls against
the Keycloak Admin REST API. This adds two new modules that leverage
the existing KeycloakAPI helper methods:

- keycloak_client_scope: create/update/delete client scopes with
  protocol mappers (supports check_mode and diff)
- keycloak_authentication_flow: create/delete authentication flows
  with execution steps, or copy existing flows (supports check_mode
  and diff)

Also adds three example playbooks using the new modules:
- keycloak_client_scope.yml
- keycloak_authentication_flow.yml
- keycloak_realm_client.yml

Made-with: Cursor
2026-04-23 12:53:03 +01:00
pamenon
03fffaaf5f Fix keycloak_context default from /auth to empty string
The /auth context path was used by legacy WildFly-based Keycloak but
is no longer needed for Quarkus-based Keycloak (17+) or RHBK. The
current default of /auth forces users to explicitly pass an empty
keycloak_context to avoid broken API URLs.

This changes the default to an empty string, updates argument_specs
and README documentation, and removes the now-unnecessary
keycloak_context: '' overrides from all molecule converge files.

Users on legacy WildFly-based Keycloak can still set
keycloak_context: /auth explicitly.

Made-with: Cursor
2026-04-23 12:25:03 +01:00
Ranabir Chakraborty
a337a1d70c AMW-518 Validating arguments against arg spec 'main' fails unexpectedly. 2026-04-17 19:24:46 +05:30
Ranabir Chakraborty
28168a9a4f Merge pull request #307 from sgoericke/fix-client-id
manage_client_roles.yml: use "client.id" instead of "client.name" to fix client role creation.
2026-04-09 23:37:31 +05:30
Helmut Wolf
64469b6fac Merge pull request #320 from world-direct/fix/ispn_config
fix: include ispn config file conditionally
2026-01-15 08:05:26 +01:00
Ranabir Chakraborty
75e308b710 Merge pull request #321 from RanabirChakraborty/AMW-467
AMW-467 Download keycloak binary from password protected HTTP location
2026-01-14 21:59:45 +05:30
Ranabir Chakraborty
9cdf24ce28 AMW-467 Download keycloak binary from password protected HTTP location 2026-01-13 23:48:01 +05:30
Helmut Wolf
a00a602c3c fix: include ispn config file conditionally 2026-01-13 10:17:04 +01:00
Ranabir Chakraborty
a5a75c6d46 Merge pull request #317 from SLedunois/main
v26.4.x compability
2026-01-12 23:34:28 +05:30
Helmut Wolf
7212e572cd chore(defaults): raise default keycloak/rhbk versions to 26.4.7 2026-01-12 10:28:47 +01:00
Helmut Wolf
bc669ce0cd chore(deps): Update default SQL driver versions
As per https://access.redhat.com/articles/7027683
2026-01-12 10:28:47 +01:00
Simon LEDUNOIS
3c097ebf09 chore: add molecule test for quarkus ha 26.4- 2026-01-12 10:28:47 +01:00
Simon Ledunois
9562bf727e chore: manage infinispan configuration file 2026-01-12 10:28:47 +01:00
Simon Ledunois
6c3e327294 chore: upgrade to 26.4.7 2026-01-12 10:28:47 +01:00
Ranabir Chakraborty
be0c8a4ae3 Merge pull request #319 from RanabirChakraborty/fixing-lint
Removing parseable from lint file as Additional properties are not allowed
2026-01-09 22:16:00 +05:30
Ranabir Chakraborty
6bf10cc3e9 Removing parseable from lint file as Additional properties are not allowed 2026-01-09 22:14:03 +05:30
ansible-middleware-core
d0161dbeef Bump version to 3.0.4 2025-12-16 15:53:14 +00:00
Sven Goericke
07063353b8 fixed wrong variable lookup 2025-07-16 13:50:07 +02:00
83 changed files with 2120 additions and 242 deletions

View File

@@ -40,4 +40,3 @@ skip_list:
- var-naming[no-role-prefix]
use_default_rules: true
parseable: true

View File

@@ -15,14 +15,11 @@ on:
jobs:
ci:
uses: ansible-middleware/github-actions/.github/workflows/cish.yml@main
uses: ansible-middleware/github-actions/.github/workflows/ci.yml@rootperm
secrets: inherit
with:
fqcn: 'middleware_automation/keycloak'
root_permission_varname: 'keycloak_install_requires_become'
debug_verbosity: "${{ github.event.inputs.debug_verbosity }}"
molecule_tests: >-
[ "debian", "quarkus", "quarkus_ha", "quarkus_ha_remote" ]
podman_tests_current: >-
[ "default", "quarkus_devmode", "quarkus_upgrade" ]
podman_tests_next: >-
[ "default", "quarkus_devmode", "quarkus_upgrade" ]
[ "debian", "quarkus", "quarkus_ha", "quarkus_ha_remote", "quarkus_ha_26.4_below", "default", "quarkus_devmode", "quarkus_upgrade" ]

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ changelogs/.plugin-cache.yaml
*.pem
*.key
*.p12
.ansible/

View File

@@ -6,6 +6,25 @@ middleware\_automation.keycloak Release Notes
This changelog describes changes after version 0.2.6.
v3.0.4
======
Major Changes
-------------
- AMW-467 Download keycloak binary from password protected HTTP location `#321 <https://github.com/ansible-middleware/keycloak/pull/321>`_
- v26.4.x compability `#317 <https://github.com/ansible-middleware/keycloak/pull/317>`_
Minor Changes
-------------
- AMW-518 Validating arguments against arg spec 'main' fails unexpectedly. `#324 <https://github.com/ansible-middleware/keycloak/pull/324>`_
Bugfixes
--------
- Removing parseable from lint file as Additional properties are not allowed `#319 <https://github.com/ansible-middleware/keycloak/pull/319>`_
v3.0.3
======

View File

@@ -55,6 +55,15 @@ A requirement file is provided to install:
<!--end roles_paths -->
### Included modules
* `keycloak_realm`: module for managing Keycloak realms (create/update/delete).
* `keycloak_client`: module for managing Keycloak clients (create/update/delete).
* `keycloak_role`: module for managing Keycloak roles — realm roles and client roles (create/update/delete).
* `keycloak_user_federation`: module for managing user federations such as LDAP/AD (create/update/delete).
* `keycloak_client_scope`: module for managing client scopes and protocol mappers (create/update/delete).
* `keycloak_authentication_flow`: module for managing authentication flows and execution steps (create/delete, copy existing flows).
## Usage
@@ -109,10 +118,13 @@ Note: when deploying clustered configurations, all hosts belonging to the cluste
## Configuration
### Config Playbook
### Config Playbooks
<!--start rhbk_realm_playbook -->
[`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s).
* [`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s).
<!--end rhbk_realm_playbook -->
* [`playbooks/keycloak_realm_client.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm_client.yml) creates a realm with clients, roles and users using the `keycloak_realm` role.
* [`playbooks/keycloak_client_scope.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_client_scope.yml) creates a client scope with protocol mappers using the `keycloak_client_scope` module.
* [`playbooks/keycloak_authentication_flow.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_authentication_flow.yml) creates a custom authentication flow with execution steps using the `keycloak_authentication_flow` module.
### Example configuration command
@@ -134,10 +146,21 @@ ansible-playbook -i <ansible_hosts> playbooks/keycloak_realm.yml -e keycloak_adm
For full configuration details, refer to the [keycloak_realm role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md).
<!--end rhbk_realm_readme -->
## Support
<!--start support -->
For bug reports and feature requests, use [GitHub Issues](https://github.com/ansible-middleware/keycloak/issues).
<!--end support -->
## Release and Upgrade Notes
For details on changes between versions, please see the [CHANGELOG](https://github.com/ansible-middleware/keycloak/blob/main/CHANGELOG.rst) for this collection.
## License
Apache License v2.0 or later

View File

@@ -755,3 +755,29 @@ releases:
- 310.yaml
- 312.yaml
release_date: '2025-12-16'
3.0.4:
changes:
bugfixes:
- 'Removing parseable from lint file as Additional properties are not allowed
`#319 <https://github.com/ansible-middleware/keycloak/pull/319>`_
'
major_changes:
- 'AMW-467 Download keycloak binary from password protected HTTP location `#321
<https://github.com/ansible-middleware/keycloak/pull/321>`_
'
- 'v26.4.x compability `#317 <https://github.com/ansible-middleware/keycloak/pull/317>`_
'
minor_changes:
- 'AMW-518 Validating arguments against arg spec ''main'' fails unexpectedly.
`#324 <https://github.com/ansible-middleware/keycloak/pull/324>`_
'
fragments:
- 317.yaml
- 319.yaml
- 321.yaml
- 324.yaml
release_date: '2026-05-20'

View File

@@ -64,7 +64,7 @@ master_doc = 'index'
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.

View File

@@ -1,3 +1,5 @@
# ansible_basic_sphinx_ext still imports pkg_resources (removed in setuptools 82+).
setuptools>=70.0.0,<81.0.0
antsibull>=0.17.0
antsibull-docs
antsibull-changelog

View File

@@ -1,7 +1,7 @@
---
namespace: middleware_automation
name: keycloak
version: "3.0.3"
version: "3.0.4"
readme: README.md
authors:
- Romain Pelisse <rpelisse@redhat.com>
@@ -35,7 +35,9 @@ issues: https://github.com/ansible-middleware/keycloak/issues
build_ignore:
- .gitignore
- .github
- .ansible-lint
- .yamllint
- .DS_Store
- '*.tar.gz'
- '*.zip'
- molecule

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
@@ -13,7 +15,6 @@
- role: keycloak_quarkus
- role: keycloak_realm
keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_context: ''
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_users:

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
tasks:
- name: Install sudo

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
@@ -18,7 +20,6 @@
- role: keycloak_quarkus
- role: keycloak_realm
keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_context: ''
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_users:

View File

@@ -24,6 +24,9 @@ provisioner:
converge: converge.yml
verify: verify.yml
inventory:
group_vars:
all:
keycloak_install_requires_become: true
host_vars:
localhost:
ansible_python_interpreter: "{{ ansible_playbook_python }}"

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
vars:
sudo_pkg_name: sudo
@@ -18,7 +20,7 @@
- name: Download keycloak archive to controller directory
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: https://github.com/keycloak/keycloak/releases/download/26.3.0/keycloak-26.3.0.zip
url: https://github.com/keycloak/keycloak/releases/download/26.4.7/keycloak-26.4.7.zip
dest: /tmp/keycloak
mode: '0640'
delegate_to: localhost

View File

@@ -0,0 +1,26 @@
---
keycloak_quarkus_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_systemd_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_install_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_firewalld_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_iptables_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_jdbc_driver_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_config_store_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_restart_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_start_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_rebuild_config_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_fastpackages_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_bootstrapped_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_quarkus_invalidate_theme_cache_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_systemd_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_install_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_firewalld_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_iptables_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_jdbc_driver_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_restart_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_start_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_stop_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_fastpackages_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
keycloak_rhsso_patch_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"
molecule_prepare_require_privilege_escalation: "{{ keycloak_install_requires_become | default(true) }}"

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Install sudo
ansible.builtin.dnf:
@@ -27,6 +29,8 @@
pre_tasks:
- 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=proxy'
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: false
- name: Make certificate directory
@@ -39,11 +43,11 @@
src: "{{ item.name }}"
dest: "{{ item.dest }}"
mode: 0444
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
loop:
- { name: 'cert.pem', dest: '/etc/nginx/tls/certificate.crt' }
- { name: 'key.pem', dest: '/etc/nginx/tls/certificate.key' }
- name: Update CA trust
ansible.builtin.command: update-ca-trust
changed_when: false
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_config_override_template: custom.xml.j2

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
vars:
sudo_pkg_name: sudo

View File

@@ -25,11 +25,12 @@
fail_msg: "sudo is not installed on target system"
- name: "Install iproute"
become: true
ansible.builtin.yum:
name:
- iproute
state: present
when:
- ansible_user_id == 'root'
- name: "Retrieve assets server from env"
ansible.builtin.set_fact:

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
@@ -23,7 +25,7 @@
keycloak_quarkus_systemd_wait_for_delay: 2
keycloak_quarkus_systemd_wait_for_log: true
keycloak_quarkus_restart_health_check: false # would fail because of self-signed cert
keycloak_quarkus_version: 26.3.0
keycloak_quarkus_version: 26.4.7
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx1024m"
keycloak_quarkus_additional_env_vars:
- key: KC_FEATURES_DISABLED
@@ -46,7 +48,7 @@
repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4
group_id: org.keycloak
artifact_id: keycloak-kerberos-federation
version: 26.3.0 # optional
version: 26.4.7 # optional
# username: myUser # optional
# password: myPAT # optional
# - id: my-static-theme
@@ -61,7 +63,6 @@
- role: keycloak_quarkus
- role: keycloak_realm
keycloak_url: http://instance:8080
keycloak_context: ''
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_default_roles:

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
@@ -11,11 +13,13 @@
- 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'
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: false
- name: Create vault directory
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file:
state: directory
path: "/opt/keycloak/vault"
@@ -26,18 +30,20 @@
ansible.builtin.package:
name: java-21-openjdk-headless
state: present
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
failed_when: false
- name: Create vault keystore
ansible.builtin.command: keytool -importpass -alias TestRealm_testalias -keystore keystore.p12 -storepass keystorepassword
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
register: keytool_cmd
changed_when: False
failed_when: not 'already exists' in keytool_cmd.stdout and keytool_cmd.rc != 0
- name: Copy certificates and vault
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.copy:
src: keystore.p12
dest: /opt/keycloak/vault/keystore.p12

View File

@@ -1,6 +1,8 @@
---
- name: Verify
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
@@ -56,7 +58,7 @@
fail_msg: "Service log symlink not correctly created"
- name: Check log file
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat:
path: /tmp/keycloak/keycloak.log
register: keycloak_log_file
@@ -68,7 +70,7 @@
- not keycloak_log_file.stat.isdir
- name: Check default log folder
become: yes
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat:
path: /var/log/keycloak
register: keycloak_default_log_folder
@@ -80,7 +82,7 @@
- not keycloak_default_log_folder.stat.exists
- name: Verify vault SPI in logfile
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.shell: |
set -o pipefail
zgrep 'Configured KeystoreVaultProviderFactory with the keystore file' /opt/keycloak/keycloak-*/data/log/keycloak.log*zip

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
@@ -17,7 +19,6 @@
- role: keycloak_quarkus
- role: keycloak_realm
keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_context: ''
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_default_roles:

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Install sudo
ansible.builtin.apt:
@@ -15,7 +17,7 @@
ansible.builtin.include_tasks: ../prepare.yml
- name: Install JDK17
become: yes
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.yum:
name:
- java-17-openjdk-headless
@@ -24,7 +26,7 @@
- ansible_facts.os_family == 'RedHat'
- name: Link default logs directory
become: yes
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file:
state: link
src: "{{ item }}"

View File

@@ -1,6 +1,8 @@
---
- name: Converge
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"

View File

@@ -42,7 +42,7 @@ platforms:
mounts:
- type: bind
target: /etc/postgresql/postgresql.conf
source: ${PWD}/molecule/quarkus_ha/postgresql/postgresql.conf
source: ${MOLECULE_PROJECT_DIRECTORY}/molecule/quarkus_ha/postgresql/postgresql.conf
env:
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: mysecretpass

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
@@ -11,11 +13,13 @@
- name: Create certificate request
ansible.builtin.command: "openssl req -x509 -newkey rsa:4096 -keyout {{ inventory_hostname }}.key -out {{ inventory_hostname }}.pem -sha256 -days 365 -nodes -subj '/CN={{ inventory_hostname }}'"
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: False
- name: Create vault directory
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file:
state: directory
path: "/opt/keycloak/vault"
@@ -26,7 +30,7 @@
ansible.builtin.package:
name: "{{ 'java-17-openjdk-headless' if hera_home | length > 0 else 'openjdk-17-jdk-headless' }}"
state: present
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
failed_when: false
- name: Create vault keystore
@@ -37,7 +41,7 @@
failed_when: not 'already exists' in keytool_cmd.stdout and keytool_cmd.rc != 0
- name: Copy certificates and vault
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.copy:
src: keystore.p12
dest: /opt/keycloak/vault/keystore.p12

View File

@@ -1,6 +1,8 @@
---
- name: Verify
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
@@ -17,7 +19,7 @@
hera_home: "{{ lookup('env', 'HERA_HOME') }}"
- name: Check log file
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat:
path: /var/log/keycloak/keycloak.log
register: keycloak_log_file

View File

@@ -0,0 +1,32 @@
---
- name: Converge
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_quarkus_hostname: "http://{{ inventory_hostname }}:8080"
keycloak_quarkus_log: file
keycloak_quarkus_log_level: info
keycloak_quarkus_https_key_file_enabled: true
keycloak_quarkus_key_file_copy_enabled: true
keycloak_quarkus_key_content: "{{ lookup('file', inventory_hostname + '.key') }}"
keycloak_quarkus_cert_file_copy_enabled: true
keycloak_quarkus_cert_file_src: "{{ inventory_hostname }}.pem"
keycloak_quarkus_ks_vault_enabled: true
keycloak_quarkus_ks_vault_file: "/opt/keycloak/vault/keystore.p12"
keycloak_quarkus_ks_vault_pass: keystorepassword
keycloak_quarkus_systemd_wait_for_port: true
keycloak_quarkus_systemd_wait_for_timeout: 20
keycloak_quarkus_systemd_wait_for_delay: 2
keycloak_quarkus_systemd_wait_for_log: true
keycloak_quarkus_ha_enabled: true
keycloak_quarkus_restart_strategy: restart/serial.yml
keycloak_quarkus_db_user: keycloak
keycloak_quarkus_db_pass: mysecretpass
keycloak_quarkus_db_url: jdbc:postgresql://postgres:5432/keycloak
keycloak_quarkus_version: 26.3.5
roles:
- role: keycloak_quarkus

View File

@@ -0,0 +1,82 @@
---
driver:
name: docker
platforms:
- name: instance1
image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true
privileged: true
command: "/usr/sbin/init"
groups:
- keycloak
networks:
- name: rhbk
port_bindings:
- "8080/tcp"
- "8443/tcp"
- "9000/tcp"
- name: instance2
image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true
privileged: true
command: "/usr/sbin/init"
groups:
- keycloak
networks:
- name: rhbk
port_bindings:
- "8080/tcp"
- "8443/tcp"
- "9000/tcp"
- name: postgres
image: ubuntu/postgres:14-22.04_beta
pre_build_image: true
privileged: true
command: postgres
groups:
- database
networks:
- name: rhbk
port_bindings:
- "5432/tcp"
mounts:
- type: bind
target: /etc/postgresql/postgresql.conf
source: ${MOLECULE_PROJECT_DIRECTORY}/molecule/quarkus_ha/postgresql/postgresql.conf
env:
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: mysecretpass
POSTGRES_DB: keycloak
POSTGRES_HOST_AUTH_METHOD: trust
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: "{{ ansible_playbook_python }}"
env:
ANSIBLE_FORCE_COLOR: "true"
PYTHONHTTPSVERIFY: 0
verifier:
name: ansible
scenario:
test_sequence:
- cleanup
- destroy
- create
- prepare
- converge
- idempotence
- side_effect
- verify
- cleanup
- destroy

View File

@@ -0,0 +1,750 @@
# -----------------------------
# PostgreSQL configuration file
# -----------------------------
#
# This file consists of lines of the form:
#
# name = value
#
# (The "=" is optional.) Whitespace may be used. Comments are introduced with
# "#" anywhere on a line. The complete list of parameter names and allowed
# values can be found in the PostgreSQL documentation.
#
# The commented-out settings shown in this file represent the default values.
# Re-commenting a setting is NOT sufficient to revert it to the default value;
# you need to reload the server.
#
# This file is read on server startup and when the server receives a SIGHUP
# signal. If you edit the file on a running system, you have to SIGHUP the
# server for the changes to take effect, run "pg_ctl reload", or execute
# "SELECT pg_reload_conf()". Some parameters, which are marked below,
# require a server shutdown and restart to take effect.
#
# Any parameter can also be given as a command-line option to the server, e.g.,
# "postgres -c log_connections=on". Some parameters can be changed at run time
# with the "SET" SQL command.
#
# Memory units: kB = kilobytes Time units: ms = milliseconds
# MB = megabytes s = seconds
# GB = gigabytes min = minutes
# TB = terabytes h = hours
# d = days
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
# The default values of these variables are driven from the -D command-line
# option or PGDATA environment variable, represented here as ConfigDir.
#data_directory = 'ConfigDir' # use data in another directory
# (change requires restart)
#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file
# (change requires restart)
#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file
# (change requires restart)
# If external_pid_file is not explicitly set, no extra PID file is written.
#external_pid_file = '' # write an extra PID file
# (change requires restart)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------
# - Connection Settings -
listen_addresses = '*' # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost'; use '*' for all
# (change requires restart)
#port = 5432 # (change requires restart)
#max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp' # comma-separated list of directories
# (change requires restart)
#unix_socket_group = '' # (change requires restart)
#unix_socket_permissions = 0777 # begin with 0 to use octal notation
# (change requires restart)
#bonjour = off # advertise server via Bonjour
# (change requires restart)
#bonjour_name = '' # defaults to the computer name
# (change requires restart)
# - TCP settings -
# see "man 7 tcp" for details
#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
# 0 selects the system default
#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
# 0 selects the system default
#tcp_keepalives_count = 0 # TCP_KEEPCNT;
# 0 selects the system default
#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds;
# 0 selects the system default
# - Authentication -
#authentication_timeout = 1min # 1s-600s
#password_encryption = md5 # md5 or scram-sha-256
#db_user_namespace = off
# GSSAPI using Kerberos
#krb_server_keyfile = ''
#krb_caseins_users = off
# - SSL -
#ssl = off
#ssl_ca_file = ''
#ssl_cert_file = 'server.crt'
#ssl_crl_file = ''
#ssl_key_file = 'server.key'
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_min_protocol_version = 'TLSv1'
#ssl_max_protocol_version = ''
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------
# - Memory -
#shared_buffers = 32MB # min 128kB
# (change requires restart)
#huge_pages = try # on, off, or try
# (change requires restart)
#temp_buffers = 8MB # min 800kB
#max_prepared_transactions = 0 # zero disables the feature
# (change requires restart)
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
# you actively intend to use prepared transactions.
#work_mem = 4MB # min 64kB
#maintenance_work_mem = 64MB # min 1MB
#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
#max_stack_depth = 2MB # min 100kB
#shared_memory_type = mmap # the default is the first option
# supported by the operating system:
# mmap
# sysv
# windows
# (change requires restart)
#dynamic_shared_memory_type = posix # the default is the first option
# supported by the operating system:
# posix
# sysv
# windows
# mmap
# (change requires restart)
# - Disk -
#temp_file_limit = -1 # limits per-process temp file space
# in kB, or -1 for no limit
# - Kernel Resources -
#max_files_per_process = 1000 # min 25
# (change requires restart)
# - Cost-Based Vacuum Delay -
#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
#vacuum_cost_page_hit = 1 # 0-10000 credits
#vacuum_cost_page_miss = 10 # 0-10000 credits
#vacuum_cost_page_dirty = 20 # 0-10000 credits
#vacuum_cost_limit = 200 # 1-10000 credits
# - Background Writer -
#bgwriter_delay = 200ms # 10-10000ms between rounds
#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
#bgwriter_flush_after = 0 # measured in pages, 0 disables
# - Asynchronous Behavior -
#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching
#max_worker_processes = 8 # (change requires restart)
#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers
#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers
#parallel_leader_participation = on
#max_parallel_workers = 8 # maximum number of max_worker_processes that
# can be used in parallel operations
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
# (change requires restart)
#backend_flush_after = 0 # measured in pages, 0 disables
#------------------------------------------------------------------------------
# WRITE-AHEAD LOG
#------------------------------------------------------------------------------
# - Settings -
#wal_level = replica # minimal, replica, or logical
# (change requires restart)
#fsync = on # flush data to disk for crash safety
# (turning this off can cause
# unrecoverable data corruption)
#synchronous_commit = on # synchronization level;
# off, local, remote_write, remote_apply, or on
#wal_sync_method = fsync # the default is the first option
# supported by the operating system:
# open_datasync
# fdatasync (default on Linux)
# fsync
# fsync_writethrough
# open_sync
#full_page_writes = on # recover from partial page writes
#wal_compression = off # enable compression of full-page writes
#wal_log_hints = off # also do full page writes of non-critical updates
# (change requires restart)
#wal_init_zero = on # zero-fill new WAL files
#wal_recycle = on # recycle WAL files
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
#wal_writer_flush_after = 1MB # measured in pages, 0 disables
#commit_delay = 0 # range 0-100000, in microseconds
#commit_siblings = 5 # range 1-1000
# - Checkpoints -
#checkpoint_timeout = 5min # range 30s-1d
#max_wal_size = 1GB
#min_wal_size = 80MB
#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0
#checkpoint_flush_after = 0 # measured in pages, 0 disables
#checkpoint_warning = 30s # 0 disables
# - Archiving -
#archive_mode = off # enables archiving; off, on, or always
# (change requires restart)
#archive_command = '' # command to use to archive a logfile segment
# placeholders: %p = path of file to archive
# %f = file name only
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
#archive_timeout = 0 # force a logfile segment switch after this
# number of seconds; 0 disables
# - Archive Recovery -
# These are only used in recovery mode.
#restore_command = '' # command to use to restore an archived logfile segment
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
# (change requires restart)
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
# - Recovery Target -
# Set these only when performing a targeted recovery.
#recovery_target = '' # 'immediate' to end recovery as soon as a
# consistent state is reached
# (change requires restart)
#recovery_target_name = '' # the named restore point to which recovery will proceed
# (change requires restart)
#recovery_target_time = '' # the time stamp up to which recovery will proceed
# (change requires restart)
#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
# (change requires restart)
#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
# (change requires restart)
#recovery_target_inclusive = on # Specifies whether to stop:
# just after the specified recovery target (on)
# just before the recovery target (off)
# (change requires restart)
#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
# (change requires restart)
#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
# (change requires restart)
#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------
# - Sending Servers -
# Set these on the master and on any standby that will send replication data.
#max_wal_senders = 10 # max number of walsender processes
# (change requires restart)
#wal_keep_segments = 0 # in logfile segments; 0 disables
#wal_sender_timeout = 60s # in milliseconds; 0 disables
#max_replication_slots = 10 # max number of replication slots
# (change requires restart)
#track_commit_timestamp = off # collect timestamp of transaction commit
# (change requires restart)
# - Master Server -
# These settings are ignored on a standby server.
#synchronous_standby_names = '' # standby servers that provide sync rep
# method to choose sync standbys, number of sync standbys,
# and comma-separated list of application_name
# from standby(s); '*' = all
#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed
# - Standby Servers -
# These settings are ignored on a master server.
#primary_conninfo = '' # connection string to sending server
# (change requires restart)
#primary_slot_name = '' # replication slot on sending server
# (change requires restart)
#promote_trigger_file = '' # file name whose presence ends recovery
#hot_standby = on # "off" disallows queries during recovery
# (change requires restart)
#max_standby_archive_delay = 30s # max delay before canceling queries
# when reading WAL from archive;
# -1 allows indefinite delay
#max_standby_streaming_delay = 30s # max delay before canceling queries
# when reading streaming WAL;
# -1 allows indefinite delay
#wal_receiver_status_interval = 10s # send replies at least this often
# 0 disables
#hot_standby_feedback = off # send info from standby to prevent
# query conflicts
#wal_receiver_timeout = 60s # time that receiver waits for
# communication from master
# in milliseconds; 0 disables
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
# retrieve WAL after a failed attempt
#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
# - Subscribers -
# These settings are ignored on a publisher.
#max_logical_replication_workers = 4 # taken from max_worker_processes
# (change requires restart)
#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers
#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------
# - Planner Method Configuration -
#enable_bitmapscan = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
#enable_indexonlyscan = on
#enable_material = on
#enable_mergejoin = on
#enable_nestloop = on
#enable_parallel_append = on
#enable_seqscan = on
#enable_sort = on
#enable_tidscan = on
#enable_partitionwise_join = off
#enable_partitionwise_aggregate = off
#enable_parallel_hash = on
#enable_partition_pruning = on
# - Planner Cost Constants -
#seq_page_cost = 1.0 # measured on an arbitrary scale
#random_page_cost = 4.0 # same scale as above
#cpu_tuple_cost = 0.01 # same scale as above
#cpu_index_tuple_cost = 0.005 # same scale as above
#cpu_operator_cost = 0.0025 # same scale as above
#parallel_tuple_cost = 0.1 # same scale as above
#parallel_setup_cost = 1000.0 # same scale as above
#jit_above_cost = 100000 # perform JIT compilation if available
# and query more expensive than this;
# -1 disables
#jit_inline_above_cost = 500000 # inline small functions if query is
# more expensive than this; -1 disables
#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if
# query is more expensive than this;
# -1 disables
#min_parallel_table_scan_size = 8MB
#min_parallel_index_scan_size = 512kB
#effective_cache_size = 4GB
# - Genetic Query Optimizer -
#geqo = on
#geqo_threshold = 12
#geqo_effort = 5 # range 1-10
#geqo_pool_size = 0 # selects default based on effort
#geqo_generations = 0 # selects default based on effort
#geqo_selection_bias = 2.0 # range 1.5-2.0
#geqo_seed = 0.0 # range 0.0-1.0
# - Other Planner Options -
#default_statistics_target = 100 # range 1-10000
#constraint_exclusion = partition # on, off, or partition
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
#from_collapse_limit = 8
#join_collapse_limit = 8 # 1 disables collapsing of explicit
# JOIN clauses
#force_parallel_mode = off
#jit = on # allow JIT compilation
#plan_cache_mode = auto # auto, force_generic_plan or
# force_custom_plan
#------------------------------------------------------------------------------
# REPORTING AND LOGGING
#------------------------------------------------------------------------------
# - Where to Log -
#log_destination = 'stderr' # Valid values are combinations of
# stderr, csvlog, syslog, and eventlog,
# depending on platform. csvlog
# requires logging_collector to be on.
# This is used when logging to stderr:
#logging_collector = off # Enable capturing of stderr and csvlog
# into log files. Required to be on for
# csvlogs.
# (change requires restart)
# These are only used if logging_collector is on:
#log_directory = 'log' # directory where log files are written,
# can be absolute or relative to PGDATA
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
# can include strftime() escapes
#log_file_mode = 0600 # creation mode for log files,
# begin with 0 to use octal notation
#log_truncate_on_rotation = off # If on, an existing log file with the
# same name as the new log file will be
# truncated rather than appended to.
# But such truncation only occurs on
# time-driven rotation, not on restarts
# or size-driven rotation. Default is
# off, meaning append to existing files
# in all cases.
#log_rotation_age = 1d # Automatic rotation of logfiles will
# happen after that time. 0 disables.
#log_rotation_size = 10MB # Automatic rotation of logfiles will
# happen after that much log output.
# 0 disables.
# These are relevant when logging to syslog:
#syslog_facility = 'LOCAL0'
#syslog_ident = 'postgres'
#syslog_sequence_numbers = on
#syslog_split_messages = on
# This is only relevant when logging to eventlog (win32):
# (change requires restart)
#event_source = 'PostgreSQL'
# - When to Log -
#log_min_messages = warning # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# info
# notice
# warning
# error
# log
# fatal
# panic
#log_min_error_statement = error # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# info
# notice
# warning
# error
# log
# fatal
# panic (effectively off)
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
# and their durations, > 0 logs only
# statements running at least this number
# of milliseconds
#log_transaction_sample_rate = 0.0 # Fraction of transactions whose statements
# are logged regardless of their duration. 1.0 logs all
# statements from all transactions, 0.0 never logs.
# - What to Log -
#debug_print_parse = off
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = on
#log_checkpoints = off
#log_connections = off
#log_disconnections = off
#log_duration = off
#log_error_verbosity = default # terse, default, or verbose messages
#log_hostname = off
#log_line_prefix = '%m [%p] ' # special values:
# %a = application name
# %u = user name
# %d = database name
# %r = remote host and port
# %h = remote host
# %p = process ID
# %t = timestamp without milliseconds
# %m = timestamp with milliseconds
# %n = timestamp with milliseconds (as a Unix epoch)
# %i = command tag
# %e = SQL state
# %c = session ID
# %l = session line number
# %s = session start timestamp
# %v = virtual transaction ID
# %x = transaction ID (0 if none)
# %q = stop here in non-session
# processes
# %% = '%'
# e.g. '<%u%%%d> '
#log_lock_waits = off # log lock waits >= deadlock_timeout
#log_statement = 'none' # none, ddl, mod, all
#log_replication_commands = off
#log_temp_files = -1 # log temporary files equal or larger
# than the specified size in kilobytes;
# -1 disables, 0 logs all temp files
#log_timezone = 'GMT'
#------------------------------------------------------------------------------
# PROCESS TITLE
#------------------------------------------------------------------------------
#cluster_name = '' # added to process titles if nonempty
# (change requires restart)
#update_process_title = on
#------------------------------------------------------------------------------
# STATISTICS
#------------------------------------------------------------------------------
# - Query and Index Statistics Collector -
#track_activities = on
#track_counts = on
#track_io_timing = off
#track_functions = none # none, pl, all
#track_activity_query_size = 1024 # (change requires restart)
#stats_temp_directory = 'pg_stat_tmp'
# - Monitoring -
#log_parser_stats = off
#log_planner_stats = off
#log_executor_stats = off
#log_statement_stats = off
#------------------------------------------------------------------------------
# AUTOVACUUM
#------------------------------------------------------------------------------
#autovacuum = on # Enable autovacuum subprocess? 'on'
# requires track_counts to also be on.
#log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and
# their durations, > 0 logs only
# actions running at least this number
# of milliseconds.
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
# (change requires restart)
#autovacuum_naptime = 1min # time between autovacuum runs
#autovacuum_vacuum_threshold = 50 # min number of row updates before
# vacuum
#autovacuum_analyze_threshold = 50 # min number of row updates before
# analyze
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
# (change requires restart)
#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
# before forced vacuum
# (change requires restart)
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
# autovacuum, in milliseconds;
# -1 means use vacuum_cost_delay
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
# autovacuum, -1 means use
# vacuum_cost_limit
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------
# - Statement Behavior -
#client_min_messages = notice # values in order of decreasing detail:
# debug5
# debug4
# debug3
# debug2
# debug1
# log
# notice
# warning
# error
#search_path = '"$user", public' # schema names
#row_security = on
#default_tablespace = '' # a tablespace name, '' uses the default
#temp_tablespaces = '' # a list of tablespace names, '' uses
# only default tablespace
#default_table_access_method = 'heap'
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#default_transaction_deferrable = off
#session_replication_role = 'origin'
#statement_timeout = 0 # in milliseconds, 0 is disabled
#lock_timeout = 0 # in milliseconds, 0 is disabled
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
#vacuum_freeze_min_age = 50000000
#vacuum_freeze_table_age = 150000000
#vacuum_multixact_freeze_min_age = 5000000
#vacuum_multixact_freeze_table_age = 150000000
#vacuum_cleanup_index_scale_factor = 0.1 # fraction of total number of tuples
# before index cleanup, 0 always performs
# index cleanup
#bytea_output = 'hex' # hex, escape
#xmlbinary = 'base64'
#xmloption = 'content'
#gin_fuzzy_search_limit = 0
#gin_pending_list_limit = 4MB
# - Locale and Formatting -
#datestyle = 'iso, mdy'
#intervalstyle = 'postgres'
#timezone = 'GMT'
#timezone_abbreviations = 'Default' # Select the set of available time zone
# abbreviations. Currently, there are
# Default
# Australia (historical usage)
# India
# You can create your own file in
# share/timezonesets/.
#extra_float_digits = 1 # min -15, max 3; any value >0 actually
# selects precise output mode
#client_encoding = sql_ascii # actually, defaults to database
# encoding
# These settings are initialized by initdb, but they can be changed.
#lc_messages = 'C' # locale for system error message
# strings
#lc_monetary = 'C' # locale for monetary formatting
#lc_numeric = 'C' # locale for number formatting
#lc_time = 'C' # locale for time formatting
# default configuration for text search
#default_text_search_config = 'pg_catalog.simple'
# - Shared Library Preloading -
#shared_preload_libraries = '' # (change requires restart)
#local_preload_libraries = ''
#session_preload_libraries = ''
#jit_provider = 'llvmjit' # JIT library to use
# - Other Defaults -
#dynamic_library_path = '$libdir'
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
#max_locks_per_transaction = 64 # min 10
# (change requires restart)
#max_pred_locks_per_transaction = 64 # min 10
# (change requires restart)
#max_pred_locks_per_relation = -2 # negative values mean
# (max_pred_locks_per_transaction
# / -max_pred_locks_per_relation) - 1
#max_pred_locks_per_page = 2 # min 0
#------------------------------------------------------------------------------
# VERSION AND PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------
# - Previous PostgreSQL Versions -
#array_nulls = on
#backslash_quote = safe_encoding # on, off, or safe_encoding
#escape_string_warning = on
#lo_compat_privileges = off
#operator_precedence_warning = off
#quote_all_identifiers = off
#standard_conforming_strings = on
#synchronize_seqscans = on
# - Other Platforms and Clients -
#transform_null_equals = off
#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------
#exit_on_error = off # terminate session on any error?
#restart_after_crash = on # reinitialize after backend crash?
#data_sync_retry = off # retry or panic on failure to fsync
# data?
# (change requires restart)
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
# These options allow settings to be loaded from files other than the
# default postgresql.conf. Note that these are directives, not variable
# assignments, so they can usefully be given more than once.
#include_dir = '...' # include files ending in '.conf' from
# a directory, e.g., 'conf.d'
#include_if_exists = '...' # include file only if it exists
#include = '...' # include file
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here

View File

@@ -0,0 +1,48 @@
---
- name: Prepare
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
tasks:
- 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 {{ inventory_hostname }}.key -out {{ inventory_hostname }}.pem -sha256 -days 365 -nodes -subj '/CN={{ inventory_hostname }}'"
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: False
- name: Create vault directory
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file:
state: directory
path: "/opt/keycloak/vault"
mode: 0755
- name: Make sure a jre is available (for keytool to prepare keystore)
delegate_to: localhost
ansible.builtin.package:
name: "{{ 'java-17-openjdk-headless' if hera_home | length > 0 else 'openjdk-17-jdk-headless' }}"
state: present
become: "{{ molecule_prepare_require_privilege_escalation }}"
failed_when: false
- name: Create vault keystore
ansible.builtin.command: keytool -importpass -alias TestRealm_testalias -keystore keystore.p12 -storepass keystorepassword
delegate_to: localhost
register: keytool_cmd
changed_when: False
failed_when: not 'already exists' in keytool_cmd.stdout and keytool_cmd.rc != 0
- name: Copy certificates and vault
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.copy:
src: keystore.p12
dest: /opt/keycloak/vault/keystore.p12
mode: 0444

View File

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

View File

@@ -0,0 +1,31 @@
---
- name: Verify
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
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"
fail_msg: "Service not running"
- name: Set internal envvar
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"
- name: Check log file
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat:
path: /var/log/keycloak/keycloak.log
register: keycloak_log_file
- name: Check if keycloak file exists
ansible.builtin.assert:
that:
- keycloak_log_file.stat.exists
- not keycloak_log_file.stat.isdir

View File

@@ -1,6 +1,10 @@
---
- name: Converge
hosts: infinispan
vars_files:
- ../group_vars/all/vars.yml
vars:
ansible_become: "{{ keycloak_install_requires_become | default(true) }}"
roles:
- role: middleware_automation.infinispan.infinispan
infinispan_service_name: infinispan
@@ -18,6 +22,8 @@
- name: Converge
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"

View File

@@ -40,7 +40,7 @@ platforms:
mounts:
- type: bind
target: /etc/postgresql/postgresql.conf
source: ${PWD}/molecule/quarkus_ha/postgresql/postgresql.conf
source: ${MOLECULE_PROJECT_DIRECTORY}/molecule/quarkus_ha/postgresql/postgresql.conf
env:
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: mysecretpass

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: 'keycloak:infinispan'
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
@@ -11,11 +13,13 @@
- name: Create certificate request
ansible.builtin.command: "openssl req -x509 -newkey rsa:4096 -keyout {{ inventory_hostname }}.key -out {{ inventory_hostname }}.pem -sha256 -days 365 -nodes -subj '/CN={{ inventory_hostname }}'"
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: False
- name: Create vault directory
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file:
state: directory
path: "/opt/keycloak/vault"
@@ -26,18 +30,20 @@
ansible.builtin.package:
name: "{{ 'java-17-openjdk-headless' if hera_home | length > 0 else 'openjdk-17-jdk-headless' }}"
state: present
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
failed_when: false
- name: Create vault keystore
ansible.builtin.command: keytool -importpass -alias TestRealm_testalias -keystore keystore.p12 -storepass keystorepassword
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
register: keytool_cmd
changed_when: False
failed_when: not 'already exists' in keytool_cmd.stdout and keytool_cmd.rc != 0
- name: Copy certificates and vault
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.copy:
src: keystore.p12
dest: /opt/keycloak/vault/keystore.p12

View File

@@ -1,6 +1,8 @@
---
- name: Verify
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
@@ -17,7 +19,7 @@
hera_home: "{{ lookup('env', 'HERA_HOME') }}"
- name: Check log file
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat:
path: /var/log/keycloak/keycloak.log
register: keycloak_log_file

View File

@@ -2,6 +2,7 @@
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
- vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false

View File

@@ -2,6 +2,7 @@
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
- vars.yml
vars:
sudo_pkg_name: sudo
@@ -43,6 +44,8 @@
- 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'
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: false
roles:
@@ -53,4 +56,4 @@
ansible.builtin.file:
path: /etc/ansible/facts.d/keycloak.fact
state: absent
become: true
become: "{{ molecule_prepare_require_privilege_escalation }}"

View File

@@ -14,10 +14,9 @@
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify we are running on requested jvm
ansible.builtin.shell: |
set -eo pipefail
ps -ef | grep 'etc/alternatives/.*21' | grep -v grep
- name: Verify Java 21 runtime is installed (UBI/RHEL)
ansible.builtin.command:
cmd: rpm -q java-21-openjdk-headless
changed_when: false
- name: Verify token api call
@@ -28,5 +27,5 @@
validate_certs: no
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 2
delay: 2
retries: 45
delay: 5

View File

@@ -7,6 +7,8 @@ collections:
- name: ansible.posix
- name: community.docker
version: ">=3.8.0"
- name: containers.podman
version: ">=1.8.1"
roles:
- name: elan.simple_nginx_reverse_proxy

View File

@@ -0,0 +1,27 @@
---
- name: Playbook for Keycloak Authentication Flow Configuration
hosts: all
vars:
keycloak_admin_user: admin
keycloak_admin_password: "remembertochangeme"
keycloak_url: "http://localhost:8080"
keycloak_realm: TestRealm
tasks:
- name: Create authentication flow with executions
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: "{{ keycloak_url }}"
auth_realm: master
auth_username: "{{ keycloak_admin_user }}"
auth_password: "{{ keycloak_admin_password }}"
realm: "{{ keycloak_realm }}"
alias: my-browser-flow
description: "Custom browser authentication flow"
provider_id: basic-flow
executions:
- provider_id: auth-cookie
requirement: ALTERNATIVE
- provider_id: auth-password
requirement: REQUIRED
- provider_id: auth-otp-form
requirement: ALTERNATIVE
state: present

View File

@@ -0,0 +1,48 @@
---
- name: Playbook for Keycloak Client Scope Configuration
hosts: all
vars:
keycloak_admin_user: admin
keycloak_admin_password: "remembertochangeme"
keycloak_url: "http://localhost:8080"
keycloak_realm: TestRealm
tasks:
- name: Create client scope with protocol mappers
middleware_automation.keycloak.keycloak_client_scope:
auth_keycloak_url: "{{ keycloak_url }}"
auth_realm: master
auth_username: "{{ keycloak_admin_user }}"
auth_password: "{{ keycloak_admin_password }}"
realm: "{{ keycloak_realm }}"
name: TestClientScope
description: "Client scope created via Ansible"
protocol: openid-connect
protocol_mappers:
- name: email
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: email
claim.name: email
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
- name: firstName
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: firstName
claim.name: given_name
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
- name: username
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: username
claim.name: preferred_username
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
state: present

View File

@@ -0,0 +1,39 @@
---
- name: Playbook for Keycloak Realm and Client Configuration
hosts: all
tasks:
- name: Keycloak Realm Role
ansible.builtin.include_role:
name: middleware_automation.keycloak.keycloak_realm
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_realm: TestRealm
keycloak_client_default_roles:
- TestRoleAdmin
- TestRoleUser
keycloak_client_users:
- username: TestUser
password: password
client_roles:
- client: TestClient1
role: TestRoleUser
realm: TestRealm
- username: TestAdmin
password: password
client_roles:
- client: TestClient1
role: TestRoleUser
realm: TestRealm
- client: TestClient1
role: TestRoleAdmin
realm: TestRealm
keycloak_clients:
- name: TestClient1
client_id: TestClient1
roles: "{{ keycloak_client_default_roles }}"
realm: TestRealm
public_client: true
web_origins:
- http://testclient1origin/application
- http://testclient1origin/other
users: "{{ keycloak_client_users }}"

View File

@@ -0,0 +1,296 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2024, Contributors to the middleware_automation.keycloak collection
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: keycloak_authentication_flow
short_description: Allows administration of Keycloak authentication flows via Keycloak API
description:
- This module allows you to add, remove or modify Keycloak authentication flows via the Keycloak REST API.
It requires access to the REST API via OpenID Connect; the user connecting and the client being
used must have the requisite access rights. In a default Keycloak installation, admin-cli
and an admin user would work, as would a separate client definition with the scope tailored
to your needs and a user having the expected roles.
- This module supports creating new top-level authentication flows, copying existing flows,
and adding execution steps to a flow.
attributes:
check_mode:
support: full
diff_mode:
support: full
options:
state:
description:
- State of the authentication flow.
- On V(present), the flow will be created if it does not yet exist.
- On V(absent), the flow will be removed if it exists.
default: 'present'
type: str
choices:
- present
- absent
alias:
type: str
required: true
description:
- Alias (name) of the authentication flow.
description:
type: str
description:
- Description of the authentication flow.
default: ''
realm:
type: str
description:
- The Keycloak realm under which this authentication flow resides.
default: 'master'
provider_id:
type: str
description:
- The provider ID for the flow.
default: 'basic-flow'
aliases:
- providerId
copy_from:
type: str
description:
- If set, the new flow is created as a copy of the flow with this alias.
- Cannot be used together with O(executions).
aliases:
- copyFrom
executions:
type: list
elements: dict
description:
- A list of executions (authenticator steps) to add to the flow.
- Each execution is a dict with keys C(provider_id) (or C(providerId)) and C(requirement).
- Executions are only added when the flow is first created.
default: []
suboptions:
provider_id:
type: str
required: true
description:
- The authenticator provider ID (e.g. V(auth-cookie), V(auth-password), V(auth-otp-form)).
aliases:
- providerId
requirement:
type: str
required: true
description:
- The requirement level for this execution.
choices:
- REQUIRED
- ALTERNATIVE
- DISABLED
- CONDITIONAL
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.attributes
author:
- Paulo Menon (@paulomenon)
'''
EXAMPLES = '''
- name: Create an authentication flow with executions
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-browser-flow
description: "Custom browser flow"
provider_id: basic-flow
executions:
- provider_id: auth-cookie
requirement: ALTERNATIVE
- provider_id: auth-password
requirement: REQUIRED
- provider_id: auth-otp-form
requirement: ALTERNATIVE
state: present
delegate_to: localhost
- name: Create an authentication flow by copying an existing one
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-copy-of-browser
copy_from: browser
state: present
delegate_to: localhost
- name: Create a flow using token authentication
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
token: MY_TOKEN
realm: TestRealm
alias: my-flow
state: present
delegate_to: localhost
- name: Delete an authentication flow
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-browser-flow
state: absent
delegate_to: localhost
'''
RETURN = '''
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Authentication flow my-browser-flow has been created"
end_state:
description: Representation of the authentication flow after module execution.
returned: on success
type: dict
sample: {
"id": "uuid-here",
"alias": "my-browser-flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": false
}
'''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \
keycloak_argument_spec, get_token, KeycloakError
from ansible.module_utils.basic import AnsibleModule
def main():
argument_spec = keycloak_argument_spec()
execution_spec = dict(
provider_id=dict(type='str', required=True, aliases=['providerId']),
requirement=dict(type='str', required=True, choices=['REQUIRED', 'ALTERNATIVE', 'DISABLED', 'CONDITIONAL']),
)
meta_args = dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
alias=dict(type='str', required=True),
description=dict(type='str', default=''),
realm=dict(type='str', default='master'),
provider_id=dict(type='str', default='basic-flow', aliases=['providerId']),
copy_from=dict(type='str', aliases=['copyFrom']),
executions=dict(type='list', default=[], options=execution_spec, elements='dict'),
)
argument_spec.update(meta_args)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]),
required_together=([['auth_realm', 'auth_username', 'auth_password']]),
mutually_exclusive=[['copy_from', 'executions']])
result = dict(changed=False, msg='', diff={}, end_state={})
try:
connection_header = get_token(module.params)
except KeycloakError as e:
module.fail_json(msg=str(e))
kc = KeycloakAPI(module, connection_header)
realm = module.params.get('realm')
alias = module.params.get('alias')
state = module.params.get('state')
description = module.params.get('description')
provider_id = module.params.get('provider_id')
copy_from = module.params.get('copy_from')
executions = module.params.get('executions')
before_flow = kc.get_authentication_flow_by_alias(alias, realm=realm)
flow_exists = bool(before_flow)
if state == 'absent':
if flow_exists:
result['changed'] = True
if module._diff:
result['diff'] = dict(before=before_flow, after='')
if module.check_mode:
module.exit_json(**result)
kc.delete_authentication_flow_by_id(before_flow['id'], realm=realm)
result['msg'] = "Authentication flow {alias} has been deleted".format(alias=alias)
else:
result['msg'] = "Authentication flow {alias} does not exist, doing nothing".format(alias=alias)
result['end_state'] = {}
module.exit_json(**result)
if flow_exists:
result['changed'] = False
result['end_state'] = before_flow
result['msg'] = "Authentication flow {alias} already exists".format(alias=alias)
module.exit_json(**result)
result['changed'] = True
flow_config = {
'alias': alias,
'description': description,
'providerId': provider_id,
}
if module._diff:
result['diff'] = dict(before='', after=flow_config)
if module.check_mode:
module.exit_json(**result)
if copy_from:
flow_config['copyFrom'] = copy_from
after_flow = kc.copy_auth_flow(flow_config, realm=realm)
result['msg'] = "Authentication flow {alias} has been created (copied from {src})".format(alias=alias, src=copy_from)
else:
after_flow = kc.create_empty_auth_flow(flow_config, realm=realm)
if executions:
for execution in executions:
exec_rep = {
'providerId': execution['provider_id'],
'requirement': execution['requirement'],
}
kc.create_execution(exec_rep, alias, realm=realm)
result['msg'] = "Authentication flow {alias} has been created".format(alias=alias)
after_flow = kc.get_authentication_flow_by_alias(alias, realm=realm)
result['end_state'] = after_flow
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,324 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2024, Contributors to the middleware_automation.keycloak collection
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: keycloak_client_scope
short_description: Allows administration of Keycloak client scopes via Keycloak API
description:
- This module allows you to add, remove or modify Keycloak client scopes via the Keycloak REST API.
It requires access to the REST API via OpenID Connect; the user connecting and the client being
used must have the requisite access rights. In a default Keycloak installation, admin-cli
and an admin user would work, as would a separate client definition with the scope tailored
to your needs and a user having the expected roles.
- This module also supports managing protocol mappers within a client scope.
attributes:
check_mode:
support: full
diff_mode:
support: full
options:
state:
description:
- State of the client scope.
- On V(present), the client scope will be created if it does not yet exist, or updated with the parameters you provide.
- On V(absent), the client scope will be removed if it exists.
default: 'present'
type: str
choices:
- present
- absent
name:
type: str
required: true
description:
- Name of the client scope.
description:
type: str
default: ''
description:
- Description of the client scope.
realm:
type: str
description:
- The Keycloak realm under which this client scope resides.
default: 'master'
protocol:
type: str
description:
- The protocol associated with the client scope.
default: 'openid-connect'
choices:
- openid-connect
- saml
attributes:
type: dict
description:
- A dict of key/value pairs to set as attributes for the client scope.
protocol_mappers:
type: list
elements: dict
description:
- A list of protocol mappers to associate with the client scope.
- Each mapper is a dict with the keys C(name), C(protocol), C(protocolMapper), and C(config).
default: []
suboptions:
name:
type: str
required: true
description:
- Name of the protocol mapper.
protocol:
type: str
description:
- Protocol for the mapper.
default: 'openid-connect'
protocolMapper:
type: str
required: true
description:
- The mapper type (e.g. V(oidc-usermodel-attribute-mapper), V(oidc-audience-mapper)).
aliases:
- protocol_mapper_type
config:
type: dict
required: true
description:
- Configuration for the protocol mapper.
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.attributes
author:
- Paulo Menon (@paulomenon)
'''
EXAMPLES = '''
- name: Create a client scope with protocol mappers
middleware_automation.keycloak.keycloak_client_scope:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
name: my-client-scope
description: "A custom client scope"
protocol: openid-connect
protocol_mappers:
- name: email
protocol: openid-connect
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: email
claim.name: email
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
state: present
delegate_to: localhost
- name: Create a client scope using token authentication
middleware_automation.keycloak.keycloak_client_scope:
auth_keycloak_url: http://localhost:8080
token: MY_TOKEN
realm: TestRealm
name: my-scope
state: present
delegate_to: localhost
- name: Delete a client scope
middleware_automation.keycloak.keycloak_client_scope:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
name: my-client-scope
state: absent
delegate_to: localhost
'''
RETURN = '''
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Client scope my-scope has been created"
end_state:
description: Representation of the client scope after module execution.
returned: on success
type: dict
sample: {
"id": "uuid-here",
"name": "my-scope",
"protocol": "openid-connect",
"description": "A custom scope"
}
'''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \
keycloak_argument_spec, get_token, KeycloakError
from ansible.module_utils.basic import AnsibleModule
def main():
argument_spec = keycloak_argument_spec()
mapper_spec = dict(
name=dict(type='str', required=True),
protocol=dict(type='str', default='openid-connect'),
protocolMapper=dict(type='str', required=True, aliases=['protocol_mapper_type']),
config=dict(type='dict', required=True),
)
meta_args = dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
name=dict(type='str', required=True),
description=dict(type='str', default=''),
realm=dict(type='str', default='master'),
protocol=dict(type='str', default='openid-connect', choices=['openid-connect', 'saml']),
attributes=dict(type='dict'),
protocol_mappers=dict(type='list', default=[], options=mapper_spec, elements='dict'),
)
argument_spec.update(meta_args)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]),
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
result = dict(changed=False, msg='', diff={}, end_state={})
try:
connection_header = get_token(module.params)
except KeycloakError as e:
module.fail_json(msg=str(e))
kc = KeycloakAPI(module, connection_header)
realm = module.params.get('realm')
name = module.params.get('name')
state = module.params.get('state')
protocol = module.params.get('protocol')
description = module.params.get('description')
attributes = module.params.get('attributes')
protocol_mappers = module.params.get('protocol_mappers')
before_scope = kc.get_clientscope_by_name(name, realm=realm)
if state == 'absent':
if before_scope:
result['changed'] = True
if module._diff:
result['diff'] = dict(before=before_scope, after='')
if module.check_mode:
module.exit_json(**result)
kc.delete_clientscope(cid=before_scope['id'], realm=realm)
result['msg'] = "Client scope {name} has been deleted".format(name=name)
else:
result['msg'] = "Client scope {name} does not exist, doing nothing".format(name=name)
result['end_state'] = {}
module.exit_json(**result)
scope_rep = {
'name': name,
'protocol': protocol,
'description': description,
}
if attributes:
scope_rep['attributes'] = attributes
if not before_scope:
result['changed'] = True
if module._diff:
result['diff'] = dict(before='', after=scope_rep)
if module.check_mode:
module.exit_json(**result)
kc.create_clientscope(scope_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
if protocol_mappers:
for mapper in protocol_mappers:
mapper_rep = {
'name': mapper['name'],
'protocol': mapper.get('protocol', protocol),
'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'],
}
kc.create_clientscope_protocolmapper(after_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
result['end_state'] = after_scope
result['msg'] = "Client scope {name} has been created".format(name=name)
module.exit_json(**result)
else:
changed = False
for key in ('protocol', 'description'):
if scope_rep.get(key) and scope_rep[key] != before_scope.get(key):
changed = True
break
if attributes and attributes != before_scope.get('attributes', {}):
changed = True
if changed:
result['changed'] = True
scope_rep['id'] = before_scope['id']
if module._diff:
result['diff'] = dict(before=before_scope, after=scope_rep)
if module.check_mode:
module.exit_json(**result)
kc.update_clientscope(scope_rep, realm=realm)
if protocol_mappers:
existing_mappers = kc.get_clientscope_protocolmappers(before_scope['id'], realm=realm)
existing_mapper_names = {m['name'] for m in existing_mappers}
for mapper in protocol_mappers:
if mapper['name'] not in existing_mapper_names:
result['changed'] = True
if not module.check_mode:
mapper_rep = {
'name': mapper['name'],
'protocol': mapper.get('protocol', protocol),
'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'],
}
kc.create_clientscope_protocolmapper(before_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
result['end_state'] = after_scope
if result['changed']:
result['msg'] = "Client scope {name} has been updated".format(name=name)
else:
result['msg'] = "No changes required to client scope {name}".format(name=name)
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -7,6 +7,10 @@ keycloak_download_url_9x: "https://downloads.jboss.org/keycloak/{{ keycloak_vers
keycloak_installdir: "{{ keycloak_dest }}/keycloak-{{ keycloak_version }}"
keycloak_offline_install: false
# Authentication for Keycloak binary download (e.g. from internal artifact repository)
keycloak_binary_download_user:
keycloak_binary_download_pass:
### Install location and service settings
keycloak_java_home:
keycloak_dest: /opt/keycloak

View File

@@ -333,6 +333,14 @@ argument_specs:
default: true
description: "Allow the option to ignore invalid certificates when downloading JDBC drivers from a custom URL"
type: "bool"
keycloak_binary_download_user:
description: "Username for HTTP Basic Auth when downloading Keycloak binary"
type: "str"
required: false
keycloak_binary_download_pass:
description: "Password for HTTP Basic Auth when downloading Keycloak binary"
type: "str"
required: false
downstream:
options:
sso_version:

View File

@@ -13,7 +13,7 @@
when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_to_install }}"
become: true
become: "{{ keycloak_fastpackages_require_privilege_escalation }}"
ansible.builtin.dnf:
name: "{{ packages_to_install }}"
state: present
@@ -22,7 +22,7 @@
- ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}"
become: true
become: "{{ keycloak_fastpackages_require_privilege_escalation }}"
ansible.builtin.package:
name: "{{ packages_list }}"
state: present

View File

@@ -6,14 +6,14 @@
- firewalld
- name: Enable and start the firewalld service
become: true
become: "{{ keycloak_firewalld_require_privilege_escalation }}"
ansible.builtin.systemd:
name: firewalld
enabled: true
state: started
- name: "Configure firewall ports for {{ keycloak.service_name }}"
become: true
become: "{{ keycloak_firewalld_require_privilege_escalation }}"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true

View File

@@ -11,7 +11,7 @@
quiet: true
- name: Check for an existing deployment
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}"
register: existing_deploy
@@ -20,24 +20,24 @@
when: existing_deploy.stat.exists and keycloak_force_install | bool
block:
- name: "Stop the old {{ keycloak.service_name }} service"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
failed_when: false
ansible.builtin.systemd:
name: keycloak
state: stopped
- name: "Remove the old {{ keycloak.service_name }} deployment"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file:
path: "{{ keycloak_jboss_home }}"
state: absent
- name: Check for an existing deployment after possible forced removal
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}"
- name: "Create service user/group for {{ keycloak.service_name }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.user:
name: "{{ keycloak_service_user }}"
home: /opt/keycloak
@@ -45,7 +45,7 @@
create_home: false
- name: "Create install location for {{ keycloak.service_name }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file:
dest: "{{ keycloak_dest }}"
state: directory
@@ -54,7 +54,7 @@
mode: '0750'
- name: Create pidfile folder
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file:
dest: "{{ keycloak_service_pidfile | dirname }}"
state: directory
@@ -68,7 +68,7 @@
archive: "{{ keycloak_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ archive }}"
register: archive_path
@@ -85,6 +85,8 @@
url: "{{ keycloak_download_url }}"
dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: '0644'
url_username: "{{ keycloak_binary_download_user | default(omit) }}"
url_password: "{{ keycloak_binary_download_pass | default(omit) }}"
delegate_to: localhost
run_once: true
when:
@@ -166,13 +168,13 @@
- not archive_path.stat.exists
- local_archive_path.stat is defined
- local_archive_path.stat.exists
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
- name: "Check target directory: {{ keycloak.home }}"
ansible.builtin.stat:
path: "{{ keycloak.home }}"
register: path_to_workdir
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
- name: "Extract {{ keycloak_service_desc }} archive on target"
ansible.builtin.unarchive:
@@ -182,7 +184,7 @@
creates: "{{ keycloak.home }}"
owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
when:
- new_version_downloaded.changed or not path_to_workdir.stat.exists
notify:
@@ -200,13 +202,13 @@
owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}"
recurse: true
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
changed_when: false
- name: Ensure permissions are correct on existing deploy
ansible.builtin.command: chown -R "{{ keycloak_service_user }}:{{ keycloak_service_group }}" "{{ keycloak.home }}"
when: keycloak_service_runas
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
changed_when: false
# driver and configuration
@@ -215,7 +217,7 @@
when: keycloak_jdbc[keycloak_jdbc_engine].enabled
- name: "Deploy custom {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }} from {{ keycloak_config_override_template }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template:
src: "templates/{{ keycloak_config_override_template }}"
dest: "{{ keycloak_config_path_to_standalone_xml }}"
@@ -227,7 +229,7 @@
when: keycloak_config_override_template | length > 0
- name: "Deploy standalone {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template:
src: templates/standalone.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}"
@@ -255,7 +257,7 @@
when: keycloak_ha_enabled and keycloak_ha_discovery == 'TCPPING'
- name: "Deploy HA {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template:
src: templates/standalone-ha.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}"
@@ -270,7 +272,7 @@
- keycloak_config_override_template | length == 0
- name: "Deploy HA {{ keycloak.service_name }} config with infinispan remote cache store to {{ keycloak_config_path_to_standalone_xml }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template:
src: templates/standalone-infinispan.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}"
@@ -285,7 +287,7 @@
- keycloak_config_override_template | length == 0
- name: "Deploy profile.properties file to {{ keycloak_config_path_to_properties }}"
become: true
become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template:
src: keycloak-profile.properties.j2
dest: "{{ keycloak_config_path_to_properties }}"

View File

@@ -6,7 +6,7 @@
- iptables
- name: "Configure firewall ports for {{ keycloak.service_name }}"
become: true
become: "{{ keycloak_iptables_require_privilege_escalation }}"
ansible.builtin.iptables:
destination_port: "{{ item }}"
action: "insert"

View File

@@ -3,7 +3,7 @@
ansible.builtin.stat:
path: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}"
register: dest_path
become: true
become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"
- name: "Set up module dir for JDBC Driver {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_name }}"
ansible.builtin.file:
@@ -13,7 +13,7 @@
owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}"
mode: '0750'
become: true
become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"
when:
- not dest_path.stat.exists
- name: "Verify valid parameters for download credentials when specified"
@@ -34,7 +34,7 @@
url_password: "{{ keycloak_jdbc_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_jdbc_download_validate_certs | default(omit) }}"
mode: '0640'
become: true
become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"
- name: "Deploy module.xml for JDBC Driver"
ansible.builtin.template:
@@ -43,4 +43,4 @@
group: "{{ keycloak_service_group }}"
owner: "{{ keycloak_service_user }}"
mode: '0640'
become: true
become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"

View File

@@ -51,7 +51,7 @@
state: link
src: "{{ keycloak_jboss_home }}/standalone/log"
dest: "{{ keycloak_log_target }}"
become: true
become: "{{ keycloak_require_privilege_escalation }}"
- name: Set admin credentials and restart if not already created
block:
@@ -75,7 +75,7 @@
- "-u{{ keycloak_admin_user }}"
- "-p{{ keycloak_admin_password }}"
changed_when: true
become: true
become: "{{ keycloak_require_privilege_escalation }}"
- name: "Restart {{ keycloak.service_name }}"
ansible.builtin.include_tasks: tasks/restart_keycloak.yml
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"

View File

@@ -5,7 +5,7 @@
enabled: true
state: restarted
daemon_reload: true
become: true
become: "{{ keycloak_restart_require_privilege_escalation }}"
delegate_to: "{{ ansible_play_hosts | first }}"
run_once: true
@@ -24,5 +24,5 @@
name: keycloak
enabled: true
state: restarted
become: true
become: "{{ keycloak_restart_require_privilege_escalation }}"
when: inventory_hostname != ansible_play_hosts | first

View File

@@ -12,7 +12,7 @@
path: "{{ patch_archive }}"
register: patch_archive_path
when: sso_patch_version is defined
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
- name: Perform patch download from RHN via JBossNetwork API
delegate_to: localhost
@@ -86,7 +86,7 @@
ansible.builtin.stat:
path: "{{ patch_archive }}"
register: patch_archive_path
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
## copy and unpack
- name: Copy patch archive to target nodes
@@ -101,7 +101,7 @@
- not patch_archive_path.stat.exists
- local_archive_path.stat is defined
- local_archive_path.stat.exists
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
- name: "Check installed patches"
ansible.builtin.include_tasks: rhsso_cli.yml
@@ -109,7 +109,7 @@
cli_query: "patch info"
args:
apply:
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}"
- name: "Perform patching"
@@ -124,7 +124,7 @@
cli_query: "patch apply {{ patch_archive }}"
args:
apply:
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}"
- name: "Restart server to ensure patch content is running"
@@ -135,7 +135,7 @@
- cli_result.rc == 0
args:
apply:
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}"
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
@@ -152,7 +152,7 @@
cli_query: "patch info"
args:
apply:
become: true
become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}"
- name: "Verify installed patch version"

View File

@@ -5,7 +5,7 @@
enabled: true
state: started
daemon_reload: true
become: true
become: "{{ keycloak_start_require_privilege_escalation }}"
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri:

View File

@@ -4,4 +4,4 @@
name: keycloak
enabled: true
state: stopped
become: true
become: "{{ keycloak_stop_require_privilege_escalation }}"

View File

@@ -1,6 +1,6 @@
---
- name: "Configure {{ keycloak.service_name }} service script wrapper"
become: true
become: "{{ keycloak_systemd_require_privilege_escalation }}"
ansible.builtin.template:
src: keycloak-service.sh.j2
dest: "{{ keycloak_dest }}/keycloak-service.sh"
@@ -11,7 +11,7 @@
- restart keycloak
- name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: true
become: "{{ keycloak_systemd_require_privilege_escalation }}"
ansible.builtin.template:
src: keycloak-sysconfig.j2
dest: "{{ keycloak_sysconf_file }}"
@@ -28,7 +28,7 @@
owner: root
group: root
mode: '0644'
become: true
become: "{{ keycloak_systemd_require_privilege_escalation }}"
register: systemdunit
notify:
- restart keycloak

View File

@@ -33,7 +33,7 @@ Role Defaults
| Variable | Description | Default |
|:---------|:------------|:--------|
|`keycloak_quarkus_version`| keycloak.org package version | `26.3.0` |
|`keycloak_quarkus_version`| keycloak.org package version | `26.4.7` |
|`keycloak_quarkus_offline_install` | Perform an offline install | `False`|
|`keycloak_quarkus_dest`| Installation root path | `/opt/keycloak` |
|`keycloak_quarkus_download_url` | Download URL for keycloak | `https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}` |
@@ -132,6 +132,17 @@ Role Defaults
|`keycloak_quarkus_http_enabled`| Enable listener on HTTP port | `True` |
#### Infinispan configuration
| Variable | Description | Default |
| :------------------------------------------------- | :------------------------------ | :----------------------------------------------------------- |
| `keycloak_quarkus_cache_managed_infinispan_config` | Manage infinispan configuration | `"{{ keycloak_quarkus_version is version('26.4.0', '<') }}"` |
| `keycloak_quarkus_cache_infinispan_template` | Infinispan cache template file | `cache-ispn.xml` |
As explained in the [official documentation](https://www.keycloak.org/server/caching#_modifying_cache_configuration_defaults), since version 26.4, it is recommended not to modify the XML configuration file but rather to configure the cache via the keycloak.properties file. By default, the role will no longer automatically deploy this file for versions higher than 26.4.
For earlier versions, it is possible to override the given template to customize the cache using the `keycloak_quarkus_cache_infinispan_template` variable.
#### Database configuration
| Variable | Description | Default |
@@ -154,6 +165,7 @@ Role Defaults
|`keycloak_quarkus_cache_remote_port`| Port for connecting to infinispan | `11222` |
|`keycloak_quarkus_cache_remote_sasl_mechanism` | Infinispan auth mechanism | `SCRAM-SHA-512` |
|`keycloak_quarkus_cache_remote_tls_enabled` | Whether infinispan uses TLS connection | `false` |
|`keycloak_quarkus_cache_embedded_properties` | Embedded cache properties | `` |
#### Logging configuration

View File

@@ -1,10 +1,14 @@
---
### Configuration specific to keycloak
keycloak_quarkus_version: 26.3.0
keycloak_quarkus_version: 26.4.7
keycloak_quarkus_archive: "keycloak-{{ keycloak_quarkus_version }}.zip"
keycloak_quarkus_download_url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}"
keycloak_quarkus_installdir: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}"
# Authentication for Keycloak binary download (e.g. from internal artifact repository)
keycloak_quarkus_binary_download_user:
keycloak_quarkus_binary_download_pass:
# whether to install from local archive
keycloak_quarkus_offline_install: false
@@ -117,25 +121,14 @@ keycloak_quarkus_spi_sticky_session_encoder_infinispan_should_attach_route: true
keycloak_quarkus_metrics_enabled: false
keycloak_quarkus_health_enabled: true
### infinispan; must read: https://forum.keycloak.org/t/keycloak-26-4-7-ha/31202
keycloak_quarkus_cache_managed_infinispan_config: "{{ keycloak_quarkus_version is version('26.4.0', '<') }}"
keycloak_quarkus_cache_infinispan_template: cache-ispn.xml
### caches; must read: https://www.keycloak.org/2024/12/storing-sessions-in-kc26
### embedded caches
# https://www.keycloak.org/server/caching
keycloak_quarkus_cache_metrics_enabled: false
keycloak_quarkus_cache_embedded_authorization_max_count:
keycloak_quarkus_cache_embedded_client_sessions_max_count:
keycloak_quarkus_cache_embedded_crl_max_count:
keycloak_quarkus_cache_embedded_keys_max_count:
keycloak_quarkus_cache_embedded_offline_client_sessions_max_count:
keycloak_quarkus_cache_embedded_offline_sessions_max_count:
keycloak_quarkus_cache_embedded_realms_max_count:
keycloak_quarkus_cache_embedded_sessions_max_count:
keycloak_quarkus_cache_embedded_users_max_count:
keycloak_quarkus_cache_embedded_mtls_enabled: true
keycloak_quarkus_cache_embedded_mtls_key_store_file: "{{ keycloak.home }}/conf/cache_key_store.p12"
keycloak_quarkus_cache_embedded_mtls_key_store_password: ''
keycloak_quarkus_cache_embedded_mtls_rotation_interval_days: 30
keycloak_quarkus_cache_embedded_mtls_trust_store_file: "{{ keycloak.home }}/conf/cache_trust_store.p12"
keycloak_quarkus_cache_embedded_mtls_trust_store_password: ''
keycloak_quarkus_cache_embedded_properties: ""
### infinispan remote caches access (hotrod)
# https://www.keycloak.org/server/caching#_remote_cache
@@ -160,14 +153,14 @@ keycloak_quarkus_db_driver_version: "{{ keycloak_quarkus_default_jdbc[keycloak_q
keycloak_quarkus_default_jdbc:
postgres:
url: 'jdbc:postgresql://localhost:5432/keycloak'
version: 42.7.5
version: 42.7.7
mariadb:
url: 'jdbc:mariadb://localhost:3306/keycloak'
version: 3.5.2
mssql:
url: 'jdbc:sqlserver://localhost:1433;databaseName=keycloak;'
version: 12.8.1
driver_jar_url: "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/12.8.1.jre11/mssql-jdbc-12.8.1.jre11.jar"
version: 13.2.0
driver_jar_url: "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/13.2.0.jre11/mssql-jdbc-13.2.0.jre11.jar"
### logging configuration
keycloak_quarkus_log: file
keycloak_quarkus_log_level: info

View File

@@ -2,7 +2,7 @@ argument_specs:
main:
options:
keycloak_quarkus_version:
default: "26.3.0"
default: "26.4.7"
description: "keycloak.org package version"
type: "str"
keycloak_quarkus_archive:
@@ -199,6 +199,26 @@ argument_specs:
default: 9000
description: "Port of the management interface. Relevant only when something is exposed on the management interface - see the guide for details."
type: "int"
keycloak_quarkus_jgroups_port:
description: 'jgroups bind port'
default: 7800
type: "int"
keycloak_quarkus_jgroups_bind_address:
description: 'jgroups bind address'
default: "{{ ansible_default_ipv4.address }}"
type: "str"
keycloak_quarkus_jgroups_external_addr:
description: 'IP address that other instances in the Keycloak should use to contact this node'
default: "{{ keycloak_quarkus_jgroups_bind_address }}"
type: "str"
keycloak_quarkus_jgroups_external_port:
description: 'Port that other instances in the Keycloak cluster should use to contact this node'
default: "{{ keycloak_quarkus_jgroups_port }}"
type: "int"
keycloak_quarkus_jgroups_opts:
description: "JVM arguments for jgroups configuration"
default: "-Djgroups.bind.address={{ keycloak_quarkus_jgroups_bind_address }} -Djgroups.external_port={{ keycloak_quarkus_jgroups_external_port }} -Djgroups.external_addr={{ keycloak_quarkus_jgroups_external_addr }}"
type: "str"
keycloak_quarkus_java_heap_opts:
default: "-Xms1024m -Xmx2048m"
description: "Heap memory JVM setting"
@@ -476,94 +496,30 @@ argument_specs:
description: "Path local to controller for offline/download of install archives"
default: "{{ lookup('env', 'PWD') }}"
type: "str"
keycloak_quarkus_cache_metrics_enabled:
description: 'Enable histograms for metrics for the embedded caches'
default: false
type: 'bool'
keycloak_quarkus_cache_embedded_authorization_max_count:
description: 'The maximum number of entries that can be stored in-memory by the authorization cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_client_sessions_max_count:
description: 'The maximum number of entries that can be stored in-memory by the clientSessions cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_crl_max_count:
description: 'The maximum number of entries that can be stored in-memory by the crl cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_keys_max_count:
description: 'The maximum number of entries that can be stored in-memory by the keys cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_offline_client_sessions_max_count:
description: 'The maximum number of entries that can be stored in-memory by the offlineClientSessions cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_offline_sessions_max_count:
description: 'The maximum number of entries that can be stored in-memory by the offlineSessions cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_realms_max_count:
description: 'The maximum number of entries that can be stored in-memory by the realms cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_sessions_max_count:
description: 'The maximum number of entries that can be stored in-memory by the sessions cache'
required: false
type: "int"
keycloak_quarkus_cache_embedded_users_max_count:
description: 'The maximum number of entries that can be stored in-memory by the users cache'
required: false
type: 'int'
keycloak_quarkus_cache_embedded_mtls_enabled:
description: 'Encrypts the network communication between Keycloak servers'
default: true
type: 'bool'
keycloak_quarkus_cache_embedded_mtls_key_store_file:
description: 'The Keystore file path'
default: "{{ keycloak.home }}/conf/cache_key_store.p12"
keycloak_quarkus_cache_managed_infinispan_config:
description: "Manage infinispan configuration"
default: "{{ keycloak_quarkus_version is version('26.4.0', '<') }}"
type: bool
keycloak_quarkus_cache_infinispan_template:
description: "Infinispan cache template file"
default: "cache-ispn.xml"
type: str
keycloak_quarkus_cache_embedded_properties:
description: Embedded cache properties
default: ""
type: str
keycloak_quarkus_binary_download_user:
description: "Username for HTTP Basic Auth when downloading Keycloak binary"
type: "str"
keycloak_quarkus_cache_embedded_mtls_key_store_password:
description: 'The password to access the Keystore'
default: ''
type: "str"
keycloak_quarkus_cache_embedded_mtls_rotation_interval_days:
description: 'Rotation period in days of automatic JGroups MTLS certificates'
default: 30
type: 'int'
keycloak_quarkus_cache_embedded_mtls_trust_store_file:
description: 'The Truststore file path'
default: "{{ keycloak.home }}/conf/cache_trust_store.p12"
type: "str"
keycloak_quarkus_cache_embedded_mtls_trust_store_password:
description: 'The password to access the Truststore.'
default: ''
type: "str"
keycloak_quarkus_jgroups_port:
description: 'jgroups bind port'
default: 7800
type: "int"
keycloak_quarkus_jgroups_bind_address:
description: 'jgroups bind address'
default: "{{ ansible_default_ipv4.address }}"
type: "str"
keycloak_quarkus_jgroups_external_addr:
description: 'IP address that other instances in the Keycloak should use to contact this node'
default: "{{ keycloak_quarkus_jgroups_bind_address }}"
type: "str"
keycloak_quarkus_jgroups_external_port:
description: 'Port that other instances in the Keycloak cluster should use to contact this node'
default: "{{ keycloak_quarkus_jgroups_port }}"
type: "int"
keycloak_quarkus_jgroups_opts:
description: "JVM arguments for jgroups configuration"
default: "-Djgroups.bind.address={{ keycloak_quarkus_jgroups_bind_address }} -Djgroups.external_port={{ keycloak_quarkus_jgroups_external_port }} -Djgroups.external_addr={{ keycloak_quarkus_jgroups_external_addr }}"
required: false
keycloak_quarkus_binary_download_pass:
description: "Password for HTTP Basic Auth when downloading Keycloak binary"
type: "str"
required: false
downstream:
options:
rhbk_version:
default: "26.2.5"
default: "26.4.7"
description: "Red Hat Build of Keycloak version"
type: "str"
rhbk_archive:

View File

@@ -1,6 +1,6 @@
---
- name: Save ansible custom facts
become: true
become: "{{ keycloak_quarkus_bootstrapped_require_privilege_escalation }}"
ansible.builtin.template:
src: keycloak.fact.j2
dest: /etc/ansible/facts.d/keycloak.fact

View File

@@ -6,7 +6,7 @@
value: "{{ keycloak_quarkus_db_pass }}"
- name: "Initialize empty configuration key store"
become: true
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation }}"
# 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
@@ -38,7 +38,7 @@
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: "{{ store_items }}"
no_log: true
become: true
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation }}"
changed_when: true
notify:
- restart keycloak
@@ -49,4 +49,4 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0400'
become: true
become: "{{ keycloak_quarkus_config_store_require_privilege_escalation }}"

View File

@@ -13,7 +13,7 @@
when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_to_install }}"
become: true
become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation }}"
ansible.builtin.dnf:
name: "{{ packages_to_install }}"
state: present
@@ -22,7 +22,7 @@
- ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}"
become: true
become: "{{ keycloak_quarkus_fastpackages_require_privilege_escalation }}"
ansible.builtin.package:
name: "{{ packages_list }}"
state: present

View File

@@ -6,14 +6,14 @@
- firewalld
- name: Enable and start the firewalld service
become: true
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation }}"
ansible.builtin.systemd:
name: firewalld
enabled: true
state: started
- name: "Configure firewall for {{ keycloak.service_name }} http port"
become: true
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation }}"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true
@@ -24,7 +24,7 @@
when: keycloak_quarkus_http_enabled | bool
- name: "Configure firewall for {{ keycloak.service_name }} ports"
become: true
become: "{{ keycloak_quarkus_firewalld_require_privilege_escalation }}"
ansible.posix.firewalld:
port: "{{ item }}"
permanent: true

View File

@@ -12,7 +12,7 @@
quiet: true
- name: Check for an existing deployment
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ keycloak.home }}"
register: existing_deploy
@@ -21,25 +21,25 @@
when: existing_deploy.stat.exists and keycloak_quarkus_force_install | bool
block:
- name: "Stop the old {{ keycloak.service_name }} service"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
failed_when: false
ansible.builtin.systemd:
name: keycloak
state: stopped
- name: "Remove the old {{ keycloak.service_name }} deployment"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.file:
path: "{{ keycloak_quarkus_home }}"
state: absent
- name: Check for an existing deployment after possible forced removal
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ keycloak_quarkus_home }}"
register: existing_deploy
- name: "Create {{ keycloak.service_name }} service user/group"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.user:
name: "{{ keycloak.service_user }}"
home: /opt/keycloak
@@ -47,7 +47,7 @@
create_home: false
- name: "Create {{ keycloak.service_name }} install location"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.file:
dest: "{{ keycloak_quarkus_dest }}"
state: directory
@@ -56,7 +56,7 @@
mode: '0750'
- name: Create directory for ansible custom facts
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.file:
state: directory
recurse: true
@@ -68,7 +68,7 @@
archive: "{{ keycloak_quarkus_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
ansible.builtin.stat:
path: "{{ archive }}"
register: archive_path
@@ -79,6 +79,8 @@
url: "{{ keycloak_quarkus_download_url }}"
dest: "{{ 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) }}"
delegate_to: localhost
become: false
run_once: true
@@ -170,13 +172,13 @@
- not archive_path.stat.exists
- local_archive_path.stat is defined
- local_archive_path.stat.exists
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
- name: "Check target directory: {{ keycloak.home }}/bin/"
ansible.builtin.stat:
path: "{{ keycloak.home }}/bin/"
register: path_to_workdir
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
- name: "Extract Keycloak archive on target" # noqa no-handler need to run this here
ansible.builtin.unarchive:
@@ -186,7 +188,7 @@
creates: "{{ keycloak.home }}/bin/"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
when:
- (not path_to_workdir.stat.exists) or new_version_downloaded.changed
notify:
@@ -205,7 +207,7 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
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
@@ -218,7 +220,7 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0644'
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
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
@@ -238,7 +240,7 @@
group: "{{ keycloak.service_group }}"
mode: '0640'
checksum: "{{ item.checksum | default(omit) }}"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.url is defined and item.url | length > 0
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
@@ -267,7 +269,7 @@
group: "{{ keycloak.service_group }}"
mode: '0640'
checksum: "{{ item.checksum | default(omit) }}"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.maven is defined
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
@@ -281,7 +283,7 @@
group: "{{ keycloak.service_group }}"
mode: '0640'
remote_src: "{{ item.remote | default(false) }}"
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_providers }}"
when: item.local_path is defined
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
@@ -293,7 +295,7 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0750'
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_supported_policy_types }}"
- name: "Install custom policies"
@@ -303,7 +305,7 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
become: true
become: "{{ keycloak_quarkus_install_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_policies }}"
when: item.url is defined and item.url | length > 0
notify: "restart keycloak"

View File

@@ -8,4 +8,4 @@
ansible.builtin.file:
path: "{{ keycloak.home }}/data/tmp/kc-gzip-cache"
state: absent
become: true
become: "{{ keycloak_quarkus_invalidate_theme_cache_require_privilege_escalation }}"

View File

@@ -6,7 +6,7 @@
- iptables
- name: "Configure firewall ports for {{ keycloak.service_name }}"
become: true
become: "{{ keycloak_quarkus_iptables_require_privilege_escalation }}"
ansible.builtin.iptables:
destination_port: "{{ item }}"
action: "insert"

View File

@@ -17,6 +17,6 @@
url_password: "{{ keycloak_quarkus_jdbc_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_quarkus_jdbc_download_validate_certs | default(omit) }}"
mode: '0640'
become: true
become: "{{ keycloak_quarkus_jdbc_driver_require_privilege_escalation }}"
notify:
- restart keycloak

View File

@@ -70,6 +70,11 @@
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"
@@ -77,11 +82,8 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0640'
become: true
loop:
- keycloak.conf
- quarkus.properties
- cache-ispn.xml
become: "{{ keycloak_quarkus_require_privilege_escalation }}"
loop: "{{ keycloak_quarkus_config_files }}"
notify:
- rebuild keycloak config
- restart keycloak
@@ -93,7 +95,16 @@
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0775'
become: true
become: "{{ keycloak_quarkus_require_privilege_escalation }}"
- 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 }}"
- name: Flush pending handlers
ansible.builtin.meta: flush_handlers
@@ -107,7 +118,7 @@
src: "{{ keycloak.log.file | dirname }}"
dest: "{{ keycloak_quarkus_log_target }}"
force: true
become: true
become: "{{ keycloak_quarkus_require_privilege_escalation }}"
- name: Check service status
ansible.builtin.systemd_service:

View File

@@ -3,5 +3,5 @@
- name: "Rebuild {{ keycloak.service_name }} config"
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: true
become: "{{ keycloak_quarkus_rebuild_config_require_privilege_escalation }}"
changed_when: true

View File

@@ -5,7 +5,7 @@
enabled: true
state: restarted
daemon_reload: true
become: true
become: "{{ keycloak_quarkus_restart_require_privilege_escalation }}"
- name: "Wait until {{ keycloak.service_name }} service becomes active {{ keycloak.health_url }}"
ansible.builtin.uri:

View File

@@ -16,5 +16,5 @@
enabled: true
state: restarted
daemon_reload: true
become: true
become: "{{ keycloak_quarkus_restart_require_privilege_escalation }}"
when: inventory_hostname != ansible_play_hosts | first

View File

@@ -5,7 +5,7 @@
enabled: true
state: started
daemon_reload: true
become: true
become: "{{ keycloak_quarkus_start_require_privilege_escalation }}"
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
ansible.builtin.uri:

View File

@@ -1,6 +1,6 @@
---
- name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: true
become: "{{ keycloak_quarkus_systemd_require_privilege_escalation }}"
ansible.builtin.template:
src: keycloak-sysconfig.j2
dest: "{{ keycloak_quarkus_sysconf_file }}"
@@ -20,7 +20,7 @@
owner: root
group: root
mode: '0644'
become: true
become: "{{ keycloak_quarkus_systemd_require_privilege_escalation }}"
register: systemdunit
notify:
- rebuild keycloak config

View File

@@ -58,7 +58,9 @@ hostname-backchannel-dynamic={{ keycloak_quarkus_hostname_backchannel_dynamic |
# Cluster
{% if keycloak_quarkus_ha_enabled %}
cache=ispn
{% if keycloak_quarkus_cache_managed_infinispan_config %}
cache-config-file=cache-ispn.xml
{% endif %}
{% if keycloak_quarkus_cache_remote %}
cache-remote-username={{ keycloak_quarkus_cache_remote_username }}
cache-remote-password={{ keycloak_quarkus_cache_remote_password }}
@@ -66,6 +68,7 @@ cache-remote-host={{ keycloak_quarkus_cache_remote_host }}
cache-remote-port={{ keycloak_quarkus_cache_remote_port }}
cache-remote-tls-enabled={{ keycloak_quarkus_cache_remote_tls_enabled | lower }}
{% endif %}
{{ keycloak_quarkus_cache_embedded_properties }}
{% endif %}
{% if keycloak_quarkus_proxy_headers | length > 0 %}

View File

@@ -12,7 +12,7 @@ Role Defaults
|:---------|:------------|:--------|
|`keycloak_admin_user`| Administration console user account | `admin` |
|`keycloak_host`| hostname | `localhost` |
|`keycloak_context`| Context path for rest calls | `/auth` |
|`keycloak_context`| Context path for rest calls (set to `/auth` for legacy WildFly-based Keycloak) | `` |
|`keycloak_http_port`| HTTP port | `8080` |
|`keycloak_https_port`| TLS HTTP port | `8443` |
|`keycloak_auth_realm`| Name of the main authentication realm | `master` |
@@ -107,6 +107,20 @@ Refer to [docs](https://docs.ansible.com/ansible/latest/collections/community/ge
For a comprehensive example, refer to the [playbook](../../playbooks/keycloak_realm.yml).
Related Modules
---------------
For features not covered by this role, the collection provides dedicated modules:
| Module | What It Manages |
|:-------|:----------------|
| `keycloak_client_scope` | Client scopes and protocol mappers — see [example playbook](../../playbooks/keycloak_client_scope.yml) |
| `keycloak_authentication_flow` | Authentication flows and execution steps — see [example playbook](../../playbooks/keycloak_authentication_flow.yml) |
| `keycloak_client` | Clients (also used internally by this role) |
| `keycloak_role` | Realm and client roles |
| `keycloak_user_federation` | User federations such as LDAP (also used internally by this role) |
Example Playbook
----------------
@@ -127,6 +141,47 @@ The following is an example playbook that makes use of the role to create a real
keycloak_clients: [...]
```
The following example uses the `keycloak_client_scope` module to create a client scope with protocol mappers:
```yaml
- name: Create client scope
middleware_automation.keycloak.keycloak_client_scope:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: changeme
realm: TestRealm
name: my-scope
protocol_mappers:
- name: email
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: email
claim.name: email
id.token.claim: "true"
access.token.claim: "true"
state: present
```
The following example uses the `keycloak_authentication_flow` module to create a custom authentication flow:
```yaml
- name: Create authentication flow
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: changeme
realm: TestRealm
alias: my-browser-flow
executions:
- provider_id: auth-cookie
requirement: ALTERNATIVE
- provider_id: auth-password
requirement: REQUIRED
state: present
```
License
-------

View File

@@ -9,7 +9,7 @@ keycloak_management_http_port: 9990
keycloak_admin_user: admin
keycloak_auth_realm: master
keycloak_auth_client: admin-cli
keycloak_context: /auth
keycloak_context: ''
# administrator console password, this is a required variable
keycloak_admin_password: ''

View File

@@ -8,8 +8,8 @@ argument_specs:
type: "str"
keycloak_context:
# line 5 of keycloak_realm/defaults/main.yml
default: "/auth"
description: "Context path for rest calls"
default: ""
description: "Context path for rest calls (was /auth for legacy WildFly-based Keycloak, empty for Quarkus-based Keycloak/RHBK)"
type: "str"
keycloak_http_port:
# line 4 of keycloak_realm/defaults/main.yml

View File

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