Compare commits

..

283 Commits
2.1.2 ... 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
ansible-middleware-core
bf5c805fcd Update changelog for release 3.0.3
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2025-12-16 15:52:59 +00:00
Ranabir Chakraborty
2b1c07d87e Merge pull request #306 from fxwgr/patch0
Declared proxy_mode as deprecated, updated quarkus and realm readme
2025-12-16 20:08:58 +05:30
Andreas Wagner
f1305e5aac Updated quarkus and realm readme, declared proxy_mode as deprecated
Updated argument_specs and declared keycloak_quarkus_proxy_mode as deprecated
2025-11-14 11:55:24 +01:00
Ranabir Chakraborty
412e17e9ea Merge pull request #312 from RanabirChakraborty/ci_label_fix
keycloak collection CI label is showing no status
2025-10-04 20:46:52 +05:30
Ranabir Chakraborty
fa87c004e3 keycloak collection CI label is showing no status 2025-10-04 20:19:09 +05:30
Ranabir Chakraborty
6c9bddbd61 Merge pull request #308 from tinsjourney/fix_config_key_store_password
Fix config_key_store_file description to match variable name
2025-09-25 21:47:46 +05:30
Ranabir Chakraborty
4602d254cf Merge pull request #310 from world-direct/fix/309
ansible-core 2.19 compatibility
2025-09-18 18:58:54 +05:30
Helmut Wolf
8b2ef22023 fix ansible-core v2.19.0: initialize keycloak_quarkus_hostname_admin to an empty string 2025-07-22 12:11:09 +02:00
Helmut Wolf
66228c3a13 ansible 2.19.0: fix error
'item' is undefined error, https://github.com/ansible-middleware/keycloak/issues/309#issuecomment-3101960407
2025-07-22 12:09:14 +02:00
Stephane Vigan
556d155533 Fix config_key_store_file description to match variable name 2025-07-21 16:15:59 +02:00
Sven Goericke
07063353b8 fixed wrong variable lookup 2025-07-16 13:50:07 +02:00
Guido Grazioli
c1bf9727f9 Merge pull request #293 from world-direct/fix/292
Update to keycloak 26.3.0
2025-07-09 11:38:56 +02:00
Helmut Wolf
f79fd227eb chore: bump KC/RHBK to v26.3.0/v26.2.5 2025-07-07 11:09:35 +02:00
Helmut Wolf
19564987ca fix(quarkus): update infinispan-client configuration to include port in server-list and hosts 2025-07-07 11:05:44 +02:00
Helmut Wolf
1ff25325a7 fix(ispn): use legacy JGroups stack configuration for < 26.2 only 2025-07-07 11:05:44 +02:00
Guido Grazioli
0099f1cf07 Merge pull request #303 from fxwgr/main
Allow to install provider jars from remote paths
2025-07-04 12:47:10 +02:00
Guido Grazioli
725ec8e37b Merge pull request #304 from SLedunois/client_secret
keycloak_realm: allow secret in keycloak_clients
2025-07-04 12:46:40 +02:00
Andreas Wagner
bbe568baa5 Added support for copy remote_src function for providers 2025-07-02 16:39:49 +02:00
LEDUNOIS Simon
dcd448443f feat: allow secret in keycloak_clients 2025-07-02 14:36:25 +00:00
ansible-middleware-core
3780a4e3c0 Bump version to 3.0.3 2025-07-01 16:56:26 +00:00
ansible-middleware-core
e60a5b7cf6 Update changelog for release 3.0.2
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2025-07-01 16:56:10 +00:00
Guido Grazioli
6143ae25e2 Merge pull request #296 from RanabirChakraborty/keycloak_force_install_quarkus_role
Fix `keycloak_quarkus_force_install` parameter being ignored by install
2025-07-01 18:45:34 +02:00
Ranabir Chakraborty
ef6d8890fb keycloak_quarkus_force_install does not ignore bootstrapped 2025-07-01 21:55:39 +05:30
Guido Grazioli
55185a1439 Merge pull request #302 from tinsjourney/fix_federation_provider_type
keycloak_realm: federation default provider type should be a string
2025-07-01 17:50:34 +02:00
Guido Grazioli
bb64b97e43 Merge pull request #298 from tinsjourney/alternate_download_fix
Fix alternate download location being ignored (JBossNeworkAPI always used)
2025-07-01 16:10:35 +02:00
Stephane Vigan
a9c9e05569 Default provider type should be a string 2025-07-01 10:27:00 +02:00
Stephane Vigan
8b27cb0706 Fix case where archive is always downalod from JBoss Nework API
Even is we set keycloak_quarkus_alternate_download_url
previous block was run and we always try to download
archive from JBoss Network Api.
2025-06-26 10:42:25 +02:00
Guido Grazioli
41127504dc Merge pull request #287 from guidograzioli/kc_26_caches
Session storage / distributed caches
2025-06-13 11:43:00 +02:00
Guido Grazioli
bcc961999c implement Single site - Sessions stored in external Infinispan 2025-06-05 12:02:43 +02:00
Guido Grazioli
b8907d765d Merge pull request #289 from guidograzioli/288_jdk_21
Use jdk21 as default in debian
2025-06-05 09:30:26 +02:00
Guido Grazioli
5c5e84b63e Use jdk21 as default in debian 2025-06-04 20:53:28 +02:00
Guido Grazioli
3d4bd734f1 document new parameters 2025-05-29 22:20:08 +02:00
Guido Grazioli
3de96a6666 single site remote cache 2025-05-29 21:37:11 +02:00
Guido Grazioli
de0ea02272 ci: move intermittent test back 2025-05-29 21:03:03 +02:00
Guido Grazioli
b6e585f503 Merge pull request #284 from guidograzioli/caches
Refactor test scenarios
2025-05-29 20:45:37 +02:00
Guido Grazioli
18de37706f tune Xmx Xms jvm args 2025-05-29 20:37:39 +02:00
Guido Grazioli
b569e4e713 update remote cache test 2025-05-29 20:14:04 +02:00
Guido Grazioli
919d55f806 Merge pull request #286 from RanabirChakraborty/AMW-398
Use tags to decorate the role workflow
2025-05-27 11:12:07 +02:00
Guido Grazioli
476bc0ec0b update test config 2025-05-23 14:59:50 +02:00
Helmut Wolf
2954bf81e8 Merge pull request #285 from world-direct/fix/keycloak_config_rebuild
Run config rebuild after SPI providers update
2025-05-21 09:26:07 +02:00
Ranabir Chakraborty
0403939c03 AMW-398 Use tags to decorate the role workflow 2025-05-20 21:40:32 +05:30
Helmut Wolf
88e4ea8d99 fix: MVN provider invokes KC config rebuild 2025-05-20 13:53:56 +02:00
Guido Grazioli
0a5fc3ae25 restructure molecule tests 2025-05-20 10:35:01 +02:00
Guido Grazioli
f4a1798f26 Merge pull request #283 from world-direct/feature/282
RHBK v26.2 (#282)
2025-05-19 18:37:01 +02:00
Helmut Wolf
d23ae39c25 chore(molecule): RHBK v26.2 (#282) 2025-05-19 14:44:34 +02:00
Helmut Wolf
8f95bcb9e6 feat(HA): Change default ispn discovery mechanism to JDBCPING as per v26.2.* (#282) 2025-05-19 14:44:34 +02:00
Helmut Wolf
f8c75de5d5 chore: RHBK v26.2: Bump KC version to v26.2.4 2025-05-19 14:44:34 +02:00
Helmut Wolf
8093b1af2a chore: RHBK v26.2: Bump RHBK version to v26.2.4 2025-05-19 14:12:22 +02:00
Helmut Wolf
a70aece0d9 chore: RHBK v26.2: Update recommended JDBC driver versions 2025-05-19 14:12:22 +02:00
Guido Grazioli
d427a6b721 Merge pull request #281 from jonathanspw/keycloak_quarkus_jgroups_ip
New parameter to set the jgroups host IP address
2025-05-13 19:04:56 +02:00
Jonathan Wright
c614af127e Add var to set the jgroups IP per host
This is useful if the default route does not
represent the network you want/need to use for
cluster communication.
2025-05-13 09:48:46 -05:00
Guido Grazioli
0936d415c7 ci: update test linking removed url 2025-05-09 15:20:57 +02:00
Guido Grazioli
a120b1c9b5 Merge pull request #280 from world-direct/feature/279_provider_checksums
New `checksum` property for keycloak_quarkus_providers
2025-05-06 17:49:41 +02:00
Helmut Wolf
5cd400b053 feat: introduce checksum for keycloak_quarkus_providers (#279) 2025-05-06 15:16:50 +02:00
ansible-middleware-core
e0c4b1e1ff Bump version to 3.0.2 2025-05-02 09:49:08 +00:00
ansible-middleware-core
88be789260 Update changelog for release 3.0.1
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2025-05-02 09:48:54 +00:00
Guido Grazioli
868dac4f72 Merge pull request #277 from guidograzioli/26_0_11_update
Version update to 26.0.8 / rhbk 26.0.11
2025-05-02 11:42:01 +02:00
Guido Grazioli
c45f7c0d60 Update remote cache default 2025-05-02 11:33:28 +02:00
Guido Grazioli
77c5b893b1 Merge pull request #276 from guidograzioli/275_envvars_handler
Trigger rebuild handler on envvars file change
2025-05-02 11:25:27 +02:00
Guido Grazioli
9974ab2ee1 update molecule scenario 2025-05-02 11:18:57 +02:00
Guido Grazioli
b8a2ebc699 update keycloak version 2025-05-02 10:55:17 +02:00
Guido Grazioli
5beb5dcda4 Add trigger on envvars file change 2025-05-02 10:50:01 +02:00
ansible-middleware-core
d97044523d Bump version to 3.0.1 2025-04-23 11:47:54 +00:00
ansible-middleware-core
2abc580041 Update changelog for release 3.0.0
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2025-04-23 11:47:38 +00:00
Guido Grazioli
2379e10091 Merge pull request #274 from guidograzioli/273_extra_envvars_rebuild
Load environment vars during kc rebuild
2025-04-23 10:47:36 +02:00
Guido Grazioli
c86dff66ba double quote sysconfig envvars 2025-04-22 20:18:48 +02:00
Guido Grazioli
f750e93d02 Add bash to preinstalled packages 2025-04-22 14:51:15 +02:00
Guido Grazioli
1a4590b0b8 Load envvars in kc rebuild 2025-04-18 17:59:16 +02:00
Guido Grazioli
5e9535c866 Merge pull request #271 from guidograzioli/honor_http_host_setting
Rename and honour parameter `keycloak_quarkus_http_host`
2025-04-18 09:11:11 +02:00
Guido Grazioli
b8028d376a Rename and honor parameter keycloak_quarkus_http_host 2025-04-16 14:16:07 +02:00
Guido Grazioli
20797e4cad Merge pull request #270 from guidograzioli/jdbc_params_rename
Rename parameters to follow upstream
2025-04-16 13:55:19 +02:00
Guido Grazioli
70d61ce8de rename ispn parameters 2025-04-16 11:58:04 +02:00
Guido Grazioli
69a947c0b6 rename _admin to _hostname_admin 2025-04-16 11:34:12 +02:00
Guido Grazioli
c7ce7be6c4 drop ajp port parameter 2025-04-16 10:42:07 +02:00
Guido Grazioli
e9061b29ef Rename parameters from jdbc to db 2025-04-16 10:31:48 +02:00
Guido Grazioli
c92bf19720 Merge pull request #269 from Preskton/patch-1
Update example playbooks to use new `bootstrap` var
2025-04-14 12:52:51 +02:00
Guido Grazioli
1ca0b30a81 Merge pull request #268 from RanabirChakraborty/AMW-384
keycloak_realm: change url variables to defaults
2025-04-14 10:49:01 +02:00
Preston Doster
7738e0feb1 Update keycloak_quarkus_dev.yml 2025-04-13 10:18:27 -05:00
Preston Doster
671cf4eb53 Updating example playbooks to use bootstrap admin password
It looks like the underlying `quarkus` provider has changed to use `keycloak_quarkus_bootstrap_admin_password`.
2025-04-13 10:17:22 -05:00
Ranabir Chakraborty
f146eb5fda AMW-384 Keycloak realm variable keycloak_url with hard-coded http 2025-04-11 21:49:01 +05:30
Guido Grazioli
a10bc95bfc Merge pull request #266 from guidograzioli/major_bump_3
Bump major and ansible-core versions
2025-04-09 20:28:52 +02:00
Guido Grazioli
314e2f26b2 Fix spell in parameter name 2025-04-09 18:08:18 +02:00
Guido Grazioli
f628b84fb0 disable restart health check because of selfsigned cert 2025-04-09 17:21:49 +02:00
Guido Grazioli
ac0ceca35f Update to ubi9 2025-04-09 09:45:22 +02:00
Guido Grazioli
744766fe3b update doc generation to 2.16 2025-04-08 15:36:38 +02:00
Guido Grazioli
7f980c44d2 Bump major and ansible-core versions 2025-04-08 11:58:47 +02:00
Guido Grazioli
532dc12a60 Merge pull request #254 from world-direct/feature/253_rhbk_v26
Role support for keycloak/RHBK v26
2025-04-08 11:47:35 +02:00
Guido Grazioli
173a85638f Merge pull request #257 from NilsDeckert/main
Skip certificate checking
2025-04-01 15:27:39 +02:00
Guido Grazioli
81f019f8b5 Merge pull request #264 from guidograzioli/linter_reserved_words
Variables names must not be Ansible reserved names
2025-04-01 15:25:10 +02:00
Guido Grazioli
5db96afa56 Variables names must not be Ansible reserved names 2025-04-01 15:20:37 +02:00
Helmut Wolf
fa36721207 Improve string interpolation 2025-01-20 09:44:27 +01:00
Helmut Wolf
86284b12c2 Fix molecule tests 2025-01-09 12:17:07 +01:00
Nils Deckert
b3e93dd89b Skip certificate checking 2024-12-20 17:21:49 +01:00
Helmut Wolf
e029e1c2fd keycloak_quarkus: Introduce keycloak_quarkus_health_check_url 2024-12-13 12:12:02 +01:00
Helmut Wolf
d0f19b59dc keycloak_quarkus: Add http_management_port and http_management_relative_path options
RHBK v26 exposes health endpoints and metrics on this port moving forward.
Note that the scheme of the MGMT interface is defined by the overall keycloak configuration: if https is enabled and configured, th MGMT interface is exposed via https and NOT via http; this might be breaking some configured load balancer health checks
2024-12-13 12:11:35 +01:00
Helmut Wolf
213449ec58 RHBK v26: Add hostname v2 (KC/RHBK v26 Support #253)
Cf. https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options - especially the removed options
2024-12-13 12:11:35 +01:00
Helmut Wolf
277e1336ee RHBK v26: Migrate to keycloak_quarkus_bootstrap_admin_user[_password] (Process for creation of admin account changed #248) 2024-12-13 12:11:35 +01:00
Helmut Wolf
58233549a7 keycloak.conf: Remove config-keystore-type (KC/RHBK v26 Support #253)
Cf. <https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#keystore_and_trust_store_default_format_change>
2024-12-13 12:11:35 +01:00
Helmut Wolf
0c58ae48ff RHBK v26: Update ispn session usages (KC/RHBK v26 Support #253)
Cf. <https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#restricting_the_size_of_session_caches>
2024-12-13 12:11:35 +01:00
Helmut Wolf
bf0bd9e1da RHBK v26: Update mssqj jdbc driver (KC/RHBK v26 Support #253)
As per <https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/server_configuration_guide/index#db-installing-the-microsoft-sql-server-driver>
2024-12-13 12:11:35 +01:00
Helmut Wolf
5d15d37890 RHBK v26: Raise default KC+RHBK versions to v26.x (#253) 2024-12-13 12:11:35 +01:00
Guido Grazioli
910a2aa5d4 Merge pull request #252 from world-direct/feature/cache_buster
Add theme cache invalidation handler
2024-12-12 09:36:18 +01:00
Helmut Wolf
5f534ca566 keycloak_quarkus: Add theme cache invalidation handler 2024-12-12 09:05:09 +01:00
Guido Grazioli
692fb59797 Merge pull request #251 from RanabirChakraborty/increase_access_token_lifespan
Access token lifespan is too short for ansible run
2024-12-11 16:19:13 +01:00
Ranabir Chakraborty
d1859aaff2 Access token lifespan is too short for ansible run 2024-12-03 22:40:25 +05:30
Guido Grazioli
0d0e52f9ff Merge pull request #250 from world-direct/feature/249
Rebuild config and restart service for local providers
2024-11-25 08:40:54 +01:00
Helmut Wolf
68a0f88423 keycloak_quarkus: Rebuild config and restart service for local providers (#249) 2024-11-22 08:08:09 +01:00
ansible-middleware-core
333d55ad73 Bump version to 2.4.4 2024-10-16 07:24:57 +00:00
ansible-middleware-core
f6fdae4aa8 Update changelog for release 2.4.3
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-10-16 07:24:42 +00:00
Guido Grazioli
b8c11f3ca8 Merge pull request #241 from guidograzioli/update_24_0_5
Update keycloak to 24.0.5
2024-10-16 09:02:43 +02:00
Guido Grazioli
1279937bb0 Update keycloak to 24.0.5 2024-10-16 08:56:53 +02:00
Guido Grazioli
c57753f608 Merge pull request #240 from guidograzioli/update_modules
update keycloak modules
2024-10-14 15:12:44 +02:00
Guido Grazioli
be19ec1289 update tests 2024-10-14 14:36:58 +02:00
Guido Grazioli
5f1b43f37b update docs 2024-10-14 10:34:20 +02:00
Guido Grazioli
c6bb815979 update keycloak modules 2024-10-14 10:22:10 +02:00
ansible-middleware-core
ac4511bea9 Bump version to 2.4.3 2024-09-26 08:52:17 +00:00
ansible-middleware-core
c8021f3102 Update changelog for release 2.4.2
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-09-26 08:52:04 +00:00
Guido Grazioli
0386254073 Merge pull request #239 from guidograzioli/238_controller_download_path
New parameter `keycloak_quarkus_download_path`
2024-09-26 10:35:18 +02:00
Guido Grazioli
b2edea8777 linter 2024-09-26 10:22:54 +02:00
Guido Grazioli
fc0ee5a896 refactor default test for keycloak-quarkus offline 2024-09-26 10:22:42 +02:00
Guido Grazioli
eb66d4a412 update prereqs validation 2024-09-26 10:22:28 +02:00
Guido Grazioli
f170257205 Add local download path 2024-09-24 09:21:10 +02:00
Guido Grazioli
3f4617c32c Merge pull request #237 from guidograzioli/236_waitfor_port_number
Add wait_for_port number parameter
2024-07-31 17:30:35 +02:00
Guido Grazioli
34caf6a490 add wait_for_port number parameter 2024-07-31 17:18:30 +02:00
Guido Grazioli
fa6ac99b34 Merge pull request #235 from guidograzioli/keycloak_realm_test
add verify steps for quarkus/keycloak_realm
2024-07-31 15:04:35 +02:00
Guido Grazioli
a35c963a65 add verify steps for quarkus/keycloak_realm 2024-07-18 13:01:01 +02:00
Guido Grazioli
11aab0f5e2 add verify steps for quarkus/keycloak_realm 2024-07-18 12:53:49 +02:00
ansible-middleware-core
fa2319d5da Bump version to 2.4.2 2024-07-02 14:23:53 +00:00
ansible-middleware-core
7c520dcdd2 Update changelog for release 2.4.1
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-07-02 14:23:37 +00:00
Guido Grazioli
35b3b090f6 ci: update READMEs 2024-07-02 15:59:16 +02:00
Guido Grazioli
94f1b8b355 ci: update README 2024-07-02 15:46:05 +02:00
Guido Grazioli
e40f554936 ci: add traffic wf 2024-06-27 11:02:32 +02:00
Guido Grazioli
64e2a95685 ci: add traffic wf 2024-06-27 11:01:38 +02:00
Guido Grazioli
c6fac7bb70 ci: add traffic wf 2024-06-27 11:00:29 +02:00
ansible-middleware-core
5f059e8d63 Bump version to 2.4.1 2024-06-04 15:44:35 +00:00
ansible-middleware-core
e927ddbb6c Update changelog for release 2.4.0
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-06-04 15:44:20 +00:00
Guido Grazioli
a82bdfbbb6 Bump to 2.4.0 2024-06-04 17:36:20 +02:00
Guido Grazioli
c850484e67 Merge pull request #234 from gionn/fix-restart-health-check-default
Enable by default health check on restart
2024-06-04 17:25:52 +02:00
Giovanni Toraldo
a4deaa005a Enable by default health check on restart 2024-06-04 17:00:11 +02:00
Guido Grazioli
4fb44091d6 ci: fix missing symlink 2024-05-30 08:44:04 +02:00
Guido Grazioli
883127d280 Merge pull request #232 from guidograzioli/linter_ansible_215
Update minimum ansible-core version > 2.15
2024-05-22 10:04:41 +02:00
Guido Grazioli
e69e5b7ba4 readme 2024-05-21 12:41:31 +02:00
Guido Grazioli
bf1871182b linter 2024-05-21 12:35:33 +02:00
Guido Grazioli
adfee5f6e1 ci 2024-05-21 12:34:11 +02:00
Guido Grazioli
ef53ca545a update yamllint 2024-05-21 12:31:40 +02:00
Guido Grazioli
2092c2d23a Update minimum ansible-core version > 2.15 2024-05-21 12:27:45 +02:00
ansible-middleware-core
8ca73364e9 Bump version to 2.3.1 2024-05-20 10:22:09 +00:00
ansible-middleware-core
df1939e387 Update changelog for release 2.3.0
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-05-20 10:21:55 +00:00
Guido Grazioli
0de0b654ee Merge pull request #227 from world-direct/feature/226
`proxy-header` enhancement
2024-05-16 16:49:06 +02:00
Helmut Wolf
62cbaa3596 Introduce keycloak_quarkus_show_deprecation_warnings, disabled in molecule tests 2024-05-16 16:30:57 +02:00
Helmut Wolf
92c24e49e7 #226: add proper default value for proxy-headers 2024-05-16 12:45:24 +02:00
Helmut Wolf
cc012767a4 #226 - add deprecation warning 2024-05-16 12:45:24 +02:00
Helmut Wolf
4d31117c16 Fix RHBK version 2024-05-16 12:45:24 +02:00
Helmut Wolf
0fd8eb52d2 #226: CR changes 2024-05-16 12:45:24 +02:00
Helmut Wolf
6f2ed4d53b Fix #226 - minor proxy-header enhancement 2024-05-16 12:45:24 +02:00
Guido Grazioli
1519d46f0e Merge pull request #231 from ansible-middleware/feature/182_restart_handler
Restart handler strategy behaviour
2024-05-16 11:25:02 +02:00
Guido Grazioli
4b21569f36 parameterize health check; refactor serial_then_parallel 2024-05-16 11:16:20 +02:00
Guido Grazioli
f63b20b9d4 Update verify steps 2024-05-15 20:01:58 +02:00
Guido Grazioli
fdcf1b2ed2 Add molecule scenario for HA restart 2024-05-15 19:53:33 +02:00
Guido Grazioli
c22389c86f address review reqs 2024-05-15 15:58:21 +02:00
Guido Grazioli
2d573c2b62 Add restart strategies, and allow custom task include
Co-authored-by: Helmut Wolf <hwo@world-direct.at>
Co-authored-by: Guido Grazioli <ggraziol@redhat.com>
2024-05-15 13:48:00 +02:00
Helmut Wolf
1e9a669dea #221 - add keycloak_quarkus_health_check_url_path config option 2024-05-15 10:28:46 +02:00
Helmut Wolf
db831fa339 #182 - CR changes 2024-05-15 10:17:32 +02:00
Helmut Wolf
d57be1f188 Close #182, #221: improve restart handler logic 2024-05-15 09:47:03 +02:00
Guido Grazioli
5adb28dcd8 Bump to 2.3.0 2024-05-15 09:22:45 +02:00
Guido Grazioli
477ce5eaa3 Merge pull request #223 from world-direct/feature/222_mvn_providers
Feature/222  keycloak_quarkus: Add support for custom providers hosted on MVN
2024-05-15 09:17:23 +02:00
Helmut Wolf
d2ece93c12 #222 Migrate to middleware_automation.common.maven_artifact 2024-05-14 20:33:29 +02:00
Guido Grazioli
1a23350a8f Merge pull request #229 from world-direct/feature/228_sysconfig_custom_env_variables
Allow to add extra custom env vars in sysconfig file
2024-05-14 17:06:49 +02:00
Helmut Wolf
26316ddc50 #222: add support for local providers to be uploaded 2024-05-14 11:59:16 +02:00
Helmut Wolf
6d01ffbb77 Close #228: add support for custom env vars in sysconfig file 2024-05-14 11:10:38 +02:00
Helmut Wolf
d87c8ca8ac wip 2024-05-14 10:14:43 +02:00
Helmut Wolf
d8e9620a8a #222: Molecule tests 2024-05-14 10:14:43 +02:00
Helmut Wolf
4b902adc8d #222: Add support for maven providers 2024-05-14 10:14:15 +02:00
Guido Grazioli
1b69191a6e Merge pull request #225 from world-direct/feature/224_policy_files
#224:  keycloak_quarkus: Add support for policy files
2024-05-14 09:11:51 +02:00
Helmut Wolf
6682853a2d #224: Add missing argument specs 2024-05-14 08:58:57 +02:00
Helmut Wolf
9f4623b05a #224: keycloak_quarkus: Add support for policy files 2024-05-14 08:36:55 +02:00
Guido Grazioli
599ce0179c Merge pull request #220 from guidograzioli/rhbk_alternate_download_location
Download from alternate URL with optional http authentication
2024-05-10 15:33:51 +02:00
Guido Grazioli
8f14be37d7 add functionality 2024-05-10 10:17:37 +02:00
Guido Grazioli
3076c3d5ce Merge pull request #218 from Footur/update-keycloak-v24.0.4
Update Keycloak to version 24.0.4
2024-05-09 11:08:47 +02:00
Guido Grazioli
6610a310ff Merge pull request #217 from guidograzioli/major_upgrade_test
Port downstream upgrade
2024-05-09 11:08:23 +02:00
Footur
fcf629d05e Update Keycloak to version 24.0.4 2024-05-09 09:24:47 +02:00
Guido Grazioli
4bbc8e0256 update systemd service name in verify 2024-05-08 19:14:04 +02:00
Guido Grazioli
4c96cbe7f6 use sane version to be upgraded 2024-05-08 19:09:59 +02:00
Guido Grazioli
22f5ad902f add test to github actions 2024-05-08 19:05:24 +02:00
Guido Grazioli
3c22417674 Port downstream upgrade 2024-05-08 19:03:30 +02:00
Guido Grazioli
cd36eacb07 Merge pull request #215 from world-direct/feature/214_sqlserver_jdbc_version
Close #214: RHBK 24.*: Update sqlserver JDBC version
2024-05-08 18:45:03 +02:00
Helmut Wolf
a019823871 Close #214: RHBK 24.*: Update sqlserver JDBC version 2024-05-08 17:15:50 +02:00
Guido Grazioli
3863508df5 Merge pull request #213 from guidograzioli/2_2_3_linter
Linter warnings fix pass
2024-05-07 10:30:16 +02:00
Guido Grazioli
1115ee409a Linter warnings fix pass 2024-05-07 10:18:43 +02:00
Guido Grazioli
b497e946cc Bump tp 2.2.3 2024-05-07 09:47:12 +02:00
Guido Grazioli
5067c03201 Merge pull request #211 from guidograzioli/keycloak_rebuild_java_home
`kc.sh build` uses configured jdk
2024-05-06 13:13:20 +02:00
Guido Grazioli
a45b18dc85 kc.sh build uses configured jdk 2024-05-06 13:08:41 +02:00
Guido Grazioli
70834ccf13 downstream: remove problematic owner of downloaded zipfile 2024-05-06 12:03:44 +02:00
Guido Grazioli
2a7395c444 downstream: update default to rhbk 24.0.3 2024-05-06 11:20:00 +02:00
ansible-middleware-core
4da0e83ae9 Bump version to 2.2.3 2024-05-06 08:11:28 +00:00
ansible-middleware-core
b427cb8a24 Update changelog for release 2.2.2
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-05-06 08:11:11 +00:00
Guido Grazioli
fa39e9b824 Merge pull request #210 from Footur/copy-key-material
Enable copying of key material
2024-05-06 08:28:09 +02:00
Footur
320a5f0d9a Copy the TLS private key from memory
This change should avoid storing plain private keys on disk due to
security risks. It also makes it easier to encrypt the data with SOPS.
2024-05-05 13:58:19 +02:00
Footur
7141e1c9b2 Test: Installation of key material via Ansible role 2024-05-05 12:11:51 +02:00
Footur
9bc1ae69e9 Enable copying of key material
This commit updates the configuration to use the standard Red Hat
Enterprise Linux (RHEL) default path for TLS certificates, which is
/etc/pki/tls.

Also, it copies the private key and certificate to the target host.
2024-05-03 16:34:57 +02:00
Guido Grazioli
bfbbacc72b Merge pull request #209 from guidograzioli/205_controller_become
controller priv escalation
2024-05-03 14:45:55 +02:00
Guido Grazioli
feec4d9f8b controller priv escalation 2024-05-03 13:03:18 +02:00
Guido Grazioli
ba127153ff Merge pull request #207 from InfoSec812/Issue_206-_-fix-misnamed-params-and-allow-invalid-certs
jdbc_download and validate_certs params update
2024-05-03 08:47:38 +02:00
Deven Phillips
b14d75dfab jdbc_download and validate_certs params update
- Added jdbc_download customization to both keycloak releases
- Added option to allow invalid certificates to download JDBC drivers
2024-05-02 14:33:36 -04:00
ansible-middleware-core
1d6a6eb7ee Bump version to 2.2.2 2024-05-02 17:00:01 +00:00
ansible-middleware-core
1ab3ebc2a4 Update changelog for release 2.2.1
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-05-02 16:59:47 +00:00
Guido Grazioli
d16c23faf9 Merge pull request #204 from InfoSec812/Issue_203-_-fix-input-validation-when-clause
Fix logic in when clause
2024-05-02 18:46:05 +02:00
Deven Phillips
978494524f Fix errors introduced 2024-05-02 12:31:16 -04:00
Deven Phillips
1a73c39a91 Fix logic in when clause 2024-05-02 12:09:36 -04:00
ansible-middleware-core
9e6a6f6076 Bump version to 2.2.1 2024-05-01 14:44:15 +00:00
ansible-middleware-core
55f6881b2f Update changelog for release 2.2.0
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-05-01 14:44:01 +00:00
Guido Grazioli
41cbcc41e8 Merge pull request #202 from InfoSec812/Issue_200-_-customize-jdbc-driver-downloads
Customize jdbc driver downloads, optional authentication
2024-05-01 10:01:32 +02:00
Deven Phillips
c2904bf20d Use FQCN for fail module 2024-04-30 14:48:10 -04:00
Deven Phillips
e76b33e1db Merge remote-tracking branch 'upstream/main' into Issue_200-_-customize-jdbc-driver-downloads 2024-04-30 14:29:26 -04:00
Deven Phillips
a7b9f0ef97 Add option to override JDBC download parameters 2024-04-30 14:27:42 -04:00
Guido Grazioli
eafc4586d6 ci: turn historicized docs off 2024-04-30 13:09:27 +02:00
Guido Grazioli
8493adc5c8 Merge pull request #201 from guidograzioli/custom_providers
Providers config and custom providers
2024-04-30 12:47:53 +02:00
Guido Grazioli
43b9ffcb64 Providers config and custom providers 2024-04-30 10:45:20 +02:00
Guido Grazioli
a33393a477 ci: downstream molecule fixes 2024-04-25 14:11:05 +02:00
Guido Grazioli
278a70d627 ci: downstream molecule fixes 2024-04-25 13:57:31 +02:00
Guido Grazioli
6967385c7f ci: downstream molecule fixes 2024-04-25 13:03:03 +02:00
Guido Grazioli
ac23e04d6a ci: downstream molecule fixes 2024-04-25 08:16:56 +02:00
Guido Grazioli
4c056d886e ci: downstream molecule fixes 2024-04-24 21:20:16 +02:00
Guido Grazioli
213a9a0766 ci: downstream molecule fixes 2024-04-24 17:56:15 +02:00
Guido Grazioli
2925ea8cf1 Add wait_for systemd logic 2024-04-24 16:17:05 +02:00
Guido Grazioli
82498ab3f5 Merge pull request #195 from InfoSec812/Issue-193_-_add-option-for-hostname-strict-https
Added hostname-strict-https option
2024-04-19 16:05:46 +02:00
Guido Grazioli
16accd5e30 Merge branch 'main' into Issue-193_-_add-option-for-hostname-strict-https 2024-04-19 16:00:09 +02:00
Deven Phillips
04bb465992 Added argument specs 2024-04-19 09:55:08 -04:00
Guido Grazioli
b978e8bb88 Merge pull request #197 from world-direct/feature/190_remove_KEYCLOAK_ADMIN_envs
#190: remove `keycloak_quarkus_admin_user[_pass]` once keycloak is bootstrapped
2024-04-19 14:44:05 +02:00
Helmut Wolf
289b4767e0 #190: remove keycloak_quarkus_admin_user[_pass] once keycloak is bootstrapped 2024-04-19 13:42:28 +02:00
Guido Grazioli
9a961f743b Merge pull request #196 from guidograzioli/172_vaults
Keystore based vault SPI
2024-04-19 09:06:38 +02:00
Deven Phillips
b8cba487ac Add better error trapping for booleans 2024-04-18 13:15:46 -04:00
Guido Grazioli
ff198bcd3e workaround debug logfile too long for slurp 2024-04-18 11:06:14 +02:00
Guido Grazioli
d06dcea998 Add argument specs, update README 2024-04-18 10:49:38 +02:00
Guido Grazioli
89db3fa36f Implement vault config 2024-04-18 10:44:17 +02:00
Guido Grazioli
cd8d61afc3 Update molecule test for keystore vault 2024-04-18 10:43:48 +02:00
Deven Phillips
47e6644fdd Ensure that value for keycloak_quarkus_hostname_strict_https is boolean, otherwise ignore it 2024-04-17 16:57:52 -04:00
Deven Phillips
3e28b3f4f7 Added hostname-strict-https option 2024-04-17 16:52:18 -04:00
Guido Grazioli
f7bcac79d0 Merge pull request #194 from guidograzioli/keycloak_24_update
Update keycloak to 24.0
2024-04-17 18:16:34 +02:00
Guido Grazioli
10057262bc 'fix' changelog 2024-04-17 18:07:42 +02:00
Guido Grazioli
5808d055ae Update keycloak to 24.0 2024-04-17 17:53:13 +02:00
Guido Grazioli
8060dd7fb8 Bump minor and start 2.2 2024-04-17 17:51:33 +02:00
Guido Grazioli
4f8ed5194c Merge pull request #189 from world-direct/feature/188_config_keystore
#188: add support for configuration key store
2024-04-17 17:50:30 +02:00
ansible-middleware-core
462389cf0f Bump version to 2.1.3 2024-04-17 15:49:15 +00:00
Helmut Wolf
e991bd32c8 Fix typos 2024-04-17 17:09:44 +02:00
Helmut Wolf
d469d389f3 Fix linter issues 2024-04-17 17:09:44 +02:00
Helmut Wolf
c38642e0cd #188: fail early when no keytool installed 2024-04-17 17:09:44 +02:00
Helmut Wolf
0ee29eb483 #188: keycloak_quarkus: allow setting "sensitive options" using a Java KeyStore file #188 2024-04-17 17:09:44 +02:00
Helmut Wolf
60ca798e1a Rename keycloak_quarkus_*_store_* attributes 2024-04-17 17:09:44 +02:00
Helmut Wolf
921364b451 Fix docs 2024-04-17 17:09:44 +02:00
145 changed files with 8977 additions and 1037 deletions

View File

@@ -28,14 +28,15 @@ warn_list:
- name[casing] - name[casing]
- fqcn[action] - fqcn[action]
- schema[meta] - schema[meta]
- var-naming[no-role-prefix]
- key-order[task] - key-order[task]
- blocked_modules - blocked_modules
- run-once[task]
skip_list: skip_list:
- vars_should_not_be_used - vars_should_not_be_used
- file_is_small_enough - file_is_small_enough
- file_has_valid_name
- name[template] - name[template]
- var-naming[no-role-prefix]
use_default_rules: true use_default_rules: true
parseable: true

View File

@@ -5,14 +5,21 @@ on:
branches: branches:
- main - main
pull_request: pull_request:
workflow_dispatch:
inputs:
debug_verbosity:
description: 'ANSIBLE_VERBOSITY envvar value'
required: false
schedule: schedule:
- cron: '15 6 * * *' - cron: '15 6 * * *'
jobs: jobs:
ci: ci:
uses: ansible-middleware/github-actions/.github/workflows/ci.yml@main uses: ansible-middleware/github-actions/.github/workflows/ci.yml@rootperm
secrets: inherit secrets: inherit
with: with:
fqcn: 'middleware_automation/keycloak' fqcn: 'middleware_automation/keycloak'
root_permission_varname: 'keycloak_install_requires_become'
debug_verbosity: "${{ github.event.inputs.debug_verbosity }}"
molecule_tests: >- molecule_tests: >-
[ "default", "overridexml", "https_revproxy", "quarkus", "quarkus-devmode", "debian" ] [ "debian", "quarkus", "quarkus_ha", "quarkus_ha_remote", "quarkus_ha_26.4_below", "default", "quarkus_devmode", "quarkus_upgrade" ]

View File

@@ -15,3 +15,4 @@ jobs:
with: with:
fqcn: 'middleware_automation/keycloak' fqcn: 'middleware_automation/keycloak'
collection_fqcn: 'middleware_automation.keycloak' collection_fqcn: 'middleware_automation.keycloak'
historical_docs: 'false'

26
.github/workflows/traffic.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Collect traffic stats
on:
schedule:
- cron: "51 23 * * 0"
workflow_dispatch:
jobs:
traffic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: "gh-pages"
- name: GitHub traffic
uses: sangonzal/repository-traffic-action@v.0.1.6
env:
TRAFFIC_ACTION_TOKEN: ${{ secrets.TRIGGERING_PAT }}
- name: Commit changes
uses: EndBug/add-and-commit@v4
with:
author_name: Ansible Middleware
message: "GitHub traffic"
add: "./traffic/*"
ref: "gh-pages"

3
.gitignore vendored
View File

@@ -12,3 +12,6 @@ docs/_build/
*.retry *.retry
changelogs/.plugin-cache.yaml changelogs/.plugin-cache.yaml
*.pem *.pem
*.key
*.p12
.ansible/

View File

@@ -15,7 +15,8 @@ rules:
commas: commas:
max-spaces-after: -1 max-spaces-after: -1
level: error level: error
comments: disable comments:
min-spaces-from-content: 1
comments-indentation: disable comments-indentation: disable
document-start: disable document-start: disable
empty-lines: empty-lines:
@@ -31,3 +32,7 @@ rules:
type: unix type: unix
trailing-spaces: disable trailing-spaces: disable
truthy: disable truthy: disable
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true

View File

@@ -6,9 +6,224 @@ middleware\_automation.keycloak Release Notes
This changelog describes changes after version 0.2.6. 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
======
Major Changes
-------------
- Update to keycloak 26.3.0 `#293 <https://github.com/ansible-middleware/keycloak/pull/293>`_
- ansible-core 2.19 compatibility `#310 <https://github.com/ansible-middleware/keycloak/pull/310>`_
Minor Changes
-------------
- Allow to install provider jars from remote paths `#303 <https://github.com/ansible-middleware/keycloak/pull/303>`_
- Declared proxy_mode as deprecated, updated quarkus and realm readme `#306 <https://github.com/ansible-middleware/keycloak/pull/306>`_
- Fix config_key_store_file description to match variable name `#308 <https://github.com/ansible-middleware/keycloak/pull/308>`_
Bugfixes
--------
- keycloak collection CI label is showing no status `#312 <https://github.com/ansible-middleware/keycloak/pull/312>`_
- keycloak_realm: allow secret in keycloak_clients `#304 <https://github.com/ansible-middleware/keycloak/pull/304>`_
v3.0.2
======
Minor Changes
-------------
- New ``checksum`` property for keycloak_quarkus_providers `#280 <https://github.com/ansible-middleware/keycloak/pull/280>`_
- New parameter to set the jgroups host IP address `#281 <https://github.com/ansible-middleware/keycloak/pull/281>`_
- Session storage / distributed caches `#287 <https://github.com/ansible-middleware/keycloak/pull/287>`_
- Update keycloak/RHBK to v26.2.4 `#283 <https://github.com/ansible-middleware/keycloak/pull/283>`_
Bugfixes
--------
- Fix ``keycloak_quarkus_force_install`` parameter being ignored by install `#296 <https://github.com/ansible-middleware/keycloak/pull/296>`_
- Fix alternate download location being ignored (JBossNeworkAPI always used) `#298 <https://github.com/ansible-middleware/keycloak/pull/298>`_
- Run config rebuild after SPI providers update `#285 <https://github.com/ansible-middleware/keycloak/pull/285>`_
- Use jdk21 as default in debian `#289 <https://github.com/ansible-middleware/keycloak/pull/289>`_
- keycloak_realm: federation default provider type should be a string `#302 <https://github.com/ansible-middleware/keycloak/pull/302>`_
v3.0.1
======
Minor Changes
-------------
- Version update to 26.0.8 / rhbk 26.0.11 `#277 <https://github.com/ansible-middleware/keycloak/pull/277>`_
Bugfixes
--------
- Trigger rebuild handler on envvars file change `#276 <https://github.com/ansible-middleware/keycloak/pull/276>`_
v3.0.0
======
Minor Changes
-------------
- Add theme cache invalidation handler `#252 <https://github.com/ansible-middleware/keycloak/pull/252>`_
- keycloak_realm: change url variables to defaults `#268 <https://github.com/ansible-middleware/keycloak/pull/268>`_
Breaking Changes / Porting Guide
--------------------------------
- Bump major and ansible-core versions `#266 <https://github.com/ansible-middleware/keycloak/pull/266>`_
- Rename parameters to follow upstream `#270 <https://github.com/ansible-middleware/keycloak/pull/270>`_
- Update for keycloak v26 `#254 <https://github.com/ansible-middleware/keycloak/pull/254>`_
Bugfixes
--------
- Access token lifespan is too short for ansible run `#251 <https://github.com/ansible-middleware/keycloak/pull/251>`_
- Load environment vars during kc rebuild `#274 <https://github.com/ansible-middleware/keycloak/pull/274>`_
- Rebuild config and restart service for local providers `#250 <https://github.com/ansible-middleware/keycloak/pull/250>`_
- Rename and honour parameter ``keycloak_quarkus_http_host`` `#271 <https://github.com/ansible-middleware/keycloak/pull/271>`_
New Modules
-----------
- middleware_automation.keycloak.keycloak_realm - Allows administration of Keycloak realm via Keycloak API
v2.4.3
======
Minor Changes
-------------
- Update keycloak to 24.0.5 `#241 <https://github.com/ansible-middleware/keycloak/pull/241>`_
v2.4.2
======
Minor Changes
-------------
- New parameter ``keycloak_quarkus_download_path`` `#239 <https://github.com/ansible-middleware/keycloak/pull/239>`_
Bugfixes
--------
- Add wait_for_port number parameter `#237 <https://github.com/ansible-middleware/keycloak/pull/237>`_
v2.4.1
======
Release Summary
---------------
Internal release, documentation or test changes only.
v2.4.0
======
Major Changes
-------------
- Enable by default health check on restart `#234 <https://github.com/ansible-middleware/keycloak/pull/234>`_
- Update minimum ansible-core version > 2.15 `#232 <https://github.com/ansible-middleware/keycloak/pull/232>`_
v2.3.0
======
Major Changes
-------------
- Allow for custom providers hosted on maven repositories `#223 <https://github.com/ansible-middleware/keycloak/pull/223>`_
- Restart handler strategy behaviour `#231 <https://github.com/ansible-middleware/keycloak/pull/231>`_
Minor Changes
-------------
- Add support for policy files `#225 <https://github.com/ansible-middleware/keycloak/pull/225>`_
- Allow to add extra custom env vars in sysconfig file `#229 <https://github.com/ansible-middleware/keycloak/pull/229>`_
- Download from alternate URL with optional http authentication `#220 <https://github.com/ansible-middleware/keycloak/pull/220>`_
- Update Keycloak to version 24.0.4 `#218 <https://github.com/ansible-middleware/keycloak/pull/218>`_
- ``proxy-header`` enhancement `#227 <https://github.com/ansible-middleware/keycloak/pull/227>`_
Bugfixes
--------
- ``kc.sh build`` uses configured jdk `#211 <https://github.com/ansible-middleware/keycloak/pull/211>`_
v2.2.2
======
Minor Changes
-------------
- Copying of key material for TLS configuration `#210 <https://github.com/ansible-middleware/keycloak/pull/210>`_
- Validate certs parameter for JDBC driver downloads `#207 <https://github.com/ansible-middleware/keycloak/pull/207>`_
Bugfixes
--------
- Turn off controller privilege escalation `#209 <https://github.com/ansible-middleware/keycloak/pull/209>`_
v2.2.1
======
Release Summary
---------------
Internal release, documentation or test changes only.
Bugfixes
--------
- JDBC provider: fix clause in argument validation `#204 <https://github.com/ansible-middleware/keycloak/pull/204>`_
v2.2.0
======
Major Changes
-------------
- Support java keystore for configuration of sensitive options `#189 <https://github.com/ansible-middleware/keycloak/pull/189>`_
Minor Changes
-------------
- Add ``wait_for_port`` and ``wait_for_log`` systemd unit logic `#199 <https://github.com/ansible-middleware/keycloak/pull/199>`_
- Customize jdbc driver downloads, optional authentication `#202 <https://github.com/ansible-middleware/keycloak/pull/202>`_
- Keystore-based vault SPI configuration `#196 <https://github.com/ansible-middleware/keycloak/pull/196>`_
- New ``keycloak_quarkus_hostname_strict_https`` parameter `#195 <https://github.com/ansible-middleware/keycloak/pull/195>`_
- Providers config and custom providers `#201 <https://github.com/ansible-middleware/keycloak/pull/201>`_
- Remove administrator credentials from files once keycloak is bootstrapped `#197 <https://github.com/ansible-middleware/keycloak/pull/197>`_
- Update keycloak to 24.0 `#194 <https://github.com/ansible-middleware/keycloak/pull/194>`_
v2.1.2 v2.1.2
====== ======
Release Summary
---------------
Internal release, documentation or test changes only.
v2.1.1 v2.1.1
====== ======
@@ -275,6 +490,11 @@ Minor Changes
v1.0.4 v1.0.4
====== ======
Release Summary
---------------
Internal release, documentation or test changes only.
v1.0.3 v1.0.3
====== ======

View File

@@ -1,3 +1,37 @@
## Developing
### Build and install locally
Clone the repository, checkout the tag you want to build, or pick the main branch for the development version; then:
ansible-galaxy collection build .
ansible-galaxy collection install middleware_automation-keycloak-*.tar.gz
### Development environment
Make sure your development machine has avilable:
* python 3.11+
* virtualenv
* docker (or podman)
In order to run setup the development environment and run the molecule tests locally, after cloning the repository:
```
# create new virtualenv using python 3
virtualenv $PATH_TO_DEV_VIRTUALENV
# activate the virtual env
source $PATH_TO_DEV_VIRTUALENV/bin/activate
# install ansible and tools onto the virtualenv
pip install yamllint 'molecule>=6.0' 'molecule-plugins[docker]' 'ansible-core>=2.16' ansible-lint
# install collection dependencies
ansible-galaxy collection install -r requirements.yml
# install python dependencies
pip install -r requirements.txt molecule/requirements.txt
# execute the tests (replace --all with -s subdirectory to run a single test)
molecule test --all
```
## Contributor's Guidelines ## Contributor's Guidelines

View File

@@ -1,17 +1,18 @@
# Ansible Collection - middleware_automation.keycloak # Ansible Collection - middleware_automation.keycloak
<!--start build_status --> <!--start build_status -->
[![Build Status](https://github.com/ansible-middleware/keycloak/workflows/CI/badge.svg?branch=main)](https://github.com/ansible-middleware/keycloak/actions/workflows/ci.yml) [![Build Status](https://github.com/ansible-middleware/keycloak/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ansible-middleware/keycloak/actions/workflows/ci.yml)
> **_NOTE:_ If you are Red Hat customer, install `redhat.sso` (for Red Hat Single Sign-On) or `redhat.rhbk` (for Red Hat Build of Keycloak) from [Automation Hub](https://console.redhat.com/ansible/ansible-dashboard) as the certified version of this collection.** > **_NOTE:_ If you are Red Hat customer, install `redhat.rhbk` (for Red Hat Build of Keycloak) or `redhat.sso` (for Red Hat Single Sign-On) from [Automation Hub](https://console.redhat.com/ansible/ansible-dashboard) as the certified version of this collection.**
<!--end build_status --> <!--end build_status -->
<!--start description -->
Collection to install and configure [Keycloak](https://www.keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) / [Red Hat Build of Keycloak](https://access.redhat.com/products/red-hat-build-of-keycloak). Collection to install and configure [Keycloak](https://www.keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) / [Red Hat Build of Keycloak](https://access.redhat.com/products/red-hat-build-of-keycloak).
<!--end description -->
<!--start requires_ansible--> <!--start requires_ansible-->
## Ansible version compatibility ## Ansible version compatibility
This collection has been tested against following Ansible versions: **>=2.14.0**. This collection has been tested against following Ansible versions: **>=2.16.0**.
Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions. Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions.
<!--end requires_ansible--> <!--end requires_ansible-->
@@ -39,6 +40,7 @@ collections:
The keycloak collection also depends on the following python packages to be present on the controller host: The keycloak collection also depends on the following python packages to be present on the controller host:
* netaddr * netaddr
* lxml
A requirement file is provided to install: A requirement file is provided to install:
@@ -47,18 +49,28 @@ A requirement file is provided to install:
<!--start roles_paths --> <!--start roles_paths -->
### Included roles ### Included roles
* [`keycloak`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md): role for installing the service (keycloak <= 19.0). * `keycloak_quarkus`: role for installing keycloak (>= 19.0.0, quarkus based).
* [`keycloak_realm`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md): role for configuring a realm, user federation(s), clients and users, in an installed service. * `keycloak_realm`: role for configuring a realm, user federation(s), clients and users, in an installed service.
* [`keycloak_quarkus`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md): role for installing the quarkus variant of keycloak (>= 17.0.0). * `keycloak`: role for installing legacy keycloak (<= 19.0, wildfly based).
<!--end roles_paths --> <!--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 ## Usage
### Install Playbook ### Install Playbook
<!--start rhbk_playbook --> <!--start rhbk_playbook -->
* [`playbooks/keycloak.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak.yml) installs keycloak legacy based on the defined variables (using most defaults).
* [`playbooks/keycloak_quarkus.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_quarkus.yml) installs keycloak >= 17 based on the defined variables (using most defaults). * [`playbooks/keycloak_quarkus.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_quarkus.yml) installs keycloak >= 17 based on the defined variables (using most defaults).
* [`playbooks/keycloak.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak.yml) installs keycloak legacy based on the defined variables (using most defaults).
Both playbooks include the `keycloak` role, with different settings, as described in the following sections. Both playbooks include the `keycloak` role, with different settings, as described in the following sections.
@@ -100,16 +112,19 @@ ansible-playbook -i <ansible_hosts> -e @rhn-creds.yml playbooks/keycloak.yml -e
localhost ansible_connection=local localhost ansible_connection=local
``` ```
Note: when deploying clustered configurations, all hosts belonging to the cluster must be present in ansible_play_batch; ie. they must be targeted by the same ansible-playbook execution. Note: when deploying clustered configurations, all hosts belonging to the cluster must be present in `ansible_play_batch`; ie. they must be targeted by the same ansible-playbook execution.
## Configuration ## Configuration
### Config Playbook ### Config Playbooks
<!--start rhbk_realm_playbook --> <!--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 --> <!--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 ### Example configuration command
@@ -131,14 +146,24 @@ 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). 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 --> <!--end rhbk_realm_readme -->
## Support
<!--start support --> <!--start support -->
For bug reports and feature requests, use [GitHub Issues](https://github.com/ansible-middleware/keycloak/issues).
<!--end support --> <!--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 ## License
Apache License v2.0 or later Apache License v2.0 or later
<!--start license --> <!--start license -->
See [LICENSE](LICENSE) to view the full text. See [LICENSE](LICENSE) to view the full text.
<!--end license --> <!--end license -->

View File

@@ -59,6 +59,10 @@ releases:
- 31.yaml - 31.yaml
release_date: '2022-05-09' release_date: '2022-05-09'
1.0.4: 1.0.4:
changes:
release_summary: 'Internal release, documentation or test changes only.
'
release_date: '2022-05-11' release_date: '2022-05-11'
1.0.5: 1.0.5:
changes: changes:
@@ -455,4 +459,325 @@ releases:
- 191.yaml - 191.yaml
release_date: '2024-04-17' release_date: '2024-04-17'
2.1.2: 2.1.2:
changes:
release_summary: 'Internal release, documentation or test changes only.
'
release_date: '2024-04-17' release_date: '2024-04-17'
2.2.0:
changes:
major_changes:
- 'Support java keystore for configuration of sensitive options `#189 <https://github.com/ansible-middleware/keycloak/pull/189>`_
'
minor_changes:
- 'Add ``wait_for_port`` and ``wait_for_log`` systemd unit logic `#199 <https://github.com/ansible-middleware/keycloak/pull/199>`_
'
- 'Customize jdbc driver downloads, optional authentication `#202 <https://github.com/ansible-middleware/keycloak/pull/202>`_
'
- 'Keystore-based vault SPI configuration `#196 <https://github.com/ansible-middleware/keycloak/pull/196>`_
'
- 'New ``keycloak_quarkus_hostname_strict_https`` parameter `#195 <https://github.com/ansible-middleware/keycloak/pull/195>`_
'
- 'Providers config and custom providers `#201 <https://github.com/ansible-middleware/keycloak/pull/201>`_
'
- 'Remove administrator credentials from files once keycloak is bootstrapped
`#197 <https://github.com/ansible-middleware/keycloak/pull/197>`_
'
- 'Update keycloak to 24.0 `#194 <https://github.com/ansible-middleware/keycloak/pull/194>`_
'
fragments:
- 189.yaml
- 194.yaml
- 195.yaml
- 196.yaml
- 197.yaml
- 199.yaml
- 201.yaml
- 202.yaml
release_date: '2024-05-01'
2.2.1:
changes:
bugfixes:
- 'JDBC provider: fix clause in argument validation `#204 <https://github.com/ansible-middleware/keycloak/pull/204>`_
'
release_summary: Internal release, documentation or test changes only.
fragments:
- 204.yaml
- v2.2.1-devel_summary.yaml
release_date: '2024-05-02'
2.2.2:
changes:
bugfixes:
- 'Turn off controller privilege escalation `#209 <https://github.com/ansible-middleware/keycloak/pull/209>`_
'
minor_changes:
- 'Copying of key material for TLS configuration `#210 <https://github.com/ansible-middleware/keycloak/pull/210>`_
'
- 'Validate certs parameter for JDBC driver downloads `#207 <https://github.com/ansible-middleware/keycloak/pull/207>`_
'
fragments:
- 207.yaml
- 209.yaml
- 210.yaml
release_date: '2024-05-06'
2.3.0:
changes:
bugfixes:
- '``kc.sh build`` uses configured jdk `#211 <https://github.com/ansible-middleware/keycloak/pull/211>`_
'
major_changes:
- 'Allow for custom providers hosted on maven repositories `#223 <https://github.com/ansible-middleware/keycloak/pull/223>`_
'
- 'Restart handler strategy behaviour `#231 <https://github.com/ansible-middleware/keycloak/pull/231>`_
'
minor_changes:
- 'Add support for policy files `#225 <https://github.com/ansible-middleware/keycloak/pull/225>`_
'
- 'Allow to add extra custom env vars in sysconfig file `#229 <https://github.com/ansible-middleware/keycloak/pull/229>`_
'
- 'Download from alternate URL with optional http authentication `#220 <https://github.com/ansible-middleware/keycloak/pull/220>`_
'
- 'Update Keycloak to version 24.0.4 `#218 <https://github.com/ansible-middleware/keycloak/pull/218>`_
'
- '``proxy-header`` enhancement `#227 <https://github.com/ansible-middleware/keycloak/pull/227>`_
'
fragments:
- 211.yaml
- 218.yaml
- 220.yaml
- 223.yaml
- 225.yaml
- 227.yaml
- 229.yaml
- 231.yaml
release_date: '2024-05-20'
2.4.0:
changes:
major_changes:
- 'Enable by default health check on restart `#234 <https://github.com/ansible-middleware/keycloak/pull/234>`_
'
- 'Update minimum ansible-core version > 2.15 `#232 <https://github.com/ansible-middleware/keycloak/pull/232>`_
'
fragments:
- 232.yaml
- 234.yaml
release_date: '2024-06-04'
2.4.1:
changes:
release_summary: Internal release, documentation or test changes only.
fragments:
- v2.4.1-devel_summary.yaml
release_date: '2024-07-02'
2.4.2:
changes:
bugfixes:
- 'Add wait_for_port number parameter `#237 <https://github.com/ansible-middleware/keycloak/pull/237>`_
'
minor_changes:
- 'New parameter ``keycloak_quarkus_download_path`` `#239 <https://github.com/ansible-middleware/keycloak/pull/239>`_
'
fragments:
- 237.yaml
- 239.yaml
release_date: '2024-09-26'
2.4.3:
changes:
minor_changes:
- 'Update keycloak to 24.0.5 `#241 <https://github.com/ansible-middleware/keycloak/pull/241>`_
'
fragments:
- 241.yaml
release_date: '2024-10-16'
3.0.0:
changes:
breaking_changes:
- 'Bump major and ansible-core versions `#266 <https://github.com/ansible-middleware/keycloak/pull/266>`_
'
- 'Rename parameters to follow upstream `#270 <https://github.com/ansible-middleware/keycloak/pull/270>`_
'
- 'Update for keycloak v26 `#254 <https://github.com/ansible-middleware/keycloak/pull/254>`_
'
bugfixes:
- 'Access token lifespan is too short for ansible run `#251 <https://github.com/ansible-middleware/keycloak/pull/251>`_
'
- 'Load environment vars during kc rebuild `#274 <https://github.com/ansible-middleware/keycloak/pull/274>`_
'
- 'Rebuild config and restart service for local providers `#250 <https://github.com/ansible-middleware/keycloak/pull/250>`_
'
- 'Rename and honour parameter ``keycloak_quarkus_http_host`` `#271 <https://github.com/ansible-middleware/keycloak/pull/271>`_
'
minor_changes:
- 'Add theme cache invalidation handler `#252 <https://github.com/ansible-middleware/keycloak/pull/252>`_
'
- 'keycloak_realm: change url variables to defaults `#268 <https://github.com/ansible-middleware/keycloak/pull/268>`_
'
fragments:
- 250.yaml
- 251.yaml
- 252.yaml
- 254.yaml
- 266.yaml
- 268.yaml
- 270.yaml
- 271.yaml
- 274.yaml
modules:
- description: Allows administration of Keycloak realm via Keycloak API
name: keycloak_realm
namespace: ''
release_date: '2025-04-23'
3.0.1:
changes:
bugfixes:
- 'Trigger rebuild handler on envvars file change `#276 <https://github.com/ansible-middleware/keycloak/pull/276>`_
'
minor_changes:
- 'Version update to 26.0.8 / rhbk 26.0.11 `#277 <https://github.com/ansible-middleware/keycloak/pull/277>`_
'
fragments:
- 276.yaml
- 277.yaml
release_date: '2025-05-02'
3.0.2:
changes:
bugfixes:
- 'Fix ``keycloak_quarkus_force_install`` parameter being ignored by install
`#296 <https://github.com/ansible-middleware/keycloak/pull/296>`_
'
- 'Fix alternate download location being ignored (JBossNeworkAPI always used)
`#298 <https://github.com/ansible-middleware/keycloak/pull/298>`_
'
- 'Run config rebuild after SPI providers update `#285 <https://github.com/ansible-middleware/keycloak/pull/285>`_
'
- 'Use jdk21 as default in debian `#289 <https://github.com/ansible-middleware/keycloak/pull/289>`_
'
- 'keycloak_realm: federation default provider type should be a string `#302
<https://github.com/ansible-middleware/keycloak/pull/302>`_
'
minor_changes:
- 'New ``checksum`` property for keycloak_quarkus_providers `#280 <https://github.com/ansible-middleware/keycloak/pull/280>`_
'
- 'New parameter to set the jgroups host IP address `#281 <https://github.com/ansible-middleware/keycloak/pull/281>`_
'
- 'Session storage / distributed caches `#287 <https://github.com/ansible-middleware/keycloak/pull/287>`_
'
- 'Update keycloak/RHBK to v26.2.4 `#283 <https://github.com/ansible-middleware/keycloak/pull/283>`_
'
fragments:
- 280.yaml
- 281.yaml
- 283.yaml
- 285.yaml
- 287.yaml
- 289.yaml
- 296.yaml
- 298.yaml
- 302.yaml
release_date: '2025-07-01'
3.0.3:
changes:
bugfixes:
- 'keycloak collection CI label is showing no status `#312 <https://github.com/ansible-middleware/keycloak/pull/312>`_
'
- 'keycloak_realm: allow secret in keycloak_clients `#304 <https://github.com/ansible-middleware/keycloak/pull/304>`_
'
major_changes:
- 'Update to keycloak 26.3.0 `#293 <https://github.com/ansible-middleware/keycloak/pull/293>`_
'
- 'ansible-core 2.19 compatibility `#310 <https://github.com/ansible-middleware/keycloak/pull/310>`_
'
minor_changes:
- 'Allow to install provider jars from remote paths `#303 <https://github.com/ansible-middleware/keycloak/pull/303>`_
'
- 'Declared proxy_mode as deprecated, updated quarkus and realm readme `#306
<https://github.com/ansible-middleware/keycloak/pull/306>`_
'
- 'Fix config_key_store_file description to match variable name `#308 <https://github.com/ansible-middleware/keycloak/pull/308>`_
'
fragments:
- 293.yaml
- 303.yaml
- 304.yaml
- 306.yaml
- 308.yaml
- 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

@@ -7,7 +7,7 @@
</div> </div>
<hr/> <hr/>
<div role="contentinfo"> <div role="contentinfo">
<p>&#169; Copyright 2022, Red Hat, Inc.</p> <p>&#169; Copyright 2024, Red Hat, Inc.</p>
</div> </div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a> <a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>

View File

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

View File

@@ -10,31 +10,25 @@ Welcome to Keycloak Collection documentation
README README
plugins/index plugins/index
roles/index roles/index
Changelog <CHANGELOG>
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Developer documentation :caption: Developer documentation
testing Developing <developing>
developing Testing <testing>
releasing Releasing <releasing>
.. toctree::
:maxdepth: 2
:caption: General
Changelog <CHANGELOG>
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Middleware collections :caption: Middleware collections
Infinispan / Red Hat Data Grid <https://ansible-middleware.github.io/infinispan/main/>
Keycloak / Red Hat Single Sign-On <https://ansible-middleware.github.io/keycloak/main/> Keycloak / Red Hat Single Sign-On <https://ansible-middleware.github.io/keycloak/main/>
Infinispan / Red Hat Data Grid <https://ansible-middleware.github.io/infinispan/main/>
Wildfly / Red Hat JBoss EAP <https://ansible-middleware.github.io/wildfly/main/> Wildfly / Red Hat JBoss EAP <https://ansible-middleware.github.io/wildfly/main/>
Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/main/> Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/main/>
ActiveMQ / Red Hat AMQ Broker <https://ansible-middleware.github.io/amq/main/> ActiveMQ / Red Hat AMQ Broker <https://ansible-middleware.github.io/amq/main/>
Kafka / Red Hat AMQ Streams <https://ansible-middleware.github.io/amq_streams/main/> Kafka / Red Hat AMQ Streams <https://ansible-middleware.github.io/amq_streams/main/>
Ansible Middleware utilities <https://ansible-middleware.github.io/common/main/> Ansible Middleware utilities <https://ansible-middleware.github.io/common/main/>
Red Hat CSP Download <https://ansible-middleware.github.io/redhat-csp-download/main/>
JCliff <https://ansible-middleware.github.io/ansible_collections_jcliff/main/> JCliff <https://ansible-middleware.github.io/ansible_collections_jcliff/main/>

View File

@@ -1,7 +1,9 @@
# ansible_basic_sphinx_ext still imports pkg_resources (removed in setuptools 82+).
setuptools>=70.0.0,<81.0.0
antsibull>=0.17.0 antsibull>=0.17.0
antsibull-docs antsibull-docs
antsibull-changelog antsibull-changelog
ansible-core>=2.14.1 ansible-core>=2.16.0
ansible-pygments ansible-pygments
sphinx-rtd-theme sphinx-rtd-theme
git+https://github.com/felixfontein/ansible-basic-sphinx-ext git+https://github.com/felixfontein/ansible-basic-sphinx-ext

View File

@@ -4,24 +4,7 @@
The collection is tested with a [molecule](https://github.com/ansible-community/molecule) setup covering the included roles and verifying correct installation and idempotency. The collection is tested with a [molecule](https://github.com/ansible-community/molecule) setup covering the included roles and verifying correct installation and idempotency.
In order to run the molecule tests locally with python 3.9 available, after cloning the repository: In order to run the molecule tests locally with python 3.9 available, after cloning the repository:
The test scenarios are available on the source code repository each on his own subdirectory under [molecule/](https://github.com/ansible-middleware/keycloak/molecule).
```
pip install yamllint 'molecule[docker]~=3.5.2' ansible-core flake8 ansible-lint voluptuous
molecule test --all
```
## Integration testing
Demo repositories which depend on the collection, and aggregate functionality with other middleware_automation collections, are automatically rebuilt
at every collection release to ensure non-breaking changes and consistent behaviour.
The repository are:
- [Flange demo](https://github.com/ansible-middleware/flange-demo)
A deployment of Wildfly cluster integrated with keycloak and infinispan.
- [CrossDC keycloak demo](https://github.com/ansible-middleware/cross-dc-rhsso-demo)
A clustered multi-regional installation of keycloak with infinispan remote caches.
## Test playbooks ## Test playbooks
@@ -29,15 +12,7 @@ The repository are:
Sample playbooks are provided in the `playbooks/` directory; to run the playbooks locally (requires a rhel system with python 3.9+, ansible, and systemd) the steps are as follows: Sample playbooks are provided in the `playbooks/` directory; to run the playbooks locally (requires a rhel system with python 3.9+, ansible, and systemd) the steps are as follows:
``` ```
# setup environment # setup environment as in developing
pip install ansible-core
# clone the repository
git clone https://github.com/ansible-middleware/keycloak
cd keycloak
# install collection dependencies
ansible-galaxy collection install -r requirements.yml
# install collection python deps
pip install -r requirements.txt
# create inventory for localhost # create inventory for localhost
cat << EOF > inventory cat << EOF > inventory
[keycloak] [keycloak]

View File

@@ -1,7 +1,7 @@
--- ---
namespace: middleware_automation namespace: middleware_automation
name: keycloak name: keycloak
version: "2.1.2" version: "3.0.4"
readme: README.md readme: README.md
authors: authors:
- Romain Pelisse <rpelisse@redhat.com> - Romain Pelisse <rpelisse@redhat.com>
@@ -26,7 +26,7 @@ tags:
- middleware - middleware
- a4mw - a4mw
dependencies: dependencies:
"middleware_automation.common": ">=1.1.0" "middleware_automation.common": ">=1.2.1"
"ansible.posix": ">=1.4.0" "ansible.posix": ">=1.4.0"
repository: https://github.com/ansible-middleware/keycloak repository: https://github.com/ansible-middleware/keycloak
documentation: https://ansible-middleware.github.io/keycloak documentation: https://ansible-middleware.github.io/keycloak
@@ -35,7 +35,9 @@ issues: https://github.com/ansible-middleware/keycloak/issues
build_ignore: build_ignore:
- .gitignore - .gitignore
- .github - .github
- .ansible-lint
- .yamllint - .yamllint
- .DS_Store
- '*.tar.gz' - '*.tar.gz'
- '*.zip' - '*.zip'
- molecule - molecule

View File

@@ -1,2 +1,2 @@
--- ---
requires_ansible: ">=2.14.0" requires_ansible: ">=2.16.0"

View File

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

View File

@@ -3,7 +3,7 @@ driver:
name: docker name: docker
platforms: platforms:
- name: instance - name: instance
image: ghcr.io/hspaans/molecule-containers:debian-11 image: ghcr.io/hspaans/molecule-containers:debian-13
pre_build_image: true pre_build_image: true
privileged: true privileged: true
port_bindings: port_bindings:

View File

@@ -1,11 +1,13 @@
--- ---
- name: Prepare - name: Prepare
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes gather_facts: yes
tasks: tasks:
- name: Install sudo - name: Install sudo
ansible.builtin.apt: ansible.builtin.apt:
name: name:
- sudo - sudo
- openjdk-17-jdk-headless - openjdk-21-jdk-headless
state: present - iproute2

View File

@@ -2,7 +2,7 @@
- name: Verify - name: Verify
hosts: all hosts: all
vars: vars:
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}" keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}" keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_jboss_port_offset: 10 keycloak_jboss_port_offset: 10

View File

@@ -1,27 +1,27 @@
--- ---
- name: Converge - name: Converge
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars: vars:
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_show_deprecation_warnings: false
keycloak_jvm_package: java-11-openjdk-headless keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_modcluster_enabled: True keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_modcluster_urls: keycloak_quarkus_hostname: http://instance:8080
- host: myhost1 keycloak_quarkus_log: file
port: 16667 keycloak_quarkus_log_level: debug
- host: myhost2 keycloak_quarkus_log_target: /tmp/keycloak
port: 16668 keycloak_quarkus_start_dev: true
keycloak_jboss_port_offset: 10 keycloak_quarkus_proxy_mode: none
keycloak_log_target: /tmp/keycloak keycloak_quarkus_offline_install: true
keycloak_quarkus_download_path: /tmp/keycloak/
keycloak_quarkus_java_heap_opts: "-Xms640m -Xmx640m "
roles: roles:
- role: keycloak - role: keycloak_quarkus
tasks: - role: keycloak_realm
- name: Keycloak Realm Role keycloak_url: "{{ keycloak_quarkus_hostname }}"
ansible.builtin.include_role: keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
name: keycloak_realm keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
vars:
keycloak_client_default_roles:
- TestRoleAdmin
- TestRoleUser
keycloak_client_users: keycloak_client_users:
- username: TestUser - username: TestUser
password: password password: password
@@ -41,22 +41,8 @@
keycloak_realm: TestRealm keycloak_realm: TestRealm
keycloak_clients: keycloak_clients:
- name: TestClient - name: TestClient
roles: "{{ keycloak_client_default_roles }}"
realm: "{{ keycloak_realm }}" realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_client_public }}" public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_client_web_origins }}" web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_client_users }}" users: "{{ keycloak_client_users }}"
client_id: TestClient client_id: TestClient
attributes:
post.logout.redirect.uris: '/public/logout'
pre_tasks:
- name: "Retrieve assets server from env"
ansible.builtin.set_fact:
assets_server: "{{ lookup('env', 'MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:
sso_offline_install: True
when:
- assets_server is defined
- assets_server | length > 0

View File

@@ -1,9 +1,9 @@
--- ---
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"
@@ -11,6 +11,7 @@ platforms:
- "8080/tcp" - "8080/tcp"
- "8443/tcp" - "8443/tcp"
- "8009/tcp" - "8009/tcp"
- "9000/tcp"
provisioner: provisioner:
name: ansible name: ansible
config_options: config_options:
@@ -23,11 +24,16 @@ provisioner:
converge: converge.yml converge: converge.yml
verify: verify.yml verify: verify.yml
inventory: inventory:
group_vars:
all:
keycloak_install_requires_become: true
host_vars: host_vars:
localhost: localhost:
ansible_python_interpreter: "{{ ansible_playbook_python }}" ansible_python_interpreter: "{{ ansible_playbook_python }}"
env: env:
ANSIBLE_FORCE_COLOR: "true" ANSIBLE_FORCE_COLOR: "true"
PROXY: "${PROXY}"
NO_PROXY: "${NO_PROXY}"
verifier: verifier:
name: ansible name: ansible
scenario: scenario:

View File

@@ -1,29 +1,27 @@
--- ---
- name: Prepare - name: Prepare
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes gather_facts: yes
vars: vars:
sudo_pkg_name: sudo sudo_pkg_name: sudo
tasks: tasks:
- name: "Run preparation common to all scenario" - name: "Run preparation common to all scenario"
ansible.builtin.include_tasks: ../prepare.yml ansible.builtin.include_tasks: ../prepare.yml
vars:
assets:
- "{{ assets_server }}/sso/7.6.0/rh-sso-7.6.0-server-dist.zip"
- "{{ assets_server }}/sso/7.6.1/rh-sso-7.6.1-patch.zip"
- name: Install JDK8 - name: Create controller directory for downloads
become: yes ansible.builtin.file: # noqa risky-file-permissions delegated, uses controller host user
ansible.builtin.yum: path: /tmp/keycloak
name: state: directory
- java-1.8.0-openjdk mode: '0750'
state: present delegate_to: localhost
when: ansible_facts['os_family'] == "RedHat" run_once: true
- name: Install JDK8 - name: Download keycloak archive to controller directory
become: yes ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
ansible.builtin.apt: url: https://github.com/keycloak/keycloak/releases/download/26.4.7/keycloak-26.4.7.zip
name: dest: /tmp/keycloak
- openjdk-8-jdk mode: '0640'
state: present delegate_to: localhost
when: ansible_facts['os_family'] == "Debian" run_once: true

View File

@@ -2,11 +2,9 @@
- name: Verify - name: Verify
hosts: all hosts: all
vars: vars:
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_jvm_package: java-11-openjdk-headless keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}" keycloak_uri: "http://localhost:8080"
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_jboss_port_offset: 10
tasks: tasks:
- name: Populate service facts - name: Populate service facts
ansible.builtin.service_facts: ansible.builtin.service_facts:
@@ -15,75 +13,13 @@
that: that:
- ansible_facts.services["keycloak.service"]["state"] == "running" - ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled" - ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify we are running on requested jvm # noqa blocked_modules command-instead-of-module
ansible.builtin.shell: |
set -o pipefail
ps -ef | grep '/etc/alternatives/jre_11/' | grep -v grep
args:
executable: /bin/bash
changed_when: no
- name: Verify token api call - name: Verify token api call
ansible.builtin.uri: ansible.builtin.uri:
url: "{{ keycloak_uri }}/auth/realms/master/protocol/openid-connect/token" url: "{{ keycloak_uri }}/realms/master/protocol/openid-connect/token"
method: POST method: POST
body: "client_id=admin-cli&username=admin&password={{ keycloak_admin_password }}&grant_type=password" body: "client_id=admin-cli&username={{ keycloak_quarkus_bootstrap_admin_user }}&password={{ keycloak_quarkus_bootstrap_admin_user }}&grant_type=password"
validate_certs: no validate_certs: no
register: keycloak_auth_response register: keycloak_auth_response
until: keycloak_auth_response.status == 200 until: keycloak_auth_response.status == 200
retries: 2 retries: 2
delay: 2 delay: 2
- name: Fetch openid-connect config
ansible.builtin.uri:
url: "{{ keycloak_uri }}/auth/realms/TestRealm/.well-known/openid-configuration"
method: GET
validate_certs: no
status_code: 200
register: keycloak_openid_config
- name: Verify expected config
ansible.builtin.assert:
that:
- keycloak_openid_config.json.registration_endpoint == 'http://localhost:8080/auth/realms/TestRealm/clients-registrations/openid-connect'
- name: Get test realm clients
ansible.builtin.uri:
url: "{{ keycloak_uri }}/auth/admin/realms/TestRealm/clients"
method: GET
validate_certs: no
status_code: 200
headers:
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
register: keycloak_query_clients
- name: Verify expected config
ansible.builtin.assert:
that:
- (keycloak_query_clients.json | selectattr('clientId','equalto','TestClient') | first)["attributes"]["post.logout.redirect.uris"] == '/public/logout'
- name: "Privilege escalation as some files/folders may requires it"
become: yes
block:
- name: Check log folder
ansible.builtin.stat:
path: "/tmp/keycloak"
register: keycloak_log_folder
- name: Check that keycloak log folder exists and is a link
ansible.builtin.assert:
that:
- keycloak_log_folder.stat.exists
- not keycloak_log_folder.stat.isdir
- keycloak_log_folder.stat.islnk
- name: Check log file
ansible.builtin.stat:
path: "/tmp/keycloak/server.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
- name: Check default log folder
ansible.builtin.stat:
path: "/var/log/keycloak"
register: keycloak_default_log_folder
failed_when: false
- name: Check that default keycloak log folder doesn't exist
ansible.builtin.assert:
that:
- not keycloak_default_log_folder.stat.exists

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,16 +1,18 @@
--- ---
- name: Converge - name: Converge
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars: vars:
keycloak_quarkus_admin_pass: "remembertochangeme" keycloak_quarkus_show_deprecation_warnings: false
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_realm: TestRealm keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_quarkus_host: instance keycloak_quarkus_hostname: https://proxy
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_http_enabled: True keycloak_quarkus_http_enabled: True
keycloak_quarkus_http_port: 8080 keycloak_quarkus_http_port: 8080
keycloak_quarkus_proxy_mode: edge keycloak_quarkus_proxy_mode: edge
keycloak_quarkus_http_relative_path: / keycloak_quarkus_http_relative_path: /
keycloak_quarkus_frontend_url: https://proxy/ keycloak_quarkus_health_check_url: http://proxy:8080/realms/master/.well-known/openid-configuration
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus

View File

@@ -3,7 +3,7 @@ driver:
name: docker name: docker
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"
@@ -14,7 +14,7 @@ platforms:
published_ports: published_ports:
- 0.0.0.0:8080:8080/tcp - 0.0.0.0:8080:8080/tcp
- name: proxy - name: proxy
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ driver:
name: docker name: docker
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"
@@ -11,6 +11,7 @@ platforms:
- "8080/tcp" - "8080/tcp"
- "8443/tcp" - "8443/tcp"
- "8009/tcp" - "8009/tcp"
- "9000/tcp"
provisioner: provisioner:
name: ansible name: ansible
config_options: config_options:

View File

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

View File

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

View File

@@ -1,20 +1,70 @@
--- ---
- name: Converge - name: Converge
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars: vars:
keycloak_quarkus_admin_pass: "remembertochangeme" keycloak_quarkus_show_deprecation_warnings: false
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm: TestRealm keycloak_realm: TestRealm
keycloak_quarkus_host: instance keycloak_quarkus_hostname: https://instance:8443
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_https_key_file_enabled: True keycloak_quarkus_log_level: debug # needed for the verify step
keycloak_quarkus_key_file: "/opt/keycloak/certs/key.pem" keycloak_quarkus_https_key_file_enabled: true
keycloak_quarkus_cert_file: "/opt/keycloak/certs/cert.pem" keycloak_quarkus_key_file_copy_enabled: true
keycloak_quarkus_key_content: "{{ lookup('file', 'key.pem') }}"
keycloak_quarkus_cert_file_copy_enabled: true
keycloak_quarkus_cert_file_src: cert.pem
keycloak_quarkus_log_target: /tmp/keycloak keycloak_quarkus_log_target: /tmp/keycloak
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_restart_health_check: false # would fail because of self-signed cert
keycloak_quarkus_version: 26.4.7
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx1024m"
keycloak_quarkus_additional_env_vars:
- key: KC_FEATURES_DISABLED
value: impersonation,kerberos
keycloak_quarkus_providers:
- id: http-client
spi: connections
default: true
restart: true
properties:
- key: default-connection-pool-size
value: 10
- id: spid-saml
url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
- id: spid-saml-w-checksum
url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
checksum: sha256:fbb50e73739d7a6d35b5bff611b1c01668b29adf6f6259624b95e466a305f377
- id: keycloak-kerberos-federation
maven:
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.4.7 # optional
# username: myUser # optional
# password: myPAT # optional
# - id: my-static-theme
# local_path: /tmp/my-static-theme.jar
keycloak_quarkus_policies:
- name: "cain-and-abel.txt"
url: "https://github.com/danielmiessler/SecLists/raw/master/Passwords/Software/cain-and-abel.txt"
- name: "john-the-ripper.txt"
url: "https://github.com/danielmiessler/SecLists/raw/master/Passwords/Software/john-the-ripper.txt"
type: password-blacklists
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_context: '' keycloak_url: http://instance:8080
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_default_roles: keycloak_client_default_roles:
- TestRoleAdmin - TestRoleAdmin
- TestRoleUser - TestRoleUser

View File

@@ -3,7 +3,7 @@ driver:
name: docker name: docker
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"
@@ -11,6 +11,7 @@ platforms:
- "8080/tcp" - "8080/tcp"
- "8443/tcp" - "8443/tcp"
- "8009/tcp" - "8009/tcp"
- "9000/tcp"
published_ports: published_ports:
- 0.0.0.0:8443:8443/tcp - 0.0.0.0:8443:8443/tcp
provisioner: provisioner:
@@ -30,6 +31,9 @@ provisioner:
ansible_python_interpreter: "{{ ansible_playbook_python }}" ansible_python_interpreter: "{{ ansible_playbook_python }}"
env: env:
ANSIBLE_FORCE_COLOR: "true" ANSIBLE_FORCE_COLOR: "true"
PYTHONHTTPSVERIFY: 0
PROXY: "${PROXY}"
NO_PROXY: "${NO_PROXY}"
verifier: verifier:
name: ansible name: ansible
scenario: scenario:

View File

@@ -1,6 +1,8 @@
--- ---
- name: Prepare - name: Prepare
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks: tasks:
- name: "Display hera_home if defined." - name: "Display hera_home if defined."
ansible.builtin.set_fact: ansible.builtin.set_fact:
@@ -11,22 +13,38 @@
- name: Create certificate request - 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' 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 delegate_to: localhost
changed_when: False changed_when: false
- name: Create conf directory # risky-file-permissions in test user account does not exist yet - name: Create vault directory
become: yes become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.file: ansible.builtin.file:
state: directory state: directory
path: "/opt/keycloak/certs/" path: "/opt/keycloak/vault"
mode: 0755 mode: '0755'
- name: Copy certificates - name: Make sure a jre is available (for keytool to prepare keystore)
become: yes delegate_to: localhost
ansible.builtin.package:
name: java-21-openjdk-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
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: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ item }}" src: keystore.p12
dest: "/opt/keycloak/certs/{{ item }}" dest: /opt/keycloak/vault/keystore.p12
mode: 0444 mode: '0444'
loop:
- cert.pem
- key.pem

View File

@@ -1,6 +1,11 @@
--- ---
- name: Verify - name: Verify
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
tasks: tasks:
- name: Populate service facts - name: Populate service facts
ansible.builtin.service_facts: ansible.builtin.service_facts:
@@ -10,6 +15,7 @@
that: that:
- ansible_facts.services["keycloak.service"]["state"] == "running" - ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled" - ansible_facts.services["keycloak.service"]["status"] == "enabled"
fail_msg: "Service not running"
- name: Set internal envvar - name: Set internal envvar
ansible.builtin.set_fact: ansible.builtin.set_fact:
@@ -32,15 +38,15 @@
- name: Verify endpoint URLs - name: Verify endpoint URLs
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'https://instance/realms/master/protocol/openid-connect/ext/ciba/auth' - (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'https://instance:8443/realms/master/protocol/openid-connect/ext/ciba/auth'
- (openid_config.stdout | from_json)['issuer'] == 'https://instance/realms/master' - (openid_config.stdout | from_json)['issuer'] == 'https://instance:8443/realms/master'
- (openid_config.stdout | from_json)['authorization_endpoint'] == 'https://instance/realms/master/protocol/openid-connect/auth' - (openid_config.stdout | from_json)['authorization_endpoint'] == 'https://instance:8443/realms/master/protocol/openid-connect/auth'
- (openid_config.stdout | from_json)['token_endpoint'] == 'https://instance/realms/master/protocol/openid-connect/token' - (openid_config.stdout | from_json)['token_endpoint'] == 'https://instance:8443/realms/master/protocol/openid-connect/token'
delegate_to: localhost delegate_to: localhost
- name: Check log folder - name: Check log folder
ansible.builtin.stat: ansible.builtin.stat:
path: "/tmp/keycloak" path: /tmp/keycloak
register: keycloak_log_folder register: keycloak_log_folder
- name: Check that keycloak log folder exists and is a link - name: Check that keycloak log folder exists and is a link
@@ -49,11 +55,12 @@
- keycloak_log_folder.stat.exists - keycloak_log_folder.stat.exists
- not keycloak_log_folder.stat.isdir - not keycloak_log_folder.stat.isdir
- keycloak_log_folder.stat.islnk - keycloak_log_folder.stat.islnk
fail_msg: "Service log symlink not correctly created"
- name: Check log file - name: Check log file
become: yes become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "/tmp/keycloak/keycloak.log" path: /tmp/keycloak/keycloak.log
register: keycloak_log_file register: keycloak_log_file
- name: Check if keycloak file exists - name: Check if keycloak file exists
@@ -63,9 +70,9 @@
- not keycloak_log_file.stat.isdir - not keycloak_log_file.stat.isdir
- name: Check default log folder - name: Check default log folder
become: yes become: "{{ molecule_prepare_require_privilege_escalation }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "/var/log/keycloak" path: /var/log/keycloak
register: keycloak_default_log_folder register: keycloak_default_log_folder
failed_when: false failed_when: false
@@ -73,3 +80,51 @@
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- not keycloak_default_log_folder.stat.exists - not keycloak_default_log_folder.stat.exists
- name: Verify vault SPI in logfile
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
changed_when: false
failed_when: slurped_log.rc != 0
register: slurped_log
- name: Verify token api call
ansible.builtin.uri:
url: "https://instance:8443/realms/master/protocol/openid-connect/token"
method: POST
body: "client_id=admin-cli&username={{ keycloak_quarkus_bootstrap_admin_user }}&password={{ keycloak_quarkus_bootstrap_admin_password}}&grant_type=password"
validate_certs: no
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 2
delay: 2
- name: "Get Clients"
ansible.builtin.uri:
url: "https://instance:8443/admin/realms/TestRealm/clients"
validate_certs: false
headers:
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
register: keycloak_clients
- name: Get client uuid
ansible.builtin.set_fact:
keycloak_client_uuid: "{{ ((keycloak_clients.json | selectattr('clientId', '==', 'TestClient')) | first).id }}"
- name: "Get Client {{ keycloak_client_uuid }}"
ansible.builtin.uri:
url: "https://instance:8443/admin/realms/TestRealm/clients/{{ keycloak_client_uuid }}"
validate_certs: false
headers:
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
register: keycloak_test_client
- name: "Get Client roles"
ansible.builtin.uri:
url: "https://instance:8443/admin/realms/TestRealm/clients/{{ keycloak_client_uuid }}/roles"
validate_certs: false
headers:
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
register: keycloak_test_client_roles

View File

@@ -1,19 +1,26 @@
--- ---
- name: Converge - name: Converge
hosts: all hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars: vars:
keycloak_quarkus_admin_pass: "remembertochangeme" keycloak_quarkus_show_deprecation_warnings: false
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm: TestRealm keycloak_realm: TestRealm
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_frontend_url: 'http://localhost:8080/' keycloak_quarkus_hostname: 'http://localhost:8080'
keycloak_quarkus_start_dev: True keycloak_quarkus_start_dev: True
keycloak_quarkus_proxy_mode: none keycloak_quarkus_proxy_mode: none
keycloak_quarkus_java_home: /opt/openjdk/ keycloak_quarkus_java_home: /opt/openjdk/
keycloak_quarkus_java_heap_opts: "-Xms640m -Xmx640m"
roles: roles:
- role: keycloak_quarkus - role: keycloak_quarkus
- role: keycloak_realm - role: keycloak_realm
keycloak_context: '' keycloak_url: "{{ keycloak_quarkus_hostname }}"
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
keycloak_client_default_roles: keycloak_client_default_roles:
- TestRoleAdmin - TestRoleAdmin
- TestRoleUser - TestRoleUser

View File

@@ -1,17 +1,19 @@
--- ---
driver: driver:
name: docker name: podman
platforms: platforms:
- name: instance - name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true pre_build_image: true
privileged: true privileged: true
command: "/usr/sbin/init" command: "/usr/sbin/init"
port_bindings: port_bindings:
- "8080/tcp" - "8080/tcp"
- "8009/tcp" - "8009/tcp"
- "9000/tcp"
published_ports: published_ports:
- 0.0.0.0:8080:8080/tcp - 0.0.0.0:8080:8080/tcp
- 0.0.0.0:9000:9000/TCP
provisioner: provisioner:
name: ansible name: ansible
config_options: config_options:
@@ -29,6 +31,8 @@ provisioner:
ansible_python_interpreter: "{{ ansible_playbook_python }}" ansible_python_interpreter: "{{ ansible_playbook_python }}"
env: env:
ANSIBLE_FORCE_COLOR: "true" ANSIBLE_FORCE_COLOR: "true"
PROXY: "${PROXY}"
NO_PROXY: "${NO_PROXY}"
verifier: verifier:
name: ansible name: ansible
scenario: scenario:

View File

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

View File

@@ -0,0 +1,31 @@
---
- 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
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

1
molecule/quarkus_ha/roles Symbolic link
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

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

@@ -0,0 +1,63 @@
---
- 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
infinispan_supervisor_password: remembertochangeme
infinispan_keycloak_caches: true
infinispan_keycloak_persistence: False
infinispan_jdbc_engine: postgres
infinispan_jdbc_url: jdbc:postgresql://postgres:5432/keycloak
infinispan_jdbc_driver_version: 9.4.1212
infinispan_jdbc_user: keycloak
infinispan_jdbc_pass: mysecretpass
infinispan_bind_address: "{{ ansible_default_ipv4.address }}"
infinispan_users:
- { name: 'testuser', password: 'test', roles: 'observer' }
- 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_cache_remote: true
keycloak_quarkus_cache_remote_username: supervisor
keycloak_quarkus_cache_remote_password: remembertochangeme
keycloak_quarkus_cache_remote_host: "infinispan1"
keycloak_quarkus_cache_remote_port: 11222
keycloak_quarkus_cache_remote_tls_enabled: false
keycloak_quarkus_additional_env_vars:
- key: KC_FEATURES
value: clusterless
- key: KC_FEATURES_DISABLED
value: persistent-user-sessions
roles:
- role: keycloak_quarkus

View File

@@ -0,0 +1,80 @@
---
driver:
name: docker
platforms:
- name: keycloak1
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: infinispan1
image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true
privileged: true
command: "/usr/sbin/init"
groups:
- infinispan
networks:
- name: rhbk
port_bindings:
- "11222/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,50 @@
---
- name: Prepare
hosts: 'keycloak:infinispan'
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
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: "{{ 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

@@ -0,0 +1,14 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
- vars.yml
vars:
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_additional_env_vars:
- key: KC_FEATURES_DISABLED
value: ciba,device-flow,impersonation,kerberos,docker
keycloak_quarkus_version: 26.0.7
roles:
- role: keycloak_quarkus

View File

@@ -0,0 +1,49 @@
---
dependency:
name: galaxy
options:
requirements-file: molecule/requirements.yml
driver:
name: podman
platforms:
- name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest
command: "/usr/sbin/init"
pre_build_image: true
privileged: true
port_bindings:
- 8080:8080
- "9000/tcp"
published_ports:
- 0.0.0.0:8080:8080/TCP
- 0.0.0.0:9000:9000/TCP
provisioner:
name: ansible
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"
PROXY: "${PROXY}"
NO_PROXY: "${NO_PROXY}"
verifier:
name: ansible
scenario:
test_sequence:
- dependency
- cleanup
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- side_effect
- verify
- cleanup
- destroy

View File

@@ -0,0 +1,59 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
- vars.yml
vars:
sudo_pkg_name: sudo
keycloak_quarkus_version: 26.0.4
keycloak_quarkus_additional_env_vars:
- key: KC_FEATURES_DISABLED
value: impersonation,kerberos
pre_tasks:
- name: Install sudo
ansible.builtin.apt:
name:
- sudo
- openjdk-17-jdk-headless
state: present
when:
- ansible_facts.os_family == 'Debian'
- name: "Ensure common prepare phase are set."
ansible.builtin.include_tasks: ../prepare.yml
- name: Display Ansible version
ansible.builtin.debug:
msg: "Ansible version is {{ ansible_version.full }}"
- name: "Ensure {{ sudo_pkg_name }} is installed (if user is root)."
ansible.builtin.dnf:
name: "{{ sudo_pkg_name }}"
when:
- ansible_user_id == 'root'
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: "Check if {{ sudo_pkg_name }} is installed."
ansible.builtin.assert:
that:
- sudo_pkg_name in ansible_facts.packages
- 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:
- role: keycloak_quarkus
post_tasks:
- name: "Delete custom fact"
ansible.builtin.file:
path: /etc/ansible/facts.d/keycloak.fact
state: absent
become: "{{ molecule_prepare_require_privilege_escalation }}"

View File

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

View File

@@ -0,0 +1,13 @@
---
keycloak_quarkus_offline_install: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_realm: TestRealm
keycloak_quarkus_hostname: http://instance:8080
keycloak_quarkus_log: file
keycloak_quarkus_https_key_file_enabled: true
keycloak_quarkus_log_target: /tmp/keycloak
keycloak_quarkus_hostname_strict: false
keycloak_quarkus_cert_file_copy_enabled: true
keycloak_quarkus_key_file_copy_enabled: true
keycloak_quarkus_key_content: "{{ lookup('file', 'key.pem') }}"
keycloak_quarkus_cert_file_src: cert.pem

View File

@@ -0,0 +1,31 @@
---
- name: Verify
hosts: instance
vars:
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_port: http://localhost:8080
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
- name: Check if keycloak service started
ansible.builtin.assert:
that:
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify 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
ansible.builtin.uri:
url: "{{ keycloak_quarkus_port }}/realms/master/protocol/openid-connect/token"
method: POST
body: "client_id=admin-cli&username=admin&password={{ keycloak_quarkus_bootstrap_admin_password }}&grant_type=password"
validate_certs: no
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 45
delay: 5

View File

@@ -2,10 +2,13 @@
collections: collections:
- name: middleware_automation.common - name: middleware_automation.common
- name: middleware_automation.jbcs - name: middleware_automation.jbcs
- name: middleware_automation.infinispan
- name: community.general - name: community.general
- name: ansible.posix - name: ansible.posix
- name: community.docker - name: community.docker
version: ">=3.8.0" version: ">=3.8.0"
- name: containers.podman
version: ">=1.8.1"
roles: roles:
- name: elan.simple_nginx_reverse_proxy - 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

@@ -2,8 +2,8 @@
- name: Playbook for Keycloak X Hosts with HTTPS enabled - name: Playbook for Keycloak X Hosts with HTTPS enabled
hosts: all hosts: all
vars: vars:
keycloak_quarkus_admin_pass: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_host: localhost keycloak_quarkus_hostname: http://localhost
keycloak_quarkus_port: 8443 keycloak_quarkus_port: 8443
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_proxy_mode: none keycloak_quarkus_proxy_mode: none

View File

@@ -2,8 +2,8 @@
- name: Playbook for Keycloak X Hosts in develop mode - name: Playbook for Keycloak X Hosts in develop mode
hosts: all hosts: all
vars: vars:
keycloak_admin_password: "remembertochangeme" keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_host: localhost keycloak_quarkus_hostname: http://localhost
keycloak_quarkus_port: 8080 keycloak_quarkus_port: 8080
keycloak_quarkus_log: file keycloak_quarkus_log: file
keycloak_quarkus_start_dev: true keycloak_quarkus_start_dev: true

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 }}"

File diff suppressed because it is too large Load Diff

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

@@ -40,8 +40,8 @@ options:
state: state:
description: description:
- State of the client - State of the client
- On C(present), the client will be created (or updated if it exists already). - On V(present), the client will be created (or updated if it exists already).
- On C(absent), the client will be removed if it exists - On V(absent), the client will be removed if it exists
choices: ['present', 'absent'] choices: ['present', 'absent']
default: 'present' default: 'present'
type: str type: str
@@ -55,7 +55,7 @@ options:
client_id: client_id:
description: description:
- Client id of client to be worked on. This is usually an alphanumeric name chosen by - Client id of client to be worked on. This is usually an alphanumeric name chosen by
you. Either this or I(id) is required. If you specify both, I(id) takes precedence. you. Either this or O(id) is required. If you specify both, O(id) takes precedence.
This is 'clientId' in the Keycloak REST API. This is 'clientId' in the Keycloak REST API.
aliases: aliases:
- clientId - clientId
@@ -63,13 +63,13 @@ options:
id: id:
description: description:
- Id of client to be worked on. This is usually an UUID. Either this or I(client_id) - Id of client to be worked on. This is usually an UUID. Either this or O(client_id)
is required. If you specify both, this takes precedence. is required. If you specify both, this takes precedence.
type: str type: str
name: name:
description: description:
- Name of the client (this is not the same as I(client_id)). - Name of the client (this is not the same as O(client_id)).
type: str type: str
description: description:
@@ -108,20 +108,21 @@ options:
client_authenticator_type: client_authenticator_type:
description: description:
- How do clients authenticate with the auth server? Either C(client-secret) or - How do clients authenticate with the auth server? Either V(client-secret),
C(client-jwt) can be chosen. When using C(client-secret), the module parameter V(client-jwt), or V(client-x509) can be chosen. When using V(client-secret), the module parameter
I(secret) can set it, while for C(client-jwt), you can use the keys C(use.jwks.url), O(secret) can set it, for V(client-jwt), you can use the keys C(use.jwks.url),
C(jwks.url), and C(jwt.credential.certificate) in the I(attributes) module parameter C(jwks.url), and C(jwt.credential.certificate) in the O(attributes) module parameter
to configure its behavior. to configure its behavior. For V(client-x509) you can use the keys C(x509.allow.regex.pattern.comparison)
This is 'clientAuthenticatorType' in the Keycloak REST API. and C(x509.subjectdn) in the O(attributes) module parameter to configure which certificate(s) to accept.
choices: ['client-secret', 'client-jwt'] - This is 'clientAuthenticatorType' in the Keycloak REST API.
choices: ['client-secret', 'client-jwt', 'client-x509']
aliases: aliases:
- clientAuthenticatorType - clientAuthenticatorType
type: str type: str
secret: secret:
description: description:
- When using I(client_authenticator_type) C(client-secret) (the default), you can - When using O(client_authenticator_type=client-secret) (the default), you can
specify a secret here (otherwise one will be generated if it does not exit). If specify a secret here (otherwise one will be generated if it does not exit). If
changing this secret, the module will not register a change currently (but the changing this secret, the module will not register a change currently (but the
changed secret will be saved). changed secret will be saved).
@@ -246,9 +247,11 @@ options:
protocol: protocol:
description: description:
- Type of client (either C(openid-connect) or C(saml). - Type of client.
- At creation only, default value will be V(openid-connect) if O(protocol) is omitted.
- The V(docker-v2) value was added in community.general 8.6.0.
type: str type: str
choices: ['openid-connect', 'saml'] choices: ['openid-connect', 'saml', 'docker-v2']
full_scope_allowed: full_scope_allowed:
description: description:
@@ -286,7 +289,7 @@ options:
use_template_config: use_template_config:
description: description:
- Whether or not to use configuration from the I(client_template). - Whether or not to use configuration from the O(client_template).
This is 'useTemplateConfig' in the Keycloak REST API. This is 'useTemplateConfig' in the Keycloak REST API.
aliases: aliases:
- useTemplateConfig - useTemplateConfig
@@ -294,7 +297,7 @@ options:
use_template_scope: use_template_scope:
description: description:
- Whether or not to use scope configuration from the I(client_template). - Whether or not to use scope configuration from the O(client_template).
This is 'useTemplateScope' in the Keycloak REST API. This is 'useTemplateScope' in the Keycloak REST API.
aliases: aliases:
- useTemplateScope - useTemplateScope
@@ -302,7 +305,7 @@ options:
use_template_mappers: use_template_mappers:
description: description:
- Whether or not to use mapper configuration from the I(client_template). - Whether or not to use mapper configuration from the O(client_template).
This is 'useTemplateMappers' in the Keycloak REST API. This is 'useTemplateMappers' in the Keycloak REST API.
aliases: aliases:
- useTemplateMappers - useTemplateMappers
@@ -338,6 +341,42 @@ options:
description: description:
- Override realm authentication flow bindings. - Override realm authentication flow bindings.
type: dict type: dict
suboptions:
browser:
description:
- Flow ID of the browser authentication flow.
- O(authentication_flow_binding_overrides.browser)
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
type: str
browser_name:
description:
- Flow name of the browser authentication flow.
- O(authentication_flow_binding_overrides.browser)
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
aliases:
- browserName
type: str
version_added: 9.1.0
direct_grant:
description:
- Flow ID of the direct grant authentication flow.
- O(authentication_flow_binding_overrides.direct_grant)
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
aliases:
- directGrant
type: str
direct_grant_name:
description:
- Flow name of the direct grant authentication flow.
- O(authentication_flow_binding_overrides.direct_grant)
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
aliases:
- directGrantName
type: str
version_added: 9.1.0
aliases: aliases:
- authenticationFlowBindingOverrides - authenticationFlowBindingOverrides
version_added: 3.4.0 version_added: 3.4.0
@@ -391,38 +430,37 @@ options:
protocol: protocol:
description: description:
- This is either C(openid-connect) or C(saml), this specifies for which protocol this protocol mapper. - This specifies for which protocol this protocol mapper is active.
is active. choices: ['openid-connect', 'saml', 'docker-v2']
choices: ['openid-connect', 'saml']
type: str type: str
protocolMapper: protocolMapper:
description: description:
- The Keycloak-internal name of the type of this protocol-mapper. While an exhaustive list is - "The Keycloak-internal name of the type of this protocol-mapper. While an exhaustive list is
impossible to provide since this may be extended through SPIs by the user of Keycloak, impossible to provide since this may be extended through SPIs by the user of Keycloak,
by default Keycloak as of 3.4 ships with at least by default Keycloak as of 3.4 ships with at least:"
- C(docker-v2-allow-all-mapper) - V(docker-v2-allow-all-mapper)
- C(oidc-address-mapper) - V(oidc-address-mapper)
- C(oidc-full-name-mapper) - V(oidc-full-name-mapper)
- C(oidc-group-membership-mapper) - V(oidc-group-membership-mapper)
- C(oidc-hardcoded-claim-mapper) - V(oidc-hardcoded-claim-mapper)
- C(oidc-hardcoded-role-mapper) - V(oidc-hardcoded-role-mapper)
- C(oidc-role-name-mapper) - V(oidc-role-name-mapper)
- C(oidc-script-based-protocol-mapper) - V(oidc-script-based-protocol-mapper)
- C(oidc-sha256-pairwise-sub-mapper) - V(oidc-sha256-pairwise-sub-mapper)
- C(oidc-usermodel-attribute-mapper) - V(oidc-usermodel-attribute-mapper)
- C(oidc-usermodel-client-role-mapper) - V(oidc-usermodel-client-role-mapper)
- C(oidc-usermodel-property-mapper) - V(oidc-usermodel-property-mapper)
- C(oidc-usermodel-realm-role-mapper) - V(oidc-usermodel-realm-role-mapper)
- C(oidc-usersessionmodel-note-mapper) - V(oidc-usersessionmodel-note-mapper)
- C(saml-group-membership-mapper) - V(saml-group-membership-mapper)
- C(saml-hardcode-attribute-mapper) - V(saml-hardcode-attribute-mapper)
- C(saml-hardcode-role-mapper) - V(saml-hardcode-role-mapper)
- C(saml-role-list-mapper) - V(saml-role-list-mapper)
- C(saml-role-name-mapper) - V(saml-role-name-mapper)
- C(saml-user-attribute-mapper) - V(saml-user-attribute-mapper)
- C(saml-user-property-mapper) - V(saml-user-property-mapper)
- C(saml-user-session-note-mapper) - V(saml-user-session-note-mapper)
- An exhaustive list of available mappers on your installation can be obtained on - An exhaustive list of available mappers on your installation can be obtained on
the admin console by going to Server Info -> Providers and looking under the admin console by going to Server Info -> Providers and looking under
'protocol-mapper'. 'protocol-mapper'.
@@ -431,10 +469,10 @@ options:
config: config:
description: description:
- Dict specifying the configuration options for the protocol mapper; the - Dict specifying the configuration options for the protocol mapper; the
contents differ depending on the value of I(protocolMapper) and are not documented contents differ depending on the value of O(protocol_mappers[].protocolMapper) and are not documented
other than by the source of the mappers and its parent class(es). An example is given other than by the source of the mappers and its parent class(es). An example is given
below. It is easiest to obtain valid config values by dumping an already-existing below. It is easiest to obtain valid config values by dumping an already-existing
protocol mapper configuration through check-mode in the I(existing) field. protocol mapper configuration through check-mode in the RV(existing) field.
type: dict type: dict
attributes: attributes:
@@ -478,7 +516,7 @@ options:
saml.signature.algorithm: saml.signature.algorithm:
description: description:
- Signature algorithm used to sign SAML documents. One of C(RSA_SHA256), C(RSA_SHA1), C(RSA_SHA512), or C(DSA_SHA1). - Signature algorithm used to sign SAML documents. One of V(RSA_SHA256), V(RSA_SHA1), V(RSA_SHA512), or V(DSA_SHA1).
saml.signing.certificate: saml.signing.certificate:
description: description:
@@ -496,22 +534,21 @@ options:
description: description:
- SAML Redirect Binding URL for the client's assertion consumer service (login responses). - SAML Redirect Binding URL for the client's assertion consumer service (login responses).
saml_force_name_id_format: saml_force_name_id_format:
description: description:
- For SAML clients, Boolean specifying whether to ignore requested NameID subject format and using the configured one instead. - For SAML clients, Boolean specifying whether to ignore requested NameID subject format and using the configured one instead.
saml_name_id_format: saml_name_id_format:
description: description:
- For SAML clients, the NameID format to use (one of C(username), C(email), C(transient), or C(persistent)) - For SAML clients, the NameID format to use (one of V(username), V(email), V(transient), or V(persistent))
saml_signature_canonicalization_method: saml_signature_canonicalization_method:
description: description:
- SAML signature canonicalization method. This is one of four values, namely - SAML signature canonicalization method. This is one of four values, namely
C(http://www.w3.org/2001/10/xml-exc-c14n#) for EXCLUSIVE, V(http://www.w3.org/2001/10/xml-exc-c14n#) for EXCLUSIVE,
C(http://www.w3.org/2001/10/xml-exc-c14n#WithComments) for EXCLUSIVE_WITH_COMMENTS, V(http://www.w3.org/2001/10/xml-exc-c14n#WithComments) for EXCLUSIVE_WITH_COMMENTS,
C(http://www.w3.org/TR/2001/REC-xml-c14n-20010315) for INCLUSIVE, and V(http://www.w3.org/TR/2001/REC-xml-c14n-20010315) for INCLUSIVE, and
C(http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments) for INCLUSIVE_WITH_COMMENTS. V(http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments) for INCLUSIVE_WITH_COMMENTS.
saml_single_logout_service_url_post: saml_single_logout_service_url_post:
description: description:
@@ -523,12 +560,12 @@ options:
user.info.response.signature.alg: user.info.response.signature.alg:
description: description:
- For OpenID-Connect clients, JWA algorithm for signed UserInfo-endpoint responses. One of C(RS256) or C(unsigned). - For OpenID-Connect clients, JWA algorithm for signed UserInfo-endpoint responses. One of V(RS256) or V(unsigned).
request.object.signature.alg: request.object.signature.alg:
description: description:
- For OpenID-Connect clients, JWA algorithm which the client needs to use when sending - For OpenID-Connect clients, JWA algorithm which the client needs to use when sending
OIDC request object. One of C(any), C(none), C(RS256). OIDC request object. One of V(any), V(none), V(RS256).
use.jwks.url: use.jwks.url:
description: description:
@@ -544,6 +581,18 @@ options:
- For OpenID-Connect clients, client certificate for validating JWT issued by - For OpenID-Connect clients, client certificate for validating JWT issued by
client and signed by its key, base64-encoded. client and signed by its key, base64-encoded.
x509.subjectdn:
description:
- For OpenID-Connect clients, subject which will be used to authenticate the client.
type: str
version_added: 9.5.0
x509.allow.regex.pattern.comparison:
description:
- For OpenID-Connect clients, boolean specifying whether to allow C(x509.subjectdn) as regular expression.
type: bool
version_added: 9.5.0
extends_documentation_fragment: extends_documentation_fragment:
- middleware_automation.keycloak.keycloak - middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.attributes - middleware_automation.keycloak.attributes
@@ -587,6 +636,22 @@ EXAMPLES = '''
delegate_to: localhost delegate_to: localhost
- name: Create or update a Keycloak client (minimal example), with x509 authentication
middleware_automation.keycloak.keycloak_client:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com/auth
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: master
state: present
client_id: test
client_authenticator_type: client-x509
attributes:
x509.subjectdn: "CN=client"
x509.allow.regex.pattern.comparison: false
- name: Create or update a Keycloak client (with all the bells and whistles) - name: Create or update a Keycloak client (with all the bells and whistles)
middleware_automation.keycloak.keycloak_client: middleware_automation.keycloak.keycloak_client:
auth_client_id: admin-cli auth_client_id: admin-cli
@@ -717,11 +782,17 @@ end_state:
''' '''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \ from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
keycloak_argument_spec, get_token, KeycloakError keycloak_argument_spec, get_token, KeycloakError, is_struct_included
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
import copy import copy
PROTOCOL_OPENID_CONNECT = 'openid-connect'
PROTOCOL_SAML = 'saml'
PROTOCOL_DOCKER_V2 = 'docker-v2'
CLIENT_META_DATA = ['authorizationServicesEnabled']
def normalise_cr(clientrep, remove_ids=False): def normalise_cr(clientrep, remove_ids=False):
""" Re-sorts any properties where the order so that diff's is minimised, and adds default values where appropriate so that the """ Re-sorts any properties where the order so that diff's is minimised, and adds default values where appropriate so that the
the change detection is more effective. the change detection is more effective.
@@ -737,6 +808,12 @@ def normalise_cr(clientrep, remove_ids=False):
if 'attributes' in clientrep: if 'attributes' in clientrep:
clientrep['attributes'] = list(sorted(clientrep['attributes'])) clientrep['attributes'] = list(sorted(clientrep['attributes']))
if 'defaultClientScopes' in clientrep:
clientrep['defaultClientScopes'] = list(sorted(clientrep['defaultClientScopes']))
if 'optionalClientScopes' in clientrep:
clientrep['optionalClientScopes'] = list(sorted(clientrep['optionalClientScopes']))
if 'redirectUris' in clientrep: if 'redirectUris' in clientrep:
clientrep['redirectUris'] = list(sorted(clientrep['redirectUris'])) clientrep['redirectUris'] = list(sorted(clientrep['redirectUris']))
@@ -762,11 +839,70 @@ def sanitize_cr(clientrep):
if 'secret' in result: if 'secret' in result:
result['secret'] = 'no_log' result['secret'] = 'no_log'
if 'attributes' in result: if 'attributes' in result:
if 'saml.signing.private.key' in result['attributes']: attributes = result['attributes']
result['attributes']['saml.signing.private.key'] = 'no_log' if isinstance(attributes, dict) and 'saml.signing.private.key' in attributes:
attributes['saml.signing.private.key'] = 'no_log'
return normalise_cr(result) return normalise_cr(result)
def get_authentication_flow_id(flow_name, realm, kc):
""" Get the authentication flow ID based on the flow name, realm, and Keycloak client.
Args:
flow_name (str): The name of the authentication flow.
realm (str): The name of the realm.
kc (KeycloakClient): The Keycloak client instance.
Returns:
str: The ID of the authentication flow.
Raises:
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
"""
flow = kc.get_authentication_flow_by_alias(flow_name, realm)
if flow:
return flow["id"]
kc.module.fail_json(msg='Authentification flow %s not found in realm %s' % (flow_name, realm))
def flow_binding_from_dict_to_model(newClientFlowBinding, realm, kc):
""" Convert a dictionary representing client flow bindings to a model representation.
Args:
newClientFlowBinding (dict): A dictionary containing client flow bindings.
realm (str): The name of the realm.
kc (KeycloakClient): An instance of the KeycloakClient class.
Returns:
dict: A dictionary representing the model flow bindings. The dictionary has two keys:
- "browser" (str or None): The ID of the browser authentication flow binding, or None if not provided.
- "direct_grant" (str or None): The ID of the direct grant authentication flow binding, or None if not provided.
Raises:
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
"""
modelFlow = {
"browser": None,
"direct_grant": None
}
for k, v in newClientFlowBinding.items():
if not v:
continue
if k == "browser":
modelFlow["browser"] = v
elif k == "browser_name":
modelFlow["browser"] = get_authentication_flow_id(v, realm, kc)
elif k == "direct_grant":
modelFlow["direct_grant"] = v
elif k == "direct_grant_name":
modelFlow["direct_grant"] = get_authentication_flow_id(v, realm, kc)
return modelFlow
def main(): def main():
""" """
Module execution Module execution
@@ -780,11 +916,18 @@ def main():
consentText=dict(type='str'), consentText=dict(type='str'),
id=dict(type='str'), id=dict(type='str'),
name=dict(type='str'), name=dict(type='str'),
protocol=dict(type='str', choices=['openid-connect', 'saml']), protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]),
protocolMapper=dict(type='str'), protocolMapper=dict(type='str'),
config=dict(type='dict'), config=dict(type='dict'),
) )
authentication_flow_spec = dict(
browser=dict(type='str'),
browser_name=dict(type='str', aliases=['browserName']),
direct_grant=dict(type='str', aliases=['directGrant']),
direct_grant_name=dict(type='str', aliases=['directGrantName']),
)
meta_args = dict( meta_args = dict(
state=dict(default='present', choices=['present', 'absent']), state=dict(default='present', choices=['present', 'absent']),
realm=dict(type='str', default='master'), realm=dict(type='str', default='master'),
@@ -798,7 +941,7 @@ def main():
base_url=dict(type='str', aliases=['baseUrl']), base_url=dict(type='str', aliases=['baseUrl']),
surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']), surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']),
enabled=dict(type='bool'), enabled=dict(type='bool'),
client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt'], aliases=['clientAuthenticatorType']), client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt', 'client-x509'], aliases=['clientAuthenticatorType']),
secret=dict(type='str', no_log=True), secret=dict(type='str', no_log=True),
registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True), registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True),
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']), default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
@@ -814,7 +957,7 @@ def main():
authorization_services_enabled=dict(type='bool', aliases=['authorizationServicesEnabled']), authorization_services_enabled=dict(type='bool', aliases=['authorizationServicesEnabled']),
public_client=dict(type='bool', aliases=['publicClient']), public_client=dict(type='bool', aliases=['publicClient']),
frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']), frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']),
protocol=dict(type='str', choices=['openid-connect', 'saml']), protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]),
attributes=dict(type='dict'), attributes=dict(type='dict'),
full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']), full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']),
node_re_registration_timeout=dict(type='int', aliases=['nodeReRegistrationTimeout']), node_re_registration_timeout=dict(type='int', aliases=['nodeReRegistrationTimeout']),
@@ -824,7 +967,13 @@ def main():
use_template_scope=dict(type='bool', aliases=['useTemplateScope']), use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']), use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']), always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
authentication_flow_binding_overrides=dict(type='dict', aliases=['authenticationFlowBindingOverrides']), authentication_flow_binding_overrides=dict(
type='dict',
aliases=['authenticationFlowBindingOverrides'],
options=authentication_flow_spec,
required_one_of=[['browser', 'direct_grant', 'browser_name', 'direct_grant_name']],
mutually_exclusive=[['browser', 'browser_name'], ['direct_grant', 'direct_grant_name']],
),
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
authorization_settings=dict(type='dict', aliases=['authorizationSettings']), authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']), default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
@@ -885,7 +1034,9 @@ def main():
# Unfortunately, the ansible argument spec checker introduces variables with null values when # Unfortunately, the ansible argument spec checker introduces variables with null values when
# they are not specified # they are not specified
if client_param == 'protocol_mappers': if client_param == 'protocol_mappers':
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value] new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
elif client_param == 'authentication_flow_binding_overrides':
new_param_value = flow_binding_from_dict_to_model(new_param_value, realm, kc)
changeset[camel(client_param)] = new_param_value changeset[camel(client_param)] = new_param_value
@@ -912,6 +1063,8 @@ def main():
if 'clientId' not in desired_client: if 'clientId' not in desired_client:
module.fail_json(msg='client_id needs to be specified when creating a new client') module.fail_json(msg='client_id needs to be specified when creating a new client')
if 'protocol' not in desired_client:
desired_client['protocol'] = PROTOCOL_OPENID_CONNECT
if module._diff: if module._diff:
result['diff'] = dict(before='', after=sanitize_cr(desired_client)) result['diff'] = dict(before='', after=sanitize_cr(desired_client))
@@ -940,7 +1093,7 @@ def main():
if module._diff: if module._diff:
result['diff'] = dict(before=sanitize_cr(before_norm), result['diff'] = dict(before=sanitize_cr(before_norm),
after=sanitize_cr(desired_norm)) after=sanitize_cr(desired_norm))
result['changed'] = (before_norm != desired_norm) result['changed'] = not is_struct_included(desired_norm, before_norm, CLIENT_META_DATA)
module.exit_json(**result) module.exit_json(**result)

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

@@ -0,0 +1,848 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
# 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_realm
short_description: Allows administration of Keycloak realm via Keycloak API
version_added: 3.0.0
description:
- This module allows the administration of Keycloak realm via the Keycloak REST API. It
requires access to the REST API via OpenID Connect; the user connecting and the realm 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 realm definition with the scope tailored
to your needs and a user having the expected roles.
- The names of module options are snake_cased versions of the camelCase ones found in the
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
Aliases are provided so camelCased versions can be used as well.
- The Keycloak API does not always sanity check inputs e.g. you can set
SAML-specific settings on an OpenID Connect client for instance and vice versa. Be careful.
If you do not specify a setting, usually a sensible default is chosen.
attributes:
check_mode:
support: full
diff_mode:
support: full
options:
state:
description:
- State of the realm.
- On V(present), the realm will be created (or updated if it exists already).
- On V(absent), the realm will be removed if it exists.
choices: ['present', 'absent']
default: 'present'
type: str
id:
description:
- The realm to create.
type: str
realm:
description:
- The realm name.
type: str
access_code_lifespan:
description:
- The realm access code lifespan.
aliases:
- accessCodeLifespan
type: int
access_code_lifespan_login:
description:
- The realm access code lifespan login.
aliases:
- accessCodeLifespanLogin
type: int
access_code_lifespan_user_action:
description:
- The realm access code lifespan user action.
aliases:
- accessCodeLifespanUserAction
type: int
access_token_lifespan:
description:
- The realm access token lifespan.
aliases:
- accessTokenLifespan
type: int
access_token_lifespan_for_implicit_flow:
description:
- The realm access token lifespan for implicit flow.
aliases:
- accessTokenLifespanForImplicitFlow
type: int
account_theme:
description:
- The realm account theme.
aliases:
- accountTheme
type: str
action_token_generated_by_admin_lifespan:
description:
- The realm action token generated by admin lifespan.
aliases:
- actionTokenGeneratedByAdminLifespan
type: int
action_token_generated_by_user_lifespan:
description:
- The realm action token generated by user lifespan.
aliases:
- actionTokenGeneratedByUserLifespan
type: int
admin_events_details_enabled:
description:
- The realm admin events details enabled.
aliases:
- adminEventsDetailsEnabled
type: bool
admin_events_enabled:
description:
- The realm admin events enabled.
aliases:
- adminEventsEnabled
type: bool
admin_theme:
description:
- The realm admin theme.
aliases:
- adminTheme
type: str
attributes:
description:
- The realm attributes.
type: dict
browser_flow:
description:
- The realm browser flow.
aliases:
- browserFlow
type: str
browser_security_headers:
description:
- The realm browser security headers.
aliases:
- browserSecurityHeaders
type: dict
brute_force_protected:
description:
- The realm brute force protected.
aliases:
- bruteForceProtected
type: bool
client_authentication_flow:
description:
- The realm client authentication flow.
aliases:
- clientAuthenticationFlow
type: str
client_scope_mappings:
description:
- The realm client scope mappings.
aliases:
- clientScopeMappings
type: dict
default_default_client_scopes:
description:
- The realm default default client scopes.
aliases:
- defaultDefaultClientScopes
type: list
elements: str
default_groups:
description:
- The realm default groups.
aliases:
- defaultGroups
type: list
elements: str
default_locale:
description:
- The realm default locale.
aliases:
- defaultLocale
type: str
default_optional_client_scopes:
description:
- The realm default optional client scopes.
aliases:
- defaultOptionalClientScopes
type: list
elements: str
default_roles:
description:
- The realm default roles.
aliases:
- defaultRoles
type: list
elements: str
default_signature_algorithm:
description:
- The realm default signature algorithm.
aliases:
- defaultSignatureAlgorithm
type: str
direct_grant_flow:
description:
- The realm direct grant flow.
aliases:
- directGrantFlow
type: str
display_name:
description:
- The realm display name.
aliases:
- displayName
type: str
display_name_html:
description:
- The realm display name HTML.
aliases:
- displayNameHtml
type: str
docker_authentication_flow:
description:
- The realm docker authentication flow.
aliases:
- dockerAuthenticationFlow
type: str
duplicate_emails_allowed:
description:
- The realm duplicate emails allowed option.
aliases:
- duplicateEmailsAllowed
type: bool
edit_username_allowed:
description:
- The realm edit username allowed option.
aliases:
- editUsernameAllowed
type: bool
email_theme:
description:
- The realm email theme.
aliases:
- emailTheme
type: str
enabled:
description:
- The realm enabled option.
type: bool
enabled_event_types:
description:
- The realm enabled event types.
aliases:
- enabledEventTypes
type: list
elements: str
events_enabled:
description:
- Enables or disables login events for this realm.
aliases:
- eventsEnabled
type: bool
version_added: 3.6.0
events_expiration:
description:
- The realm events expiration.
aliases:
- eventsExpiration
type: int
events_listeners:
description:
- The realm events listeners.
aliases:
- eventsListeners
type: list
elements: str
failure_factor:
description:
- The realm failure factor.
aliases:
- failureFactor
type: int
internationalization_enabled:
description:
- The realm internationalization enabled option.
aliases:
- internationalizationEnabled
type: bool
login_theme:
description:
- The realm login theme.
aliases:
- loginTheme
type: str
login_with_email_allowed:
description:
- The realm login with email allowed option.
aliases:
- loginWithEmailAllowed
type: bool
max_delta_time_seconds:
description:
- The realm max delta time in seconds.
aliases:
- maxDeltaTimeSeconds
type: int
max_failure_wait_seconds:
description:
- The realm max failure wait in seconds.
aliases:
- maxFailureWaitSeconds
type: int
minimum_quick_login_wait_seconds:
description:
- The realm minimum quick login wait in seconds.
aliases:
- minimumQuickLoginWaitSeconds
type: int
not_before:
description:
- The realm not before.
aliases:
- notBefore
type: int
offline_session_idle_timeout:
description:
- The realm offline session idle timeout.
aliases:
- offlineSessionIdleTimeout
type: int
offline_session_max_lifespan:
description:
- The realm offline session max lifespan.
aliases:
- offlineSessionMaxLifespan
type: int
offline_session_max_lifespan_enabled:
description:
- The realm offline session max lifespan enabled option.
aliases:
- offlineSessionMaxLifespanEnabled
type: bool
otp_policy_algorithm:
description:
- The realm otp policy algorithm.
aliases:
- otpPolicyAlgorithm
type: str
otp_policy_digits:
description:
- The realm otp policy digits.
aliases:
- otpPolicyDigits
type: int
otp_policy_initial_counter:
description:
- The realm otp policy initial counter.
aliases:
- otpPolicyInitialCounter
type: int
otp_policy_look_ahead_window:
description:
- The realm otp policy look ahead window.
aliases:
- otpPolicyLookAheadWindow
type: int
otp_policy_period:
description:
- The realm otp policy period.
aliases:
- otpPolicyPeriod
type: int
otp_policy_type:
description:
- The realm otp policy type.
aliases:
- otpPolicyType
type: str
otp_supported_applications:
description:
- The realm otp supported applications.
aliases:
- otpSupportedApplications
type: list
elements: str
password_policy:
description:
- The realm password policy.
aliases:
- passwordPolicy
type: str
permanent_lockout:
description:
- The realm permanent lockout.
aliases:
- permanentLockout
type: bool
quick_login_check_milli_seconds:
description:
- The realm quick login check in milliseconds.
aliases:
- quickLoginCheckMilliSeconds
type: int
refresh_token_max_reuse:
description:
- The realm refresh token max reuse.
aliases:
- refreshTokenMaxReuse
type: int
registration_allowed:
description:
- The realm registration allowed option.
aliases:
- registrationAllowed
type: bool
registration_email_as_username:
description:
- The realm registration email as username option.
aliases:
- registrationEmailAsUsername
type: bool
registration_flow:
description:
- The realm registration flow.
aliases:
- registrationFlow
type: str
remember_me:
description:
- The realm remember me option.
aliases:
- rememberMe
type: bool
reset_credentials_flow:
description:
- The realm reset credentials flow.
aliases:
- resetCredentialsFlow
type: str
reset_password_allowed:
description:
- The realm reset password allowed option.
aliases:
- resetPasswordAllowed
type: bool
revoke_refresh_token:
description:
- The realm revoke refresh token option.
aliases:
- revokeRefreshToken
type: bool
smtp_server:
description:
- The realm smtp server.
aliases:
- smtpServer
type: dict
ssl_required:
description:
- The realm ssl required option.
choices: ['all', 'external', 'none']
aliases:
- sslRequired
type: str
sso_session_idle_timeout:
description:
- The realm sso session idle timeout.
aliases:
- ssoSessionIdleTimeout
type: int
sso_session_idle_timeout_remember_me:
description:
- The realm sso session idle timeout remember me.
aliases:
- ssoSessionIdleTimeoutRememberMe
type: int
sso_session_max_lifespan:
description:
- The realm sso session max lifespan.
aliases:
- ssoSessionMaxLifespan
type: int
sso_session_max_lifespan_remember_me:
description:
- The realm sso session max lifespan remember me.
aliases:
- ssoSessionMaxLifespanRememberMe
type: int
supported_locales:
description:
- The realm supported locales.
aliases:
- supportedLocales
type: list
elements: str
user_managed_access_allowed:
description:
- The realm user managed access allowed option.
aliases:
- userManagedAccessAllowed
type: bool
verify_email:
description:
- The realm verify email option.
aliases:
- verifyEmail
type: bool
wait_increment_seconds:
description:
- The realm wait increment in seconds.
aliases:
- waitIncrementSeconds
type: int
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.attributes
author:
- Christophe Gilles (@kris2kris)
'''
EXAMPLES = '''
- name: Create or update Keycloak realm (minimal example)
middleware_automation.keycloak.keycloak_realm:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com/auth
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
id: realm
realm: realm
state: present
- name: Delete a Keycloak realm
middleware_automation.keycloak.keycloak_realm:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com/auth
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
id: test
state: absent
'''
RETURN = '''
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Realm testrealm has been updated"
proposed:
description: Representation of proposed realm.
returned: always
type: dict
sample: {
id: "test"
}
existing:
description: Representation of existing realm (sample is truncated).
returned: always
type: dict
sample: {
"adminUrl": "http://www.example.com/admin_url",
"attributes": {
"request.object.signature.alg": "RS256",
}
}
end_state:
description: Representation of realm after module execution (sample is truncated).
returned: on success
type: dict
sample: {
"adminUrl": "http://www.example.com/admin_url",
"attributes": {
"request.object.signature.alg": "RS256",
}
}
'''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
keycloak_argument_spec, get_token, KeycloakError
from ansible.module_utils.basic import AnsibleModule
def normalise_cr(realmrep):
""" Re-sorts any properties where the order is important so that diff's is minimised and the change detection is more effective.
:param realmrep: the realmrep dict to be sanitized
:return: normalised realmrep dict
"""
# Avoid the dict passed in to be modified
realmrep = realmrep.copy()
if 'enabledEventTypes' in realmrep:
realmrep['enabledEventTypes'] = list(sorted(realmrep['enabledEventTypes']))
if 'otpSupportedApplications' in realmrep:
realmrep['otpSupportedApplications'] = list(sorted(realmrep['otpSupportedApplications']))
if 'supportedLocales' in realmrep:
realmrep['supportedLocales'] = list(sorted(realmrep['supportedLocales']))
return realmrep
def sanitize_cr(realmrep):
""" Removes probably sensitive details from a realm representation.
:param realmrep: the realmrep dict to be sanitized
:return: sanitized realmrep dict
"""
result = realmrep.copy()
if 'secret' in result:
result['secret'] = '********'
if 'attributes' in result:
if 'saml.signing.private.key' in result['attributes']:
result['attributes'] = result['attributes'].copy()
result['attributes']['saml.signing.private.key'] = '********'
return normalise_cr(result)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
state=dict(default='present', choices=['present', 'absent']),
id=dict(type='str'),
realm=dict(type='str'),
access_code_lifespan=dict(type='int', aliases=['accessCodeLifespan']),
access_code_lifespan_login=dict(type='int', aliases=['accessCodeLifespanLogin']),
access_code_lifespan_user_action=dict(type='int', aliases=['accessCodeLifespanUserAction']),
access_token_lifespan=dict(type='int', aliases=['accessTokenLifespan'], no_log=False),
access_token_lifespan_for_implicit_flow=dict(type='int', aliases=['accessTokenLifespanForImplicitFlow'], no_log=False),
account_theme=dict(type='str', aliases=['accountTheme']),
action_token_generated_by_admin_lifespan=dict(type='int', aliases=['actionTokenGeneratedByAdminLifespan'], no_log=False),
action_token_generated_by_user_lifespan=dict(type='int', aliases=['actionTokenGeneratedByUserLifespan'], no_log=False),
admin_events_details_enabled=dict(type='bool', aliases=['adminEventsDetailsEnabled']),
admin_events_enabled=dict(type='bool', aliases=['adminEventsEnabled']),
admin_theme=dict(type='str', aliases=['adminTheme']),
attributes=dict(type='dict'),
browser_flow=dict(type='str', aliases=['browserFlow']),
browser_security_headers=dict(type='dict', aliases=['browserSecurityHeaders']),
brute_force_protected=dict(type='bool', aliases=['bruteForceProtected']),
client_authentication_flow=dict(type='str', aliases=['clientAuthenticationFlow']),
client_scope_mappings=dict(type='dict', aliases=['clientScopeMappings']),
default_default_client_scopes=dict(type='list', elements='str', aliases=['defaultDefaultClientScopes']),
default_groups=dict(type='list', elements='str', aliases=['defaultGroups']),
default_locale=dict(type='str', aliases=['defaultLocale']),
default_optional_client_scopes=dict(type='list', elements='str', aliases=['defaultOptionalClientScopes']),
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
default_signature_algorithm=dict(type='str', aliases=['defaultSignatureAlgorithm']),
direct_grant_flow=dict(type='str', aliases=['directGrantFlow']),
display_name=dict(type='str', aliases=['displayName']),
display_name_html=dict(type='str', aliases=['displayNameHtml']),
docker_authentication_flow=dict(type='str', aliases=['dockerAuthenticationFlow']),
duplicate_emails_allowed=dict(type='bool', aliases=['duplicateEmailsAllowed']),
edit_username_allowed=dict(type='bool', aliases=['editUsernameAllowed']),
email_theme=dict(type='str', aliases=['emailTheme']),
enabled=dict(type='bool'),
enabled_event_types=dict(type='list', elements='str', aliases=['enabledEventTypes']),
events_enabled=dict(type='bool', aliases=['eventsEnabled']),
events_expiration=dict(type='int', aliases=['eventsExpiration']),
events_listeners=dict(type='list', elements='str', aliases=['eventsListeners']),
failure_factor=dict(type='int', aliases=['failureFactor']),
internationalization_enabled=dict(type='bool', aliases=['internationalizationEnabled']),
login_theme=dict(type='str', aliases=['loginTheme']),
login_with_email_allowed=dict(type='bool', aliases=['loginWithEmailAllowed']),
max_delta_time_seconds=dict(type='int', aliases=['maxDeltaTimeSeconds']),
max_failure_wait_seconds=dict(type='int', aliases=['maxFailureWaitSeconds']),
minimum_quick_login_wait_seconds=dict(type='int', aliases=['minimumQuickLoginWaitSeconds']),
not_before=dict(type='int', aliases=['notBefore']),
offline_session_idle_timeout=dict(type='int', aliases=['offlineSessionIdleTimeout']),
offline_session_max_lifespan=dict(type='int', aliases=['offlineSessionMaxLifespan']),
offline_session_max_lifespan_enabled=dict(type='bool', aliases=['offlineSessionMaxLifespanEnabled']),
otp_policy_algorithm=dict(type='str', aliases=['otpPolicyAlgorithm']),
otp_policy_digits=dict(type='int', aliases=['otpPolicyDigits']),
otp_policy_initial_counter=dict(type='int', aliases=['otpPolicyInitialCounter']),
otp_policy_look_ahead_window=dict(type='int', aliases=['otpPolicyLookAheadWindow']),
otp_policy_period=dict(type='int', aliases=['otpPolicyPeriod']),
otp_policy_type=dict(type='str', aliases=['otpPolicyType']),
otp_supported_applications=dict(type='list', elements='str', aliases=['otpSupportedApplications']),
password_policy=dict(type='str', aliases=['passwordPolicy'], no_log=False),
permanent_lockout=dict(type='bool', aliases=['permanentLockout']),
quick_login_check_milli_seconds=dict(type='int', aliases=['quickLoginCheckMilliSeconds']),
refresh_token_max_reuse=dict(type='int', aliases=['refreshTokenMaxReuse'], no_log=False),
registration_allowed=dict(type='bool', aliases=['registrationAllowed']),
registration_email_as_username=dict(type='bool', aliases=['registrationEmailAsUsername']),
registration_flow=dict(type='str', aliases=['registrationFlow']),
remember_me=dict(type='bool', aliases=['rememberMe']),
reset_credentials_flow=dict(type='str', aliases=['resetCredentialsFlow']),
reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed'], no_log=False),
revoke_refresh_token=dict(type='bool', aliases=['revokeRefreshToken']),
smtp_server=dict(type='dict', aliases=['smtpServer']),
ssl_required=dict(choices=["external", "all", "none"], aliases=['sslRequired']),
sso_session_idle_timeout=dict(type='int', aliases=['ssoSessionIdleTimeout']),
sso_session_idle_timeout_remember_me=dict(type='int', aliases=['ssoSessionIdleTimeoutRememberMe']),
sso_session_max_lifespan=dict(type='int', aliases=['ssoSessionMaxLifespan']),
sso_session_max_lifespan_remember_me=dict(type='int', aliases=['ssoSessionMaxLifespanRememberMe']),
supported_locales=dict(type='list', elements='str', aliases=['supportedLocales']),
user_managed_access_allowed=dict(type='bool', aliases=['userManagedAccessAllowed']),
verify_email=dict(type='bool', aliases=['verifyEmail']),
wait_increment_seconds=dict(type='int', aliases=['waitIncrementSeconds']),
)
argument_spec.update(meta_args)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=([['id', 'realm', 'enabled'],
['token', 'auth_realm', 'auth_username', 'auth_password']]),
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
# Obtain access token, initialize API
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')
state = module.params.get('state')
# convert module parameters to realm representation parameters (if they belong in there)
params_to_ignore = list(keycloak_argument_spec().keys()) + ['state']
# Filter and map the parameters names that apply to the role
realm_params = [x for x in module.params
if x not in params_to_ignore and
module.params.get(x) is not None]
# See whether the realm already exists in Keycloak
before_realm = kc.get_realm_by_id(realm=realm)
if before_realm is None:
before_realm = {}
# Build a proposed changeset from parameters given to this module
changeset = {}
for realm_param in realm_params:
new_param_value = module.params.get(realm_param)
changeset[camel(realm_param)] = new_param_value
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
desired_realm = before_realm.copy()
desired_realm.update(changeset)
result['proposed'] = sanitize_cr(changeset)
before_realm_sanitized = sanitize_cr(before_realm)
result['existing'] = before_realm_sanitized
# Cater for when it doesn't exist (an empty dict)
if not before_realm:
if state == 'absent':
# Do nothing and exit
if module._diff:
result['diff'] = dict(before='', after='')
result['changed'] = False
result['end_state'] = {}
result['msg'] = 'Realm does not exist, doing nothing.'
module.exit_json(**result)
# Process a creation
result['changed'] = True
if 'id' not in desired_realm:
module.fail_json(msg='id needs to be specified when creating a new realm')
if module._diff:
result['diff'] = dict(before='', after=sanitize_cr(desired_realm))
if module.check_mode:
module.exit_json(**result)
# create it
kc.create_realm(desired_realm)
after_realm = kc.get_realm_by_id(desired_realm['id'])
result['end_state'] = sanitize_cr(after_realm)
result['msg'] = 'Realm %s has been created.' % desired_realm['id']
module.exit_json(**result)
else:
if state == 'present':
# Process an update
# doing an update
result['changed'] = True
if module.check_mode:
# We can only compare the current realm with the proposed updates we have
before_norm = normalise_cr(before_realm)
desired_norm = normalise_cr(desired_realm)
if module._diff:
result['diff'] = dict(before=sanitize_cr(before_norm),
after=sanitize_cr(desired_norm))
result['changed'] = (before_norm != desired_norm)
module.exit_json(**result)
# do the update
kc.update_realm(desired_realm, realm=realm)
after_realm = kc.get_realm_by_id(realm=realm)
if before_realm == after_realm:
result['changed'] = False
result['end_state'] = sanitize_cr(after_realm)
if module._diff:
result['diff'] = dict(before=before_realm_sanitized,
after=sanitize_cr(after_realm))
result['msg'] = 'Realm %s has been updated.' % desired_realm['id']
module.exit_json(**result)
else:
# Process a deletion (because state was not 'present')
result['changed'] = True
if module._diff:
result['diff'] = dict(before=before_realm_sanitized, after='')
if module.check_mode:
module.exit_json(**result)
# delete it
kc.delete_realm(realm=realm)
result['proposed'] = {}
result['end_state'] = {}
result['msg'] = 'Realm %s has been deleted.' % before_realm['id']
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -40,8 +40,8 @@ options:
state: state:
description: description:
- State of the role. - State of the role.
- On C(present), the role will be created if it does not yet exist, or updated with the parameters you provide. - On V(present), the role will be created if it does not yet exist, or updated with the parameters you provide.
- On C(absent), the role will be removed if it exists. - On V(absent), the role will be removed if it exists.
default: 'present' default: 'present'
type: str type: str
choices: choices:
@@ -77,6 +77,42 @@ options:
description: description:
- A dict of key/value pairs to set as custom attributes for the role. - A dict of key/value pairs to set as custom attributes for the role.
- Values may be single values (e.g. a string) or a list of strings. - Values may be single values (e.g. a string) or a list of strings.
composite:
description:
- If V(true), the role is a composition of other realm and/or client role.
default: false
type: bool
version_added: 7.1.0
composites:
description:
- List of roles to include to the composite realm role.
- If the composite role is a client role, the C(clientId) (not ID of the client) must be specified.
default: []
type: list
elements: dict
version_added: 7.1.0
suboptions:
name:
description:
- Name of the role. This can be the name of a REALM role or a client role.
type: str
required: true
client_id:
description:
- Client ID if the role is a client role. Do not include this option for a REALM role.
- Use the client ID you can see in the Keycloak console, not the technical ID of the client.
type: str
required: false
aliases:
- clientId
state:
description:
- Create the composite if present, remove it if absent.
type: str
choices:
- present
- absent
default: present
extends_documentation_fragment: extends_documentation_fragment:
- middleware_automation.keycloak.keycloak - middleware_automation.keycloak.keycloak
@@ -198,8 +234,9 @@ end_state:
''' '''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \ from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
keycloak_argument_spec, get_token, KeycloakError keycloak_argument_spec, get_token, KeycloakError, is_struct_included
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
import copy
def main(): def main():
@@ -210,6 +247,12 @@ def main():
""" """
argument_spec = keycloak_argument_spec() argument_spec = keycloak_argument_spec()
composites_spec = dict(
name=dict(type='str', required=True),
client_id=dict(type='str', aliases=['clientId'], required=False),
state=dict(type='str', default='present', choices=['present', 'absent'])
)
meta_args = dict( meta_args = dict(
state=dict(type='str', default='present', choices=['present', 'absent']), state=dict(type='str', default='present', choices=['present', 'absent']),
name=dict(type='str', required=True), name=dict(type='str', required=True),
@@ -217,6 +260,8 @@ def main():
realm=dict(type='str', default='master'), realm=dict(type='str', default='master'),
client_id=dict(type='str'), client_id=dict(type='str'),
attributes=dict(type='dict'), attributes=dict(type='dict'),
composites=dict(type='list', default=[], options=composites_spec, elements='dict'),
composite=dict(type='bool', default=False),
) )
argument_spec.update(meta_args) argument_spec.update(meta_args)
@@ -250,7 +295,7 @@ def main():
# Filter and map the parameters names that apply to the role # Filter and map the parameters names that apply to the role
role_params = [x for x in module.params role_params = [x for x in module.params
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'client_id', 'composites'] and if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'client_id'] and
module.params.get(x) is not None] module.params.get(x) is not None]
# See if it already exists in Keycloak # See if it already exists in Keycloak
@@ -269,10 +314,10 @@ def main():
new_param_value = module.params.get(param) new_param_value = module.params.get(param)
old_value = before_role[param] if param in before_role else None old_value = before_role[param] if param in before_role else None
if new_param_value != old_value: if new_param_value != old_value:
changeset[camel(param)] = new_param_value changeset[camel(param)] = copy.deepcopy(new_param_value)
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis) # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
desired_role = before_role.copy() desired_role = copy.deepcopy(before_role)
desired_role.update(changeset) desired_role.update(changeset)
result['proposed'] = changeset result['proposed'] = changeset
@@ -309,6 +354,9 @@ def main():
kc.create_client_role(desired_role, clientid, realm) kc.create_client_role(desired_role, clientid, realm)
after_role = kc.get_client_role(name, clientid, realm) after_role = kc.get_client_role(name, clientid, realm)
if after_role['composite']:
after_role['composites'] = kc.get_role_composites(rolerep=after_role, clientid=clientid, realm=realm)
result['end_state'] = after_role result['end_state'] = after_role
result['msg'] = 'Role {name} has been created'.format(name=name) result['msg'] = 'Role {name} has been created'.format(name=name)
@@ -316,10 +364,25 @@ def main():
else: else:
if state == 'present': if state == 'present':
compare_exclude = []
if 'composites' in desired_role and isinstance(desired_role['composites'], list) and len(desired_role['composites']) > 0:
composites = kc.get_role_composites(rolerep=before_role, clientid=clientid, realm=realm)
before_role['composites'] = []
for composite in composites:
before_composite = {}
if composite['clientRole']:
composite_client = kc.get_client_by_id(id=composite['containerId'], realm=realm)
before_composite['client_id'] = composite_client['clientId']
else:
before_composite['client_id'] = None
before_composite['name'] = composite['name']
before_composite['state'] = 'present'
before_role['composites'].append(before_composite)
else:
compare_exclude.append('composites')
# Process an update # Process an update
# no changes # no changes
if desired_role == before_role: if is_struct_included(desired_role, before_role, exclude=compare_exclude):
result['changed'] = False result['changed'] = False
result['end_state'] = desired_role result['end_state'] = desired_role
result['msg'] = "No changes required to role {name}.".format(name=name) result['msg'] = "No changes required to role {name}.".format(name=name)
@@ -341,6 +404,8 @@ def main():
else: else:
kc.update_client_role(desired_role, clientid, realm) kc.update_client_role(desired_role, clientid, realm)
after_role = kc.get_client_role(name, clientid, realm) after_role = kc.get_client_role(name, clientid, realm)
if after_role['composite']:
after_role['composites'] = kc.get_role_composites(rolerep=after_role, clientid=clientid, realm=realm)
result['end_state'] = after_role result['end_state'] = after_role

View File

@@ -36,9 +36,9 @@ options:
state: state:
description: description:
- State of the user federation. - State of the user federation.
- On C(present), the user federation will be created if it does not yet exist, or updated with - On V(present), the user federation will be created if it does not yet exist, or updated with
the parameters you provide. the parameters you provide.
- On C(absent), the user federation will be removed if it exists. - On V(absent), the user federation will be removed if it exists.
default: 'present' default: 'present'
type: str type: str
choices: choices:
@@ -54,7 +54,7 @@ options:
id: id:
description: description:
- The unique ID for this user federation. If left empty, the user federation will be searched - The unique ID for this user federation. If left empty, the user federation will be searched
by its I(name). by its O(name).
type: str type: str
name: name:
@@ -64,18 +64,15 @@ options:
provider_id: provider_id:
description: description:
- Provider for this user federation. - Provider for this user federation. Built-in providers are V(ldap), V(kerberos), and V(sssd).
Custom user storage providers can also be used.
aliases: aliases:
- providerId - providerId
type: str type: str
choices:
- ldap
- kerberos
- sssd
provider_type: provider_type:
description: description:
- Component type for user federation (only supported value is C(org.keycloak.storage.UserStorageProvider)). - Component type for user federation (only supported value is V(org.keycloak.storage.UserStorageProvider)).
aliases: aliases:
- providerType - providerType
default: org.keycloak.storage.UserStorageProvider default: org.keycloak.storage.UserStorageProvider
@@ -88,13 +85,37 @@ options:
- parentId - parentId
type: str type: str
remove_unspecified_mappers:
description:
- Remove mappers that are not specified in the configuration for this federation.
- Set to V(false) to keep mappers that are not listed in O(mappers).
type: bool
default: true
bind_credential_update_mode:
description:
- The value of the config parameter O(config.bindCredential) is redacted in the Keycloak responses.
Comparing the redacted value with the desired value always evaluates to not equal. This means
the before and desired states are never equal if the parameter is set.
- Set to V(always) to include O(config.bindCredential) in the comparison of before and desired state.
Because of the redacted value returned by Keycloak the module will always detect a change
and make an update if a O(config.bindCredential) value is set.
- Set to V(only_indirect) to exclude O(config.bindCredential) when comparing the before state with the
desired state. The value of O(config.bindCredential) will only be updated if there are other changes
to the user federation that require an update.
type: str
default: always
choices:
- always
- only_indirect
config: config:
description: description:
- Dict specifying the configuration options for the provider; the contents differ depending on - Dict specifying the configuration options for the provider; the contents differ depending on
the value of I(provider_id). Examples are given below for C(ldap), C(kerberos) and C(sssd). the value of O(provider_id). Examples are given below for V(ldap), V(kerberos) and V(sssd).
It is easiest to obtain valid config values by dumping an already-existing user federation It is easiest to obtain valid config values by dumping an already-existing user federation
configuration through check-mode in the I(existing) field. configuration through check-mode in the RV(existing) field.
- The value C(sssd) has been supported since middleware_automation.keycloak 1.0.0. - The value V(sssd) has been supported since middleware_automation.keycloak 2.0.0.
type: dict type: dict
suboptions: suboptions:
enabled: enabled:
@@ -111,15 +132,15 @@ options:
importEnabled: importEnabled:
description: description:
- If C(true), LDAP users will be imported into Keycloak DB and synced by the configured - If V(true), LDAP users will be imported into Keycloak DB and synced by the configured
sync policies. sync policies.
default: true default: true
type: bool type: bool
editMode: editMode:
description: description:
- C(READ_ONLY) is a read-only LDAP store. C(WRITABLE) means data will be synced back to LDAP - V(READ_ONLY) is a read-only LDAP store. V(WRITABLE) means data will be synced back to LDAP
on demand. C(UNSYNCED) means user data will be imported, but not synced back to LDAP. on demand. V(UNSYNCED) means user data will be imported, but not synced back to LDAP.
type: str type: str
choices: choices:
- READ_ONLY - READ_ONLY
@@ -136,13 +157,13 @@ options:
vendor: vendor:
description: description:
- LDAP vendor (provider). - LDAP vendor (provider).
- Use short name. For instance, write C(rhds) for "Red Hat Directory Server". - Use short name. For instance, write V(rhds) for "Red Hat Directory Server".
type: str type: str
usernameLDAPAttribute: usernameLDAPAttribute:
description: description:
- Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server - Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server
vendors it can be C(uid). For Active directory it can be C(sAMAccountName) or C(cn). vendors it can be V(uid). For Active directory it can be V(sAMAccountName) or V(cn).
The attribute should be filled for all LDAP user records you want to import from The attribute should be filled for all LDAP user records you want to import from
LDAP to Keycloak. LDAP to Keycloak.
type: str type: str
@@ -151,15 +172,15 @@ options:
description: description:
- Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN. - Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN.
Usually it's the same as Username LDAP attribute, however it is not required. For Usually it's the same as Username LDAP attribute, however it is not required. For
example for Active directory, it is common to use C(cn) as RDN attribute when example for Active directory, it is common to use V(cn) as RDN attribute when
username attribute might be C(sAMAccountName). username attribute might be V(sAMAccountName).
type: str type: str
uuidLDAPAttribute: uuidLDAPAttribute:
description: description:
- Name of LDAP attribute, which is used as unique object identifier (UUID) for objects - Name of LDAP attribute, which is used as unique object identifier (UUID) for objects
in LDAP. For many LDAP server vendors, it is C(entryUUID); however some are different. in LDAP. For many LDAP server vendors, it is V(entryUUID); however some are different.
For example for Active directory it should be C(objectGUID). If your LDAP server does For example for Active directory it should be V(objectGUID). If your LDAP server does
not support the notion of UUID, you can use any other attribute that is supposed to not support the notion of UUID, you can use any other attribute that is supposed to
be unique among LDAP users in tree. be unique among LDAP users in tree.
type: str type: str
@@ -167,7 +188,7 @@ options:
userObjectClasses: userObjectClasses:
description: description:
- All values of LDAP objectClass attribute for users in LDAP divided by comma. - All values of LDAP objectClass attribute for users in LDAP divided by comma.
For example C(inetOrgPerson, organizationalPerson). Newly created Keycloak users For example V(inetOrgPerson, organizationalPerson). Newly created Keycloak users
will be written to LDAP with all those object classes and existing LDAP user records will be written to LDAP with all those object classes and existing LDAP user records
are found just if they contain all those object classes. are found just if they contain all those object classes.
type: str type: str
@@ -251,8 +272,8 @@ options:
useTruststoreSpi: useTruststoreSpi:
description: description:
- Specifies whether LDAP connection will use the truststore SPI with the truststore - Specifies whether LDAP connection will use the truststore SPI with the truststore
configured in standalone.xml/domain.xml. C(Always) means that it will always use it. configured in standalone.xml/domain.xml. V(always) means that it will always use it.
C(Never) means that it will not use it. C(Only for ldaps) means that it will use if V(never) means that it will not use it. V(ldapsOnly) means that it will use if
your connection URL use ldaps. Note even if standalone.xml/domain.xml is not your connection URL use ldaps. Note even if standalone.xml/domain.xml is not
configured, the default Java cacerts or certificate specified by configured, the default Java cacerts or certificate specified by
C(javax.net.ssl.trustStore) property will be used. C(javax.net.ssl.trustStore) property will be used.
@@ -297,7 +318,7 @@ options:
connectionPoolingDebug: connectionPoolingDebug:
description: description:
- A string that indicates the level of debug output to produce. Example valid values are - A string that indicates the level of debug output to produce. Example valid values are
C(fine) (trace connection creation and removal) and C(all) (all debugging information). V(fine) (trace connection creation and removal) and V(all) (all debugging information).
type: str type: str
connectionPoolingInitSize: connectionPoolingInitSize:
@@ -321,7 +342,7 @@ options:
connectionPoolingProtocol: connectionPoolingProtocol:
description: description:
- A list of space-separated protocol types of connections that may be pooled. - A list of space-separated protocol types of connections that may be pooled.
Valid types are C(plain) and C(ssl). Valid types are V(plain) and V(ssl).
type: str type: str
connectionPoolingTimeout: connectionPoolingTimeout:
@@ -342,17 +363,26 @@ options:
- Name of kerberos realm. - Name of kerberos realm.
type: str type: str
krbPrincipalAttribute:
description:
- Name of the LDAP attribute, which refers to Kerberos principal.
This is used to lookup appropriate LDAP user after successful Kerberos/SPNEGO authentication in Keycloak.
When this is empty, the LDAP user will be looked based on LDAP username corresponding
to the first part of his Kerberos principal. For instance, for principal C(john@KEYCLOAK.ORG),
it will assume that LDAP username is V(john).
type: str
serverPrincipal: serverPrincipal:
description: description:
- Full name of server principal for HTTP service including server and domain name. For - Full name of server principal for HTTP service including server and domain name. For
example C(HTTP/host.foo.org@FOO.ORG). Use C(*) to accept any service principal in the example V(HTTP/host.foo.org@FOO.ORG). Use V(*) to accept any service principal in the
KeyTab file. KeyTab file.
type: str type: str
keyTab: keyTab:
description: description:
- Location of Kerberos KeyTab file containing the credentials of server principal. For - Location of Kerberos KeyTab file containing the credentials of server principal. For
example C(/etc/krb5.keytab). example V(/etc/krb5.keytab).
type: str type: str
debug: debug:
@@ -427,6 +457,16 @@ options:
- Max lifespan of cache entry in milliseconds. - Max lifespan of cache entry in milliseconds.
type: int type: int
referral:
description:
- Specifies if LDAP referrals should be followed or ignored. Please note that enabling
referrals can slow down authentication as it allows the LDAP server to decide which other
LDAP servers to use. This could potentially include untrusted servers.
type: str
choices:
- ignore
- follow
mappers: mappers:
description: description:
- A list of dicts defining mappers associated with this Identity Provider. - A list of dicts defining mappers associated with this Identity Provider.
@@ -451,7 +491,7 @@ options:
providerId: providerId:
description: description:
- The mapper type for this mapper (for instance C(user-attribute-ldap-mapper)). - The mapper type for this mapper (for instance V(user-attribute-ldap-mapper)).
type: str type: str
providerType: providerType:
@@ -704,16 +744,27 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode
from copy import deepcopy from copy import deepcopy
def normalize_kc_comp(comp):
if 'config' in comp:
# kc completely removes the parameter `krbPrincipalAttribute` if it is set to `''`; the unset kc parameter is equivalent to `''`;
# to make change detection and diff more accurate we set it again in the kc responses
if 'krbPrincipalAttribute' not in comp['config']:
comp['config']['krbPrincipalAttribute'] = ['']
# kc stores a timestamp of the last sync in `lastSync` to time the periodic sync, it is removed to minimize diff/changes
comp['config'].pop('lastSync', None)
def sanitize(comp): def sanitize(comp):
compcopy = deepcopy(comp) compcopy = deepcopy(comp)
if 'config' in compcopy: if 'config' in compcopy:
compcopy['config'] = dict((k, v[0]) for k, v in compcopy['config'].items()) compcopy['config'] = {k: v[0] for k, v in compcopy['config'].items()}
if 'bindCredential' in compcopy['config']: if 'bindCredential' in compcopy['config']:
compcopy['config']['bindCredential'] = '**********' compcopy['config']['bindCredential'] = '**********'
if 'mappers' in compcopy: if 'mappers' in compcopy:
for mapper in compcopy['mappers']: for mapper in compcopy['mappers']:
if 'config' in mapper: if 'config' in mapper:
mapper['config'] = dict((k, v[0]) for k, v in mapper['config'].items()) mapper['config'] = {k: v[0] for k, v in mapper['config'].items()}
return compcopy return compcopy
@@ -760,8 +811,10 @@ def main():
priority=dict(type='int', default=0), priority=dict(type='int', default=0),
rdnLDAPAttribute=dict(type='str'), rdnLDAPAttribute=dict(type='str'),
readTimeout=dict(type='int'), readTimeout=dict(type='int'),
referral=dict(type='str', choices=['ignore', 'follow']),
searchScope=dict(type='str', choices=['1', '2'], default='1'), searchScope=dict(type='str', choices=['1', '2'], default='1'),
serverPrincipal=dict(type='str'), serverPrincipal=dict(type='str'),
krbPrincipalAttribute=dict(type='str'),
startTls=dict(type='bool', default=False), startTls=dict(type='bool', default=False),
syncRegistrations=dict(type='bool', default=False), syncRegistrations=dict(type='bool', default=False),
trustEmail=dict(type='bool', default=False), trustEmail=dict(type='bool', default=False),
@@ -792,9 +845,11 @@ def main():
realm=dict(type='str', default='master'), realm=dict(type='str', default='master'),
id=dict(type='str'), id=dict(type='str'),
name=dict(type='str'), name=dict(type='str'),
provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos', 'sssd']), provider_id=dict(type='str', aliases=['providerId']),
provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'), provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
parent_id=dict(type='str', aliases=['parentId']), parent_id=dict(type='str', aliases=['parentId']),
remove_unspecified_mappers=dict(type='bool', default=True),
bind_credential_update_mode=dict(type='str', default='always', choices=['always', 'only_indirect']),
mappers=dict(type='list', elements='dict', options=mapper_spec), mappers=dict(type='list', elements='dict', options=mapper_spec),
) )
@@ -825,19 +880,26 @@ def main():
# Keycloak API expects config parameters to be arrays containing a single string element # Keycloak API expects config parameters to be arrays containing a single string element
if config is not None: if config is not None:
module.params['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v]) module.params['config'] = {
for k, v in config.items() if config[k] is not None) k: [str(v).lower() if not isinstance(v, str) else v]
for k, v in config.items()
if config[k] is not None
}
if mappers is not None: if mappers is not None:
for mapper in mappers: for mapper in mappers:
if mapper.get('config') is not None: if mapper.get('config') is not None:
mapper['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v]) mapper['config'] = {
for k, v in mapper['config'].items() if mapper['config'][k] is not None) k: [str(v).lower() if not isinstance(v, str) else v]
for k, v in mapper['config'].items()
if mapper['config'][k] is not None
}
# Filter and map the parameters names that apply # Filter and map the parameters names that apply
comp_params = [x for x in module.params comp_params = [x for x in module.params
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'mappers'] and if x not in list(keycloak_argument_spec().keys())
module.params.get(x) is not None] + ['state', 'realm', 'mappers', 'remove_unspecified_mappers', 'bind_credential_update_mode']
and module.params.get(x) is not None]
# See if it already exists in Keycloak # See if it already exists in Keycloak
if cid is None: if cid is None:
@@ -855,7 +917,9 @@ def main():
# if user federation exists, get associated mappers # if user federation exists, get associated mappers
if cid is not None and before_comp: if cid is not None and before_comp:
before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name')) before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
normalize_kc_comp(before_comp)
# Build a proposed changeset from parameters given to this module # Build a proposed changeset from parameters given to this module
changeset = {} changeset = {}
@@ -864,7 +928,7 @@ def main():
new_param_value = module.params.get(param) new_param_value = module.params.get(param)
old_value = before_comp[camel(param)] if camel(param) in before_comp else None old_value = before_comp[camel(param)] if camel(param) in before_comp else None
if param == 'mappers': if param == 'mappers':
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value] new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
if new_param_value != old_value: if new_param_value != old_value:
changeset[camel(param)] = new_param_value changeset[camel(param)] = new_param_value
@@ -873,17 +937,17 @@ def main():
if module.params['provider_id'] in ['kerberos', 'sssd']: if module.params['provider_id'] in ['kerberos', 'sssd']:
module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id'])) module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
for change in module.params['mappers']: for change in module.params['mappers']:
change = dict((k, v) for k, v in change.items() if change[k] is not None) change = {k: v for k, v in change.items() if v is not None}
if change.get('id') is None and change.get('name') is None: if change.get('id') is None and change.get('name') is None:
module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.') module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.')
if cid is None: if cid is None:
old_mapper = {} old_mapper = {}
elif change.get('id') is not None: elif change.get('id') is not None:
old_mapper = kc.get_component(change['id'], realm) old_mapper = next((before_mapper for before_mapper in before_comp.get('mappers', []) if before_mapper["id"] == change['id']), None)
if old_mapper is None: if old_mapper is None:
old_mapper = {} old_mapper = {}
else: else:
found = kc.get_components(urlencode(dict(parent=cid, name=change['name'])), realm) found = [before_mapper for before_mapper in before_comp.get('mappers', []) if before_mapper['name'] == change['name']]
if len(found) > 1: if len(found) > 1:
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=change['name'])) module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=change['name']))
if len(found) == 1: if len(found) == 1:
@@ -892,10 +956,16 @@ def main():
old_mapper = {} old_mapper = {}
new_mapper = old_mapper.copy() new_mapper = old_mapper.copy()
new_mapper.update(change) new_mapper.update(change)
if new_mapper != old_mapper: # changeset contains all desired mappers: those existing, to update or to create
if changeset.get('mappers') is None: if changeset.get('mappers') is None:
changeset['mappers'] = list() changeset['mappers'] = list()
changeset['mappers'].append(new_mapper) changeset['mappers'].append(new_mapper)
changeset['mappers'] = sorted(changeset['mappers'], key=lambda x: x.get('name') or '')
# to keep unspecified existing mappers we add them to the desired mappers list, unless they're already present
if not module.params['remove_unspecified_mappers'] and 'mappers' in before_comp:
changeset_mapper_ids = [mapper['id'] for mapper in changeset['mappers'] if 'id' in mapper]
changeset['mappers'].extend([mapper for mapper in before_comp['mappers'] if mapper['id'] not in changeset_mapper_ids])
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis) # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
desired_comp = before_comp.copy() desired_comp = before_comp.copy()
@@ -918,50 +988,68 @@ def main():
# Process a creation # Process a creation
result['changed'] = True result['changed'] = True
if module.check_mode:
if module._diff: if module._diff:
result['diff'] = dict(before='', after=sanitize(desired_comp)) result['diff'] = dict(before='', after=sanitize(desired_comp))
if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
# create it # create it
desired_comp = desired_comp.copy() desired_mappers = desired_comp.pop('mappers', [])
updated_mappers = desired_comp.pop('mappers', [])
after_comp = kc.create_component(desired_comp, realm) after_comp = kc.create_component(desired_comp, realm)
cid = after_comp['id'] cid = after_comp['id']
updated_mappers = []
# when creating a user federation, keycloak automatically creates default mappers
default_mappers = kc.get_components(urlencode(dict(parent=cid)), realm)
for mapper in updated_mappers: # create new mappers or update existing default mappers
found = kc.get_components(urlencode(dict(parent=cid, name=mapper['name'])), realm) for desired_mapper in desired_mappers:
found = [default_mapper for default_mapper in default_mappers if default_mapper['name'] == desired_mapper['name']]
if len(found) > 1: if len(found) > 1:
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=mapper['name'])) module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=desired_mapper['name']))
if len(found) == 1: if len(found) == 1:
old_mapper = found[0] old_mapper = found[0]
else: else:
old_mapper = {} old_mapper = {}
new_mapper = old_mapper.copy() new_mapper = old_mapper.copy()
new_mapper.update(mapper) new_mapper.update(desired_mapper)
if new_mapper.get('id') is not None: if new_mapper.get('id') is not None:
kc.update_component(new_mapper, realm) kc.update_component(new_mapper, realm)
updated_mappers.append(new_mapper)
else: else:
if new_mapper.get('parentId') is None: if new_mapper.get('parentId') is None:
new_mapper['parentId'] = after_comp['id'] new_mapper['parentId'] = cid
mapper = kc.create_component(new_mapper, realm) updated_mappers.append(kc.create_component(new_mapper, realm))
after_comp['mappers'] = updated_mappers if module.params['remove_unspecified_mappers']:
# we remove all unwanted default mappers
# we use ids so we dont accidently remove one of the previously updated default mapper
for default_mapper in default_mappers:
if not default_mapper['id'] in [x['id'] for x in updated_mappers]:
kc.delete_component(default_mapper['id'], realm)
after_comp['mappers'] = kc.get_components(urlencode(dict(parent=cid)), realm)
normalize_kc_comp(after_comp)
if module._diff:
result['diff'] = dict(before='', after=sanitize(after_comp))
result['end_state'] = sanitize(after_comp) result['end_state'] = sanitize(after_comp)
result['msg'] = "User federation {id} has been created".format(id=cid)
result['msg'] = "User federation {id} has been created".format(id=after_comp['id'])
module.exit_json(**result) module.exit_json(**result)
else: else:
if state == 'present': if state == 'present':
# Process an update # Process an update
desired_copy = deepcopy(desired_comp)
before_copy = deepcopy(before_comp)
# exclude bindCredential when checking wether an update is required, therefore
# updating it only if there are other changes
if module.params['bind_credential_update_mode'] == 'only_indirect':
desired_copy.get('config', []).pop('bindCredential', None)
before_copy.get('config', []).pop('bindCredential', None)
# no changes # no changes
if desired_comp == before_comp: if desired_copy == before_copy:
result['changed'] = False result['changed'] = False
result['end_state'] = sanitize(desired_comp) result['end_state'] = sanitize(desired_comp)
result['msg'] = "No changes required to user federation {id}.".format(id=cid) result['msg'] = "No changes required to user federation {id}.".format(id=cid)
@@ -977,22 +1065,33 @@ def main():
module.exit_json(**result) module.exit_json(**result)
# do the update # do the update
desired_comp = desired_comp.copy() desired_mappers = desired_comp.pop('mappers', [])
updated_mappers = desired_comp.pop('mappers', [])
kc.update_component(desired_comp, realm) kc.update_component(desired_comp, realm)
after_comp = kc.get_component(cid, realm)
for mapper in updated_mappers: for before_mapper in before_comp.get('mappers', []):
# remove unwanted existing mappers that will not be updated
if not before_mapper['id'] in [x['id'] for x in desired_mappers if 'id' in x]:
kc.delete_component(before_mapper['id'], realm)
for mapper in desired_mappers:
if mapper in before_comp.get('mappers', []):
continue
if mapper.get('id') is not None: if mapper.get('id') is not None:
kc.update_component(mapper, realm) kc.update_component(mapper, realm)
else: else:
if mapper.get('parentId') is None: if mapper.get('parentId') is None:
mapper['parentId'] = desired_comp['id'] mapper['parentId'] = desired_comp['id']
mapper = kc.create_component(mapper, realm) kc.create_component(mapper, realm)
after_comp['mappers'] = updated_mappers
result['end_state'] = sanitize(after_comp)
after_comp = kc.get_component(cid, realm)
after_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
normalize_kc_comp(after_comp)
after_comp_sanitized = sanitize(after_comp)
before_comp_sanitized = sanitize(before_comp)
result['end_state'] = after_comp_sanitized
if module._diff:
result['diff'] = dict(before=before_comp_sanitized, after=after_comp_sanitized)
result['changed'] = before_comp_sanitized != after_comp_sanitized
result['msg'] = "User federation {id} has been updated".format(id=cid) result['msg'] = "User federation {id} has been updated".format(id=cid)
module.exit_json(**result) module.exit_json(**result)

