Compare commits

...

385 Commits
2.0.2 ... 3.0.8

Author SHA1 Message Date
ansible-middleware-core
7369a5724c Update changelog for release 3.0.8
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2026-06-09 14:43:23 +00:00
Harsha Cherukuri
1794d4ff9b Merge pull request #348 from felix-grzelka/fix-normalize_keycloak_url
Remove normalize_keycloak_url
2026-06-05 07:40:20 -04:00
Felix Grzelka
e898a2511a rm other use of normalize_keycloak_url 2026-06-05 07:29:53 +00:00
Felix Grzelka
dd2cfaa87d remove normalize_keycloak_url 2026-06-04 13:20:03 +00:00
Harsha Cherukuri
b114c7b252 Merge pull request #345 from cihlamar/standardize-readme-gitignore-lint
Standardize README and .ansible-lint configuration
2026-06-03 12:42:23 -04:00
Martin Cihlar
9920dc93c9 Standardize README and .ansible-lint configuration
- Update role links in README to full GitHub URLs
- Add profile: production to .ansible-lint for production-level linting
2026-06-03 17:11:56 +02:00
ansible-middleware-core
5cb555d6c2 Bump version to 3.0.8 2026-06-01 14:50:07 +00:00
ansible-middleware-core
3ab0f2b259 Update changelog for release 3.0.7
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2026-06-01 14:49:51 +00:00
Harsha Cherukuri
9394e2598f Merge pull request #341 from hcherukuri/main
Migrate Keycloak modules from the community.general collection to Keycloak collection.
2026-06-01 10:23:18 -04:00
Harsha Cherukuri
123906d739 Added PRs #11841 and #11749, and updated version references from community.general to Keycloak. 2026-05-30 09:29:04 -04:00
Harsha Cherukuri
bdc090de64 Move community.general keycloak modules into keycloak collection 2026-05-28 13:14:54 -04:00
Ranabir Chakraborty
d4a92c9f4f Merge pull request #343 from RanabirChakraborty/common_module_fix
Fixing common module usage
2026-05-28 20:55:21 +05:30
Ranabir Chakraborty
926ea0192d Fixing common module usgae 2026-05-28 20:13:34 +05:30
Ranabir Chakraborty
88c825c997 Merge pull request #342 from RanabirChakraborty/JN_to_UD
Jboss Network replace with Unified Downloads
2026-05-28 18:46:17 +05:30
Ranabir Chakraborty
0e3a9e3741 Jboss Network replace with Unified Downloads 2026-05-28 18:45:36 +05:30
Harsha Cherukuri
7495385ccb Merge pull request #338 from world-direct/fix/336_downloads
fix #336: https://github.com/ansible-middleware/common/pull/38
2026-05-27 14:44:14 -04:00
Helmut Wolf
494a522ab2 fix #336: https://github.com/ansible-middleware/common/pull/38 2026-05-27 20:03:36 +02:00
Ranabir Chakraborty
64e7fa3129 Merge pull request #339 from hcherukuri/main
Fix molecule tests
2026-05-27 21:06:37 +05:30
Harsha Cherukuri
15a0c6ee46 Fix molecule tests 2026-05-27 11:10:41 -04:00
ansible-middleware-core
e4d1a79d1f Bump version to 3.0.7 2026-05-26 17:30:50 +00:00
ansible-middleware-core
f4588dbbdf Update changelog for release 3.0.6
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2026-05-26 17:30:30 +00:00
Ranabir Chakraborty
a9a771c6bc Merge pull request #337 from RanabirChakraborty/AMW-540
AMW-540 Fix the upstream collection requirements with common v1.2.4
2026-05-26 22:37:54 +05:30
Ranabir Chakraborty
f00c714798 AMW-540 Fix the upstream collection requirements with common v1.2.4 2026-05-26 21:58:05 +05:30
Harsha Cherukuri
50750ef125 Update requirements.yml 2026-05-26 12:28:04 -04:00
ansible-middleware-core
b631b07cae Bump version to 3.0.6 2026-05-20 18:44:49 +00:00
ansible-middleware-core
195e104f5e Update changelog for release 3.0.5
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2026-05-20 18:44:31 +00:00
Ranabir Chakraborty
047ddcaa92 Merge pull request #335 from RanabirChakraborty/AMW-528
AMW-528 Deployment fails in keycloak_quarkus due to missing escalation variables
2026-05-21 00:09:58 +05:30
Ranabir Chakraborty
0b2f2786dd AMW-528 Deployment fails in keycloak_quarkus due to missing escalation variables 2026-05-20 23:51:34 +05:30
ansible-middleware-core
4cc360052e Bump version to 3.0.5 2026-05-20 13:38:22 +00:00
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
ansible-middleware-core
903938ca16 Update changelog for release 2.1.2
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-04-17 15:49:00 +00:00
Guido Grazioli
74636e8629 ci: final round of linting 2024-04-17 17:29:38 +02:00
Guido Grazioli
6706fd9bf5 ci: bump and fix final linter warnings 2024-04-17 17:24:57 +02: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
Guido Grazioli
50d189ee14 ci: more linter fixes 2024-04-17 16:56:56 +02:00
Guido Grazioli
5b459f3dde ci: more linter fixes 2024-04-17 16:48:24 +02:00
Guido Grazioli
f0318b2ecf Merge pull request #192 from guidograzioli/xxx_linter_1
Comprehensive linter warning fixes
2024-04-17 16:26:18 +02:00
Guido Grazioli
1f910bd400 Comprehensive linter warning fixes 2024-04-17 16:19:34 +02:00
Guido Grazioli
d17c364257 downstream: ci sudo workaround 2024-04-17 12:14:25 +02:00
Guido Grazioli
1ff6f237a9 Bump 2.1.1 2024-04-17 11:58:11 +02:00
Guido Grazioli
0c0c4e19ea downstream: update rhbk to 2.0.10 2024-04-17 11:57:44 +02:00
Guido Grazioli
7bedb08f6e ci: update release wf params 2024-04-17 11:14:38 +02:00
Guido Grazioli
5464a01a62 ci: update doc links, test triggers 2024-04-17 11:08:04 +02:00
ansible-middleware-core
2cf3e2470d Bump version to 2.1.2 2024-04-17 08:58:56 +00:00
ansible-middleware-core
ad6021c29a Update changelog for release 2.1.1
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-04-17 08:58:43 +00:00
Guido Grazioli
05ebd90121 Merge pull request #191 from guidograzioli/190_sysconfig_worldreadable
Unrelax configuration file permissions
2024-04-17 10:51:45 +02:00
Guido Grazioli
1229a0b023 Unrelax configuration file permissions 2024-04-17 10:46:23 +02:00
Guido Grazioli
4ba9014edb Merge pull request #187 from roumano/parse_proxy_headers
Permit parse reverse proxy headers
2024-04-17 10:36:50 +02:00
Christian Iuga
ea57f8b689 remove unwanted extra code 2024-04-16 13:41:09 +02:00
Christian Iuga
3fbae4882e move keycloak_quarkus_proxy_headers into keycloak.conf 2024-04-16 13:39:33 +02:00
Christian Iuga
27717d7b4e Avoid cmd-line arguments
Fix https://github.com/ansible-middleware/keycloak/pull/187#discussion_r1565779164
2024-04-15 15:50:55 +02:00
Christian Iuga
4aa862101c Add new variable keycloak_quarkus_proxy_headers into meta/argument_specs.yml
Fix comment https://github.com/ansible-middleware/keycloak/pull/187#discussion_r1565772058
2024-04-15 15:48:02 +02:00
Christian Iuga
8e2f3eb77f Permit parse reverse proxy headers
- Via created a new optional variable : keycloak_quarkus_proxy_headers
- Fix enhancement #183
- see https://www.keycloak.org/server/reverseproxy about the official documentation
2024-04-15 14:41:56 +02:00
Guido Grazioli
10d4cb8db7 Merge pull request #186 from guidograzioli/185_java_heap_options
JVM arguments go in JAVA_OPTS
2024-04-09 17:16:17 +02:00
Guido Grazioli
8f8de33350 JVM arguments go IN JAVA_OPTS 2024-04-08 16:47:49 +02:00
Guido Grazioli
7dceb7f819 Merge pull request #184 from avskor/issue-125
Fix permissions on controller-side downloaded artifacts
2024-04-08 09:15:52 +02:00
avskor
c2e456e1d5 Fix #125. Permission error when the become variable is set to true in the playbook 2024-04-04 11:22:18 +03:00
Guido Grazioli
4421375dd5 Merge pull request #181 from guidograzioli/multi_distro_refactor
Multi distro refactor
2024-03-25 16:42:29 +01:00
Guido Grazioli
2bbf7d9cc4 revert JVM var that cannot be overridden 2024-03-25 16:30:13 +01:00
Guido Grazioli
467cfda0f7 same changes for keycloak-legacy 2024-03-25 16:00:18 +01:00
Guido Grazioli
e17505fe42 update molecule for debian container 2024-03-25 15:37:02 +01:00
Guido Grazioli
0e4df659f4 add test 2024-03-25 14:35:28 +01:00
Guido Grazioli
3400b64b10 add to ci 2024-03-25 14:34:25 +01:00
Guido Grazioli
3b1534d700 refactor 2024-03-25 10:19:28 +01:00
Guido Grazioli
dd6171f024 Add ansible_family based vars loading 2024-03-25 10:19:08 +01:00
Guido Grazioli
c1da6ea38d Merge pull request #180 from guidograzioli/keycloak_realm_default
Use `keycloak_realm` as default for sub-entities
2024-03-25 09:40:30 +01:00
Guido Grazioli
56e4a43cf9 add keycloak_realm default to sub entities 2024-03-25 09:30:25 +01:00
Guido Grazioli
7a0a99a31c Merge pull request #178 from Aeyk/ubuntu
Ubuntu compatibility
2024-03-18 09:09:07 +01:00
aeyk
fdce0bd922 Merge branch 'main' into ubuntu 2024-03-17 05:35:09 -04:00
Malik Kennedy
b9d9874a00 feat: ubuntu compatibility 2024-03-17 09:15:38 +00:00
Guido Grazioli
1cecf51f37 downstream: more updates to custom xml 2024-03-14 11:41:52 +01:00
Guido Grazioli
0cea03dfc0 downstream: simplify overridexml test 2024-03-14 10:37:09 +01:00
Guido Grazioli
0c079740e1 downstream: molecule custom xml that works with rhsso 2024-03-14 10:13:46 +01:00
Guido Grazioli
96804d8086 downstream: rhsso has new patch filename pattern 2024-03-13 17:55:30 +01:00
Guido Grazioli
a875166fe0 Merge pull request #176 from growi/templates_comment_filter
Utilize comment filter for `ansible_managed` annotations
2024-03-13 14:24:19 +01:00
Björn Großewinkelmann
a97c349f41 Utilize comment filter for {{ ansible_maanged }} annotations
Signed-off-by: Björn Großewinkelmann <bgrossew@redhat.com>
2024-03-13 00:19:42 +01:00
Romain Pelisse
a59a1fb8dd Rework Molecule prepare phase to install sudo only if root on target 2024-03-12 12:48:46 +01:00
Guido Grazioli
d74820190f ci: rename keycloak_quarkus infinispan jinja2 template 2024-02-28 17:10:02 +01:00
ansible-middleware-core
6541b5e386 Bump version to 2.1.1 2024-02-28 15:58:47 +00:00
ansible-middleware-core
1e1665adb0 Update changelog for release 2.1.0
Signed-off-by: ansible-middleware-core <ansible-middleware-core@redhat.com>
2024-02-28 15:58:33 +00:00
Guido Grazioli
33a839fec6 Merge pull request #171 from guidograzioli/170_quarkus_java_home_typo
keycloak_quarkus: fix custom JAVA_HOME parameter name
2024-02-27 19:35:31 +01:00
Guido Grazioli
d97ddbde3c add test 2024-02-27 19:27:07 +01:00
Guido Grazioli
7f021a849e Linter 2024-02-27 17:17:24 +01:00
Guido Grazioli
167bf512c5 fix typo in variable name 2024-02-27 17:17:14 +01:00
Guido Grazioli
beee25dec2 Merge pull request #169 from ansible-middleware/mol_sudo
Adapt molecule tests to work with none root user on target (sudo)
2024-02-26 18:39:42 +01:00
Romain Pelisse
5bd39a0d0e molecule: use block to skip assets download entirely if needed 2024-02-26 16:46:30 +01:00
Romain Pelisse
7324f48e8d molecule: cleanup prepare to use one play 2024-02-26 16:46:30 +01:00
Romain Pelisse
b3ca517583 molecule: adapt sudo setup to work when ansible is not connecting as root on the target 2024-02-26 16:46:26 +01:00
Guido Grazioli
b1848046dc Merge pull request #168 from Footur/update-keycloak-v23.0.7
Update Keycloak to version 23.0.7
2024-02-26 10:19:54 +01:00
Guido Grazioli
983a1fb8f2 Merge pull request #167 from guidograzioli/xa_enable_recovery
Set enable-recovery when xa transactions are enabled
2024-02-26 10:19:44 +01:00
Footur
d4fb20b230 Update Keycloak to version 23.0.7 2024-02-22 17:10:22 +01:00
Guido Grazioli
f7bef0a956 set enable-recovery when xa transactions are enabled 2024-02-22 16:28:24 +01:00
Guido Grazioli
f62a97709a Merge pull request #163 from world-direct/feature/162_keycloak_quarkus_sticky-session-encoder
keycloak_quarkus: `sticky-session`s for infinispan routes
2024-02-08 21:31:12 +01:00
Guido Grazioli
9593752e62 Merge pull request #161 from world-direct/feature/160_keycloak_quarkus_logging
keycloak_quarkus: Allow configuring log rotate options in quarkus configuration
2024-02-08 21:27:48 +01:00
Guido Grazioli
d6c29ed4fc Merge pull request #159 from world-direct/feature/inifinispan_ha
#158: Feature/inifinispan TCPPING
2024-02-08 21:24:53 +01:00
Helmut Wolf
df81dc5497 #158: move TCPPING config to ispn config file 2024-02-08 16:26:48 +01:00
Helmut Wolf
4adab64dc0 #158: support for TCPPING 2024-02-08 16:26:48 +01:00
Helmut Wolf
e0d4920a49 feature/162: keycloak_quarkus: make spi-sticky-session-encoder-infinispan-should-attach-route configurable in keycloak.conf 2024-02-08 16:19:14 +01:00
Helmut Wolf
c2009a0a12 feature/160: CR changes 2024-02-08 16:10:32 +01:00
Helmut Wolf
0c5047bcc1 feature/160: keycloak_quarkus: Allow easier log setting configuration 2024-01-22 13:53:28 +01:00
Helmut Wolf
63f83d7744 add initial support for templating cache-ispn.xml 2024-01-22 12:38:29 +01:00
Guido Grazioli
64fa8bb788 Merge pull request #157 from world-direct/fix/156_infinispan
keycloak_quarkus: renamed infinispan host list configuration
2024-01-22 08:14:36 +01:00
Helmut Wolf
688ec956fc fix #156: quarkus 3 ispn config renamings 2024-01-19 09:54:54 +01:00
ansible-middleware-core
e866d1f4e4 Bump version to 2.0.3 2024-01-17 08:50:31 +00:00
198 changed files with 25433 additions and 3516 deletions

View File

@@ -1,4 +1,6 @@
# .ansible-lint
profile: production
exclude_paths:
- .cache/
- .github/
@@ -28,14 +30,15 @@ warn_list:
- name[casing]
- fqcn[action]
- schema[meta]
- var-naming[no-role-prefix]
- key-order[task]
- blocked_modules
- run-once[task]
skip_list:
- vars_should_not_be_used
- file_is_small_enough
- file_has_valid_name
- name[template]
- var-naming[no-role-prefix]
use_default_rules: true
parseable: true

View File

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

View File

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

View File

@@ -2,20 +2,27 @@
name: Release collection
on:
workflow_dispatch:
inputs:
release_summary:
description: 'Optional release summary for changelogs'
required: false
jobs:
release:
uses: ansible-middleware/github-actions/.github/workflows/release.yml@main
with:
collection_fqcn: 'middleware_automation.keycloak'
downstream_name: 'rhbk'
release_summary: "${{ github.event.inputs.release_summary }}"
secrets:
galaxy_token: ${{ secrets.ANSIBLE_GALAXY_API_KEY }}
jira_webhook: ${{ secrets.JIRA_WEBHOOK_CREATE_VERSION }}
dispatch:
needs: release
strategy:
matrix:
repo: ['ansible-middleware/cross-dc-rhsso-demo', 'ansible-middleware/flange-demo', 'ansible-middleware/ansible-middleware-ee']
repo: ['ansible-middleware/ansible-middleware-ee']
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch

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
changelogs/.plugin-cache.yaml
*.pem
*.key
*.p12
.ansible/

View File

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

View File