View File

@@ -4,3 +4,4 @@
# pip install -r requirements.txt # pip install -r requirements.txt
# #
netaddr netaddr
lxml # for middleware_automation.common.maven_artifact

View File

@@ -1,4 +1,5 @@
--- ---
collections: collections:
- name: middleware_automation.common - name: middleware_automation.common
version: ">=1.2.1"
- name: ansible.posix - name: ansible.posix

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_installdir: "{{ keycloak_dest }}/keycloak-{{ keycloak_version }}"
keycloak_offline_install: false 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 ### Install location and service settings
keycloak_java_home: keycloak_java_home:
keycloak_dest: /opt/keycloak keycloak_dest: /opt/keycloak
@@ -118,3 +122,7 @@ keycloak_no_log: true
### logging configuration ### logging configuration
keycloak_log_target: /var/log/keycloak keycloak_log_target: /var/log/keycloak
# locations
keycloak_url: "http://{{ keycloak_host }}:{{ keycloak_http_port + keycloak_jboss_port_offset }}"
keycloak_management_url: "http://{{ keycloak_host }}:{{ keycloak_management_http_port + keycloak_jboss_port_offset }}"

View File

@@ -86,7 +86,9 @@ argument_specs:
type: "str" type: "str"
keycloak_features: keycloak_features:
default: "[]" default: "[]"
description: "List of `name`/`status` pairs of features (also known as profiles on RH-SSO) to `enable` or `disable`, example: `[ { name: 'docker', status: 'enabled' } ]`" description: >
List of `name`/`status` pairs of features (also known as profiles on RH-SSO) to `enable` or `disable`,
example: `[ { name: 'docker', status: 'enabled' } ]`
type: "list" type: "list"
keycloak_bind_address: keycloak_bind_address:
default: "0.0.0.0" default: "0.0.0.0"
@@ -310,12 +312,35 @@ argument_specs:
type: "str" type: "str"
keycloak_jgroups_subnet: keycloak_jgroups_subnet:
required: false required: false
description: "Override the subnet match for jgroups cluster formation; if not defined, it will be inferred from local machine route configuration" description: >
Override the subnet match for jgroups cluster formation; if not defined, it will be inferred from local machine route configuration
type: "str" type: "str"
keycloak_log_target: keycloak_log_target:
default: '/var/log/keycloak' default: '/var/log/keycloak'
type: "str" type: "str"
description: "Set the destination of the keycloak log folder link" description: "Set the destination of the keycloak log folder link"
keycloak_jdbc_download_url:
description: "Override the default Maven Central download URL for the JDBC driver"
type: "str"
keycloak_jdbc_download_user:
description: "Set a username with which to authenticate when downloading JDBC drivers from an alternative location"
type: "str"
keycloak_jdbc_download_pass:
description: >
Set a password with which to authenticate when downloading JDBC drivers from an alternative location (requires keycloak_jdbc_download_user)
type: "str"
keycloak_jdbc_download_validate_certs:
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: downstream:
options: options:
sso_version: sso_version:

View File

@@ -12,7 +12,7 @@ galaxy_info:
license: Apache License 2.0 license: Apache License 2.0
min_ansible_version: "2.14" min_ansible_version: "2.16"
platforms: platforms:
- name: EL - name: EL

View File

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

View File

@@ -8,12 +8,13 @@
- name: "Add missing packages to the yum install list" - name: "Add missing packages to the yum install list"
ansible.builtin.set_fact: ansible.builtin.set_fact:
packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}" packages_to_install: "{{ packages_to_install | default([]) + rpm_info.stdout_lines | \
map('regex_findall', 'package (.+) is not installed$') | default([]) | flatten }}"
when: ansible_facts.os_family == "RedHat" when: ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_to_install }}" - name: "Install packages: {{ packages_to_install }}"
become: true become: "{{ keycloak_fastpackages_require_privilege_escalation }}"
ansible.builtin.yum: ansible.builtin.dnf:
name: "{{ packages_to_install }}" name: "{{ packages_to_install }}"
state: present state: present
when: when:
@@ -21,7 +22,7 @@
- ansible_facts.os_family == "RedHat" - ansible_facts.os_family == "RedHat"
- name: "Install packages: {{ packages_list }}" - name: "Install packages: {{ packages_list }}"
become: true become: "{{ keycloak_fastpackages_require_privilege_escalation }}"
ansible.builtin.package: ansible.builtin.package:
name: "{{ packages_list }}" name: "{{ packages_list }}"
state: present state: present