@@ -1,11 +1,310 @@
============================================
middleware_automation.keycloak Release Notes
============================================
=============================================
middleware\_automation.keycloak Release Notes
=============================================
.. contents:: Topics
This changelog describes changes after version 0.2.6.
v3.0.8
======
v3.0.7
======
Major Changes
-------------
- Migrate Keycloak modules from the community.general collection to Keycloak collection. `#341 <https://github.com/ansible-middleware/keycloak/pull/341>`_
Minor Changes
-------------
- Fixing common module usage `#343 <https://github.com/ansible-middleware/keycloak/pull/343>`_
- fix #336: https://github.com/ansible-middleware/common/pull/38 `#338 <https://github.com/ansible-middleware/keycloak/pull/338>`_
Bugfixes
--------
- Fix molecule tests `#339 <https://github.com/ansible-middleware/keycloak/pull/339>`_
v3.0.6
======
Major Changes
-------------
- AMW-540 Fix the upstream collection requirements with common v1.2.4 `#337 <https://github.com/ansible-middleware/keycloak/pull/337>`_
v3.0.5
======
Minor Changes
-------------
- AMW-528 Deployment fails in keycloak_quarkus due to missing escalation variables `#335 <https://github.com/ansible-middleware/keycloak/pull/335>`_
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
======
Release Summary
---------------
Internal release, documentation or test changes only.
v2.1.1
======
Minor Changes
-------------
- Add reverse ``proxy_headers`` config, supersedes ``proxy_mode`` `#187 <https://github.com/ansible-middleware/keycloak/pull/187>`_
- Debian/Ubuntu compatibility `#178 <https://github.com/ansible-middleware/keycloak/pull/178>`_
- Use ``keycloak_realm`` as default for sub-entities `#180 <https://github.com/ansible-middleware/keycloak/pull/180>`_
Bugfixes
--------
- Fix permissions on controller-side downloaded artifacts `#184 <https://github.com/ansible-middleware/keycloak/pull/184>`_
- JVM args moved to ``JAVA_OPTS`` envvar (instead of JAVA_OPTS_APPEND) `#186 <https://github.com/ansible-middleware/keycloak/pull/186>`_
- Unrelax configuration file permissions `#191 <https://github.com/ansible-middleware/keycloak/pull/191>`_
- Utilize comment filter for ``ansible_managed`` annotations `#176 <https://github.com/ansible-middleware/keycloak/pull/176>`_
v2.1.0
======
Major Changes
-------------
- Implement infinispan TCPPING discovery protocol `#159 <https://github.com/ansible-middleware/keycloak/pull/159>`_
Minor Changes
-------------
- Set enable-recovery when xa transactions are enabled `#167 <https://github.com/ansible-middleware/keycloak/pull/167>`_
- keycloak_quarkus: Allow configuring log rotate options in quarkus configuration `#161 <https://github.com/ansible-middleware/keycloak/pull/161>`_
- keycloak_quarkus: ``sticky-session`` for infinispan routes `#163 <https://github.com/ansible-middleware/keycloak/pull/163>`_
Breaking Changes / Porting Guide
--------------------------------
- keycloak_quarkus: renamed infinispan host list configuration `#157 <https://github.com/ansible-middleware/keycloak/pull/157>`_
Bugfixes
--------
- keycloak_quarkus: fix custom JAVA_HOME parameter name `#171 <https://github.com/ansible-middleware/keycloak/pull/171>`_
v2.0.2
======
@@ -229,6 +528,11 @@ Minor Changes
v1.0.4
======
Release Summary
---------------
Internal release, documentation or test changes only.
v1.0.3
======
@@ -269,7 +573,6 @@ Release Summary
Minor enhancements, bug and documentation fixes.
Major Changes
-------------
@@ -287,4 +590,3 @@ Release Summary
---------------
This is the first stable release of the ``middleware_automation.keycloak`` collection.

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

134
README.md
View File