View File

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

View File

@@ -11,7 +11,7 @@
quiet: true quiet: true
- name: Check for an existing deployment - name: Check for an existing deployment
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
register: existing_deploy register: existing_deploy
@@ -20,47 +20,47 @@
when: existing_deploy.stat.exists and keycloak_force_install | bool when: existing_deploy.stat.exists and keycloak_force_install | bool
block: block:
- name: "Stop the old {{ keycloak.service_name }} service" - name: "Stop the old {{ keycloak.service_name }} service"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
failed_when: false failed_when: false
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
state: stopped state: stopped
- name: "Remove the old {{ keycloak.service_name }} deployment" - name: "Remove the old {{ keycloak.service_name }} deployment"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file: ansible.builtin.file:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
state: absent state: absent
- name: Check for an existing deployment after possible forced removal - name: Check for an existing deployment after possible forced removal
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jboss_home }}" path: "{{ keycloak_jboss_home }}"
- name: "Create service user/group for {{ keycloak.service_name }}" - name: "Create service user/group for {{ keycloak.service_name }}"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.user: ansible.builtin.user:
name: "{{ keycloak_service_user }}" name: "{{ keycloak_service_user }}"
home: /opt/keycloak home: /opt/keycloak
system: yes system: true
create_home: no create_home: false
- name: "Create install location for {{ keycloak.service_name }}" - name: "Create install location for {{ keycloak.service_name }}"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file: ansible.builtin.file:
dest: "{{ keycloak_dest }}" dest: "{{ keycloak_dest }}"
state: directory state: directory
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0750 mode: '0750'
- name: Create pidfile folder - name: Create pidfile folder
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.file: ansible.builtin.file:
dest: "{{ keycloak_service_pidfile | dirname }}" dest: "{{ keycloak_service_pidfile | dirname }}"
state: directory state: directory
owner: "{{ keycloak_service_user if keycloak_service_runas else omit }}" owner: "{{ keycloak_service_user if keycloak_service_runas else omit }}"
group: "{{ keycloak_service_group if keycloak_service_runas else omit }}" group: "{{ keycloak_service_group if keycloak_service_runas else omit }}"
mode: 0750 mode: '0750'
## check remote archive ## check remote archive
- name: Set download archive path - name: Set download archive path
@@ -68,7 +68,7 @@
archive: "{{ keycloak_dest }}/{{ keycloak.bundle }}" archive: "{{ keycloak_dest }}/{{ keycloak.bundle }}"
- name: Check download archive path - name: Check download archive path
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ archive }}" path: "{{ archive }}"
register: archive_path register: archive_path
@@ -84,7 +84,9 @@
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_download_url }}" url: "{{ keycloak_download_url }}"
dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: 0644 mode: '0644'
url_username: "{{ keycloak_binary_download_user | default(omit) }}"
url_password: "{{ keycloak_binary_download_pass | default(omit) }}"
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
@@ -136,7 +138,7 @@
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: "{{ keycloak_rhsso_download_url }}" url: "{{ keycloak_rhsso_download_url }}"
dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}" dest: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
mode: 0644 mode: '0644'
delegate_to: localhost delegate_to: localhost
run_once: true run_once: true
when: when:
@@ -160,19 +162,19 @@
dest: "{{ archive }}" dest: "{{ archive }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
register: new_version_downloaded register: new_version_downloaded
when: when:
- not archive_path.stat.exists - not archive_path.stat.exists
- local_archive_path.stat is defined - local_archive_path.stat is defined
- local_archive_path.stat.exists - local_archive_path.stat.exists
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
- name: "Check target directory: {{ keycloak.home }}" - name: "Check target directory: {{ keycloak.home }}"
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak.home }}" path: "{{ keycloak.home }}"
register: path_to_workdir register: path_to_workdir
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
- name: "Extract {{ keycloak_service_desc }} archive on target" - name: "Extract {{ keycloak_service_desc }} archive on target"
ansible.builtin.unarchive: ansible.builtin.unarchive:
@@ -182,7 +184,7 @@
creates: "{{ keycloak.home }}" creates: "{{ keycloak.home }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
when: when:
- new_version_downloaded.changed or not path_to_workdir.stat.exists - new_version_downloaded.changed or not path_to_workdir.stat.exists
notify: notify:
@@ -200,13 +202,13 @@
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
recurse: true recurse: true
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
changed_when: false changed_when: false
- name: Ensure permissions are correct on existing deploy - name: Ensure permissions are correct on existing deploy
ansible.builtin.command: chown -R "{{ keycloak_service_user }}:{{ keycloak_service_group }}" "{{ keycloak.home }}" ansible.builtin.command: chown -R "{{ keycloak_service_user }}:{{ keycloak_service_group }}" "{{ keycloak.home }}"
when: keycloak_service_runas when: keycloak_service_runas
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
changed_when: false changed_when: false
# driver and configuration # driver and configuration
@@ -215,25 +217,25 @@
when: keycloak_jdbc[keycloak_jdbc_engine].enabled 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 }}" - 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: ansible.builtin.template:
src: "templates/{{ keycloak_config_override_template }}" src: "templates/{{ keycloak_config_override_template }}"
dest: "{{ keycloak_config_path_to_standalone_xml }}" dest: "{{ keycloak_config_path_to_standalone_xml }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: keycloak_config_override_template | length > 0 when: keycloak_config_override_template | length > 0
- name: "Deploy standalone {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}" - 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: ansible.builtin.template:
src: templates/standalone.xml.j2 src: templates/standalone.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}" dest: "{{ keycloak_config_path_to_standalone_xml }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: when:
@@ -255,13 +257,13 @@
when: keycloak_ha_enabled and keycloak_ha_discovery == 'TCPPING' when: keycloak_ha_enabled and keycloak_ha_discovery == 'TCPPING'
- name: "Deploy HA {{ keycloak.service_name }} config to {{ keycloak_config_path_to_standalone_xml }}" - 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: ansible.builtin.template:
src: templates/standalone-ha.xml.j2 src: templates/standalone-ha.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}" dest: "{{ keycloak_config_path_to_standalone_xml }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: when:
@@ -270,13 +272,13 @@
- keycloak_config_override_template | length == 0 - 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 }}" - 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: ansible.builtin.template:
src: templates/standalone-infinispan.xml.j2 src: templates/standalone-infinispan.xml.j2
dest: "{{ keycloak_config_path_to_standalone_xml }}" dest: "{{ keycloak_config_path_to_standalone_xml }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: when:
@@ -285,13 +287,13 @@
- keycloak_config_override_template | length == 0 - keycloak_config_override_template | length == 0
- name: "Deploy profile.properties file to {{ keycloak_config_path_to_properties }}" - name: "Deploy profile.properties file to {{ keycloak_config_path_to_properties }}"
become: true become: "{{ keycloak_install_require_privilege_escalation }}"
ansible.builtin.template: ansible.builtin.template:
src: keycloak-profile.properties.j2 src: keycloak-profile.properties.j2
dest: "{{ keycloak_config_path_to_properties }}" dest: "{{ keycloak_config_path_to_properties }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
notify: notify:
- restart keycloak - restart keycloak
when: keycloak_features | length > 0 when: keycloak_features | length > 0