@@ -1,17 +1,18 @@
# Ansible Collection - middleware_automation.keycloak
<!--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 -->
<!--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).
<!--end description -->
<!--start requires_ansible-->
## 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.
<!--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:
* netaddr
* lxml
A requirement file is provided to install:
@@ -47,23 +49,61 @@ A requirement file is provided to install:
<!--start roles_paths -->
### 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`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md): 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_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`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md): role for installing legacy keycloak (<= 19.0, wildfly based).
<!--end roles_paths -->
### Included modules
All Keycloak administration modules from `community.general` are provided in this collection for Keycloak 17+ (Quarkus). Use `auth_keycloak_url` without the legacy `/auth` context path (for example `http://localhost:8080`). Set `keycloak_context` to `/auth` only when automating WildFly-based Keycloak with the `keycloak` role.
* `keycloak_authentication`: manage authentication flows and executions using Keycloak Admin REST API.
* `keycloak_authentication_flow`: manage custom authentication flows and flow executions.
* `keycloak_authentication_required_actions`: manage required actions available in realm authentication.
* `keycloak_authentication_v2`: manage authentication flows with newer Keycloak API handling.
* `keycloak_authz_authorization_scope`: manage authorization scopes for a client resource server.
* `keycloak_authz_custom_policy`: manage custom authorization policies for a client resource server.
* `keycloak_authz_permission`: manage authorization permissions for a client resource server.
* `keycloak_authz_permission_info`: retrieve authorization permission information for a client resource server.
* `keycloak_client`: manage Keycloak clients (create/update/delete).
* `keycloak_client_rolemapping`: manage client role mappings for users and groups.
* `keycloak_client_rolescope`: manage client role scope mappings.
* `keycloak_client_scope`: manage client scopes and protocol mappers (replaces `community.general.keycloak_clientscope`).
* `keycloak_clientscope_type`: manage default and optional client scope assignments.
* `keycloak_clientsecret_info`: retrieve client secret information.
* `keycloak_clientsecret_regenerate`: regenerate a client secret.
* `keycloak_clienttemplate`: manage legacy client templates.
* `keycloak_component`: manage realm components.
* `keycloak_component_info`: retrieve realm component information.
* `keycloak_group`: manage realm groups and subgroups.
* `keycloak_identity_provider`: manage identity provider instances and configuration.
* `keycloak_realm`: manage realms (create/update/delete).
* `keycloak_realm_info`: retrieve realm information.
* `keycloak_realm_key`: manage realm key providers.
* `keycloak_realm_keys_metadata_info`: retrieve realm keys metadata.
* `keycloak_realm_localization`: manage realm localization texts.
* `keycloak_realm_rolemapping`: manage realm role mappings for users and groups.
* `keycloak_role`: manage realm and client roles.
* `keycloak_user`: manage users (create/update/delete).
* `keycloak_user_execute_actions_email`: trigger execute-actions emails for users.
* `keycloak_user_federation`: manage user federation providers (for example LDAP/AD).
* `keycloak_user_rolemapping`: manage user role mappings.
* `keycloak_userprofile`: manage user profile configuration.
## Usage
The collection provides roles to install Keycloak and modules to manage realms, clients, users, and related settings via the [Keycloak Admin REST API](https://www.keycloak.org/docs-api/latest/rest-api/index.html).
### Install 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).
For Quarkus-based Keycloak (17+), set `auth_keycloak_url` to the server root URL without the legacy `/auth` path, for example `http://localhost:8080`. When using the legacy `keycloak` role with WildFly-based Keycloak, set `keycloak_context` to `/auth` in the `keycloak_realm` role.
Both playbooks include the `keycloak` role, with different settings, as described in the following sections.
### Install Keycloak
For full service configuration details, refer to the [keycloak role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md).
<!--end rhbk_playbook -->
* [`playbooks/keycloak_quarkus.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_quarkus.yml) installs Keycloak >= 17 using the `keycloak_quarkus` role.
* [`playbooks/keycloak.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak.yml) installs legacy Keycloak (<= 19) using the `keycloak` role.
For full service configuration details, refer to the [keycloak_quarkus role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md) or the [keycloak role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md).
#### Install from controller node (offline)
@@ -84,15 +124,15 @@ keycloak_offline_install: true
It is possible to perform downloads from alternate sources, using the `keycloak_download_url` variable; make sure the final downloaded filename matches with the source filename (ie. keycloak-legacy-x.y.zip or rh-sso-x.y.z-server-dist.zip).
### Example installation command
#### Example installation command
Execute the following command from the source root directory
Execute the following command from the source root directory:
```
ansible-playbook -i <ansible_hosts> -e @rhn-creds.yml playbooks/keycloak.yml -e keycloak_admin_password=<changeme>
```bash
ansible-playbook -i <ansible_hosts> playbooks/keycloak_quarkus.yml -e keycloak_quarkus_bootstrap_admin_password=<changeme>
```
- `keycloak_admin_password` Password for the administration console user account.
- `keycloak_quarkus_bootstrap_admin_password` password for the administration console user account.
- `ansible_hosts` is the inventory, below is an example inventory for deploying to localhost
```
@@ -100,18 +140,17 @@ ansible-playbook -i <ansible_hosts> -e @rhn-creds.yml playbooks/keycloak.yml -e
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.
### Configure with roles
## Configuration
### Config 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 -->
* [`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_federation.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_federation.yml) configures user federation providers.
### Example configuration command
#### Example configuration command
Execute the following command from the source root directory:
@@ -131,14 +170,55 @@ ansible-playbook -i <ansible_hosts> playbooks/keycloak_realm.yml -e keycloak_adm
For full configuration details, refer to the [keycloak_realm role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md).
<!--end rhbk_realm_readme -->
### Configure with modules
Module playbooks target an already running Keycloak instance. All modules use the `middleware_automation.keycloak` collection namespace.
* [`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 task using shared authentication defaults:
```yaml
- hosts: localhost
module_defaults:
group/middleware_automation.keycloak.keycloak:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: "{{ keycloak_admin_password }}"
tasks:
- name: Create a user in a realm
middleware_automation.keycloak.keycloak_user:
realm: TestRealm
username: testuser
first_name: Test
last_name: User
email: testuser@example.com
enabled: true
state: present
```
When migrating from `community.general`, replace the collection prefix in playbooks (for example `community.general.keycloak_user` becomes `middleware_automation.keycloak.keycloak_user`) and use `keycloak_client_scope` instead of `keycloak_clientscope`.
## Support
<!--start support -->
For bug reports and feature requests, use [GitHub Issues](https://github.com/ansible-middleware/keycloak/issues).
<!--end support -->
## Release and Upgrade Notes
For details on changes between versions, please see the [CHANGELOG](https://github.com/ansible-middleware/keycloak/blob/main/CHANGELOG.rst) for this collection.
## License
Apache License v2.0 or later
<!--start license -->
See [LICENSE](LICENSE) to view the full text.
See [LICENSE](https://github.com/ansible-middleware/keycloak/blob/main/LICENSE) to view the full text.
<!--end license -->

View File

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

View File

@@ -59,6 +59,10 @@ releases:
- 31.yaml
release_date: '2022-05-09'
1.0.4:
changes:
release_summary: 'Internal release, documentation or test changes only.
'
release_date: '2022-05-11'
1.0.5:
changes:
@@ -386,3 +390,440 @@ releases:
- 152.yaml
- 154.yaml
release_date: '2024-01-17'
2.1.0:
changes:
breaking_changes:
- 'keycloak_quarkus: renamed infinispan host list configuration `#157 <https://github.com/ansible-middleware/keycloak/pull/157>`_
'
bugfixes:
- 'keycloak_quarkus: fix custom JAVA_HOME parameter name `#171 <https://github.com/ansible-middleware/keycloak/pull/171>`_
'
major_changes:
- 'Implement infinispan TCPPING discovery protocol `#159 <https://github.com/ansible-middleware/keycloak/pull/159>`_
'
minor_changes:
- 'Set enable-recovery when xa transactions are enabled `#167 <https://github.com/ansible-middleware/keycloak/pull/167>`_
'
- 'keycloak_quarkus: Allow configuring log rotate options in quarkus configuration
`#161 <https://github.com/ansible-middleware/keycloak/pull/161>`_
'
- 'keycloak_quarkus: ``sticky-session`` for infinispan routes `#163 <https://github.com/ansible-middleware/keycloak/pull/163>`_
'
fragments:
- 157.yaml
- 159.yaml
- 161.yaml
- 163.yaml
- 167.yaml
- 171.yaml
release_date: '2024-02-28'
2.1.1:
changes:
bugfixes:
- 'Fix permissions on controller-side downloaded artifacts `#184 <https://github.com/ansible-middleware/keycloak/pull/184>`_
'
- 'JVM args moved to ``JAVA_OPTS`` envvar (instead of JAVA_OPTS_APPEND) `#186
<https://github.com/ansible-middleware/keycloak/pull/186>`_
'
- 'Unrelax configuration file permissions `#191 <https://github.com/ansible-middleware/keycloak/pull/191>`_
'
- 'Utilize comment filter for ``ansible_managed`` annotations `#176 <https://github.com/ansible-middleware/keycloak/pull/176>`_
'
minor_changes:
- 'Add reverse ``proxy_headers`` config, supersedes ``proxy_mode`` `#187 <https://github.com/ansible-middleware/keycloak/pull/187>`_
'
- 'Debian/Ubuntu compatibility `#178 <https://github.com/ansible-middleware/keycloak/pull/178>`_
'
- 'Use ``keycloak_realm`` as default for sub-entities `#180 <https://github.com/ansible-middleware/keycloak/pull/180>`_
'
fragments:
- 176.yaml
- 178.yaml
- 180.yaml
- 184.yaml
- 186.yaml
- 187.yaml
- 191.yaml
release_date: '2024-04-17'
2.1.2:
changes:
release_summary: 'Internal release, documentation or test changes only.
'
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'
3.0.5:
changes:
minor_changes:
- 'AMW-528 Deployment fails in keycloak_quarkus due to missing escalation variables
`#335 <https://github.com/ansible-middleware/keycloak/pull/335>`_
'
fragments:
- 335.yaml
release_date: '2026-05-20'
3.0.6:
changes:
major_changes:
- 'AMW-540 Fix the upstream collection requirements with common v1.2.4 `#337
<https://github.com/ansible-middleware/keycloak/pull/337>`_
'
fragments:
- 337.yaml
release_date: '2026-05-26'
3.0.7:
changes:
bugfixes:
- 'Fix molecule tests `#339 <https://github.com/ansible-middleware/keycloak/pull/339>`_
'
major_changes:
- 'Migrate Keycloak modules from the community.general collection to Keycloak
collection. `#341 <https://github.com/ansible-middleware/keycloak/pull/341>`_
'
minor_changes:
- 'Fixing common module usage `#343 <https://github.com/ansible-middleware/keycloak/pull/343>`_
'
- 'fix #336: https://github.com/ansible-middleware/common/pull/38 `#338 <https://github.com/ansible-middleware/keycloak/pull/338>`_
'
fragments:
- 338.yaml
- 339.yaml
- 341.yaml
- 343.yaml
release_date: '2026-06-01'
3.0.8:
release_date: '2026-06-09'

View File

@@ -11,21 +11,21 @@ notesdir: fragments
prelude_section_name: release_summary
prelude_section_title: Release Summary
sections:
- - major_changes
- - major_changes
- Major Changes
- - minor_changes
- - minor_changes
- Minor Changes
- - breaking_changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- - deprecated_features
- Deprecated Features
- - removed_features
- - removed_features
- Removed Features
- - security_fixes
- - security_fixes
- Security Fixes
- - bugfixes
- - bugfixes
- Bugfixes
- - known_issues
- - known_issues
- Known Issues
title: middleware_automation.keycloak
trivial_section_name: trivial

View File

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

View File

@@ -24,14 +24,15 @@
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Middleware Automation</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/infinispan/">Infinispan / Red Hat Data Grid</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/keycloak/">Keycloak / Red Hat Single Sign-On</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/wildfly/">Wildfly / Red Hat JBoss EAP</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/jws/">Tomcat / Red Hat JWS</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/amq/">ActiveMQ / Red Hat AMQ Broker</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/amq_streams/">Kafka / Red Hat AMQ Streams</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/redhat-csp-download/">Red Hat CSP Download</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/ansible_collections_jcliff/">JCliff</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/infinispan/main/">Infinispan / Red Hat Data Grid</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/keycloak/main/">Keycloak / Red Hat Single Sign-On</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/wildfly/main/">Wildfly / Red Hat JBoss EAP</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/jws/main/">Tomcat / Red Hat JWS</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/amq/main/">ActiveMQ / Red Hat AMQ Broker</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/amq_streams/main/">Kafka / Red Hat AMQ Streams</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/common/main/">Ansible Middleware utilities</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/redhat-csp-download/main/">Red Hat CSP Download</a></li>
<li class="toctree-l1"><a class="reference internal" href="https://ansible-middleware.github.io/ansible_collections_jcliff/main/">JCliff</a></li>
</ul>
</div>
</div>

View File

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

View File

@@ -10,30 +10,25 @@ Welcome to Keycloak Collection documentation
README
plugins/index
roles/index
Changelog <CHANGELOG>
.. toctree::
:maxdepth: 2
:caption: Developer documentation
testing
developing
releasing
.. toctree::
:maxdepth: 2
:caption: General
Changelog <CHANGELOG>
Developing <developing>
Testing <testing>
Releasing <releasing>
.. toctree::
:maxdepth: 2
:caption: Middleware collections
Infinispan / Red Hat Data Grid <https://ansible-middleware.github.io/infinispan/>
Keycloak / Red Hat Single Sign-On <https://ansible-middleware.github.io/keycloak/>
Wildfly / Red Hat JBoss EAP <https://ansible-middleware.github.io/wildfly/>
Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/>
ActiveMQ / Red Hat AMQ Broker <https://ansible-middleware.github.io/amq/>
Kafka / Red Hat AMQ Streams <https://ansible-middleware.github.io/amq_streams/>
Red Hat CSP Download <https://ansible-middleware.github.io/redhat-csp-download/>
JCliff <https://ansible-middleware.github.io/ansible_collections_jcliff/>
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/>
Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/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/>
Ansible Middleware utilities <https://ansible-middleware.github.io/common/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-docs
antsibull-changelog
ansible-core>=2.14.1
ansible-core>=2.16.0
ansible-pygments
sphinx-rtd-theme
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.
In order to run the molecule tests locally with python 3.9 available, after cloning the repository:
```
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.
The test scenarios are available on the source code repository each on his own subdirectory under [molecule/](https://github.com/ansible-middleware/keycloak/molecule).
## 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:
```
# setup environment
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
# setup environment as in developing
# create inventory for localhost
cat << EOF > inventory
[keycloak]

View File

@@ -1,13 +1,14 @@
---
namespace: middleware_automation
name: keycloak
version: "2.0.2"
version: "3.0.8"
readme: README.md
authors:
- Romain Pelisse <rpelisse@redhat.com>
- Guido Grazioli <ggraziol@redhat.com>
- Pavan Kumar Motaparthi <pmotapar@redhat.com>
- Helmut Wolf <hwo@world-direct.at>
- Harsha Cherukuri <hcheruku@redhat.com>
description: Install and configure a keycloak, or Red Hat Single Sign-on, service.
license_file: "LICENSE"
tags:
@@ -26,7 +27,7 @@ tags:
- middleware
- a4mw
dependencies:
"middleware_automation.common": ">=1.1.0"
"middleware_automation.common": ">=1.2.1"
"ansible.posix": ">=1.4.0"
repository: https://github.com/ansible-middleware/keycloak
documentation: https://ansible-middleware.github.io/keycloak
@@ -37,6 +38,7 @@ build_ignore:
- .github
- .ansible-lint
- .yamllint
- .DS_Store
- '*.tar.gz'
- '*.zip'
- molecule

View File

@@ -1,2 +1,46 @@
---
requires_ansible: ">=2.14.0"
requires_ansible: ">=2.16.0"
action_groups:
keycloak:
- keycloak_authentication
- keycloak_authentication_flow
- keycloak_authentication_required_actions
- keycloak_authentication_v2
- keycloak_authz_authorization_scope
- keycloak_authz_custom_policy
- keycloak_authz_permission
- keycloak_authz_permission_info
- keycloak_client
- keycloak_client_rolemapping
- keycloak_client_rolescope
- keycloak_client_scope
- keycloak_clientscope_type
- keycloak_clientscope_rolemappings
- keycloak_clientsecret_info
- keycloak_clientsecret_regenerate
- keycloak_clienttemplate
- keycloak_component
- keycloak_component_info
- keycloak_group
- keycloak_identity_provider
- keycloak_realm
- keycloak_realm_info
- keycloak_realm_key
- keycloak_realm_keys_metadata_info
- keycloak_realm_localization
- keycloak_realm_rolemapping
- keycloak_role
- keycloak_user
- keycloak_user_federation
- keycloak_user_rolemapping
- keycloak_userprofile
- keycloak_user_execute_actions_email
plugin_routing:
modules:
keycloak_clientscope:
redirect: middleware_automation.keycloak.keycloak_client_scope
deprecation:
removal_version: 5.0.0
warning_text: >-
The module has been renamed to keycloak_client_scope for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope.

View File

@@ -0,0 +1,45 @@
---
- name: Converge
hosts: all
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://instance:8080
keycloak_quarkus_log: file
keycloak_quarkus_start_dev: true
keycloak_quarkus_proxy_mode: none
roles:
- role: keycloak_quarkus
- 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:
- username: TestUser
password: password
client_roles:
- client: TestClient
role: TestRoleUser
realm: "{{ keycloak_realm }}"
- username: TestAdmin
password: password
client_roles:
- client: TestClient
role: TestRoleUser
realm: "{{ keycloak_realm }}"
- client: TestClient
role: TestRoleAdmin
realm: "{{ keycloak_realm }}"
keycloak_realm: TestRealm
keycloak_clients:
- name: TestClient
realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_client_users }}"
client_id: TestClient
attributes:
post.logout.redirect.uris: '/public/logout'

View File

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

View File

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

View File

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

View File

@@ -1,27 +1,27 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_jvm_package: java-11-openjdk-headless
keycloak_modcluster_enabled: True
keycloak_modcluster_urls:
- host: myhost1
port: 16667
- host: myhost2
port: 16668
keycloak_jboss_port_offset: 10
keycloak_log_target: /tmp/keycloak
keycloak_quarkus_show_deprecation_warnings: false
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_level: debug
keycloak_quarkus_log_target: /tmp/keycloak
keycloak_quarkus_start_dev: true
keycloak_quarkus_proxy_mode: none
keycloak_quarkus_offline_install: true
keycloak_quarkus_download_path: /tmp/keycloak/
keycloak_quarkus_java_heap_opts: "-Xms640m -Xmx640m "
roles:
- role: keycloak
tasks:
- name: Keycloak Realm Role
ansible.builtin.include_role:
name: keycloak_realm
vars:
keycloak_client_default_roles:
- TestRoleAdmin
- TestRoleUser
- role: keycloak_quarkus
- 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:
- username: TestUser
password: password
@@ -41,22 +41,8 @@
keycloak_realm: TestRealm
keycloak_clients:
- name: TestClient
roles: "{{ keycloak_client_default_roles }}"
realm: "{{ keycloak_realm }}"
public_client: "{{ keycloak_client_public }}"
web_origins: "{{ keycloak_client_web_origins }}"
users: "{{ keycloak_client_users }}"
client_id: TestClient
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:
name: docker
name: podman
platforms:
- name: instance
image: registry.access.redhat.com/ubi8/ubi-init:latest
image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true
privileged: true
command: "/usr/sbin/init"
@@ -11,6 +11,7 @@ platforms:
- "8080/tcp"
- "8443/tcp"
- "8009/tcp"
- "9000/tcp"
provisioner:
name: ansible
config_options:
@@ -23,11 +24,16 @@ provisioner:
converge: converge.yml
verify: verify.yml
inventory:
group_vars:
all:
keycloak_install_requires_become: true
host_vars:
localhost:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
env:
ANSIBLE_FORCE_COLOR: "true"
PROXY: "${PROXY}"
NO_PROXY: "${NO_PROXY}"
verifier:
name: ansible
scenario:

View File

@@ -1,20 +1,27 @@
---
- name: Prepare
hosts: all
tasks:
- name: Install sudo
ansible.builtin.yum:
name:
- sudo
- java-1.8.0-openjdk
state: present
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
vars:
sudo_pkg_name: sudo
tasks:
- name: "Run preparation common to all scenario"
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: Create controller directory for downloads
ansible.builtin.file: # noqa risky-file-permissions delegated, uses controller host user
path: /tmp/keycloak
state: directory
mode: '0750'
delegate_to: localhost
run_once: true
- name: Download keycloak archive to controller directory
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
url: https://github.com/keycloak/keycloak/releases/download/26.4.7/keycloak-26.4.7.zip
dest: /tmp/keycloak
mode: '0640'
delegate_to: localhost
run_once: true

View File

@@ -2,11 +2,9 @@
- name: Verify
hosts: all
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_jvm_package: java-11-openjdk-headless
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
keycloak_jboss_port_offset: 10
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_uri: "http://localhost:8080"
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
@@ -15,72 +13,13 @@
that:
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify we are running on requested jvm # noqa blocked_modules command-instead-of-module
ansible.builtin.shell: |
set -o pipefail
ps -ef | grep '/etc/alternatives/jre_11/' | grep -v grep
args:
executable: /bin/bash
changed_when: no
- name: Verify token api call
ansible.builtin.uri:
url: "{{ keycloak_uri }}/auth/realms/master/protocol/openid-connect/token"
url: "{{ keycloak_uri }}/realms/master/protocol/openid-connect/token"
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
register: keycloak_auth_response
until: keycloak_auth_response.status == 200
retries: 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: 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
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_admin_pass: "remembertochangeme"
keycloak_admin_password: "remembertochangeme"
keycloak_realm: TestRealm
keycloak_quarkus_host: instance
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_quarkus_hostname: https://proxy
keycloak_quarkus_log: file
keycloak_quarkus_http_enabled: True
keycloak_quarkus_http_port: 8080
keycloak_quarkus_proxy_mode: edge
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:
- role: keycloak_quarkus

View File

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

View File

@@ -1,6 +1,8 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Install sudo
ansible.builtin.dnf:
@@ -27,22 +29,25 @@
pre_tasks:
- name: Create certificate request
ansible.builtin.command: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=proxy'
args:
chdir: "{{ playbook_dir }}"
delegate_to: localhost
changed_when: false
- name: Make certificate directory
ansible.builtin.file:
path: /etc/nginx/tls
state: directory
mode: 0755
- name: Copy certificates
ansible.builtin.copy:
src: "{{ item.name }}"
dest: "{{ item.dest }}"
mode: 0444
become: true
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
loop:
- { name: 'cert.pem', dest: '/etc/nginx/tls/certificate.crt' }
- { name: 'key.pem', dest: '/etc/nginx/tls/certificate.key' }
- name: Update CA trust
ansible.builtin.command: update-ca-trust
changed_when: false
become: true
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"

View File

@@ -0,0 +1,2 @@
---
- import_playbook: ../default/converge.yml

View File

@@ -0,0 +1,51 @@
---
driver:
name: podman
platforms:
- name: instance
image: registry.access.redhat.com/ubi9/ubi-init:latest
pre_build_image: true
privileged: true
command: "/usr/sbin/init"
port_bindings:
- "8080/tcp"
- "8443/tcp"
- "8009/tcp"
- "9000/tcp"
provisioner:
name: ansible
config_options:
defaults:
interpreter_python: auto_silent
roles_path: ../../roles
ssh_connection:
pipelining: false
playbooks:
prepare: prepare.yml
converge: converge.yml
verify: verify.yml
inventory:
group_vars:
all:
keycloak_install_requires_become: true
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:
- cleanup
- destroy
- create
- prepare
- converge
- idempotence
- side_effect
- verify
- cleanup
- destroy

View File

@@ -0,0 +1,2 @@
---
- import_playbook: ../default/prepare.yml

View File

@@ -0,0 +1,657 @@
---
- name: Verify migrated Keycloak modules
hosts: all
vars:
auth_keycloak_url: http://instance:8080
auth_realm: master
auth_username: remembertochangeme
auth_password: remembertochangeme
target_realm: TestRealm
role: molecule-role
client_role: molecule-client-role
client: molecule-client
scope: molecule-scope
group: molecule-group
user: molecule-user
flow: molecule-flow
flow_v2: molecule-flow-v2
auth_copy: molecule-auth-copy
idp: molecule-idp
ephemeral_realm: molecule-realm
template: molecule-template
realm_key: molecule-aes-key
authz_scope: molecule-authz-scope
authz_permission: molecule-authz-perm
federation: molecule-federation
component: molecule-component
migrated_keycloak_modules:
- keycloak_authentication
- keycloak_authentication_flow
- keycloak_authentication_required_actions
- keycloak_authentication_v2
- keycloak_authz_authorization_scope
- keycloak_authz_custom_policy
- keycloak_authz_permission
- keycloak_authz_permission_info
- keycloak_client
- keycloak_client_rolemapping
- keycloak_client_rolescope
- keycloak_client_scope
- keycloak_clientscope_type
- keycloak_clientscope_rolemappings
- keycloak_clientsecret_info
- keycloak_clientsecret_regenerate
- keycloak_clienttemplate
- keycloak_component
- keycloak_component_info
- keycloak_group
- keycloak_identity_provider
- keycloak_realm
- keycloak_realm_info
- keycloak_realm_key
- keycloak_realm_keys_metadata_info
- keycloak_realm_localization
- keycloak_realm_rolemapping
- keycloak_role
- keycloak_user
- keycloak_user_execute_actions_email
- keycloak_user_federation
- keycloak_user_rolemapping
- keycloak_userprofile
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: Verify migrated modules are discoverable by ansible-doc # noqa command-instead-of-module
ansible.builtin.command:
cmd: "ansible-doc -t module middleware_automation.keycloak.{{ item }}"
loop: "{{ migrated_keycloak_modules }}"
delegate_to: localhost
register: docs_check
changed_when: false
- name: Ensure module docs check succeeded
ansible.builtin.assert:
that:
- docs_check is not failed
- name: Provision shared test fixtures
module_defaults:
group/middleware_automation.keycloak.keycloak:
auth_keycloak_url: "{{ auth_keycloak_url }}"
auth_realm: "{{ auth_realm }}"
auth_username: "{{ auth_username }}"
auth_password: "{{ auth_password }}"
block:
- name: Reset fixtures from a previous verify run
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
state: absent
failed_when: false
- name: keycloak_role — create realm role for module tests
middleware_automation.keycloak.keycloak_role:
realm: "{{ target_realm }}"
name: "{{ role }}"
state: present
- name: keycloak_client — create confidential client for module tests
middleware_automation.keycloak.keycloak_client:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ client }}"
enabled: true
public_client: false
standard_flow_enabled: true
client_authenticator_type: client-secret
secret: molecule-client-secret
state: present
- name: keycloak_client — enable authorization services on test client
middleware_automation.keycloak.keycloak_client:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
service_accounts_enabled: true
authorization_services_enabled: true
full_scope_allowed: false
state: present
- name: keycloak_role — create client role for rolemapping tests
middleware_automation.keycloak.keycloak_role:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ client_role }}"
state: present
- name: keycloak_client_scope — create client scope
middleware_automation.keycloak.keycloak_client_scope:
realm: "{{ target_realm }}"
name: "{{ scope }}"
state: present
- name: keycloak_group — create group
middleware_automation.keycloak.keycloak_group:
realm: "{{ target_realm }}"
name: "{{ group }}"
state: present
- name: keycloak_user — create user
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
first_name: Molecule
last_name: User
email: molecule-user@example.invalid
enabled: true
state: present
- name: keycloak_authentication_flow — create browser-style flow
middleware_automation.keycloak.keycloak_authentication_flow:
realm: "{{ target_realm }}"
alias: "{{ flow }}"
description: Molecule module test authentication flow
executions:
- provider_id: auth-cookie
requirement: REQUIRED
state: present
- name: keycloak_realm_info — query TestRealm (public endpoint, no admin auth)
middleware_automation.keycloak.keycloak_realm_info:
auth_keycloak_url: "{{ auth_keycloak_url }}"
realm: "{{ target_realm }}"
- name: Exercise migrated modules against running Keycloak
module_defaults:
group/middleware_automation.keycloak.keycloak:
auth_keycloak_url: "{{ auth_keycloak_url }}"
auth_realm: "{{ auth_realm }}"
auth_username: "{{ auth_username }}"
auth_password: "{{ auth_password }}"
block:
- name: keycloak_realm_keys_metadata_info — query realm keys
middleware_automation.keycloak.keycloak_realm_keys_metadata_info:
realm: "{{ target_realm }}"
- name: keycloak_component_info — list realm components
middleware_automation.keycloak.keycloak_component_info:
realm: "{{ target_realm }}"
- name: keycloak_realm — create ephemeral realm
middleware_automation.keycloak.keycloak_realm:
id: "{{ ephemeral_realm }}"
realm: "{{ ephemeral_realm }}"
enabled: true
state: present
- name: keycloak_realm_localization — set locale override
middleware_automation.keycloak.keycloak_realm_localization:
parent_id: "{{ target_realm }}"
locale: en
state: present
overrides:
- key: molecule.module.test
value: molecule module test
- name: keycloak_realm_key — create generated AES key component
middleware_automation.keycloak.keycloak_realm_key:
parent_id: "{{ target_realm }}"
name: "{{ realm_key }}"
provider_id: aes-generated
state: present
- name: keycloak_authentication — copy browser flow
middleware_automation.keycloak.keycloak_authentication:
realm: "{{ target_realm }}"
alias: "{{ auth_copy }}"
copyFrom: browser
state: present
- name: keycloak_authentication_v2 — manage flow with safe-swap semantics
middleware_automation.keycloak.keycloak_authentication_v2:
realm: "{{ target_realm }}"
alias: "{{ flow_v2 }}"
description: Molecule module test flow v2
authenticationExecutions:
- requirement: REQUIRED
providerId: auth-cookie
state: present
- name: keycloak_authentication_required_actions — ensure VERIFY_EMAIL is enabled
middleware_automation.keycloak.keycloak_authentication_required_actions:
realm: "{{ target_realm }}"
state: present
required_actions:
- alias: VERIFY_EMAIL
enabled: true
- name: keycloak_userprofile — set unmanaged attribute policy
middleware_automation.keycloak.keycloak_userprofile:
parent_id: "{{ target_realm }}"
state: present
config:
kc_user_profile_config:
- unmanagedAttributePolicy: ENABLED
- name: keycloak_identity_provider — create OIDC broker stub
middleware_automation.keycloak.keycloak_identity_provider:
realm: "{{ target_realm }}"
alias: "{{ idp }}"
provider_id: oidc
enabled: false
config:
authorizationUrl: http://localhost:8080/realms/master/protocol/openid-connect/auth
tokenUrl: http://localhost:8080/realms/master/protocol/openid-connect/token
clientId: molecule-idp-client
state: present
- name: keycloak_clienttemplate — create client template
middleware_automation.keycloak.keycloak_clienttemplate:
realm: "{{ target_realm }}"
name: "{{ template }}"
protocol: openid-connect
state: present
register: clienttemplate_result
failed_when:
- clienttemplate_result is failed
- "'404' not in (clienttemplate_result.msg | default(''))"
- "'Not Found' not in (clienttemplate_result.msg | default(''))"
- name: keycloak_clientscope_type — attach scope as optional on realm
middleware_automation.keycloak.keycloak_clientscope_type:
realm: "{{ target_realm }}"
optional_clientscopes:
- "{{ scope }}"
- name: keycloak_user_rolemapping — assign realm role to user
middleware_automation.keycloak.keycloak_user_rolemapping:
realm: "{{ target_realm }}"
target_username: "{{ user }}"
state: present
roles:
- name: "{{ role }}"
- name: keycloak_realm_rolemapping — assign realm role to group
middleware_automation.keycloak.keycloak_realm_rolemapping:
realm: "{{ target_realm }}"
group_name: "{{ group }}"
state: present
roles:
- name: "{{ role }}"
- name: keycloak_client_rolemapping — assign client role to group
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
group_name: "{{ group }}"
state: present
roles:
- name: "{{ client_role }}"
- name: keycloak_client_rolescope — restrict realm role on client
middleware_automation.keycloak.keycloak_client_rolescope:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
role_names:
- "{{ role }}"
state: present
- name: keycloak_clientscope_rolemappings — map client roles to clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
register: clientscope_rolemappings_result
- name: Assert clientscope role mappings were created
ansible.builtin.assert:
that:
- clientscope_rolemappings_result is changed
- clientscope_rolemappings_result.end_state | length == 1
- name: keycloak_clientscope_rolemappings — remap client role (idempotency)
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
register: clientscope_rolemappings_idempotent_result
- name: Assert clientscope role mappings are idempotent
ansible.builtin.assert:
that:
- clientscope_rolemappings_idempotent_result is not changed
- clientscope_rolemappings_idempotent_result.end_state | length == 1
- name: keycloak_clientscope_rolemappings — map realm role to clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}"
clientscope_id: "{{ scope }}"
role_names:
- "{{ role }}"
register: clientscope_realm_rolemappings_result
- name: Assert realm role was mapped to clientscope
ansible.builtin.assert:
that:
- clientscope_realm_rolemappings_result is changed
- clientscope_realm_rolemappings_result.end_state | length == 1
- name: keycloak_user — set email_verified explicitly
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
email_verified: true
state: present
register: user_email_verified_result
- name: Assert email_verified was set
ansible.builtin.assert:
that:
- user_email_verified_result is changed
- user_email_verified_result.end_state.emailVerified == true
- name: keycloak_user — leave email_verified unchanged with no_defaults
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
email_verified_behavior: no_defaults
state: present
register: user_email_verified_idempotent_result
- name: Assert email_verified is unchanged
ansible.builtin.assert:
that:
- user_email_verified_idempotent_result is not changed
- user_email_verified_idempotent_result.end_state.emailVerified == true
- name: keycloak_user — set required actions
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
required_actions:
- UPDATE_PASSWORD
- VERIFY_EMAIL
state: present
register: user_required_actions_result
- name: Assert required actions were set
ansible.builtin.assert:
that:
- user_required_actions_result is changed
- "'UPDATE_PASSWORD' in user_required_actions_result.end_state.requiredActions"
- "'VERIFY_EMAIL' in user_required_actions_result.end_state.requiredActions"
- name: keycloak_user — leave required actions unchanged when omitted
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
state: present
register: user_required_actions_idempotent_result
- name: Assert required actions are unchanged
ansible.builtin.assert:
that:
- user_required_actions_idempotent_result is not changed
- "'UPDATE_PASSWORD' in user_required_actions_idempotent_result.end_state.requiredActions"
- "'VERIFY_EMAIL' in user_required_actions_idempotent_result.end_state.requiredActions"
- name: keycloak_clientsecret_info — read client secret
middleware_automation.keycloak.keycloak_clientsecret_info:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
no_log: true
- name: keycloak_clientsecret_regenerate — rotate client secret
middleware_automation.keycloak.keycloak_clientsecret_regenerate:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
no_log: true
- name: keycloak_authz_authorization_scope — create authorization scope
middleware_automation.keycloak.keycloak_authz_authorization_scope:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ authz_scope }}"
display_name: Molecule module test scope
state: present
- name: keycloak_authz_permission — create scope permission
middleware_automation.keycloak.keycloak_authz_permission:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ authz_permission }}"
permission_type: scope
scopes:
- "{{ authz_scope }}"
state: present
- name: keycloak_authz_permission_info — query scope permission
middleware_automation.keycloak.keycloak_authz_permission_info:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ authz_permission }}"
- name: keycloak_authz_custom_policy — requires deployed policy provider on server
middleware_automation.keycloak.keycloak_authz_custom_policy:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: molecule-script-policy
policy_type: script-policy.js
state: present
register: authz_custom_policy_result
failed_when:
- authz_custom_policy_result is failed
- "'No policy provider' not in (authz_custom_policy_result.msg | default(''))"
- "'Policy provider' not in (authz_custom_policy_result.msg | default(''))"
- "'405' not in (authz_custom_policy_result.msg | default(''))"
- "'Method Not Allowed' not in (authz_custom_policy_result.msg | default(''))"
- "'not found' not in (authz_custom_policy_result.msg | default('') | lower)"
- name: keycloak_user_execute_actions_email — trigger execute-actions email
middleware_automation.keycloak.keycloak_user_execute_actions_email:
realm: "{{ target_realm }}"
username: "{{ user }}"
actions:
- VERIFY_EMAIL
register: execute_actions_email_result
failed_when:
- execute_actions_email_result is failed
- "'Connection refused' in (execute_actions_email_result.msg | default(''))"
- "'Failed to send' in (execute_actions_email_result.msg | default(''))"
- name: keycloak_user_federation — remove non-existent federation (module API test)
middleware_automation.keycloak.keycloak_user_federation:
realm: "{{ target_realm }}"
name: "{{ federation }}"
state: absent
- name: keycloak_component — remove non-existent component (module API test)
middleware_automation.keycloak.keycloak_component:
parent_id: "{{ target_realm }}"
name: "{{ component }}"
provider_id: ldap
provider_type: org.keycloak.storage.UserStorageProvider
state: absent
- name: Remove shared test fixtures
module_defaults:
group/middleware_automation.keycloak.keycloak:
auth_keycloak_url: "{{ auth_keycloak_url }}"
auth_realm: "{{ auth_realm }}"
auth_username: "{{ auth_username }}"
auth_password: "{{ auth_password }}"
block:
- name: keycloak_authz_custom_policy — remove custom policy if created
middleware_automation.keycloak.keycloak_authz_custom_policy:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: molecule-script-policy
policy_type: script-policy.js
state: absent
failed_when: false
- name: keycloak_authz_permission — remove scope permission
middleware_automation.keycloak.keycloak_authz_permission:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ authz_permission }}"
permission_type: scope
state: absent
- name: keycloak_authz_authorization_scope — remove authorization scope
middleware_automation.keycloak.keycloak_authz_authorization_scope:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ authz_scope }}"
state: absent
- name: keycloak_clientscope_rolemappings — remove realm role from clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}"
clientscope_id: "{{ scope }}"
role_names:
- "{{ role }}"
state: absent
- name: keycloak_clientscope_rolemappings — remove client role from clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
state: absent
- name: keycloak_client_rolescope — remove role scope mapping
middleware_automation.keycloak.keycloak_client_rolescope:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
role_names:
- "{{ role }}"
state: absent
- name: keycloak_client_rolemapping — remove group client role mapping
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
group_name: "{{ group }}"
state: absent
roles:
- name: "{{ client_role }}"
- name: keycloak_realm_rolemapping — remove group realm role mapping
middleware_automation.keycloak.keycloak_realm_rolemapping:
realm: "{{ target_realm }}"
group_name: "{{ group }}"
state: absent
roles:
- name: "{{ role }}"
- name: keycloak_user_rolemapping — remove user role mapping
middleware_automation.keycloak.keycloak_user_rolemapping:
realm: "{{ target_realm }}"
target_username: "{{ user }}"
state: absent
roles:
- name: "{{ role }}"
- name: keycloak_identity_provider — remove OIDC provider
middleware_automation.keycloak.keycloak_identity_provider:
realm: "{{ target_realm }}"
alias: "{{ idp }}"
state: absent
- name: keycloak_authentication_v2 — remove flow
middleware_automation.keycloak.keycloak_authentication_v2:
realm: "{{ target_realm }}"
alias: "{{ flow_v2 }}"
state: absent
- name: keycloak_authentication — remove copied flow
middleware_automation.keycloak.keycloak_authentication:
realm: "{{ target_realm }}"
alias: "{{ auth_copy }}"
state: absent
- name: keycloak_realm_key — remove generated key
middleware_automation.keycloak.keycloak_realm_key:
parent_id: "{{ target_realm }}"
name: "{{ realm_key }}"
provider_id: aes-generated
state: absent
- name: keycloak_realm_localization — remove locale override
middleware_automation.keycloak.keycloak_realm_localization:
parent_id: "{{ target_realm }}"
locale: en
state: absent
overrides:
- key: molecule.module.test
- name: keycloak_realm — remove ephemeral realm
middleware_automation.keycloak.keycloak_realm:
id: "{{ ephemeral_realm }}"
realm: "{{ ephemeral_realm }}"
state: absent
- name: keycloak_clienttemplate — remove client template
middleware_automation.keycloak.keycloak_clienttemplate:
realm: "{{ target_realm }}"
name: "{{ template }}"
state: absent
failed_when: false
- name: keycloak_authentication_flow — remove authentication flow
middleware_automation.keycloak.keycloak_authentication_flow:
realm: "{{ target_realm }}"
alias: "{{ flow }}"
state: absent
- name: keycloak_user — remove user
middleware_automation.keycloak.keycloak_user:
realm: "{{ target_realm }}"
username: "{{ user }}"
state: absent
- name: keycloak_group — remove group
middleware_automation.keycloak.keycloak_group:
realm: "{{ target_realm }}"
name: "{{ group }}"
state: absent
- name: keycloak_role — remove client role
middleware_automation.keycloak.keycloak_role:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
name: "{{ client_role }}"
state: absent
- name: keycloak_client — remove confidential client
middleware_automation.keycloak.keycloak_client:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
state: absent
- name: keycloak_client_scope — remove client scope
middleware_automation.keycloak.keycloak_client_scope:
realm: "{{ target_realm }}"
name: "{{ scope }}"
state: absent
- name: keycloak_role — remove realm role
middleware_automation.keycloak.keycloak_role:
realm: "{{ target_realm }}"
name: "{{ role }}"
state: absent