View File

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

View File

@@ -3,7 +3,7 @@
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}" path: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}"
register: dest_path 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 }}" - name: "Set up module dir for JDBC Driver {{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_name }}"
ansible.builtin.file: ansible.builtin.file:
@@ -12,10 +12,17 @@
recurse: true recurse: true
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0750 mode: '0750'
become: true become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"
when: when:
- not dest_path.stat.exists - not dest_path.stat.exists
- name: "Verify valid parameters for download credentials when specified"
ansible.builtin.fail:
msg: >-
When JDBC driver download credentials are set, both the username and the password MUST be set
when: >
(keycloak_jdbc_download_user is undefined and keycloak_jdbc_download_pass is not undefined) or
(keycloak_jdbc_download_pass is undefined and keycloak_jdbc_download_user is not undefined)
- name: "Retrieve JDBC Driver from {{ keycloak_jdbc[keycloak_jdbc_engine].driver_jar_url }}" - name: "Retrieve JDBC Driver from {{ keycloak_jdbc[keycloak_jdbc_engine].driver_jar_url }}"
ansible.builtin.get_url: ansible.builtin.get_url:
@@ -23,8 +30,11 @@
dest: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}/{{ keycloak_jdbc[keycloak_jdbc_engine].driver_jar_filename }}" dest: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}/{{ keycloak_jdbc[keycloak_jdbc_engine].driver_jar_filename }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
mode: 0640 url_username: "{{ keycloak_jdbc_download_user | default(omit) }}"
become: true url_password: "{{ keycloak_jdbc_download_pass | default(omit) }}"
validate_certs: "{{ keycloak_jdbc_download_validate_certs | default(omit) }}"
mode: '0640'
become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"
- name: "Deploy module.xml for JDBC Driver" - name: "Deploy module.xml for JDBC Driver"
ansible.builtin.template: ansible.builtin.template:
@@ -32,5 +42,5 @@
dest: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}/module.xml" dest: "{{ keycloak_jdbc[keycloak_jdbc_engine].driver_module_dir }}/module.xml"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
mode: 0640 mode: '0640'
become: true become: "{{ keycloak_jdbc_driver_require_privilege_escalation }}"

View File

@@ -1,22 +1,38 @@
--- ---
# tasks file for keycloak # tasks file for keycloak
- name: Check prerequisites - name: Check prerequisites
ansible.builtin.include_tasks: prereqs.yml ansible.builtin.include_tasks:
file: prereqs.yml
apply:
tags:
- prereqs
tags: tags:
- prereqs - prereqs
- name: Distro specific tasks - name: Distro specific tasks
ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml" ansible.builtin.include_tasks:
file: "{{ ansible_os_family | lower }}.yml"
apply:
tags:
- unbound
tags: tags:
- unbound - unbound
- name: Include install tasks - name: Include install tasks
ansible.builtin.include_tasks: install.yml ansible.builtin.include_tasks:
file: install.yml
apply:
tags:
- install
tags: tags:
- install - install
- name: Include systemd tasks - name: Include systemd tasks
ansible.builtin.include_tasks: systemd.yml ansible.builtin.include_tasks:
file: systemd.yml
apply:
tags:
- systemd
tags: tags:
- systemd - systemd
@@ -35,7 +51,7 @@
state: link state: link
src: "{{ keycloak_jboss_home }}/standalone/log" src: "{{ keycloak_jboss_home }}/standalone/log"
dest: "{{ keycloak_log_target }}" dest: "{{ keycloak_log_target }}"
become: true become: "{{ keycloak_require_privilege_escalation }}"
- name: Set admin credentials and restart if not already created - name: Set admin credentials and restart if not already created
block: block:
@@ -59,7 +75,7 @@
- "-u{{ keycloak_admin_user }}" - "-u{{ keycloak_admin_user }}"
- "-p{{ keycloak_admin_password }}" - "-p{{ keycloak_admin_password }}"
changed_when: true changed_when: true
become: true become: "{{ keycloak_require_privilege_escalation }}"
- name: "Restart {{ keycloak.service_name }}" - name: "Restart {{ keycloak.service_name }}"
ansible.builtin.include_tasks: tasks/restart_keycloak.yml ansible.builtin.include_tasks: tasks/restart_keycloak.yml
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"