View File

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

View File

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

View File

@@ -1,6 +1,11 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
gather_facts: yes
vars:
sudo_pkg_name: sudo
tasks:
- name: "Run preparation common to all scenario"
ansible.builtin.include_tasks: ../prepare.yml

View File

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

View File

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

View File

@@ -3,33 +3,57 @@
ansible.builtin.debug:
msg: "Ansible version is {{ ansible_version.full }}"
- name: Install sudo
- name: "Set package name for sudo"
ansible.builtin.set_fact:
sudo_pkg_name: sudo
- name: "Ensure {{ sudo_pkg_name }} is installed (if user is root)."
ansible.builtin.yum:
name: "{{ sudo_pkg_name }}"
state: present
when:
- ansible_user_id == 'root'
- name: Gather the package facts
ansible.builtin.package_facts:
manager: auto
- name: "Check if sudo is installed."
ansible.builtin.assert:
that:
- sudo_pkg_name in ansible_facts.packages
fail_msg: "sudo is not installed on target system"
- name: "Install iproute"
ansible.builtin.yum:
name:
- sudo
- iproute
state: present
when:
- ansible_user_id == 'root'
- name: "Retrieve assets server from env"
ansible.builtin.set_fact:
assets_server: "{{ lookup('env','MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
assets_server: "{{ lookup('env', 'MIDDLEWARE_DOWNLOAD_RELEASE_SERVER_URL') }}"
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:
sso_offline_install: True
- name: "Download artefacts only if assets_server is set"
when:
- assets_server is defined
- assets_server | length > 0
- assets is defined
- assets | length > 0
block:
- name: "Set offline when assets server from env is defined"
ansible.builtin.set_fact:
sso_offline_install: True
- name: "Download and deploy zips from {{ assets_server }}"
- name: "Download and deploy zips from {{ assets_server }}"
ansible.builtin.get_url:
url: "{{ asset }}"
dest: "{{ lookup('env', 'PWD') }}"
validate_certs: no
mode: '0644'
delegate_to: localhost
loop: "{{ assets }}"
loop_control:
loop_var: asset
when:
- assets_server is defined
- assets_server | length > 0

View File

@@ -1,12 +0,0 @@
---
- name: Prepare
hosts: all
tasks:
- name: Install sudo
ansible.builtin.yum:
name: sudo
state: present
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"

View File

@@ -1,20 +1,70 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_admin_pass: "remembertochangeme"
keycloak_admin_password: "remembertochangeme"
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm: TestRealm
keycloak_quarkus_host: instance
keycloak_quarkus_hostname: https://instance:8443
keycloak_quarkus_log: file
keycloak_quarkus_https_key_file_enabled: True
keycloak_quarkus_key_file: "/opt/keycloak/certs/key.pem"
keycloak_quarkus_cert_file: "/opt/keycloak/certs/cert.pem"
keycloak_quarkus_log_level: debug # needed for the verify step
keycloak_quarkus_https_key_file_enabled: true
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_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:
- role: keycloak_quarkus
- 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:
- TestRoleAdmin
- TestRoleUser

View File

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

View File

@@ -1,32 +1,50 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
tasks:
- name: Install sudo
ansible.builtin.yum:
name: sudo
state: present
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"
- name: "Ensure common prepare phase are set."
ansible.builtin.include_tasks: ../prepare.yml
- name: Create certificate request
ansible.builtin.command: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=instance'
args:
chdir: "{{ playbook_dir }}"
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: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
ansible.builtin.file:
state: directory
path: "/opt/keycloak/certs/"
mode: 0755
path: "/opt/keycloak/vault"
mode: '0755'
- name: Copy certificates
- name: Make sure a jre is available (for keytool to prepare keystore)
delegate_to: localhost
ansible.builtin.package:
name: java-21-openjdk-headless
state: present
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
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 | default(true) }}"
ansible.builtin.copy:
src: "{{ item }}"
dest: "/opt/keycloak/certs/{{ item }}"
mode: 0444
loop:
- cert.pem
- key.pem
src: keystore.p12
dest: /opt/keycloak/vault/keystore.p12
mode: '0444'

View File

@@ -1,6 +1,11 @@
---
- name: Verify
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
tasks:
- name: Populate service facts
ansible.builtin.service_facts:
@@ -10,6 +15,7 @@
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:
@@ -32,15 +38,15 @@
- name: Verify endpoint URLs
ansible.builtin.assert:
that:
- (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'https://instance/realms/master/protocol/openid-connect/ext/ciba/auth'
- (openid_config.stdout | from_json)['issuer'] == 'https://instance/realms/master'
- (openid_config.stdout | from_json)['authorization_endpoint'] == 'https://instance/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)["backchannel_authentication_endpoint"] == 'https://instance:8443/realms/master/protocol/openid-connect/ext/ciba/auth'
- (openid_config.stdout | from_json)['issuer'] == 'https://instance:8443/realms/master'
- (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:8443/realms/master/protocol/openid-connect/token'
delegate_to: localhost
- name: Check log folder
ansible.builtin.stat:
path: "/tmp/keycloak"
path: /tmp/keycloak
register: keycloak_log_folder
- name: Check that keycloak log folder exists and is a link
@@ -49,10 +55,12 @@
- keycloak_log_folder.stat.exists
- not keycloak_log_folder.stat.isdir
- keycloak_log_folder.stat.islnk
fail_msg: "Service log symlink not correctly created"
- name: Check log file
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
ansible.builtin.stat:
path: "/tmp/keycloak/keycloak.log"
path: /tmp/keycloak/keycloak.log
register: keycloak_log_file
- name: Check if keycloak file exists
@@ -62,8 +70,9 @@
- not keycloak_log_file.stat.isdir
- name: Check default log folder
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
ansible.builtin.stat:
path: "/var/log/keycloak"
path: /var/log/keycloak
register: keycloak_default_log_folder
failed_when: false
@@ -71,3 +80,51 @@
ansible.builtin.assert:
that:
- not keycloak_default_log_folder.stat.exists
- name: Verify vault SPI in logfile
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
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,18 +1,26 @@
---
- name: Converge
hosts: all
vars_files:
- ../group_vars/all/vars.yml
vars:
keycloak_quarkus_admin_pass: "remembertochangeme"
keycloak_admin_password: "remembertochangeme"
keycloak_quarkus_show_deprecation_warnings: false
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
keycloak_realm: TestRealm
keycloak_quarkus_log: file
keycloak_quarkus_frontend_url: 'http://localhost:8080/'
keycloak_quarkus_hostname: 'http://localhost:8080'
keycloak_quarkus_start_dev: True
keycloak_quarkus_proxy_mode: none
keycloak_quarkus_java_home: /opt/openjdk/
keycloak_quarkus_java_heap_opts: "-Xms640m -Xmx640m"
roles:
- role: keycloak_quarkus
- 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:
- TestRoleAdmin
- TestRoleUser

View File

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

View File

@@ -0,0 +1,51 @@
---
- name: Prepare
hosts: all
vars_files:
- ../group_vars/all/vars.yml
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: Install JDK17
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
ansible.builtin.yum:
name:
- java-17-openjdk-headless
state: present
when:
- ansible_facts.os_family == 'RedHat'
- name: Link default logs directory
become: "{{ molecule_prepare_require_privilege_escalation | default(true) }}"
ansible.builtin.file:
state: link
src: "{{ item }}"
dest: /opt/openjdk
force: true
with_fileglob:
- /usr/lib/jvm/java-17-openjdk*
when:
- ansible_facts.os_family == "Debian"
- name: Link default logs directory
ansible.builtin.file:
state: link
src: /usr/lib/jvm/jre-17-openjdk
dest: /opt/openjdk
force: true
when:
- ansible_facts.os_family == "RedHat"
- name: "Display hera_home if defined."
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"

View File

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

View File

@@ -11,6 +11,14 @@
- ansible_facts.services["keycloak.service"]["state"] == "running"
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
- name: Verify we are running on requested JAVA_HOME # noqa blocked_modules command-instead-of-module
ansible.builtin.shell: |
set -o pipefail
ps -ef | grep '/opt/openjdk' | grep -v grep
args:
executable: /bin/bash
changed_when: False
- name: Set internal envvar
ansible.builtin.set_fact:
hera_home: "{{ lookup('env', 'HERA_HOME') }}"

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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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,70 @@
---
- 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_jgroups_discovery: TCPPING
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: 0.0.0.0
infinispan_users:
- { name: 'testuser', password: 'test', roles: 'observer' }
- name: Converge
hosts: keycloak
vars_files:
- ../group_vars/all/vars.yml
pre_tasks:
- name: Wait for Infinispan Hot Rod port before starting Keycloak
ansible.builtin.wait_for:
host: infinispan1
port: 11222
timeout: 120
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: 120
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_remote/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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"
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 | default(true) }}"

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:
- name: middleware_automation.common
- name: middleware_automation.jbcs
- name: middleware_automation.infinispan
- name: community.general
- name: ansible.posix
- name: community.docker
version: ">=1.9.1"
version: ">=3.8.0"
- name: containers.podman
version: ">=1.8.1"
roles:
- name: elan.simple_nginx_reverse_proxy

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@
- name: Playbook for Keycloak X Hosts in develop mode
hosts: all
vars:
keycloak_admin_password: "remembertochangeme"
keycloak_quarkus_host: localhost
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
keycloak_quarkus_hostname: http://localhost
keycloak_quarkus_port: 8080
keycloak_quarkus_log: file
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 }}"

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
# 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
class ModuleDocFragment(object):
DOCUMENTATION = r'''
options: {}
attributes:
action_group:
description: Use C(group/middleware_automation.keycloak.keycloak) in C(module_defaults) to set defaults for this module.
support: full
membership:
- middleware_automation.keycloak.keycloak
'''

View File

@@ -55,7 +55,11 @@ options:
description:
- Authentication token for Keycloak API.
type: str
version_added: 3.0.0
refresh_token:
description:
- Authentication refresh token for Keycloak API.
type: str
validate_certs:
description:
@@ -68,11 +72,9 @@ options:
- Controls the HTTP connections timeout period (in seconds) to Keycloak API.
type: int
default: 10
version_added: 4.5.0
http_agent:
description:
- Configures the HTTP User-Agent header.
type: str
default: Ansible
version_added: 5.4.0
'''

View File

@@ -0,0 +1,32 @@
# Copyright (c) Ansible Project
# 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
# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
# Do not use this from other collections or standalone plugins/modules!
from __future__ import annotations
import typing as t
def merge_settings_without_absent_nulls(
existing_settings: dict[str, t.Any], desired_settings: dict[str, t.Any]
) -> dict[str, t.Any]:
"""
Merges existing and desired settings into a new dictionary while excluding null values in desired settings that are absent in the existing settings.
This ensures idempotency by treating absent keys in existing settings and null values in desired settings as equivalent, preventing unnecessary updates.
Args:
existing_settings (dict): Dictionary representing the current settings in Keycloak
desired_settings (dict): Dictionary representing the desired settings
Returns:
dict: A new dictionary containing all entries from existing_settings and desired_settings,
excluding null values in desired_settings whose corresponding keys are not present in existing_settings
"""
existing = existing_settings or {}
desired = desired_settings or {}
return {**existing, **{k: v for k, v in desired.items() if v is not None or k in existing}}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
# Copyright (c) 2022, John Cant <a.johncant@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 annotations
import typing as t
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
keycloak_argument_spec,
)
def keycloak_clientsecret_module() -> AnsibleModule:
"""
Returns an AnsibleModule definition for modules that interact with a client
secret.
:return: argument_spec dict
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
realm=dict(default="master"),
id=dict(type="str"),
client_id=dict(type="str", aliases=["clientId"]),
)
argument_spec.update(meta_args)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=(
[
["id", "client_id"],
["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"],
]
),
required_together=([["auth_username", "auth_password"]]),
mutually_exclusive=[["token", "auth_realm"], ["token", "auth_username"], ["token", "auth_password"]],
)
return module
def keycloak_clientsecret_module_resolve_params(module: AnsibleModule, kc: KeycloakAPI) -> tuple[str, dict[str, t.Any]]:
"""
Given an AnsibleModule definition for keycloak_clientsecret_*, and a
KeycloakAPI client, resolve the params needed to interact with the Keycloak
client secret, looking up the client by clientId if necessary via an API
call.
:return: tuple of id, realm
"""
realm = module.params.get("realm")
id = module.params.get("id")
client_id = module.params.get("client_id")
# only lookup the client_id if id isn't provided.
# in the case that both are provided, prefer the ID, since it is one
# less lookup.
if id is None:
# Due to the required_one_of spec, client_id is guaranteed to not be None
client = kc.get_client_by_clientid(client_id, realm=realm)
if client is None:
module.fail_json(msg=f"Client does not exist {client_id}")
id = client["id"]
return id, realm

View File

@@ -0,0 +1,518 @@
# Copyright (c) 2019, INSPQ <philippe.gauthier@inspq.qc.ca>
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authentication
short_description: Configure authentication in Keycloak
description:
- This module actually can only make a copy of an existing authentication flow, add an execution to it and configure it.
- It can also delete the flow.
# Originally added in community.general 3.3.0
version_added: "3.0.0"
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
realm:
description:
- The name of the realm in which is the authentication.
required: true
type: str
alias:
description:
- Alias for the authentication flow.
required: true
type: str
description:
description:
- Description of the flow.
type: str
providerId:
description:
- C(providerId) for the new flow when not copied from an existing flow.
choices: ["basic-flow", "client-flow"]
type: str
copyFrom:
description:
- C(flowAlias) of the authentication flow to use for the copy.
type: str
authenticationExecutions:
description:
- Configuration structure for the executions.
type: list
elements: dict
suboptions:
providerId:
description:
- C(providerID) for the new flow when not copied from an existing flow.
type: str
displayName:
description:
- Name of the execution or subflow to create or update.
type: str
requirement:
description:
- Control status of the subflow or execution.
choices: ["REQUIRED", "ALTERNATIVE", "DISABLED", "CONDITIONAL"]
type: str
flowAlias:
description:
- Alias of parent flow.
type: str
authenticationConfig:
description:
- Describe the config of the authentication.
type: dict
index:
description:
- Priority order of the execution.
type: int
subFlowType:
description:
- For new subflows, optionally specify the type.
- Is only used at creation.
choices: ["basic-flow", "form-flow"]
default: "basic-flow"
type: str
state:
description:
- Control if the authentication flow must exists or not.
choices: ["present", "absent"]
default: present
type: str
force:
type: bool
default: false
description:
- If V(true), allows to remove the authentication flow and recreate it.
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Philippe Gauthier (@elfelip)
- Gaëtan Daubresse (@Gaetan2907)
"""
EXAMPLES = r"""
- name: Create an authentication flow from first broker login and add an execution to it.
middleware_automation.keycloak.keycloak_authentication:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: master
alias: "Copy of first broker login"
copyFrom: "first broker login"
authenticationExecutions:
- providerId: "test-execution1"
requirement: "REQUIRED"
authenticationConfig:
alias: "test.execution1.property"
config:
test1.property: "value"
- providerId: "test-execution2"
requirement: "REQUIRED"
authenticationConfig:
alias: "test.execution2.property"
config:
test2.property: "value"
state: present
- name: Re-create the authentication flow
middleware_automation.keycloak.keycloak_authentication:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: master
alias: "Copy of first broker login"
copyFrom: "first broker login"
authenticationExecutions:
- providerId: "test-provisioning"
requirement: "REQUIRED"
authenticationConfig:
alias: "test.provisioning.property"
config:
test.provisioning.property: "value"
state: present
force: true
- name: Create an authentication flow with subflow containing an execution.
middleware_automation.keycloak.keycloak_authentication:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: master
alias: "Copy of first broker login"
copyFrom: "first broker login"
authenticationExecutions:
- providerId: "test-execution1"
requirement: "REQUIRED"
- displayName: "New Subflow"
requirement: "REQUIRED"
- providerId: "auth-cookie"
requirement: "REQUIRED"
flowAlias: "New Sublow"
state: present
- name: Remove authentication.
middleware_automation.keycloak.keycloak_authentication:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: master
alias: "Copy of first broker login"
state: absent
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
end_state:
description: Representation of the authentication after module execution.
returned: on success
type: dict
sample:
{
"alias": "Copy of first broker login",
"authenticationExecutions": [
{
"alias": "review profile config",
"authenticationConfig": {
"alias": "review profile config",
"config": {
"update.profile.on.first.login": "missing"
},
"id": "6f09e4fb-aad4-496a-b873-7fa9779df6d7"
},
"configurable": true,
"displayName": "Review Profile",
"id": "8f77dab8-2008-416f-989e-88b09ccf0b4c",
"index": 0,
"level": 0,
"providerId": "idp-review-profile",
"requirement": "REQUIRED",
"requirementChoices": [
"REQUIRED",
"ALTERNATIVE",
"DISABLED"
]
}
],
"builtIn": false,
"description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"id": "bc228863-5887-4297-b898-4d988f8eaa5c",
"providerId": "basic-flow",
"topLevel": true
}
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
is_struct_included,
keycloak_argument_spec,
)
def find_exec_in_executions(searched_exec, executions):
"""
Search if exec is contained in the executions.
:param searched_exec: Execution to search for.
:param executions: List of executions.
:return: Index of the execution, -1 if not found..
"""
for i, existing_exec in enumerate(executions, start=0):
if (
"providerId" in existing_exec
and "providerId" in searched_exec
and existing_exec["providerId"] == searched_exec["providerId"]
or "displayName" in existing_exec
and "displayName" in searched_exec
and existing_exec["displayName"] == searched_exec["displayName"]
):
return i
return -1
def create_or_update_executions(kc, config, realm="master"):
"""
Create or update executions for an authentication flow.
:param kc: Keycloak API access.
:param config: Representation of the authentication flow including its executions.
:param realm: Realm
:return: tuple (changed, dict(before, after)
WHERE
bool changed indicates if changes have been made
dict(str, str) shows state before and after creation/update
"""
try:
changed = False
after = ""
before = ""
execution = None
if config.get("authenticationExecutions") is not None:
# Get existing executions on the Keycloak server for this alias
existing_executions = kc.get_executions_representation(config, realm=realm)
for new_exec_index, new_exec in enumerate(config["authenticationExecutions"], start=0):
if new_exec["index"] is not None:
new_exec_index = new_exec["index"]
exec_found = False
# Get flowalias parent if given
if new_exec["flowAlias"] is not None:
flow_alias_parent = new_exec["flowAlias"]
else:
flow_alias_parent = config["alias"]
# Check if same providerId or displayName name between existing and new execution
exec_index = find_exec_in_executions(new_exec, existing_executions)
if exec_index != -1:
# Remove key that doesn't need to be compared with existing_exec
exclude_key = ["flowAlias", "subFlowType"]
for key in new_exec:
if new_exec[key] is None:
exclude_key.append(key)
# Compare the executions to see if it need changes
if (
not is_struct_included(new_exec, existing_executions[exec_index], exclude_key)
or exec_index != new_exec_index
):
exec_found = True
if new_exec["index"] is None:
new_exec_index = exec_index
before += f"{existing_executions[exec_index]}\n"
execution = existing_executions[exec_index].copy()
# Remove exec from list in case 2 exec with same name
existing_executions[exec_index].clear()
elif new_exec["providerId"] is not None:
kc.create_execution(new_exec, flowAlias=flow_alias_parent, realm=realm)
execution = kc.get_executions_representation(config, realm=realm)[exec_index]
exec_found = True
exec_index = new_exec_index
after += f"{new_exec}\n"
elif new_exec["displayName"] is not None:
kc.create_subflow(
new_exec["displayName"], flow_alias_parent, realm=realm, flowType=new_exec["subFlowType"]
)
execution = kc.get_executions_representation(config, realm=realm)[exec_index]
exec_found = True
exec_index = new_exec_index
after += f"{new_exec}\n"
if exec_found:
changed = True
if exec_index != -1:
# Update the existing execution
updated_exec = {"id": execution["id"]}
# add the execution configuration
if new_exec["authenticationConfig"] is not None:
if "authenticationConfig" in execution and "id" in execution["authenticationConfig"]:
kc.delete_authentication_config(execution["authenticationConfig"]["id"], realm=realm)
kc.add_authenticationConfig_to_execution(
updated_exec["id"], new_exec["authenticationConfig"], realm=realm
)
for key in new_exec:
# remove unwanted key for the next API call
if key not in ("flowAlias", "authenticationConfig", "subFlowType"):
updated_exec[key] = new_exec[key]
if new_exec["requirement"] is not None:
if "priority" in execution:
updated_exec["priority"] = execution["priority"]
kc.update_authentication_executions(flow_alias_parent, updated_exec, realm=realm)
diff = exec_index - new_exec_index
kc.change_execution_priority(updated_exec["id"], diff, realm=realm)
after += f"{kc.get_executions_representation(config, realm=realm)[new_exec_index]}\n"
return changed, dict(before=before, after=after)
except Exception as e:
kc.module.fail_json(
msg=f"Could not create or update executions for authentication flow {config['alias']} in realm {realm}: {e}"
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
realm=dict(type="str", required=True),
alias=dict(type="str", required=True),
providerId=dict(type="str", choices=["basic-flow", "client-flow"]),
description=dict(type="str"),
copyFrom=dict(type="str"),
authenticationExecutions=dict(
type="list",
elements="dict",
options=dict(
providerId=dict(type="str"),
displayName=dict(type="str"),
requirement=dict(choices=["REQUIRED", "ALTERNATIVE", "DISABLED", "CONDITIONAL"], type="str"),
flowAlias=dict(type="str"),
authenticationConfig=dict(type="dict"),
index=dict(type="int"),
subFlowType=dict(choices=["basic-flow", "form-flow"], default="basic-flow", type="str"),
),
),
state=dict(choices=["absent", "present"], default="present"),
force=dict(type="bool", default=False),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
result = dict(changed=False, msg="", flow={})
# 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")
force = module.params.get("force")
new_auth_repr = {
"alias": module.params.get("alias"),
"copyFrom": module.params.get("copyFrom"),
"providerId": module.params.get("providerId"),
"authenticationExecutions": module.params.get("authenticationExecutions"),
"description": module.params.get("description"),
"builtIn": module.params.get("builtIn"),
"subflow": module.params.get("subflow"),
}
auth_repr = kc.get_authentication_flow_by_alias(alias=new_auth_repr["alias"], realm=realm)
# Cater for when it doesn't exist (an empty dict)
if not auth_repr:
if state == "absent":
# Do nothing and exit
if module._diff:
result["diff"] = dict(before="", after="")
result["changed"] = False
result["end_state"] = {}
result["msg"] = f"{new_auth_repr['alias']} absent"
module.exit_json(**result)
elif state == "present":
# Process a creation
result["changed"] = True
if module._diff:
result["diff"] = dict(before="", after=new_auth_repr)
if module.check_mode:
module.exit_json(**result)
# If copyFrom is defined, create authentication flow from a copy
if "copyFrom" in new_auth_repr and new_auth_repr["copyFrom"] is not None:
auth_repr = kc.copy_auth_flow(config=new_auth_repr, realm=realm)
else: # Create an empty authentication flow
auth_repr = kc.create_empty_auth_flow(config=new_auth_repr, realm=realm)
# If the authentication still not exist on the server, raise an exception.
if auth_repr is None:
result["msg"] = f"Authentication just created not found: {new_auth_repr}"
module.fail_json(**result)
# Configure the executions for the flow
create_or_update_executions(kc=kc, config=new_auth_repr, realm=realm)
# Get executions created
exec_repr = kc.get_executions_representation(config=new_auth_repr, realm=realm)
if exec_repr is not None:
auth_repr["authenticationExecutions"] = exec_repr
result["end_state"] = auth_repr
else:
if state == "present":
# Process an update
if force: # If force option is true
# Delete the actual authentication flow
result["changed"] = True
if module._diff:
result["diff"] = dict(before=auth_repr, after=new_auth_repr)
if module.check_mode:
module.exit_json(**result)
kc.delete_authentication_flow_by_id(id=auth_repr["id"], realm=realm)
# If copyFrom is defined, create authentication flow from a copy
if "copyFrom" in new_auth_repr and new_auth_repr["copyFrom"] is not None:
auth_repr = kc.copy_auth_flow(config=new_auth_repr, realm=realm)
else: # Create an empty authentication flow
auth_repr = kc.create_empty_auth_flow(config=new_auth_repr, realm=realm)
# If the authentication still not exist on the server, raise an exception.
if auth_repr is None:
result["msg"] = f"Authentication just created not found: {new_auth_repr}"
module.fail_json(**result)
# Configure the executions for the flow
if module.check_mode:
module.exit_json(**result)
changed, diff = create_or_update_executions(kc=kc, config=new_auth_repr, realm=realm)
result["changed"] |= changed
if module._diff:
result["diff"] = diff
# Get executions created
exec_repr = kc.get_executions_representation(config=new_auth_repr, realm=realm)
if exec_repr is not None:
auth_repr["authenticationExecutions"] = exec_repr
result["end_state"] = auth_repr
else:
# Process a deletion (because state was not 'present')
result["changed"] = True
if module._diff:
result["diff"] = dict(before=auth_repr, after="")
if module.check_mode:
module.exit_json(**result)
# delete it
kc.delete_authentication_flow_by_id(id=auth_repr["id"], realm=realm)
result["msg"] = f"Authentication flow: {new_auth_repr['alias']} id: {auth_repr['id']} is deleted"
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,299 @@
#!/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
version_added: "3.0.0"
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.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Paulo Menon (@paulomenon)
'''
EXAMPLES = '''
- name: Create an authentication flow with executions
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-browser-flow
description: "Custom browser flow"
provider_id: basic-flow
executions:
- provider_id: auth-cookie
requirement: ALTERNATIVE
- provider_id: auth-password
requirement: REQUIRED
- provider_id: auth-otp-form
requirement: ALTERNATIVE
state: present
delegate_to: localhost
- name: Create an authentication flow by copying an existing one
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-copy-of-browser
copy_from: browser
state: present
delegate_to: localhost
- name: Create a flow using token authentication
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
token: MY_TOKEN
realm: TestRealm
alias: my-flow
state: present
delegate_to: localhost
- name: Delete an authentication flow
middleware_automation.keycloak.keycloak_authentication_flow:
auth_keycloak_url: http://localhost:8080
auth_realm: master
auth_username: admin
auth_password: password
realm: TestRealm
alias: my-browser-flow
state: absent
delegate_to: localhost
'''
RETURN = '''
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Authentication flow my-browser-flow has been created"
end_state:
description: Representation of the authentication flow after module execution.
returned: on success
type: dict
sample: {
"id": "uuid-here",
"alias": "my-browser-flow",
"providerId": "basic-flow",
"topLevel": true,
"builtIn": false
}
'''
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \
keycloak_argument_spec, get_token, KeycloakError
from ansible.module_utils.basic import AnsibleModule
def main():
argument_spec = keycloak_argument_spec()
execution_spec = dict(
provider_id=dict(type='str', required=True, aliases=['providerId']),
requirement=dict(type='str', required=True, choices=['REQUIRED', 'ALTERNATIVE', 'DISABLED', 'CONDITIONAL']),
)
meta_args = dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
alias=dict(type='str', required=True),
description=dict(type='str', default=''),
realm=dict(type='str', default='master'),
provider_id=dict(type='str', default='basic-flow', aliases=['providerId']),
copy_from=dict(type='str', aliases=['copyFrom']),
executions=dict(type='list', default=[], options=execution_spec, elements='dict'),
)
argument_spec.update(meta_args)
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]),
required_together=([['auth_realm', 'auth_username', 'auth_password']]),
mutually_exclusive=[['copy_from', 'executions']])
result = dict(changed=False, msg='', diff={}, end_state={})
try:
connection_header = get_token(module.params)
except KeycloakError as e:
module.fail_json(msg=str(e))
kc = KeycloakAPI(module, connection_header)
realm = module.params.get('realm')
alias = module.params.get('alias')
state = module.params.get('state')
description = module.params.get('description')
provider_id = module.params.get('provider_id')
copy_from = module.params.get('copy_from')
executions = module.params.get('executions')
before_flow = kc.get_authentication_flow_by_alias(alias, realm=realm)
flow_exists = bool(before_flow)
if state == 'absent':
if flow_exists:
result['changed'] = True
if module._diff:
result['diff'] = dict(before=before_flow, after='')
if module.check_mode:
module.exit_json(**result)
kc.delete_authentication_flow_by_id(before_flow['id'], realm=realm)
result['msg'] = "Authentication flow {alias} has been deleted".format(alias=alias)
else:
result['msg'] = "Authentication flow {alias} does not exist, doing nothing".format(alias=alias)
result['end_state'] = {}
module.exit_json(**result)
if flow_exists:
result['changed'] = False
result['end_state'] = before_flow
result['msg'] = "Authentication flow {alias} already exists".format(alias=alias)
module.exit_json(**result)
result['changed'] = True
flow_config = {
'alias': alias,
'description': description,
'providerId': provider_id,
}
if module._diff:
result['diff'] = dict(before='', after=flow_config)
if module.check_mode:
module.exit_json(**result)
if copy_from:
flow_config['copyFrom'] = copy_from
after_flow = kc.copy_auth_flow(flow_config, realm=realm)
result['msg'] = "Authentication flow {alias} has been created (copied from {src})".format(alias=alias, src=copy_from)
else:
after_flow = kc.create_empty_auth_flow(flow_config, realm=realm)
if executions:
for execution in executions:
exec_rep = {
'providerId': execution['provider_id'],
'requirement': execution['requirement'],
}
kc.create_execution(exec_rep, alias, realm=realm)
result['msg'] = "Authentication flow {alias} has been created".format(alias=alias)
after_flow = kc.get_authentication_flow_by_alias(alias, realm=realm)
result['end_state'] = after_flow
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,463 @@
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authentication_required_actions
short_description: Allows administration of Keycloak authentication required actions
description:
- This module can register, update and delete required actions.
- It also filters out any duplicate required actions by their alias. The first occurrence is preserved.
# Originally added in community.general 7.1.0
version_added: "3.0.0"
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
realm:
description:
- The name of the realm in which are the authentication required actions.
required: true
type: str
required_actions:
elements: dict
description:
- Authentication required action.
suboptions:
alias:
description:
- Unique name of the required action.
required: true
type: str
config:
description:
- Configuration for the required action.
type: dict
defaultAction:
description:
- Indicates whether new users have the required action assigned to them.
type: bool
enabled:
description:
- Indicates, if the required action is enabled or not.
type: bool
name:
description:
- Displayed name of the required action. Required for registration.
type: str
priority:
description:
- Priority of the required action.
type: int
providerId:
description:
- Provider ID of the required action. Required for registration.
type: str
type: list
state:
choices: ["absent", "present"]
description:
- Control if the realm authentication required actions are going to be registered/updated (V(present)) or deleted (V(absent)).
required: true
type: str
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Skrekulko (@Skrekulko)
"""
EXAMPLES = r"""
- name: Register a new required action.
middleware_automation.keycloak.keycloak_authentication_required_actions:
auth_client_id: "admin-cli"
auth_keycloak_url: "http://localhost:8080"
auth_password: "password"
auth_realm: "master"
auth_username: "admin"
realm: "master"
required_actions:
- alias: "TERMS_AND_CONDITIONS"
name: "Terms and conditions"
providerId: "TERMS_AND_CONDITIONS"
enabled: true
state: "present"
- name: Update the newly registered required action.
middleware_automation.keycloak.keycloak_authentication_required_actions:
auth_client_id: "admin-cli"
auth_keycloak_url: "http://localhost:8080"
auth_password: "password"
auth_realm: "master"
auth_username: "admin"
realm: "master"
required_actions:
- alias: "TERMS_AND_CONDITIONS"
enabled: false
state: "present"
- name: Delete the updated registered required action.
middleware_automation.keycloak.keycloak_authentication_required_actions:
auth_client_id: "admin-cli"
auth_keycloak_url: "http://localhost:8080"
auth_password: "password"
auth_realm: "master"
auth_username: "admin"
realm: "master"
required_actions:
- alias: "TERMS_AND_CONDITIONS"
state: "absent"
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
end_state:
description: Representation of the authentication required actions after module execution.
returned: on success
type: complex
contains:
alias:
description:
- Unique name of the required action.
sample: test-provider-id
type: str
config:
description:
- Configuration for the required action.
sample: {}
type: dict
defaultAction:
description:
- Indicates whether new users have the required action assigned to them.
sample: false
type: bool
enabled:
description:
- Indicates, if the required action is enabled or not.
sample: false
type: bool
name:
description:
- Displayed name of the required action. Required for registration.
sample: Test provider ID
type: str
priority:
description:
- Priority of the required action.
sample: 90
type: int
providerId:
description:
- Provider ID of the required action. Required for registration.
sample: test-provider-id
type: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def sanitize_required_actions(objects):
for obj in objects:
alias = obj["alias"]
name = obj["name"]
provider_id = obj["providerId"]
if not name:
obj["name"] = alias
if provider_id != alias:
obj["providerId"] = alias
return objects
def filter_duplicates(objects):
filtered_objects = {}
for obj in objects:
alias = obj["alias"]
if alias not in filtered_objects:
filtered_objects[alias] = obj
return list(filtered_objects.values())
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
realm=dict(type="str", required=True),
required_actions=dict(
type="list",
elements="dict",
options=dict(
alias=dict(type="str", required=True),
config=dict(type="dict"),
defaultAction=dict(type="bool"),
enabled=dict(type="bool"),
name=dict(type="str"),
priority=dict(type="int"),
providerId=dict(type="str"),
),
),
state=dict(type="str", choices=["present", "absent"], required=True),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
result = dict(changed=False, msg="", end_state={}, diff=dict(before={}, after={}))
# 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)
# Convenience variables
realm = module.params.get("realm")
desired_required_actions = module.params.get("required_actions")
state = module.params.get("state")
# Sanitize required actions
desired_required_actions = sanitize_required_actions(desired_required_actions)
# Filter out duplicate required actions
desired_required_actions = filter_duplicates(desired_required_actions)
# Get required actions
before_required_actions = kc.get_required_actions(realm=realm)
if state == "present":
# Initialize empty lists to hold the required actions that need to be
# registered, updated, and original ones of the updated one
register_required_actions = []
before_updated_required_actions = []
updated_required_actions = []
# Loop through the desired required actions and check if they exist in the before required actions
for desired_required_action in desired_required_actions:
found = False
# Loop through the before required actions and check if the aliases match
for before_required_action in before_required_actions:
if desired_required_action["alias"] == before_required_action["alias"]:
update_required = False
# Fill in the parameters
for k, v in before_required_action.items():
if k not in desired_required_action or desired_required_action[k] is None:
desired_required_action[k] = v
# Loop through the keys of the desired and before required actions
# and check if there are any differences between them
for key in desired_required_action.keys():
if (
key in before_required_action
and desired_required_action[key] != before_required_action[key]
):
update_required = True
break
# If there are differences, add the before and desired required actions
# to their respective lists for updating
if update_required:
before_updated_required_actions.append(before_required_action)
updated_required_actions.append(desired_required_action)
found = True
break
# If the desired required action is not found in the before required actions,
# add it to the list of required actions to register
if not found:
# Check if name is provided
if "name" not in desired_required_action or desired_required_action["name"] is None:
module.fail_json(
msg=f"Unable to register required action {desired_required_action['alias']} in realm {realm}: name not included"
)
# Check if provider ID is provided
if "providerId" not in desired_required_action or desired_required_action["providerId"] is None:
module.fail_json(
msg=f"Unable to register required action {desired_required_action['alias']} in realm {realm}: providerId not included"
)
register_required_actions.append(desired_required_action)
# Handle diff
if module._diff:
diff_required_actions = updated_required_actions.copy()
diff_required_actions.extend(register_required_actions)
result["diff"] = dict(before=before_updated_required_actions, after=diff_required_actions)
# Handle changed
if register_required_actions or updated_required_actions:
result["changed"] = True
# Handle check mode
if module.check_mode:
if register_required_actions or updated_required_actions:
result["change"] = True
result["msg"] = "Required actions would be registered/updated"
else:
result["change"] = False
result["msg"] = "Required actions would not be registered/updated"
module.exit_json(**result)
# Register required actions
if register_required_actions:
for register_required_action in register_required_actions:
kc.register_required_action(realm=realm, rep=register_required_action)
kc.update_required_action(
alias=register_required_action["alias"], realm=realm, rep=register_required_action
)
# Update required actions
if updated_required_actions:
for updated_required_action in updated_required_actions:
kc.update_required_action(
alias=updated_required_action["alias"], realm=realm, rep=updated_required_action
)
# Initialize the final list of required actions
final_required_actions = []
# Iterate over the before_required_actions
for before_required_action in before_required_actions:
# Check if there is an updated_required_action with the same alias
updated_required_action_found = False
for updated_required_action in updated_required_actions:
if updated_required_action["alias"] == before_required_action["alias"]:
# Merge the two dictionaries, favoring the values from updated_required_action
merged_dict = {}
for key in before_required_action.keys():
if key in updated_required_action:
merged_dict[key] = updated_required_action[key]
else:
merged_dict[key] = before_required_action[key]
for key in updated_required_action.keys():
if key not in before_required_action:
merged_dict[key] = updated_required_action[key]
# Add the merged dictionary to the final list of required actions
final_required_actions.append(merged_dict)
# Mark the updated_required_action as found
updated_required_action_found = True
# Stop looking for updated_required_action
break
# If no matching updated_required_action was found, add the before_required_action to the final list of required actions
if not updated_required_action_found:
final_required_actions.append(before_required_action)
# Append any remaining updated_required_actions that were not merged
for updated_required_action in updated_required_actions:
if not any(updated_required_action["alias"] == action["alias"] for action in final_required_actions):
final_required_actions.append(updated_required_action)
# Append newly registered required actions
final_required_actions.extend(register_required_actions)
# Handle message and end state
result["msg"] = "Required actions registered/updated"
result["end_state"] = final_required_actions
else:
# Filter out the deleted required actions
final_required_actions = []
delete_required_actions = []
for before_required_action in before_required_actions:
delete_action = False
for desired_required_action in desired_required_actions:
if before_required_action["alias"] == desired_required_action["alias"]:
delete_action = True
break
if not delete_action:
final_required_actions.append(before_required_action)
else:
delete_required_actions.append(before_required_action)
# Handle diff
if module._diff:
result["diff"] = dict(before=before_required_actions, after=final_required_actions)
# Handle changed
if delete_required_actions:
result["changed"] = True
# Handle check mode
if module.check_mode:
if final_required_actions:
result["change"] = True
result["msg"] = "Required actions would be deleted"
else:
result["change"] = False
result["msg"] = "Required actions would not be deleted"
module.exit_json(**result)
# Delete required actions
if delete_required_actions:
for delete_required_action in delete_required_actions:
kc.delete_required_action(alias=delete_required_action["alias"], realm=realm)
# Handle message and end state
result["msg"] = "Required actions deleted"
result["end_state"] = final_required_actions
module.exit_json(**result)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authz_authorization_scope
short_description: Allows administration of Keycloak client authorization scopes using Keycloak API
# Originally added in community.general 6.6.0
version_added: "3.0.0"
description:
- This module allows the administration of Keycloak client Authorization Scopes using the Keycloak REST API. Authorization
Scopes are only available if a client has Authorization enabled.
- This module requires access to the REST API using 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 options used by Keycloak. The Authorization Services
paths and payloads have not officially been documented by the Keycloak project.
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/).
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
state:
description:
- State of the authorization scope.
- On V(present), the authorization scope is created (or updated if it exists already).
- On V(absent), the authorization scope is removed if it exists.
choices: ['present', 'absent']
default: 'present'
type: str
name:
description:
- Name of the authorization scope to create.
type: str
required: true
display_name:
description:
- The display name of the authorization scope.
type: str
icon_uri:
description:
- The icon URI for the authorization scope.
type: str
client_id:
description:
- The C(clientId) of the Keycloak client that should have the authorization scope.
- This is usually a human-readable name of the Keycloak client.
type: str
required: true
realm:
description:
- The name of the Keycloak realm the Keycloak client is in.
type: str
required: true
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Samuli Seppänen (@mattock)
"""
EXAMPLES = r"""
- name: Manage Keycloak file:delete authorization scope
keycloak_authz_authorization_scope:
name: file:delete
state: present
display_name: File delete
client_id: myclient
realm: myrealm
auth_keycloak_url: http://localhost:8080
auth_username: keycloak
auth_password: keycloak
auth_realm: master
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
end_state:
description: Representation of the authorization scope after module execution.
returned: on success
type: complex
contains:
id:
description: ID of the authorization scope.
type: str
returned: when O(state=present)
sample: a6ab1cf2-1001-40ec-9f39-48f23b6a0a41
name:
description: Name of the authorization scope.
type: str
returned: when O(state=present)
sample: file:delete
display_name:
description: Display name of the authorization scope.
type: str
returned: when O(state=present)
sample: File delete
icon_uri:
description: Icon URI for the authorization scope.
type: str
returned: when O(state=present)
sample: http://localhost/icon.png
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
name=dict(type="str", required=True),
display_name=dict(type="str"),
icon_uri=dict(type="str"),
client_id=dict(type="str", required=True),
realm=dict(type="str", required=True),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
result = dict(changed=False, msg="", end_state={}, diff=dict(before={}, after={}))
# 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)
# Convenience variables
state = module.params.get("state")
name = module.params.get("name")
display_name = module.params.get("display_name")
icon_uri = module.params.get("icon_uri")
client_id = module.params.get("client_id")
realm = module.params.get("realm")
# Get the "id" of the client based on the usually more human-readable
# "clientId"
cid = kc.get_client_id(client_id, realm=realm)
if not cid:
module.fail_json(msg=f"Invalid client {client_id} for realm {realm}")
# Get current state of the Authorization Scope using its name as the search
# filter. This returns False if it is not found.
before_authz_scope = kc.get_authz_authorization_scope_by_name(name=name, client_id=cid, realm=realm)
# Generate a JSON payload for Keycloak Admin API. This is needed for
# "create" and "update" operations.
desired_authz_scope = {}
desired_authz_scope["name"] = name
desired_authz_scope["displayName"] = display_name
desired_authz_scope["iconUri"] = icon_uri
# Add "id" to payload for modify operations
if before_authz_scope:
desired_authz_scope["id"] = before_authz_scope["id"]
# Ensure that undefined (null) optional parameters are presented as empty
# strings in the desired state. This makes comparisons with current state
# much easier.
for k, v in desired_authz_scope.items():
if not v:
desired_authz_scope[k] = ""
# Do the above for the current state
if before_authz_scope:
for k in ["displayName", "iconUri"]:
if k not in before_authz_scope:
before_authz_scope[k] = ""
if before_authz_scope and state == "present":
changes = False
for k, v in desired_authz_scope.items():
if before_authz_scope[k] != v:
changes = True
# At this point we know we have to update the object anyways,
# so there's no need to do more work.
break
if changes:
if module._diff:
result["diff"] = dict(before=before_authz_scope, after=desired_authz_scope)
if module.check_mode:
result["changed"] = True
result["msg"] = "Authorization scope would be updated"
module.exit_json(**result)
else:
kc.update_authz_authorization_scope(
payload=desired_authz_scope, id=before_authz_scope["id"], client_id=cid, realm=realm
)
result["changed"] = True
result["msg"] = "Authorization scope updated"
else:
result["changed"] = False
result["msg"] = "Authorization scope not updated"
result["end_state"] = desired_authz_scope
elif not before_authz_scope and state == "present":
if module._diff:
result["diff"] = dict(before={}, after=desired_authz_scope)
if module.check_mode:
result["changed"] = True
result["msg"] = "Authorization scope would be created"
module.exit_json(**result)
else:
kc.create_authz_authorization_scope(payload=desired_authz_scope, client_id=cid, realm=realm)
result["changed"] = True
result["msg"] = "Authorization scope created"
result["end_state"] = desired_authz_scope
elif before_authz_scope and state == "absent":
if module._diff:
result["diff"] = dict(before=before_authz_scope, after={})
if module.check_mode:
result["changed"] = True
result["msg"] = "Authorization scope would be removed"
module.exit_json(**result)
else:
kc.remove_authz_authorization_scope(id=before_authz_scope["id"], client_id=cid, realm=realm)
result["changed"] = True
result["msg"] = "Authorization scope removed"
elif not before_authz_scope and state == "absent":
result["changed"] = False
else:
module.fail_json(
msg=f"Unable to determine what to do with authorization scope {name} of client {client_id} in realm {realm}"
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,213 @@
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authz_custom_policy
short_description: Allows administration of Keycloak client custom Javascript policies using Keycloak API
# Originally added in community.general 7.5.0
version_added: "3.0.0"
description:
- This module allows the administration of Keycloak client custom Javascript using the Keycloak REST API. Custom Javascript
policies are only available if a client has Authorization enabled and if they have been deployed to the Keycloak server
as JAR files.
- This module requires access to the REST API using 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 options used by Keycloak. The Authorization Services
paths and payloads have not officially been documented by the Keycloak project.
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/).
attributes:
check_mode:
support: full
diff_mode:
support: none
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
state:
description:
- State of the custom policy.
- On V(present), the custom policy is created (or updated if it exists already).
- On V(absent), the custom policy is removed if it exists.
choices: ['present', 'absent']
default: 'present'
type: str
name:
description:
- Name of the custom policy to create.
type: str
required: true
policy_type:
description:
- The type of the policy. This must match the name of the custom policy deployed to the server.
- Multiple policies pointing to the same policy type can be created, but their names have to differ.
type: str
required: true
client_id:
description:
- The V(clientId) of the Keycloak client that should have the custom policy attached to it.
- This is usually a human-readable name of the Keycloak client.
type: str
required: true
realm:
description:
- The name of the Keycloak realm the Keycloak client is in.
type: str
required: true
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Samuli Seppänen (@mattock)
"""
EXAMPLES = r"""
- name: Manage Keycloak custom authorization policy
middleware_automation.keycloak.keycloak_authz_custom_policy:
name: OnlyOwner
state: present
policy_type: script-policy.js
client_id: myclient
realm: myrealm
auth_keycloak_url: http://localhost:8080
auth_username: keycloak
auth_password: keycloak
auth_realm: master
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
end_state:
description: Representation of the custom policy after module execution.
returned: on success
type: dict
contains:
name:
description: Name of the custom policy.
type: str
returned: when I(state=present)
sample: file:delete
policy_type:
description: Type of custom policy.
type: str
returned: when I(state=present)
sample: File delete
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
name=dict(type="str", required=True),
policy_type=dict(type="str", required=True),
client_id=dict(type="str", required=True),
realm=dict(type="str", required=True),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
result = dict(changed=False, msg="", 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)
# Convenience variables
state = module.params.get("state")
name = module.params.get("name")
policy_type = module.params.get("policy_type")
client_id = module.params.get("client_id")
realm = module.params.get("realm")
cid = kc.get_client_id(client_id, realm=realm)
if not cid:
module.fail_json(msg=f"Invalid client {client_id} for realm {realm}")
before_authz_custom_policy = kc.get_authz_policy_by_name(name=name, client_id=cid, realm=realm)
desired_authz_custom_policy = {}
desired_authz_custom_policy["name"] = name
desired_authz_custom_policy["type"] = policy_type
# Modifying existing custom policies is not possible
if before_authz_custom_policy and state == "present":
result["msg"] = f"Custom policy {name} already exists"
result["changed"] = False
result["end_state"] = desired_authz_custom_policy
elif not before_authz_custom_policy and state == "present":
if module.check_mode:
result["msg"] = f"Would create custom policy {name}"
else:
kc.create_authz_custom_policy(
payload=desired_authz_custom_policy, policy_type=policy_type, client_id=cid, realm=realm
)
result["msg"] = f"Custom policy {name} created"
result["changed"] = True
result["end_state"] = desired_authz_custom_policy
elif before_authz_custom_policy and state == "absent":
if module.check_mode:
result["msg"] = f"Would remove custom policy {name}"
else:
kc.remove_authz_custom_policy(policy_id=before_authz_custom_policy["id"], client_id=cid, realm=realm)
result["msg"] = f"Custom policy {name} removed"
result["changed"] = True
result["end_state"] = {}
elif not before_authz_custom_policy and state == "absent":
result["msg"] = f"Custom policy {name} does not exist"
result["changed"] = False
result["end_state"] = {}
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,443 @@
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authz_permission
# Originally added in community.general 7.2.0
version_added: "3.0.0"
short_description: Allows administration of Keycloak client authorization permissions using Keycloak API
description:
- This module allows the administration of Keycloak client authorization permissions using the Keycloak REST API. Authorization
permissions are only available if a client has Authorization enabled.
- There are some peculiarities in JSON paths and payloads for authorization permissions. In particular POST and PUT operations
are targeted at permission endpoints, whereas GET requests go to policies endpoint. To make matters more interesting the
JSON responses from GET requests return data in a different format than what is expected for POST and PUT. The end result
is that it is not possible to detect changes to things like policies, scopes or resources - at least not without a large
number of additional API calls. Therefore this module always updates authorization permissions instead of attempting to
determine if changes are truly needed.
- This module requires access to the REST API using 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 options used by Keycloak. The Authorization Services
paths and payloads have not officially been documented by the Keycloak project.
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/).
attributes:
check_mode:
support: full
diff_mode:
support: none
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
state:
description:
- State of the authorization permission.
- On V(present), the authorization permission is created (or updated if it exists already).
- On V(absent), the authorization permission is removed if it exists.
choices: ['present', 'absent']
default: 'present'
type: str
name:
description:
- Name of the authorization permission to create.
type: str
required: true
description:
description:
- The description of the authorization permission.
type: str
permission_type:
description:
- The type of authorization permission.
- On V(scope) create a scope-based permission.
- On V(resource) create a resource-based permission.
type: str
required: true
choices:
- resource
- scope
decision_strategy:
description:
- The decision strategy to use with this permission.
type: str
default: UNANIMOUS
choices:
- UNANIMOUS
- AFFIRMATIVE
- CONSENSUS
resources:
description:
- Resource names to attach to this permission.
- Scope-based permissions can only include one resource.
- Resource-based permissions can include multiple resources.
type: list
elements: str
default: []
scopes:
description:
- Scope names to attach to this permission.
- Resource-based permissions cannot have scopes attached to them.
type: list
elements: str
default: []
policies:
description:
- Policy names to attach to this permission.
type: list
elements: str
default: []
client_id:
description:
- The clientId of the keycloak client that should have the authorization scope.
- This is usually a human-readable name of the Keycloak client.
type: str
required: true
realm:
description:
- The name of the Keycloak realm the Keycloak client is in.
type: str
required: true
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Samuli Seppänen (@mattock)
"""
EXAMPLES = r"""
- name: Manage scope-based Keycloak authorization permission
middleware_automation.keycloak.keycloak_authz_permission:
name: ScopePermission
state: present
description: Scope permission
permission_type: scope
scopes:
- file:delete
policies:
- Default Policy
client_id: myclient
realm: myrealm
auth_keycloak_url: http://localhost:8080
auth_username: keycloak
auth_password: keycloak
auth_realm: master
- name: Manage resource-based Keycloak authorization permission
middleware_automation.keycloak.keycloak_authz_permission:
name: ResourcePermission
state: present
description: Resource permission
permission_type: resource
resources:
- Default Resource
policies:
- Default Policy
client_id: myclient
realm: myrealm
auth_keycloak_url: http://localhost:8080
auth_username: keycloak
auth_password: keycloak
auth_realm: master
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
end_state:
description: Representation of the authorization permission after module execution.
returned: on success
type: complex
contains:
id:
description: ID of the authorization permission.
type: str
returned: when O(state=present)
sample: 9da05cd2-b273-4354-bbd8-0c133918a454
name:
description: Name of the authorization permission.
type: str
returned: when O(state=present)
sample: ResourcePermission
description:
description: Description of the authorization permission.
type: str
returned: when O(state=present)
sample: Resource Permission
type:
description: Type of the authorization permission.
type: str
returned: when O(state=present)
sample: resource
decisionStrategy:
description: The decision strategy to use.
type: str
returned: when O(state=present)
sample: UNANIMOUS
logic:
description: The logic used for the permission (part of the payload, but has a fixed value).
type: str
returned: when O(state=present)
sample: POSITIVE
resources:
description: IDs of resources attached to this permission.
type: list
returned: when O(state=present)
sample:
- 49e052ff-100d-4b79-a9dd-52669ed3c11d
scopes:
description: IDs of scopes attached to this permission.
type: list
returned: when O(state=present)
sample:
- 9da05cd2-b273-4354-bbd8-0c133918a454
policies:
description: IDs of policies attached to this permission.
type: list
returned: when O(state=present)
sample:
- 9da05cd2-b273-4354-bbd8-0c133918a454
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
name=dict(type="str", required=True),
description=dict(type="str"),
permission_type=dict(type="str", choices=["scope", "resource"], required=True),
decision_strategy=dict(type="str", default="UNANIMOUS", choices=["UNANIMOUS", "AFFIRMATIVE", "CONSENSUS"]),
resources=dict(type="list", elements="str", default=[]),
scopes=dict(type="list", elements="str", default=[]),
policies=dict(type="list", elements="str", default=[]),
client_id=dict(type="str", required=True),
realm=dict(type="str", required=True),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
# Convenience variables
state = module.params.get("state")
name = module.params.get("name")
description = module.params.get("description")
permission_type = module.params.get("permission_type")
decision_strategy = module.params.get("decision_strategy")
realm = module.params.get("realm")
client_id = module.params.get("client_id")
realm = module.params.get("realm")
resources = module.params.get("resources")
scopes = module.params.get("scopes")
policies = module.params.get("policies")
if permission_type == "scope" and state == "present":
if scopes == []:
module.fail_json(msg="Scopes need to defined when permission type is set to scope!")
if len(resources) > 1:
module.fail_json(msg="Only one resource can be defined for a scope permission!")
if permission_type == "resource" and state == "present":
if resources == []:
module.fail_json(msg="A resource need to defined when permission type is set to resource!")
if scopes != []:
module.fail_json(msg="Scopes cannot be defined when permission type is set to resource!")
result = dict(changed=False, msg="", 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)
# Get id of the client based on client_id
cid = kc.get_client_id(client_id, realm=realm)
if not cid:
module.fail_json(msg=f"Invalid client {client_id} for realm {realm}")
# Get current state of the permission using its name as the search
# filter. This returns False if it is not found.
permission = kc.get_authz_permission_by_name(name=name, client_id=cid, realm=realm)
# Generate a JSON payload for Keycloak Admin API. This is needed for
# "create" and "update" operations.
payload = {}
payload["name"] = name
payload["description"] = description
payload["type"] = permission_type
payload["decisionStrategy"] = decision_strategy
payload["logic"] = "POSITIVE"
payload["scopes"] = []
payload["resources"] = []
payload["policies"] = []
if permission_type == "scope":
# Add the resource id, if any, to the payload. While the data type is a
# list, it is only possible to have one entry in it based on what Keycloak
# Admin Console does.
r = False
resource_scopes = []
if resources:
r = kc.get_authz_resource_by_name(resources[0], cid, realm)
if not r:
module.fail_json(
msg=f"Unable to find authorization resource with name {resources[0]} for client {cid} in realm {realm}"
)
else:
payload["resources"].append(r["_id"])
for rs in r["scopes"]:
resource_scopes.append(rs["id"])
# Generate a list of scope ids based on scope names. Fail if the
# defined resource does not include all those scopes.
for scope in scopes:
s = kc.get_authz_authorization_scope_by_name(scope, cid, realm)
if r and s["id"] not in resource_scopes:
module.fail_json(
msg=f"Resource {resources[0]} does not include scope {scope} for client {client_id} in realm {realm}"
)
else:
payload["scopes"].append(s["id"])
elif permission_type == "resource":
if resources:
for resource in resources:
r = kc.get_authz_resource_by_name(resource, cid, realm)
if not r:
module.fail_json(
msg=f"Unable to find authorization resource with name {resource} for client {cid} in realm {realm}"
)
else:
payload["resources"].append(r["_id"])
# Add policy ids, if any, to the payload.
if policies:
for policy in policies:
p = kc.get_authz_policy_by_name(policy, cid, realm)
if p:
payload["policies"].append(p["id"])
else:
module.fail_json(
msg=f"Unable to find authorization policy with name {policy} for client {client_id} in realm {realm}"
)
# Add "id" to payload for update operations
if permission:
payload["id"] = permission["id"]
# Handle the special case where the user attempts to change an already
# existing permission's type - something that can't be done without a
# full delete -> (re)create cycle.
if permission["type"] != payload["type"]:
module.fail_json(
msg=(
f"Modifying the type of permission (scope/resource) is not supported: "
f"permission {permission['id']} of client {cid} in realm {realm} unchanged"
)
)
# Updating an authorization permission is tricky for several reasons.
# Firstly, the current permission is retrieved using a _policy_ endpoint,
# not from a permission endpoint. Also, the data that is returned is in a
# different format than what is expected by the payload. So, comparing the
# current state attribute by attribute to the payload is not possible. For
# example the data contains a JSON object "config" which may contain the
# authorization type, but which is no required in the payload. Moreover,
# information about resources, scopes and policies is _not_ present in the
# data. So, there is no way to determine if any of those fields have
# changed. Therefore the best options we have are
#
# a) Always apply the payload without checking the current state
# b) Refuse to make any changes to any settings (only support create and delete)
#
# The approach taken here is a).
#
if permission and state == "present":
if module.check_mode:
result["msg"] = "Notice: unable to check current resources, scopes and policies for permission. \
Would apply desired state without checking the current state."
else:
kc.update_authz_permission(
payload=payload, permission_type=permission_type, id=permission["id"], client_id=cid, realm=realm
)
result["msg"] = "Notice: unable to check current resources, scopes and policies for permission. \
Applying desired state without checking the current state."
# Assume that something changed, although we don't know if that is the case.
result["changed"] = True
result["end_state"] = payload
elif not permission and state == "present":
if module.check_mode:
result["msg"] = "Would create permission"
else:
kc.create_authz_permission(payload=payload, permission_type=permission_type, client_id=cid, realm=realm)
result["msg"] = "Permission created"
result["changed"] = True
result["end_state"] = payload
elif permission and state == "absent":
if module.check_mode:
result["msg"] = "Would remove permission"
else:
kc.remove_authz_permission(id=permission["id"], client_id=cid, realm=realm)
result["msg"] = "Permission removed"
result["changed"] = True
elif not permission and state == "absent":
result["changed"] = False
else:
module.fail_json(
msg=f"Unable to determine what to do with permission {name} of client {client_id} in realm {realm}"
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,178 @@
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_authz_permission_info
# Originally added in community.general 7.2.0
version_added: "3.0.0"
short_description: Query Keycloak client authorization permissions information
description:
- This module allows querying information about Keycloak client authorization permissions from the resources endpoint using
the Keycloak REST API. Authorization permissions are only available if a client has Authorization enabled.
- This module requires access to the REST API using 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 options used by Keycloak. The Authorization Services
paths and payloads have not officially been documented by the Keycloak project.
U(https://www.puppeteers.net/blog/keycloak-authorization-services-rest-api-paths-and-payload/).
attributes:
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
name:
description:
- Name of the authorization permission to create.
type: str
required: true
client_id:
description:
- The clientId of the keycloak client that should have the authorization scope.
- This is usually a human-readable name of the Keycloak client.
type: str
required: true
realm:
description:
- The name of the Keycloak realm the Keycloak client is in.
type: str
required: true
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
- middleware_automation.keycloak.attributes.info_module
author:
- Samuli Seppänen (@mattock)
"""
EXAMPLES = r"""
- name: Query Keycloak authorization permission
middleware_automation.keycloak.keycloak_authz_permission_info:
name: ScopePermission
client_id: myclient
realm: myrealm
auth_keycloak_url: http://localhost:8080
auth_username: keycloak
auth_password: keycloak
auth_realm: master
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
queried_state:
description: State of the resource (a policy) as seen by Keycloak.
returned: on success
type: complex
contains:
id:
description: ID of the authorization permission.
type: str
sample: 9da05cd2-b273-4354-bbd8-0c133918a454
name:
description: Name of the authorization permission.
type: str
sample: ResourcePermission
description:
description: Description of the authorization permission.
type: str
sample: Resource Permission
type:
description: Type of the authorization permission.
type: str
sample: resource
decisionStrategy:
description: The decision strategy.
type: str
sample: UNANIMOUS
logic:
description: The logic used for the permission (part of the payload, but has a fixed value).
type: str
sample: POSITIVE
config:
description: Configuration of the permission (empty in all observed cases).
type: dict
sample: {}
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
name=dict(type="str", required=True),
client_id=dict(type="str", required=True),
realm=dict(type="str", required=True),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
# Convenience variables
name = module.params.get("name")
client_id = module.params.get("client_id")
realm = module.params.get("realm")
result = dict(changed=False, msg="", queried_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)
# Get id of the client based on client_id
cid = kc.get_client_id(client_id, realm=realm)
if not cid:
module.fail_json(msg=f"Invalid client {client_id} for realm {realm}")
# Get current state of the permission using its name as the search
# filter. This returns False if it is not found.
permission = kc.get_authz_permission_by_name(name=name, client_id=cid, realm=realm)
result["queried_state"] = permission
module.exit_json(**result)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,414 @@
# Copyright (c) Ansible project
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_client_rolemapping
short_description: Allows administration of Keycloak client_rolemapping with the Keycloak API
# Originally added in community.general 3.5.0
version_added: "3.0.0"
description:
- This module allows you to add, remove or modify Keycloak client_rolemapping with the Keycloak REST API. It requires access
to the REST API using 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.
- 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/latest/rest-api/index.html).
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way
by this module. You may pass single values for attributes when calling the module, and this is translated into a list
suitable for the API.
- When updating a client_rolemapping, where possible provide the role ID to the module. This removes a lookup to the API
to translate the name into the role ID.
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
state:
description:
- State of the client_rolemapping.
- On V(present), the client_rolemapping is created if it does not yet exist, or updated with the parameters
you provide.
- On V(absent), the client_rolemapping is removed if it exists.
default: 'present'
type: str
choices:
- present
- absent
realm:
type: str
description:
- They Keycloak realm under which this role_representation resides.
default: 'master'
group_name:
type: str
description:
- Name of the group to be mapped.
- This parameter is required (can be replaced by gid for less API call).
parents:
type: list
description:
- List of parent groups for the group to handle sorted top to bottom.
- Set this if your group is a subgroup and you do not provide the GID in O(gid).
elements: dict
suboptions:
id:
type: str
description:
- Identify parent by ID.
- Needs less API calls than using O(parents[].name).
- A deep parent chain can be started at any point when first given parent is given as ID.
- Note that in principle both ID and name can be specified at the same time but current implementation only always
use just one of them, with ID being preferred.
name:
type: str
description:
- Identify parent by name.
- Needs more internal API calls than using O(parents[].id) to map names to ID's under the hood.
- When giving a parent chain with only names it must be complete up to the top.
- Note that in principle both ID and name can be specified at the same time but current implementation only always
use just one of them, with ID being preferred.
gid:
type: str
description:
- ID of the group to be mapped.
- This parameter is not required for updating or deleting the rolemapping but providing it reduces the number of API
calls required.
client_id:
type: str
description:
- Name of the client to be mapped (different than O(cid)).
- This parameter is required (can be replaced by cid for less API call).
cid:
type: str
description:
- ID of the client to be mapped.
- This parameter is not required for updating or deleting the rolemapping but providing it reduces the number of API
calls required.
roles:
description:
- Roles to be mapped to the group.
type: list
elements: dict
suboptions:
name:
type: str
description:
- Name of the role_representation.
- This parameter is required only when creating or updating the role_representation.
id:
type: str
description:
- The unique identifier for this role_representation.
- This parameter is not required for updating or deleting a role_representation but providing it reduces the number
of API calls required.
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Gaëtan Daubresse (@Gaetan2907)
"""
EXAMPLES = r"""
- name: Map a client role to a group, authentication with credentials
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: MyCustomRealm
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
state: present
client_id: client1
group_name: group1
roles:
- name: role_name1
id: role_id1
- name: role_name2
id: role_id2
delegate_to: localhost
- name: Map a client role to a group, authentication with token
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: MyCustomRealm
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
token: TOKEN
state: present
client_id: client1
group_name: group1
roles:
- name: role_name1
id: role_id1
- name: role_name2
id: role_id2
delegate_to: localhost
- name: Map a client role to a subgroup, authentication with token
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: MyCustomRealm
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
token: TOKEN
state: present
client_id: client1
group_name: subgroup1
parents:
- name: parent-group
roles:
- name: role_name1
id: role_id1
- name: role_name2
id: role_id2
delegate_to: localhost
- name: Unmap client role from a group
middleware_automation.keycloak.keycloak_client_rolemapping:
realm: MyCustomRealm
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
state: absent
client_id: client1
group_name: group1
roles:
- name: role_name1
id: role_id1
- name: role_name2
id: role_id2
delegate_to: localhost
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Role role1 assigned to group group1."
proposed:
description: Representation of proposed client role mapping.
returned: always
type: dict
sample: {"clientId": "test"}
existing:
description:
- Representation of existing client role mapping.
- The 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 client role mapping after module execution.
- The sample is truncated.
returned: on success
type: dict
sample:
{
"adminUrl": "http://www.example.com/admin_url",
"attributes": {
"request.object.signature.alg": "RS256"
}
}
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
roles_spec = dict(
name=dict(type="str"),
id=dict(type="str"),
)
meta_args = dict(
state=dict(default="present", choices=["present", "absent"]),
realm=dict(default="master"),
gid=dict(type="str"),
group_name=dict(type="str"),
parents=dict(
type="list",
elements="dict",
options=dict(id=dict(type="str"), name=dict(type="str")),
),
cid=dict(type="str"),
client_id=dict(type="str"),
roles=dict(type="list", elements="dict", options=roles_spec),
)
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", "auth_client_id", "auth_client_secret"]]
),
required_together=([["auth_username", "auth_password"]]),
required_by={"refresh_token": "auth_realm"},
)
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")
cid = module.params.get("cid")
client_id = module.params.get("client_id")
gid = module.params.get("gid")
group_name = module.params.get("group_name")
roles = module.params.get("roles")
parents = module.params.get("parents")
# Check the parameters
if cid is None and client_id is None:
module.fail_json(msg="Either the `client_id` or `cid` has to be specified.")
if gid is None and group_name is None:
module.fail_json(msg="Either the `group_name` or `gid` has to be specified.")
# Get the potential missing parameters
if gid is None:
group_rep = kc.get_group_by_name(group_name, realm=realm, parents=parents)
if group_rep is not None:
gid = group_rep["id"]
else:
module.fail_json(msg=f"Could not fetch group {group_name}:")
if cid is None:
cid = kc.get_client_id(client_id, realm=realm)
if cid is None:
module.fail_json(msg=f"Could not fetch client {client_id}:")
if roles is None:
module.exit_json(msg="Nothing to do (no roles specified).")
else:
for role in roles:
if role["name"] is None and role["id"] is None:
module.fail_json(msg="Either the `name` or `id` has to be specified on each role.")
# Fetch missing role_id
if role["id"] is None:
role_id = kc.get_client_role_id_by_name(cid, role["name"], realm=realm)
if role_id is not None:
role["id"] = role_id
else:
module.fail_json(msg=f"Could not fetch role {role['name']}:")
# Fetch missing role_name
else:
role["name"] = kc.get_client_group_rolemapping_by_id(gid, cid, role["id"], realm=realm)["name"]
if role["name"] is None:
module.fail_json(msg=f"Could not fetch role {role['id']}")
# Get effective client-level role mappings
available_roles_before = kc.get_client_group_available_rolemappings(gid, cid, realm=realm)
assigned_roles_before = kc.get_client_group_composite_rolemappings(gid, cid, realm=realm)
result["existing"] = assigned_roles_before
result["proposed"] = list(assigned_roles_before) if assigned_roles_before else []
update_roles = []
for role in roles:
# Fetch roles to assign if state present
if state == "present":
for available_role in available_roles_before:
if role["name"] == available_role["name"]:
update_roles.append(
{
"id": role["id"],
"name": role["name"],
}
)
result["proposed"].append(available_role)
# Fetch roles to remove if state absent
else:
for assigned_role in assigned_roles_before:
if role["name"] == assigned_role["name"]:
update_roles.append(
{
"id": role["id"],
"name": role["name"],
}
)
if assigned_role in result["proposed"]: # Handle double removal
result["proposed"].remove(assigned_role)
if len(update_roles):
if state == "present":
# Assign roles
result["changed"] = True
if module._diff:
result["diff"] = dict(before=assigned_roles_before, after=result["proposed"])
if module.check_mode:
module.exit_json(**result)
kc.add_group_rolemapping(gid, cid, update_roles, realm=realm)
result["msg"] = f"Roles {update_roles} assigned to group {group_name}."
assigned_roles_after = kc.get_client_group_composite_rolemappings(gid, cid, realm=realm)
result["end_state"] = assigned_roles_after
module.exit_json(**result)
else:
# Remove mapping of role
result["changed"] = True
if module._diff:
result["diff"] = dict(before=assigned_roles_before, after=result["proposed"])
if module.check_mode:
module.exit_json(**result)
kc.delete_group_rolemapping(gid, cid, update_roles, realm=realm)
result["msg"] = f"Roles {update_roles} removed from group {group_name}."
assigned_roles_after = kc.get_client_group_composite_rolemappings(gid, cid, realm=realm)
result["end_state"] = assigned_roles_after
module.exit_json(**result)
# Do nothing
else:
result["changed"] = False
result["msg"] = (
f"Nothing to do, roles {roles} are {'mapped' if state == 'present' else 'not mapped'} with group {group_name}."
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,287 @@
# Copyright (c) Ansible project
# 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 annotations
DOCUMENTATION = r"""
module: keycloak_client_rolescope
short_description: Allows administration of Keycloak client roles scope to restrict the usage of certain roles to a other
specific client applications
# Originally added in community.general 8.6.0
version_added: "3.0.0"
description:
- This module allows you to add or remove Keycloak roles from clients scope using the Keycloak REST API. It requires access
to the REST API using 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.
- Client O(client_id) must have O(middleware_automation.keycloak.keycloak_client#module:full_scope_allowed) set to V(false).
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way
by this module. You may pass single values for attributes when calling the module, and this is translated into a list
suitable for the API.
attributes:
check_mode:
support: full
diff_mode:
support: full
action_group:
# Originally added in community.general 10.2.0
version_added: "3.0.0"
options:
state:
description:
- State of the role mapping.
- On V(present), all roles in O(role_names) are mapped if not exist yet.
- On V(absent), all roles mapping in O(role_names) are removed if it exists.
default: 'present'
type: str
choices:
- present
- absent
realm:
type: str
description:
- The Keycloak realm under which clients resides.
default: 'master'
client_id:
type: str
required: true
description:
- Roles provided in O(role_names) while be added to this client scope.
client_scope_id:
type: str
description:
- If the O(role_names) are client role, the client ID under which it resides.
- If this parameter is absent, the roles are considered a realm role.
role_names:
required: true
type: list
elements: str
description:
- Names of roles to manipulate.
- If O(client_scope_id) is present, all roles must be under this client.
- If O(client_scope_id) is absent, all roles must be under the realm.
extends_documentation_fragment:
- middleware_automation.keycloak.keycloak
- middleware_automation.keycloak.actiongroup_keycloak
- middleware_automation.keycloak.attributes
author:
- Andre Desrosiers (@desand01)
"""
EXAMPLES = r"""
- name: Add roles to public client scope
middleware_automation.keycloak.keycloak_client_rolescope:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
client_id: frontend-client-public
client_scope_id: backend-client-private
role_names:
- backend-role-admin
- backend-role-user
- name: Remove roles from public client scope
middleware_automation.keycloak.keycloak_client_rolescope:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
client_id: frontend-client-public
client_scope_id: backend-client-private
role_names:
- backend-role-admin
state: absent
- name: Add realm roles to public client scope
middleware_automation.keycloak.keycloak_client_rolescope:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
client_id: frontend-client-public
role_names:
- realm-role-admin
- realm-role-user
"""
RETURN = r"""
msg:
description: Message as to what action was taken.
returned: always
type: str
sample: "Client role scope for frontend-client-public has been updated"
end_state:
description: Representation of role role scope after module execution.
returned: on success
type: list
elements: dict
sample:
[
{
"clientRole": false,
"composite": false,
"containerId": "MyCustomRealm",
"id": "47293104-59a6-46f0-b460-2e9e3c9c424c",
"name": "backend-role-admin"
},
{
"clientRole": false,
"composite": false,
"containerId": "MyCustomRealm",
"id": "39c62a6d-542c-4715-92d2-41021eb33967",
"name": "backend-role-user"
}
]
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
KeycloakAPI,
KeycloakError,
get_token,
keycloak_argument_spec,
)
def main():
"""
Module execution
:return:
"""
argument_spec = keycloak_argument_spec()
meta_args = dict(
client_id=dict(type="str", required=True),
client_scope_id=dict(type="str"),
realm=dict(type="str", default="master"),
role_names=dict(type="list", elements="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
argument_spec.update(meta_args)
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
result = dict(changed=False, msg="", diff={}, 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")
clientid = module.params.get("client_id")
client_scope_id = module.params.get("client_scope_id")
role_names = module.params.get("role_names")
state = module.params.get("state")
objRealm = kc.get_realm_by_id(realm)
if not objRealm:
module.fail_json(msg=f"Failed to retrive realm '{realm}'")
objClient = kc.get_client_by_clientid(clientid, realm)
if not objClient:
module.fail_json(msg=f"Failed to retrive client '{realm}.{clientid}'")
if objClient["fullScopeAllowed"] and state == "present":
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{clientid}'")
if client_scope_id:
objClientScope = kc.get_client_by_clientid(client_scope_id, realm)
if not objClientScope:
module.fail_json(msg=f"Failed to retrive client '{realm}.{client_scope_id}'")
before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm)
else:
before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm)
if client_scope_id:
# retrive all role from client_scope
client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm)
else:
# retrive all role from realm
client_scope_roles_by_name = kc.get_realm_roles(realm)
# convert to indexed Dict by name
client_scope_roles_by_name = {role["name"]: role for role in client_scope_roles_by_name}
role_mapping_by_name = {role["name"]: role for role in before_role_mapping}
role_mapping_to_manipulate = []
if state == "present":
# update desired
for role_name in role_names:
if role_name not in client_scope_roles_by_name:
if client_scope_id:
module.fail_json(msg=f"Failed to retrive role '{realm}.{client_scope_id}.{role_name}'")
else:
module.fail_json(msg=f"Failed to retrive role '{realm}.{role_name}'")
if role_name not in role_mapping_by_name:
role_mapping_to_manipulate.append(client_scope_roles_by_name[role_name])
role_mapping_by_name[role_name] = client_scope_roles_by_name[role_name]
else:
# remove role if present
for role_name in role_names:
if role_name in role_mapping_by_name:
role_mapping_to_manipulate.append(role_mapping_by_name[role_name])
del role_mapping_by_name[role_name]
before_role_mapping = sorted(before_role_mapping, key=lambda d: d["name"])
desired_role_mapping = sorted(role_mapping_by_name.values(), key=lambda d: d["name"])
result["changed"] = len(role_mapping_to_manipulate) > 0
if result["changed"]:
result["diff"] = dict(before=before_role_mapping, after=desired_role_mapping)
if not result["changed"]:
# no changes
result["end_state"] = before_role_mapping
result["msg"] = f"No changes required for client role scope {clientid}."
elif state == "present":
# doing update
if module.check_mode:
result["end_state"] = desired_role_mapping
elif client_scope_id:
result["end_state"] = kc.update_client_role_scope_from_client(
role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
)
else:
result["end_state"] = kc.update_client_role_scope_from_realm(
role_mapping_to_manipulate, objClient["id"], realm
)
result["msg"] = f"Client role scope for {clientid} has been updated"
else:
# doing delete
if module.check_mode:
result["end_state"] = desired_role_mapping
elif client_scope_id:
result["end_state"] = kc.delete_client_role_scope_from_client(
role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
)
else:
result["end_state"] = kc.delete_client_role_scope_from_realm(
role_mapping_to_manipulate, objClient["id"], realm
)
result["msg"] = f"Client role scope for {clientid} has been deleted"
module.exit_json(**result)
if __name__ == "__main__":
main()

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