View File

@@ -4,13 +4,16 @@
that: that:
- keycloak_admin_password | length > 12 - keycloak_admin_password | length > 12
quiet: true quiet: true
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_admin_password variable to a 12+ char long string" fail_msg: >
The console administrator password is empty or invalid. Please set the keycloak_admin_password variable to a 12+ char long string
success_msg: "{{ 'Console administrator password OK' }}" success_msg: "{{ 'Console administrator password OK' }}"
- name: Validate configuration - name: Validate configuration
ansible.builtin.assert: ansible.builtin.assert:
that: that: >
- (keycloak_ha_enabled and keycloak_db_enabled) or (not keycloak_ha_enabled and keycloak_db_enabled) or (not keycloak_ha_enabled and not keycloak_db_enabled) (keycloak_ha_enabled and keycloak_db_enabled) or
(not keycloak_ha_enabled and keycloak_db_enabled) or
(not keycloak_ha_enabled and not keycloak_db_enabled)
quiet: true quiet: true
fail_msg: "Cannot install HA setup without a backend database service. Check keycloak_ha_enabled and keycloak_db_enabled" fail_msg: "Cannot install HA setup without a backend database service. Check keycloak_ha_enabled and keycloak_db_enabled"
success_msg: "{{ 'Configuring HA' if keycloak_ha_enabled else 'Configuring standalone' }}" success_msg: "{{ 'Configuring HA' if keycloak_ha_enabled else 'Configuring standalone' }}"

View File

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

View File

@@ -5,7 +5,7 @@
enabled: true enabled: true
state: restarted state: restarted
daemon_reload: true daemon_reload: true
become: true become: "{{ keycloak_restart_require_privilege_escalation }}"
delegate_to: "{{ ansible_play_hosts | first }}" delegate_to: "{{ ansible_play_hosts | first }}"
run_once: true run_once: true
@@ -22,7 +22,7 @@
- name: "Restart and enable {{ keycloak.service_name }} service" - name: "Restart and enable {{ keycloak.service_name }} service"
ansible.builtin.systemd: ansible.builtin.systemd:
name: keycloak name: keycloak
enabled: yes enabled: true
state: restarted state: restarted
become: true become: "{{ keycloak_restart_require_privilege_escalation }}"
when: inventory_hostname != ansible_play_hosts | first when: inventory_hostname != ansible_play_hosts | first

View File

@@ -2,12 +2,12 @@
- name: Ensure required params for CLI have been provided - name: Ensure required params for CLI have been provided
ansible.builtin.assert: ansible.builtin.assert:
that: that:
- query is defined - cli_query is defined
fail_msg: "Missing required parameters to execute CLI." fail_msg: "Missing required parameters to execute CLI."
quiet: true quiet: true
- name: "Execute CLI query: {{ query }}" - name: "Execute CLI query: {{ cli_query }}"
ansible.builtin.command: > ansible.builtin.command: >
{{ keycloak.cli_path }} --connect --command='{{ query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }} {{ keycloak.cli_path }} --connect --command='{{ cli_query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }}
changed_when: false changed_when: false
register: cli_result register: cli_result

View File

@@ -12,7 +12,7 @@
path: "{{ patch_archive }}" path: "{{ patch_archive }}"
register: patch_archive_path register: patch_archive_path
when: sso_patch_version is defined when: sso_patch_version is defined
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
- name: Perform patch download from RHN via JBossNetwork API - name: Perform patch download from RHN via JBossNetwork API
delegate_to: localhost delegate_to: localhost
@@ -86,7 +86,7 @@
ansible.builtin.stat: ansible.builtin.stat:
path: "{{ patch_archive }}" path: "{{ patch_archive }}"
register: patch_archive_path register: patch_archive_path
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
## copy and unpack ## copy and unpack
- name: Copy patch archive to target nodes - name: Copy patch archive to target nodes
@@ -95,21 +95,21 @@
dest: "{{ patch_archive }}" dest: "{{ patch_archive }}"
owner: "{{ keycloak_service_user }}" owner: "{{ keycloak_service_user }}"
group: "{{ keycloak_service_group }}" group: "{{ keycloak_service_group }}"
mode: 0640 mode: '0640'
register: new_version_downloaded register: new_version_downloaded
when: when:
- not patch_archive_path.stat.exists - not patch_archive_path.stat.exists
- local_archive_path.stat is defined - local_archive_path.stat is defined
- local_archive_path.stat.exists - local_archive_path.stat.exists
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
- name: "Check installed patches" - name: "Check installed patches"
ansible.builtin.include_tasks: rhsso_cli.yml ansible.builtin.include_tasks: rhsso_cli.yml
vars: vars:
query: "patch info" cli_query: "patch info"
args: args:
apply: apply:
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}" become_user: "{{ keycloak_service_user }}"
- name: "Perform patching" - name: "Perform patching"
@@ -121,21 +121,21 @@
- name: "Apply patch {{ patch_version }} to server" - name: "Apply patch {{ patch_version }} to server"
ansible.builtin.include_tasks: rhsso_cli.yml ansible.builtin.include_tasks: rhsso_cli.yml
vars: vars:
query: "patch apply {{ patch_archive }}" cli_query: "patch apply {{ patch_archive }}"
args: args:
apply: apply:
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}" become_user: "{{ keycloak_service_user }}"
- name: "Restart server to ensure patch content is running" - name: "Restart server to ensure patch content is running"
ansible.builtin.include_tasks: rhsso_cli.yml ansible.builtin.include_tasks: rhsso_cli.yml
vars: vars:
query: "shutdown --restart" cli_query: "shutdown --restart"
when: when:
- cli_result.rc == 0 - cli_result.rc == 0
args: args:
apply: apply:
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}" become_user: "{{ keycloak_service_user }}"
- name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}" - name: "Wait until {{ keycloak.service_name }} becomes active {{ keycloak.health_url }}"
@@ -149,10 +149,10 @@
- name: "Query installed patch after restart" - name: "Query installed patch after restart"
ansible.builtin.include_tasks: rhsso_cli.yml ansible.builtin.include_tasks: rhsso_cli.yml
vars: vars:
query: "patch info" cli_query: "patch info"
args: args:
apply: apply:
become: true become: "{{ keycloak_rhsso_patch_require_privilege_escalation }}"
become_user: "{{ keycloak_service_user }}" become_user: "{{ keycloak_service_user }}"
- name: "Verify installed patch version" - name: "Verify installed patch version"

View File

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

View File

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

View File

@@ -1,23 +1,23 @@
--- ---
- name: "Configure {{ keycloak.service_name }} service script wrapper" - name: "Configure {{ keycloak.service_name }} service script wrapper"
become: true become: "{{ keycloak_systemd_require_privilege_escalation }}"
ansible.builtin.template: ansible.builtin.template:
src: keycloak-service.sh.j2 src: keycloak-service.sh.j2
dest: "{{ keycloak_dest }}/keycloak-service.sh" dest: "{{ keycloak_dest }}/keycloak-service.sh"
owner: root owner: root
group: root group: root
mode: 0755 mode: '0755'
notify: notify:
- restart keycloak - restart keycloak
- name: "Configure sysconfig file for {{ keycloak.service_name }} service" - name: "Configure sysconfig file for {{ keycloak.service_name }} service"
become: true become: "{{ keycloak_systemd_require_privilege_escalation }}"
ansible.builtin.template: ansible.builtin.template:
src: keycloak-sysconfig.j2 src: keycloak-sysconfig.j2
dest: "{{ keycloak_sysconf_file }}" dest: "{{ keycloak_sysconf_file }}"
owner: root owner: root
group: root group: root
mode: 0644 mode: '0644'
notify: notify:
- restart keycloak - restart keycloak
@@ -27,8 +27,8 @@
dest: /etc/systemd/system/keycloak.service dest: /etc/systemd/system/keycloak.service
owner: root owner: root
group: root group: root
mode: 0644 mode: '0644'
become: true become: "{{ keycloak_systemd_require_privilege_escalation }}"
register: systemdunit register: systemdunit
notify: notify:
- restart keycloak - restart keycloak

View File

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

Some files were not shown because too many files have changed in this diff Show More