mirror of
https://github.com/ansible-middleware/keycloak.git
synced 2025-04-08 03:40:29 -07:00
Compare commits
49 commits
Author | SHA1 | Date | |
---|---|---|---|
|
532dc12a60 | ||
|
173a85638f | ||
|
81f019f8b5 | ||
|
5db96afa56 | ||
|
fa36721207 | ||
|
86284b12c2 | ||
|
b3e93dd89b | ||
|
e029e1c2fd | ||
|
d0f19b59dc | ||
|
213449ec58 | ||
|
277e1336ee | ||
|
58233549a7 | ||
|
0c58ae48ff | ||
|
bf0bd9e1da | ||
|
5d15d37890 | ||
|
910a2aa5d4 | ||
|
5f534ca566 | ||
|
692fb59797 | ||
|
d1859aaff2 | ||
|
0d0e52f9ff | ||
|
68a0f88423 | ||
|
333d55ad73 | ||
|
f6fdae4aa8 | ||
|
b8c11f3ca8 | ||
|
1279937bb0 | ||
|
c57753f608 | ||
|
be19ec1289 | ||
|
5f1b43f37b | ||
|
c6bb815979 | ||
|
ac4511bea9 | ||
|
c8021f3102 | ||
|
0386254073 | ||
|
b2edea8777 | ||
|
fc0ee5a896 | ||
|
eb66d4a412 | ||
|
f170257205 | ||
|
3f4617c32c | ||
|
34caf6a490 | ||
|
fa6ac99b34 | ||
|
a35c963a65 | ||
|
11aab0f5e2 | ||
|
fa2319d5da | ||
|
7c520dcdd2 | ||
|
35b3b090f6 | ||
|
94f1b8b355 | ||
|
e40f554936 | ||
|
64e2a95685 | ||
|
c6fac7bb70 | ||
|
5f059e8d63 |
69 changed files with 3120 additions and 677 deletions
|
@ -30,10 +30,12 @@ warn_list:
|
||||||
- schema[meta]
|
- schema[meta]
|
||||||
- key-order[task]
|
- key-order[task]
|
||||||
- blocked_modules
|
- blocked_modules
|
||||||
|
- run-once[task]
|
||||||
|
|
||||||
skip_list:
|
skip_list:
|
||||||
- vars_should_not_be_used
|
- vars_should_not_be_used
|
||||||
- file_is_small_enough
|
- file_is_small_enough
|
||||||
|
- file_has_valid_name
|
||||||
- name[template]
|
- name[template]
|
||||||
- var-naming[no-role-prefix]
|
- var-naming[no-role-prefix]
|
||||||
|
|
||||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -16,4 +16,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fqcn: 'middleware_automation/keycloak'
|
fqcn: 'middleware_automation/keycloak'
|
||||||
molecule_tests: >-
|
molecule_tests: >-
|
||||||
[ "default", "overridexml", "https_revproxy", "quarkus", "quarkus-devmode", "quarkus_upgrade", "debian", "quarkus_ha" ]
|
[ "default", "overridexml", "https_revproxy", "quarkus", "quarkus-devmode", "quarkus_upgrade", "debian", "quarkus_ha" ]
|
||||||
|
|
26
.github/workflows/traffic.yml
vendored
Normal file
26
.github/workflows/traffic.yml
vendored
Normal 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"
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ docs/_build/
|
||||||
changelogs/.plugin-cache.yaml
|
changelogs/.plugin-cache.yaml
|
||||||
*.pem
|
*.pem
|
||||||
*.key
|
*.key
|
||||||
|
*.p12
|
||||||
|
|
|
@ -6,6 +6,35 @@ middleware\_automation.keycloak Release Notes
|
||||||
|
|
||||||
This changelog describes changes after version 0.2.6.
|
This changelog describes changes after version 0.2.6.
|
||||||
|
|
||||||
|
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
|
v2.4.0
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|
|
@ -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.15' ansible-lint
|
||||||
|
# install collection dependencies
|
||||||
|
ansible-galaxy collection install -r requirements.yml
|
||||||
|
# install python dependencies
|
||||||
|
pip install -r requirements.txt molecule/requirements.txt
|
||||||
|
# execute the tests (replace --all with -s subdirectory to run a single test)
|
||||||
|
molecule test --all
|
||||||
|
```
|
||||||
|
|
||||||
## Contributor's Guidelines
|
## Contributor's Guidelines
|
||||||
|
|
||||||
|
|
20
README.md
20
README.md
|
@ -3,11 +3,12 @@
|
||||||
<!--start build_status -->
|
<!--start build_status -->
|
||||||
[](https://github.com/ansible-middleware/keycloak/actions/workflows/ci.yml)
|
[](https://github.com/ansible-middleware/keycloak/actions/workflows/ci.yml)
|
||||||
|
|
||||||
> **_NOTE:_ If you are Red Hat customer, install `redhat.sso` (for Red Hat Single Sign-On) or `redhat.rhbk` (for Red Hat Build of Keycloak) from [Automation Hub](https://console.redhat.com/ansible/ansible-dashboard) as the certified version of this collection.**
|
> **_NOTE:_ If you are Red Hat customer, install `redhat.rhbk` (for Red Hat Build of Keycloak) or `redhat.sso` (for Red Hat Single Sign-On) from [Automation Hub](https://console.redhat.com/ansible/ansible-dashboard) as the certified version of this collection.**
|
||||||
|
|
||||||
<!--end build_status -->
|
<!--end build_status -->
|
||||||
|
<!--start description -->
|
||||||
Collection to install and configure [Keycloak](https://www.keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) / [Red Hat Build of Keycloak](https://access.redhat.com/products/red-hat-build-of-keycloak).
|
Collection to install and configure [Keycloak](https://www.keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) / [Red Hat Build of Keycloak](https://access.redhat.com/products/red-hat-build-of-keycloak).
|
||||||
|
<!--end description -->
|
||||||
<!--start requires_ansible-->
|
<!--start requires_ansible-->
|
||||||
## Ansible version compatibility
|
## Ansible version compatibility
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ collections:
|
||||||
The keycloak collection also depends on the following python packages to be present on the controller host:
|
The keycloak collection also depends on the following python packages to be present on the controller host:
|
||||||
|
|
||||||
* netaddr
|
* netaddr
|
||||||
|
* lxml
|
||||||
|
|
||||||
A requirement file is provided to install:
|
A requirement file is provided to install:
|
||||||
|
|
||||||
|
@ -47,9 +49,10 @@ A requirement file is provided to install:
|
||||||
<!--start roles_paths -->
|
<!--start roles_paths -->
|
||||||
### Included roles
|
### Included roles
|
||||||
|
|
||||||
* [`keycloak`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md): role for installing the service (keycloak <= 19.0).
|
* `keycloak_quarkus`: role for installing keycloak (>= 19.0.0, quarkus based).
|
||||||
* [`keycloak_realm`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md): role for configuring a realm, user federation(s), clients and users, in an installed service.
|
* `keycloak_realm`: role for configuring a realm, user federation(s), clients and users, in an installed service.
|
||||||
* [`keycloak_quarkus`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md): role for installing the quarkus variant of keycloak (>= 17.0.0).
|
* `keycloak`: role for installing legacy keycloak (<= 19.0, wildfly based).
|
||||||
|
|
||||||
<!--end roles_paths -->
|
<!--end roles_paths -->
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -57,9 +60,9 @@ A requirement file is provided to install:
|
||||||
|
|
||||||
### Install Playbook
|
### Install Playbook
|
||||||
<!--start rhbk_playbook -->
|
<!--start rhbk_playbook -->
|
||||||
* [`playbooks/keycloak.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak.yml) installs keycloak legacy based on the defined variables (using most defaults).
|
|
||||||
* [`playbooks/keycloak_quarkus.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_quarkus.yml) installs keycloak >= 17 based on the defined variables (using most defaults).
|
* [`playbooks/keycloak_quarkus.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_quarkus.yml) installs keycloak >= 17 based on the defined variables (using most defaults).
|
||||||
|
* [`playbooks/keycloak.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak.yml) installs keycloak legacy based on the defined variables (using most defaults).
|
||||||
|
|
||||||
Both playbooks include the `keycloak` role, with different settings, as described in the following sections.
|
Both playbooks include the `keycloak` role, with different settings, as described in the following sections.
|
||||||
|
|
||||||
For full service configuration details, refer to the [keycloak role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md).
|
For full service configuration details, refer to the [keycloak role README](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md).
|
||||||
|
@ -90,7 +93,7 @@ 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>
|
ansible-playbook -i <ansible_hosts> -e @rhn-creds.yml playbooks/keycloak.yml -e keycloak_admin_password=<changeme>
|
||||||
```
|
```
|
||||||
|
|
||||||
- `keycloak_admin_password` Password for the administration console user account.
|
- `keycloak_admin_password` Password for the administration console user account.
|
||||||
- `ansible_hosts` is the inventory, below is an example inventory for deploying to localhost
|
- `ansible_hosts` is the inventory, below is an example inventory for deploying to localhost
|
||||||
|
@ -141,4 +144,3 @@ Apache License v2.0 or later
|
||||||
<!--start license -->
|
<!--start license -->
|
||||||
See [LICENSE](LICENSE) to view the full text.
|
See [LICENSE](LICENSE) to view the full text.
|
||||||
<!--end license -->
|
<!--end license -->
|
||||||
|
|
||||||
|
|
|
@ -584,3 +584,32 @@ releases:
|
||||||
- 232.yaml
|
- 232.yaml
|
||||||
- 234.yaml
|
- 234.yaml
|
||||||
release_date: '2024-06-04'
|
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'
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div role="contentinfo">
|
<div role="contentinfo">
|
||||||
<p>© Copyright 2022, Red Hat, Inc.</p>
|
<p>© Copyright 2024, Red Hat, Inc.</p>
|
||||||
</div>
|
</div>
|
||||||
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
|
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
|
||||||
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
|
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
|
||||||
|
@ -18,4 +18,4 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,31 +10,25 @@ Welcome to Keycloak Collection documentation
|
||||||
README
|
README
|
||||||
plugins/index
|
plugins/index
|
||||||
roles/index
|
roles/index
|
||||||
|
Changelog <CHANGELOG>
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Developer documentation
|
:caption: Developer documentation
|
||||||
|
|
||||||
testing
|
Developing <developing>
|
||||||
developing
|
Testing <testing>
|
||||||
releasing
|
Releasing <releasing>
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
:caption: General
|
|
||||||
|
|
||||||
Changelog <CHANGELOG>
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:caption: Middleware collections
|
:caption: Middleware collections
|
||||||
|
|
||||||
Infinispan / Red Hat Data Grid <https://ansible-middleware.github.io/infinispan/main/>
|
|
||||||
Keycloak / Red Hat Single Sign-On <https://ansible-middleware.github.io/keycloak/main/>
|
Keycloak / Red Hat Single Sign-On <https://ansible-middleware.github.io/keycloak/main/>
|
||||||
|
Infinispan / Red Hat Data Grid <https://ansible-middleware.github.io/infinispan/main/>
|
||||||
Wildfly / Red Hat JBoss EAP <https://ansible-middleware.github.io/wildfly/main/>
|
Wildfly / Red Hat JBoss EAP <https://ansible-middleware.github.io/wildfly/main/>
|
||||||
Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/main/>
|
Tomcat / Red Hat JWS <https://ansible-middleware.github.io/jws/main/>
|
||||||
ActiveMQ / Red Hat AMQ Broker <https://ansible-middleware.github.io/amq/main/>
|
ActiveMQ / Red Hat AMQ Broker <https://ansible-middleware.github.io/amq/main/>
|
||||||
Kafka / Red Hat AMQ Streams <https://ansible-middleware.github.io/amq_streams/main/>
|
Kafka / Red Hat AMQ Streams <https://ansible-middleware.github.io/amq_streams/main/>
|
||||||
Ansible Middleware utilities <https://ansible-middleware.github.io/common/main/>
|
Ansible Middleware utilities <https://ansible-middleware.github.io/common/main/>
|
||||||
Red Hat CSP Download <https://ansible-middleware.github.io/redhat-csp-download/main/>
|
|
||||||
JCliff <https://ansible-middleware.github.io/ansible_collections_jcliff/main/>
|
JCliff <https://ansible-middleware.github.io/ansible_collections_jcliff/main/>
|
||||||
|
|
|
@ -4,24 +4,7 @@
|
||||||
|
|
||||||
The collection is tested with a [molecule](https://github.com/ansible-community/molecule) setup covering the included roles and verifying correct installation and idempotency.
|
The collection is tested with a [molecule](https://github.com/ansible-community/molecule) setup covering the included roles and verifying correct installation and idempotency.
|
||||||
In order to run the molecule tests locally with python 3.9 available, after cloning the repository:
|
In order to run the molecule tests locally with python 3.9 available, after cloning the repository:
|
||||||
|
The test scenarios are available on the source code repository each on his own subdirectory under [molecule/](https://github.com/ansible-middleware/keycloak/molecule).
|
||||||
```
|
|
||||||
pip install yamllint 'molecule[docker]~=3.5.2' ansible-core flake8 ansible-lint voluptuous
|
|
||||||
molecule test --all
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Integration testing
|
|
||||||
|
|
||||||
Demo repositories which depend on the collection, and aggregate functionality with other middleware_automation collections, are automatically rebuilt
|
|
||||||
at every collection release to ensure non-breaking changes and consistent behaviour.
|
|
||||||
|
|
||||||
The repository are:
|
|
||||||
|
|
||||||
- [Flange demo](https://github.com/ansible-middleware/flange-demo)
|
|
||||||
A deployment of Wildfly cluster integrated with keycloak and infinispan.
|
|
||||||
- [CrossDC keycloak demo](https://github.com/ansible-middleware/cross-dc-rhsso-demo)
|
|
||||||
A clustered multi-regional installation of keycloak with infinispan remote caches.
|
|
||||||
|
|
||||||
|
|
||||||
## Test playbooks
|
## Test playbooks
|
||||||
|
@ -29,15 +12,7 @@ The repository are:
|
||||||
Sample playbooks are provided in the `playbooks/` directory; to run the playbooks locally (requires a rhel system with python 3.9+, ansible, and systemd) the steps are as follows:
|
Sample playbooks are provided in the `playbooks/` directory; to run the playbooks locally (requires a rhel system with python 3.9+, ansible, and systemd) the steps are as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
# setup environment
|
# setup environment as in developing
|
||||||
pip install ansible-core
|
|
||||||
# clone the repository
|
|
||||||
git clone https://github.com/ansible-middleware/keycloak
|
|
||||||
cd keycloak
|
|
||||||
# install collection dependencies
|
|
||||||
ansible-galaxy collection install -r requirements.yml
|
|
||||||
# install collection python deps
|
|
||||||
pip install -r requirements.txt
|
|
||||||
# create inventory for localhost
|
# create inventory for localhost
|
||||||
cat << EOF > inventory
|
cat << EOF > inventory
|
||||||
[keycloak]
|
[keycloak]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
namespace: middleware_automation
|
namespace: middleware_automation
|
||||||
name: keycloak
|
name: keycloak
|
||||||
version: "2.4.0"
|
version: "2.4.4"
|
||||||
readme: README.md
|
readme: README.md
|
||||||
authors:
|
authors:
|
||||||
- Romain Pelisse <rpelisse@redhat.com>
|
- Romain Pelisse <rpelisse@redhat.com>
|
||||||
|
|
|
@ -3,40 +3,42 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_realm: TestRealm
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
|
keycloak_quarkus_hostname: http://instance:8080
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_frontend_url: 'http://localhost:8080/'
|
keycloak_quarkus_start_dev: true
|
||||||
keycloak_quarkus_start_dev: True
|
|
||||||
keycloak_quarkus_proxy_mode: none
|
keycloak_quarkus_proxy_mode: none
|
||||||
keycloak_client_default_roles:
|
|
||||||
- TestRoleAdmin
|
|
||||||
- TestRoleUser
|
|
||||||
keycloak_client_users:
|
|
||||||
- username: TestUser
|
|
||||||
password: password
|
|
||||||
client_roles:
|
|
||||||
- client: TestClient
|
|
||||||
role: TestRoleUser
|
|
||||||
- username: TestAdmin
|
|
||||||
password: password
|
|
||||||
client_roles:
|
|
||||||
- client: TestClient
|
|
||||||
role: TestRoleUser
|
|
||||||
- client: TestClient
|
|
||||||
role: TestRoleAdmin
|
|
||||||
keycloak_clients:
|
|
||||||
- name: TestClient
|
|
||||||
roles: "{{ keycloak_client_default_roles }}"
|
|
||||||
public_client: "{{ keycloak_client_public }}"
|
|
||||||
web_origins: "{{ keycloak_client_web_origins }}"
|
|
||||||
users: "{{ keycloak_client_users }}"
|
|
||||||
client_id: TestClient
|
|
||||||
attributes:
|
|
||||||
post.logout.redirect.uris: '/public/logout'
|
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak_quarkus
|
- role: keycloak_quarkus
|
||||||
- role: keycloak_realm
|
- role: keycloak_realm
|
||||||
keycloak_realm: TestRealm
|
keycloak_url: "{{ keycloak_quarkus_hostname }}"
|
||||||
keycloak_admin_password: "remembertochangeme"
|
|
||||||
keycloak_context: ''
|
keycloak_context: ''
|
||||||
|
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'
|
||||||
|
|
|
@ -7,5 +7,6 @@
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name:
|
name:
|
||||||
- sudo
|
- sudo
|
||||||
|
# - openjdk-21-jdk-headless # this is not available in ghcr.io/hspaans/molecule-containers:debian-11 (neither in debian-12) since the images are using outdated package sources
|
||||||
- openjdk-17-jdk-headless
|
- openjdk-17-jdk-headless
|
||||||
state: present
|
state: present
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- name: Verify
|
- name: Verify
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
||||||
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
||||||
keycloak_jboss_port_offset: 10
|
keycloak_jboss_port_offset: 10
|
||||||
|
|
|
@ -2,61 +2,45 @@
|
||||||
- name: Converge
|
- name: Converge
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_jvm_package: java-11-openjdk-headless
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_modcluster_enabled: True
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_modcluster_urls:
|
keycloak_quarkus_hostname: http://instance:8080
|
||||||
- host: myhost1
|
keycloak_quarkus_log: file
|
||||||
port: 16667
|
keycloak_quarkus_log_level: debug
|
||||||
- host: myhost2
|
keycloak_quarkus_log_target: /tmp/keycloak
|
||||||
port: 16668
|
keycloak_quarkus_start_dev: true
|
||||||
keycloak_jboss_port_offset: 10
|
keycloak_quarkus_proxy_mode: none
|
||||||
keycloak_log_target: /tmp/keycloak
|
keycloak_quarkus_offline_install: true
|
||||||
|
keycloak_quarkus_download_path: /tmp/keycloak/
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak
|
- role: keycloak_quarkus
|
||||||
tasks:
|
- role: keycloak_realm
|
||||||
- name: Keycloak Realm Role
|
keycloak_url: "{{ keycloak_quarkus_hostname }}"
|
||||||
ansible.builtin.include_role:
|
keycloak_context: ''
|
||||||
name: keycloak_realm
|
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
|
||||||
vars:
|
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
|
||||||
keycloak_client_default_roles:
|
keycloak_client_users:
|
||||||
- TestRoleAdmin
|
- username: TestUser
|
||||||
- TestRoleUser
|
password: password
|
||||||
keycloak_client_users:
|
client_roles:
|
||||||
- username: TestUser
|
- client: TestClient
|
||||||
password: password
|
role: TestRoleUser
|
||||||
client_roles:
|
realm: "{{ keycloak_realm }}"
|
||||||
- client: TestClient
|
- username: TestAdmin
|
||||||
role: TestRoleUser
|
password: password
|
||||||
realm: "{{ keycloak_realm }}"
|
client_roles:
|
||||||
- username: TestAdmin
|
- client: TestClient
|
||||||
password: password
|
role: TestRoleUser
|
||||||
client_roles:
|
realm: "{{ keycloak_realm }}"
|
||||||
- client: TestClient
|
- client: TestClient
|
||||||
role: TestRoleUser
|
role: TestRoleAdmin
|
||||||
realm: "{{ keycloak_realm }}"
|
realm: "{{ keycloak_realm }}"
|
||||||
- client: TestClient
|
keycloak_realm: TestRealm
|
||||||
role: TestRoleAdmin
|
keycloak_clients:
|
||||||
realm: "{{ keycloak_realm }}"
|
- name: TestClient
|
||||||
keycloak_realm: TestRealm
|
realm: "{{ keycloak_realm }}"
|
||||||
keycloak_clients:
|
public_client: "{{ keycloak_client_public }}"
|
||||||
- name: TestClient
|
web_origins: "{{ keycloak_client_web_origins }}"
|
||||||
roles: "{{ keycloak_client_default_roles }}"
|
users: "{{ keycloak_client_users }}"
|
||||||
realm: "{{ keycloak_realm }}"
|
client_id: TestClient
|
||||||
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
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ platforms:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8443/tcp"
|
- "8443/tcp"
|
||||||
- "8009/tcp"
|
- "8009/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
provisioner:
|
provisioner:
|
||||||
name: ansible
|
name: ansible
|
||||||
config_options:
|
config_options:
|
||||||
|
|
|
@ -12,18 +12,18 @@
|
||||||
- "{{ assets_server }}/sso/7.6.0/rh-sso-7.6.0-server-dist.zip"
|
- "{{ 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"
|
- "{{ assets_server }}/sso/7.6.1/rh-sso-7.6.1-patch.zip"
|
||||||
|
|
||||||
- name: Install JDK8
|
- name: Create controller directory for downloads
|
||||||
become: yes
|
ansible.builtin.file: # noqa risky-file-permissions delegated, uses controller host user
|
||||||
ansible.builtin.yum:
|
path: /tmp/keycloak
|
||||||
name:
|
state: directory
|
||||||
- java-1.8.0-openjdk
|
mode: '0750'
|
||||||
state: present
|
delegate_to: localhost
|
||||||
when: ansible_facts['os_family'] == "RedHat"
|
run_once: true
|
||||||
|
|
||||||
- name: Install JDK8
|
- name: Download keycloak archive to controller directory
|
||||||
become: yes
|
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
|
||||||
ansible.builtin.apt:
|
url: https://github.com/keycloak/keycloak/releases/download/26.0.7/keycloak-26.0.7.zip
|
||||||
name:
|
dest: /tmp/keycloak
|
||||||
- openjdk-8-jdk
|
mode: '0640'
|
||||||
state: present
|
delegate_to: localhost
|
||||||
when: ansible_facts['os_family'] == "Debian"
|
run_once: true
|
||||||
|
|
|
@ -2,11 +2,9 @@
|
||||||
- name: Verify
|
- name: Verify
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_jvm_package: java-11-openjdk-headless
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_uri: "http://localhost:{{ 8080 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
keycloak_uri: "http://localhost:8080"
|
||||||
keycloak_management_port: "http://localhost:{{ 9990 + ( keycloak_jboss_port_offset | default(0) ) }}"
|
|
||||||
keycloak_jboss_port_offset: 10
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Populate service facts
|
- name: Populate service facts
|
||||||
ansible.builtin.service_facts:
|
ansible.builtin.service_facts:
|
||||||
|
@ -15,75 +13,13 @@
|
||||||
that:
|
that:
|
||||||
- ansible_facts.services["keycloak.service"]["state"] == "running"
|
- ansible_facts.services["keycloak.service"]["state"] == "running"
|
||||||
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
|
- ansible_facts.services["keycloak.service"]["status"] == "enabled"
|
||||||
- name: Verify we are running on requested jvm # noqa blocked_modules command-instead-of-module
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
set -o pipefail
|
|
||||||
ps -ef | grep '/etc/alternatives/jre_11/' | grep -v grep
|
|
||||||
args:
|
|
||||||
executable: /bin/bash
|
|
||||||
changed_when: no
|
|
||||||
- name: Verify token api call
|
- name: Verify token api call
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ keycloak_uri }}/auth/realms/master/protocol/openid-connect/token"
|
url: "{{ keycloak_uri }}/realms/master/protocol/openid-connect/token"
|
||||||
method: POST
|
method: POST
|
||||||
body: "client_id=admin-cli&username=admin&password={{ keycloak_admin_password }}&grant_type=password"
|
body: "client_id=admin-cli&username={{ keycloak_quarkus_bootstrap_admin_user }}&password={{ keycloak_quarkus_bootstrap_admin_user }}&grant_type=password"
|
||||||
validate_certs: no
|
validate_certs: no
|
||||||
register: keycloak_auth_response
|
register: keycloak_auth_response
|
||||||
until: keycloak_auth_response.status == 200
|
until: keycloak_auth_response.status == 200
|
||||||
retries: 2
|
retries: 2
|
||||||
delay: 2
|
delay: 2
|
||||||
- name: Fetch openid-connect config
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "{{ keycloak_uri }}/auth/realms/TestRealm/.well-known/openid-configuration"
|
|
||||||
method: GET
|
|
||||||
validate_certs: no
|
|
||||||
status_code: 200
|
|
||||||
register: keycloak_openid_config
|
|
||||||
- name: Verify expected config
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- keycloak_openid_config.json.registration_endpoint == 'http://localhost:8080/auth/realms/TestRealm/clients-registrations/openid-connect'
|
|
||||||
- name: Get test realm clients
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "{{ keycloak_uri }}/auth/admin/realms/TestRealm/clients"
|
|
||||||
method: GET
|
|
||||||
validate_certs: no
|
|
||||||
status_code: 200
|
|
||||||
headers:
|
|
||||||
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
|
|
||||||
register: keycloak_query_clients
|
|
||||||
- name: Verify expected config
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- (keycloak_query_clients.json | selectattr('clientId','equalto','TestClient') | first)["attributes"]["post.logout.redirect.uris"] == '/public/logout'
|
|
||||||
- name: "Privilege escalation as some files/folders may requires it"
|
|
||||||
become: yes
|
|
||||||
block:
|
|
||||||
- name: Check log folder
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/tmp/keycloak"
|
|
||||||
register: keycloak_log_folder
|
|
||||||
- name: Check that keycloak log folder exists and is a link
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- keycloak_log_folder.stat.exists
|
|
||||||
- not keycloak_log_folder.stat.isdir
|
|
||||||
- keycloak_log_folder.stat.islnk
|
|
||||||
- name: Check log file
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/tmp/keycloak/server.log"
|
|
||||||
register: keycloak_log_file
|
|
||||||
- name: Check if keycloak file exists
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- keycloak_log_file.stat.exists
|
|
||||||
- not keycloak_log_file.stat.isdir
|
|
||||||
- name: Check default log folder
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "/var/log/keycloak"
|
|
||||||
register: keycloak_default_log_folder
|
|
||||||
failed_when: false
|
|
||||||
- name: Check that default keycloak log folder doesn't exist
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- not keycloak_default_log_folder.stat.exists
|
|
||||||
|
|
|
@ -3,15 +3,14 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_realm: TestRealm
|
keycloak_quarkus_hostname: https://proxy
|
||||||
keycloak_quarkus_host: instance
|
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_http_enabled: True
|
keycloak_quarkus_http_enabled: True
|
||||||
keycloak_quarkus_http_port: 8080
|
keycloak_quarkus_http_port: 8080
|
||||||
keycloak_quarkus_proxy_mode: edge
|
keycloak_quarkus_proxy_mode: edge
|
||||||
keycloak_quarkus_http_relative_path: /
|
keycloak_quarkus_http_relative_path: /
|
||||||
keycloak_quarkus_frontend_url: https://proxy/
|
keycloak_quarkus_health_check_url: http://proxy:8080/realms/master/.well-known/openid-configuration
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak_quarkus
|
- role: keycloak_quarkus
|
||||||
|
|
|
@ -11,6 +11,7 @@ platforms:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8443/tcp"
|
- "8443/tcp"
|
||||||
- "8009/tcp"
|
- "8009/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
provisioner:
|
provisioner:
|
||||||
name: ansible
|
name: ansible
|
||||||
config_options:
|
config_options:
|
||||||
|
|
|
@ -3,18 +3,21 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_realm: TestRealm
|
keycloak_realm: TestRealm
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_frontend_url: 'http://localhost:8080/'
|
keycloak_quarkus_hostname: 'http://localhost:8080'
|
||||||
keycloak_quarkus_start_dev: True
|
keycloak_quarkus_start_dev: True
|
||||||
keycloak_quarkus_proxy_mode: none
|
keycloak_quarkus_proxy_mode: none
|
||||||
keycloak_quarkus_java_home: /opt/openjdk/
|
keycloak_quarkus_java_home: /opt/openjdk/
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak_quarkus
|
- role: keycloak_quarkus
|
||||||
- role: keycloak_realm
|
- role: keycloak_realm
|
||||||
|
keycloak_url: "{{ keycloak_quarkus_hostname }}"
|
||||||
keycloak_context: ''
|
keycloak_context: ''
|
||||||
|
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
|
||||||
|
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
|
||||||
keycloak_client_default_roles:
|
keycloak_client_default_roles:
|
||||||
- TestRoleAdmin
|
- TestRoleAdmin
|
||||||
- TestRoleUser
|
- TestRoleUser
|
||||||
|
|
|
@ -10,8 +10,10 @@ platforms:
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8009/tcp"
|
- "8009/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
published_ports:
|
published_ports:
|
||||||
- 0.0.0.0:8080:8080/tcp
|
- 0.0.0.0:8080:8080/tcp
|
||||||
|
- 0.0.0.0:9000:9000/TCP
|
||||||
provisioner:
|
provisioner:
|
||||||
name: ansible
|
name: ansible
|
||||||
config_options:
|
config_options:
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_realm: TestRealm
|
keycloak_realm: TestRealm
|
||||||
keycloak_quarkus_host: instance
|
keycloak_quarkus_hostname: https://instance:8443
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_log_level: debug # needed for the verify step
|
keycloak_quarkus_log_level: debug # needed for the verify step
|
||||||
keycloak_quarkus_https_key_file_enabled: true
|
keycloak_quarkus_https_key_file_enabled: true
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4
|
repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4
|
||||||
group_id: org.keycloak
|
group_id: org.keycloak
|
||||||
artifact_id: keycloak-kerberos-federation
|
artifact_id: keycloak-kerberos-federation
|
||||||
version: 24.0.4 # optional
|
version: 26.0.7 # optional
|
||||||
# username: myUser # optional
|
# username: myUser # optional
|
||||||
# password: myPAT # optional
|
# password: myPAT # optional
|
||||||
# - id: my-static-theme
|
# - id: my-static-theme
|
||||||
|
@ -51,7 +51,10 @@
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak_quarkus
|
- role: keycloak_quarkus
|
||||||
- role: keycloak_realm
|
- role: keycloak_realm
|
||||||
|
keycloak_url: "{{ keycloak_quarkus_hostname }}"
|
||||||
keycloak_context: ''
|
keycloak_context: ''
|
||||||
|
keycloak_admin_user: "{{ keycloak_quarkus_bootstrap_admin_user }}"
|
||||||
|
keycloak_admin_password: "{{ keycloak_quarkus_bootstrap_admin_password }}"
|
||||||
keycloak_client_default_roles:
|
keycloak_client_default_roles:
|
||||||
- TestRoleAdmin
|
- TestRoleAdmin
|
||||||
- TestRoleUser
|
- TestRoleUser
|
||||||
|
|
|
@ -11,6 +11,7 @@ platforms:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8443/tcp"
|
- "8443/tcp"
|
||||||
- "8009/tcp"
|
- "8009/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
published_ports:
|
published_ports:
|
||||||
- 0.0.0.0:8443:8443/tcp
|
- 0.0.0.0:8443:8443/tcp
|
||||||
provisioner:
|
provisioner:
|
||||||
|
|
|
@ -12,19 +12,19 @@
|
||||||
- name: Create certificate request
|
- name: Create certificate request
|
||||||
ansible.builtin.command: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=instance'
|
ansible.builtin.command: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj '/CN=instance'
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
changed_when: False
|
changed_when: false
|
||||||
|
|
||||||
- name: Create vault directory
|
- name: Create vault directory
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
state: directory
|
state: directory
|
||||||
path: "/opt/keycloak/vault"
|
path: "/opt/keycloak/vault"
|
||||||
mode: 0755
|
mode: '0755'
|
||||||
|
|
||||||
- name: Make sure a jre is available (for keytool to prepare keystore)
|
- name: Make sure a jre is available (for keytool to prepare keystore)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
ansible.builtin.package:
|
ansible.builtin.package:
|
||||||
name: "{{ 'java-17-openjdk-headless' if hera_home | length > 0 else 'openjdk-17-jdk-headless' }}"
|
name: "{{ 'java-21-openjdk-headless' if hera_home | length > 0 else 'openjdk-21-jdk-headless' }}"
|
||||||
state: present
|
state: present
|
||||||
become: true
|
become: true
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
@ -41,4 +41,4 @@
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: keystore.p12
|
src: keystore.p12
|
||||||
dest: /opt/keycloak/vault/keystore.p12
|
dest: /opt/keycloak/vault/keystore.p12
|
||||||
mode: 0444
|
mode: '0444'
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
---
|
---
|
||||||
- name: Verify
|
- name: Verify
|
||||||
hosts: all
|
hosts: all
|
||||||
|
vars:
|
||||||
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
tasks:
|
tasks:
|
||||||
- name: Populate service facts
|
- name: Populate service facts
|
||||||
ansible.builtin.service_facts:
|
ansible.builtin.service_facts:
|
||||||
|
@ -33,10 +36,10 @@
|
||||||
- name: Verify endpoint URLs
|
- name: Verify endpoint URLs
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'https://instance/realms/master/protocol/openid-connect/ext/ciba/auth'
|
- (openid_config.stdout | from_json)["backchannel_authentication_endpoint"] == 'https://instance:8443/realms/master/protocol/openid-connect/ext/ciba/auth'
|
||||||
- (openid_config.stdout | from_json)['issuer'] == 'https://instance/realms/master'
|
- (openid_config.stdout | from_json)['issuer'] == 'https://instance:8443/realms/master'
|
||||||
- (openid_config.stdout | from_json)['authorization_endpoint'] == 'https://instance/realms/master/protocol/openid-connect/auth'
|
- (openid_config.stdout | from_json)['authorization_endpoint'] == 'https://instance:8443/realms/master/protocol/openid-connect/auth'
|
||||||
- (openid_config.stdout | from_json)['token_endpoint'] == 'https://instance/realms/master/protocol/openid-connect/token'
|
- (openid_config.stdout | from_json)['token_endpoint'] == 'https://instance:8443/realms/master/protocol/openid-connect/token'
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: Check log folder
|
- name: Check log folder
|
||||||
|
@ -84,3 +87,42 @@
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: slurped_log.rc != 0
|
failed_when: slurped_log.rc != 0
|
||||||
register: slurped_log
|
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"
|
||||||
|
headers:
|
||||||
|
validate_certs: false
|
||||||
|
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 }}"
|
||||||
|
headers:
|
||||||
|
validate_certs: false
|
||||||
|
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"
|
||||||
|
headers:
|
||||||
|
validate_certs: false
|
||||||
|
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
|
||||||
|
register: keycloak_test_client_roles
|
|
@ -3,10 +3,9 @@
|
||||||
hosts: keycloak
|
hosts: keycloak
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_user: "remembertochangeme"
|
||||||
keycloak_realm: TestRealm
|
keycloak_quarkus_hostname: "http://{{ inventory_hostname }}:8080"
|
||||||
keycloak_quarkus_host: "{{ inventory_hostname }}"
|
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_log_level: info
|
keycloak_quarkus_log_level: info
|
||||||
keycloak_quarkus_https_key_file_enabled: true
|
keycloak_quarkus_https_key_file_enabled: true
|
||||||
|
|
|
@ -14,6 +14,7 @@ platforms:
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8443/tcp"
|
- "8443/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
- name: instance2
|
- name: instance2
|
||||||
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
||||||
pre_build_image: true
|
pre_build_image: true
|
||||||
|
@ -26,6 +27,7 @@ platforms:
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- "8080/tcp"
|
- "8080/tcp"
|
||||||
- "8443/tcp"
|
- "8443/tcp"
|
||||||
|
- "9000/tcp"
|
||||||
- name: postgres
|
- name: postgres
|
||||||
image: ubuntu/postgres:14-22.04_beta
|
image: ubuntu/postgres:14-22.04_beta
|
||||||
pre_build_image: true
|
pre_build_image: true
|
||||||
|
|
|
@ -5,6 +5,6 @@
|
||||||
- vars.yml
|
- vars.yml
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_show_deprecation_warnings: false
|
keycloak_quarkus_show_deprecation_warnings: false
|
||||||
keycloak_quarkus_version: 24.0.3
|
keycloak_quarkus_version: 26.0.7
|
||||||
roles:
|
roles:
|
||||||
- role: keycloak_quarkus
|
- role: keycloak_quarkus
|
||||||
|
|
|
@ -13,8 +13,10 @@ platforms:
|
||||||
privileged: true
|
privileged: true
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
- "9000/tcp"
|
||||||
published_ports:
|
published_ports:
|
||||||
- 0.0.0.0:8080:8080/TCP
|
- 0.0.0.0:8080:8080/TCP
|
||||||
|
- 0.0.0.0:9000:9000/TCP
|
||||||
provisioner:
|
provisioner:
|
||||||
name: ansible
|
name: ansible
|
||||||
playbooks:
|
playbooks:
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- vars.yml
|
- vars.yml
|
||||||
vars:
|
vars:
|
||||||
sudo_pkg_name: sudo
|
sudo_pkg_name: sudo
|
||||||
keycloak_quarkus_version: 23.0.7
|
keycloak_quarkus_version: 24.0.5
|
||||||
pre_tasks:
|
pre_tasks:
|
||||||
- name: Install sudo
|
- name: Install sudo
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
---
|
---
|
||||||
keycloak_quarkus_offline_install: false
|
keycloak_quarkus_offline_install: false
|
||||||
keycloak_quarkus_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
|
||||||
keycloak_quarkus_realm: TestRealm
|
keycloak_quarkus_realm: TestRealm
|
||||||
keycloak_quarkus_host: instance
|
keycloak_quarkus_hostname: http://instance:8080
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_https_key_file_enabled: true
|
keycloak_quarkus_https_key_file_enabled: true
|
||||||
keycloak_quarkus_log_target: /tmp/keycloak
|
keycloak_quarkus_log_target: /tmp/keycloak
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- name: Verify
|
- name: Verify
|
||||||
hosts: instance
|
hosts: instance
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_admin_password: "remembertochangeme"
|
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||||
keycloak_quarkus_port: http://localhost:8080
|
keycloak_quarkus_port: http://localhost:8080
|
||||||
tasks:
|
tasks:
|
||||||
- name: Populate service facts
|
- name: Populate service facts
|
||||||
|
@ -17,14 +17,14 @@
|
||||||
- name: Verify we are running on requested jvm
|
- name: Verify we are running on requested jvm
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: |
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
ps -ef | grep 'etc/alternatives/.*17' | grep -v grep
|
ps -ef | grep 'etc/alternatives/.*21' | grep -v grep
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: Verify token api call
|
- name: Verify token api call
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ keycloak_quarkus_port }}/realms/master/protocol/openid-connect/token"
|
url: "{{ keycloak_quarkus_port }}/realms/master/protocol/openid-connect/token"
|
||||||
method: POST
|
method: POST
|
||||||
body: "client_id=admin-cli&username=admin&password={{ keycloak_quarkus_admin_password }}&grant_type=password"
|
body: "client_id=admin-cli&username=admin&password={{ keycloak_quarkus_bootstrap_admin_password }}&grant_type=password"
|
||||||
validate_certs: no
|
validate_certs: no
|
||||||
register: keycloak_auth_response
|
register: keycloak_auth_response
|
||||||
until: keycloak_auth_response.status == 200
|
until: keycloak_auth_response.status == 200
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_quarkus_admin_pass: "remembertochangeme"
|
keycloak_quarkus_admin_pass: "remembertochangeme"
|
||||||
keycloak_quarkus_host: localhost
|
keycloak_quarkus_hostname: http://localhost
|
||||||
keycloak_quarkus_port: 8443
|
keycloak_quarkus_port: 8443
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_proxy_mode: none
|
keycloak_quarkus_proxy_mode: none
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
hosts: all
|
hosts: all
|
||||||
vars:
|
vars:
|
||||||
keycloak_admin_password: "remembertochangeme"
|
keycloak_admin_password: "remembertochangeme"
|
||||||
keycloak_quarkus_host: localhost
|
keycloak_quarkus_hostname: http://localhost
|
||||||
keycloak_quarkus_port: 8080
|
keycloak_quarkus_port: 8080
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_start_dev: true
|
keycloak_quarkus_start_dev: true
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -40,8 +40,8 @@ options:
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- State of the client
|
- State of the client
|
||||||
- On C(present), the client will be created (or updated if it exists already).
|
- On V(present), the client will be created (or updated if it exists already).
|
||||||
- On C(absent), the client will be removed if it exists
|
- On V(absent), the client will be removed if it exists
|
||||||
choices: ['present', 'absent']
|
choices: ['present', 'absent']
|
||||||
default: 'present'
|
default: 'present'
|
||||||
type: str
|
type: str
|
||||||
|
@ -55,7 +55,7 @@ options:
|
||||||
client_id:
|
client_id:
|
||||||
description:
|
description:
|
||||||
- Client id of client to be worked on. This is usually an alphanumeric name chosen by
|
- Client id of client to be worked on. This is usually an alphanumeric name chosen by
|
||||||
you. Either this or I(id) is required. If you specify both, I(id) takes precedence.
|
you. Either this or O(id) is required. If you specify both, O(id) takes precedence.
|
||||||
This is 'clientId' in the Keycloak REST API.
|
This is 'clientId' in the Keycloak REST API.
|
||||||
aliases:
|
aliases:
|
||||||
- clientId
|
- clientId
|
||||||
|
@ -63,13 +63,13 @@ options:
|
||||||
|
|
||||||
id:
|
id:
|
||||||
description:
|
description:
|
||||||
- Id of client to be worked on. This is usually an UUID. Either this or I(client_id)
|
- Id of client to be worked on. This is usually an UUID. Either this or O(client_id)
|
||||||
is required. If you specify both, this takes precedence.
|
is required. If you specify both, this takes precedence.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- Name of the client (this is not the same as I(client_id)).
|
- Name of the client (this is not the same as O(client_id)).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
description:
|
description:
|
||||||
|
@ -108,20 +108,21 @@ options:
|
||||||
|
|
||||||
client_authenticator_type:
|
client_authenticator_type:
|
||||||
description:
|
description:
|
||||||
- How do clients authenticate with the auth server? Either C(client-secret) or
|
- How do clients authenticate with the auth server? Either V(client-secret),
|
||||||
C(client-jwt) can be chosen. When using C(client-secret), the module parameter
|
V(client-jwt), or V(client-x509) can be chosen. When using V(client-secret), the module parameter
|
||||||
I(secret) can set it, while for C(client-jwt), you can use the keys C(use.jwks.url),
|
O(secret) can set it, for V(client-jwt), you can use the keys C(use.jwks.url),
|
||||||
C(jwks.url), and C(jwt.credential.certificate) in the I(attributes) module parameter
|
C(jwks.url), and C(jwt.credential.certificate) in the O(attributes) module parameter
|
||||||
to configure its behavior.
|
to configure its behavior. For V(client-x509) you can use the keys C(x509.allow.regex.pattern.comparison)
|
||||||
This is 'clientAuthenticatorType' in the Keycloak REST API.
|
and C(x509.subjectdn) in the O(attributes) module parameter to configure which certificate(s) to accept.
|
||||||
choices: ['client-secret', 'client-jwt']
|
- This is 'clientAuthenticatorType' in the Keycloak REST API.
|
||||||
|
choices: ['client-secret', 'client-jwt', 'client-x509']
|
||||||
aliases:
|
aliases:
|
||||||
- clientAuthenticatorType
|
- clientAuthenticatorType
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
secret:
|
secret:
|
||||||
description:
|
description:
|
||||||
- When using I(client_authenticator_type) C(client-secret) (the default), you can
|
- When using O(client_authenticator_type=client-secret) (the default), you can
|
||||||
specify a secret here (otherwise one will be generated if it does not exit). If
|
specify a secret here (otherwise one will be generated if it does not exit). If
|
||||||
changing this secret, the module will not register a change currently (but the
|
changing this secret, the module will not register a change currently (but the
|
||||||
changed secret will be saved).
|
changed secret will be saved).
|
||||||
|
@ -246,9 +247,11 @@ options:
|
||||||
|
|
||||||
protocol:
|
protocol:
|
||||||
description:
|
description:
|
||||||
- Type of client (either C(openid-connect) or C(saml).
|
- Type of client.
|
||||||
|
- At creation only, default value will be V(openid-connect) if O(protocol) is omitted.
|
||||||
|
- The V(docker-v2) value was added in community.general 8.6.0.
|
||||||
type: str
|
type: str
|
||||||
choices: ['openid-connect', 'saml']
|
choices: ['openid-connect', 'saml', 'docker-v2']
|
||||||
|
|
||||||
full_scope_allowed:
|
full_scope_allowed:
|
||||||
description:
|
description:
|
||||||
|
@ -286,7 +289,7 @@ options:
|
||||||
|
|
||||||
use_template_config:
|
use_template_config:
|
||||||
description:
|
description:
|
||||||
- Whether or not to use configuration from the I(client_template).
|
- Whether or not to use configuration from the O(client_template).
|
||||||
This is 'useTemplateConfig' in the Keycloak REST API.
|
This is 'useTemplateConfig' in the Keycloak REST API.
|
||||||
aliases:
|
aliases:
|
||||||
- useTemplateConfig
|
- useTemplateConfig
|
||||||
|
@ -294,7 +297,7 @@ options:
|
||||||
|
|
||||||
use_template_scope:
|
use_template_scope:
|
||||||
description:
|
description:
|
||||||
- Whether or not to use scope configuration from the I(client_template).
|
- Whether or not to use scope configuration from the O(client_template).
|
||||||
This is 'useTemplateScope' in the Keycloak REST API.
|
This is 'useTemplateScope' in the Keycloak REST API.
|
||||||
aliases:
|
aliases:
|
||||||
- useTemplateScope
|
- useTemplateScope
|
||||||
|
@ -302,7 +305,7 @@ options:
|
||||||
|
|
||||||
use_template_mappers:
|
use_template_mappers:
|
||||||
description:
|
description:
|
||||||
- Whether or not to use mapper configuration from the I(client_template).
|
- Whether or not to use mapper configuration from the O(client_template).
|
||||||
This is 'useTemplateMappers' in the Keycloak REST API.
|
This is 'useTemplateMappers' in the Keycloak REST API.
|
||||||
aliases:
|
aliases:
|
||||||
- useTemplateMappers
|
- useTemplateMappers
|
||||||
|
@ -338,6 +341,42 @@ options:
|
||||||
description:
|
description:
|
||||||
- Override realm authentication flow bindings.
|
- Override realm authentication flow bindings.
|
||||||
type: dict
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
browser:
|
||||||
|
description:
|
||||||
|
- Flow ID of the browser authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.browser)
|
||||||
|
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||||
|
type: str
|
||||||
|
|
||||||
|
browser_name:
|
||||||
|
description:
|
||||||
|
- Flow name of the browser authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.browser)
|
||||||
|
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- browserName
|
||||||
|
type: str
|
||||||
|
version_added: 9.1.0
|
||||||
|
|
||||||
|
direct_grant:
|
||||||
|
description:
|
||||||
|
- Flow ID of the direct grant authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.direct_grant)
|
||||||
|
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- directGrant
|
||||||
|
type: str
|
||||||
|
|
||||||
|
direct_grant_name:
|
||||||
|
description:
|
||||||
|
- Flow name of the direct grant authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.direct_grant)
|
||||||
|
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- directGrantName
|
||||||
|
type: str
|
||||||
|
version_added: 9.1.0
|
||||||
aliases:
|
aliases:
|
||||||
- authenticationFlowBindingOverrides
|
- authenticationFlowBindingOverrides
|
||||||
version_added: 3.4.0
|
version_added: 3.4.0
|
||||||
|
@ -391,38 +430,37 @@ options:
|
||||||
|
|
||||||
protocol:
|
protocol:
|
||||||
description:
|
description:
|
||||||
- This is either C(openid-connect) or C(saml), this specifies for which protocol this protocol mapper.
|
- This specifies for which protocol this protocol mapper is active.
|
||||||
is active.
|
choices: ['openid-connect', 'saml', 'docker-v2']
|
||||||
choices: ['openid-connect', 'saml']
|
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
protocolMapper:
|
protocolMapper:
|
||||||
description:
|
description:
|
||||||
- The Keycloak-internal name of the type of this protocol-mapper. While an exhaustive list is
|
- "The Keycloak-internal name of the type of this protocol-mapper. While an exhaustive list is
|
||||||
impossible to provide since this may be extended through SPIs by the user of Keycloak,
|
impossible to provide since this may be extended through SPIs by the user of Keycloak,
|
||||||
by default Keycloak as of 3.4 ships with at least
|
by default Keycloak as of 3.4 ships with at least:"
|
||||||
- C(docker-v2-allow-all-mapper)
|
- V(docker-v2-allow-all-mapper)
|
||||||
- C(oidc-address-mapper)
|
- V(oidc-address-mapper)
|
||||||
- C(oidc-full-name-mapper)
|
- V(oidc-full-name-mapper)
|
||||||
- C(oidc-group-membership-mapper)
|
- V(oidc-group-membership-mapper)
|
||||||
- C(oidc-hardcoded-claim-mapper)
|
- V(oidc-hardcoded-claim-mapper)
|
||||||
- C(oidc-hardcoded-role-mapper)
|
- V(oidc-hardcoded-role-mapper)
|
||||||
- C(oidc-role-name-mapper)
|
- V(oidc-role-name-mapper)
|
||||||
- C(oidc-script-based-protocol-mapper)
|
- V(oidc-script-based-protocol-mapper)
|
||||||
- C(oidc-sha256-pairwise-sub-mapper)
|
- V(oidc-sha256-pairwise-sub-mapper)
|
||||||
- C(oidc-usermodel-attribute-mapper)
|
- V(oidc-usermodel-attribute-mapper)
|
||||||
- C(oidc-usermodel-client-role-mapper)
|
- V(oidc-usermodel-client-role-mapper)
|
||||||
- C(oidc-usermodel-property-mapper)
|
- V(oidc-usermodel-property-mapper)
|
||||||
- C(oidc-usermodel-realm-role-mapper)
|
- V(oidc-usermodel-realm-role-mapper)
|
||||||
- C(oidc-usersessionmodel-note-mapper)
|
- V(oidc-usersessionmodel-note-mapper)
|
||||||
- C(saml-group-membership-mapper)
|
- V(saml-group-membership-mapper)
|
||||||
- C(saml-hardcode-attribute-mapper)
|
- V(saml-hardcode-attribute-mapper)
|
||||||
- C(saml-hardcode-role-mapper)
|
- V(saml-hardcode-role-mapper)
|
||||||
- C(saml-role-list-mapper)
|
- V(saml-role-list-mapper)
|
||||||
- C(saml-role-name-mapper)
|
- V(saml-role-name-mapper)
|
||||||
- C(saml-user-attribute-mapper)
|
- V(saml-user-attribute-mapper)
|
||||||
- C(saml-user-property-mapper)
|
- V(saml-user-property-mapper)
|
||||||
- C(saml-user-session-note-mapper)
|
- V(saml-user-session-note-mapper)
|
||||||
- An exhaustive list of available mappers on your installation can be obtained on
|
- An exhaustive list of available mappers on your installation can be obtained on
|
||||||
the admin console by going to Server Info -> Providers and looking under
|
the admin console by going to Server Info -> Providers and looking under
|
||||||
'protocol-mapper'.
|
'protocol-mapper'.
|
||||||
|
@ -431,10 +469,10 @@ options:
|
||||||
config:
|
config:
|
||||||
description:
|
description:
|
||||||
- Dict specifying the configuration options for the protocol mapper; the
|
- Dict specifying the configuration options for the protocol mapper; the
|
||||||
contents differ depending on the value of I(protocolMapper) and are not documented
|
contents differ depending on the value of O(protocol_mappers[].protocolMapper) and are not documented
|
||||||
other than by the source of the mappers and its parent class(es). An example is given
|
other than by the source of the mappers and its parent class(es). An example is given
|
||||||
below. It is easiest to obtain valid config values by dumping an already-existing
|
below. It is easiest to obtain valid config values by dumping an already-existing
|
||||||
protocol mapper configuration through check-mode in the I(existing) field.
|
protocol mapper configuration through check-mode in the RV(existing) field.
|
||||||
type: dict
|
type: dict
|
||||||
|
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -478,7 +516,7 @@ options:
|
||||||
|
|
||||||
saml.signature.algorithm:
|
saml.signature.algorithm:
|
||||||
description:
|
description:
|
||||||
- Signature algorithm used to sign SAML documents. One of C(RSA_SHA256), C(RSA_SHA1), C(RSA_SHA512), or C(DSA_SHA1).
|
- Signature algorithm used to sign SAML documents. One of V(RSA_SHA256), V(RSA_SHA1), V(RSA_SHA512), or V(DSA_SHA1).
|
||||||
|
|
||||||
saml.signing.certificate:
|
saml.signing.certificate:
|
||||||
description:
|
description:
|
||||||
|
@ -496,22 +534,21 @@ options:
|
||||||
description:
|
description:
|
||||||
- SAML Redirect Binding URL for the client's assertion consumer service (login responses).
|
- SAML Redirect Binding URL for the client's assertion consumer service (login responses).
|
||||||
|
|
||||||
|
|
||||||
saml_force_name_id_format:
|
saml_force_name_id_format:
|
||||||
description:
|
description:
|
||||||
- For SAML clients, Boolean specifying whether to ignore requested NameID subject format and using the configured one instead.
|
- For SAML clients, Boolean specifying whether to ignore requested NameID subject format and using the configured one instead.
|
||||||
|
|
||||||
saml_name_id_format:
|
saml_name_id_format:
|
||||||
description:
|
description:
|
||||||
- For SAML clients, the NameID format to use (one of C(username), C(email), C(transient), or C(persistent))
|
- For SAML clients, the NameID format to use (one of V(username), V(email), V(transient), or V(persistent))
|
||||||
|
|
||||||
saml_signature_canonicalization_method:
|
saml_signature_canonicalization_method:
|
||||||
description:
|
description:
|
||||||
- SAML signature canonicalization method. This is one of four values, namely
|
- SAML signature canonicalization method. This is one of four values, namely
|
||||||
C(http://www.w3.org/2001/10/xml-exc-c14n#) for EXCLUSIVE,
|
V(http://www.w3.org/2001/10/xml-exc-c14n#) for EXCLUSIVE,
|
||||||
C(http://www.w3.org/2001/10/xml-exc-c14n#WithComments) for EXCLUSIVE_WITH_COMMENTS,
|
V(http://www.w3.org/2001/10/xml-exc-c14n#WithComments) for EXCLUSIVE_WITH_COMMENTS,
|
||||||
C(http://www.w3.org/TR/2001/REC-xml-c14n-20010315) for INCLUSIVE, and
|
V(http://www.w3.org/TR/2001/REC-xml-c14n-20010315) for INCLUSIVE, and
|
||||||
C(http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments) for INCLUSIVE_WITH_COMMENTS.
|
V(http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments) for INCLUSIVE_WITH_COMMENTS.
|
||||||
|
|
||||||
saml_single_logout_service_url_post:
|
saml_single_logout_service_url_post:
|
||||||
description:
|
description:
|
||||||
|
@ -523,12 +560,12 @@ options:
|
||||||
|
|
||||||
user.info.response.signature.alg:
|
user.info.response.signature.alg:
|
||||||
description:
|
description:
|
||||||
- For OpenID-Connect clients, JWA algorithm for signed UserInfo-endpoint responses. One of C(RS256) or C(unsigned).
|
- For OpenID-Connect clients, JWA algorithm for signed UserInfo-endpoint responses. One of V(RS256) or V(unsigned).
|
||||||
|
|
||||||
request.object.signature.alg:
|
request.object.signature.alg:
|
||||||
description:
|
description:
|
||||||
- For OpenID-Connect clients, JWA algorithm which the client needs to use when sending
|
- For OpenID-Connect clients, JWA algorithm which the client needs to use when sending
|
||||||
OIDC request object. One of C(any), C(none), C(RS256).
|
OIDC request object. One of V(any), V(none), V(RS256).
|
||||||
|
|
||||||
use.jwks.url:
|
use.jwks.url:
|
||||||
description:
|
description:
|
||||||
|
@ -544,9 +581,21 @@ options:
|
||||||
- For OpenID-Connect clients, client certificate for validating JWT issued by
|
- For OpenID-Connect clients, client certificate for validating JWT issued by
|
||||||
client and signed by its key, base64-encoded.
|
client and signed by its key, base64-encoded.
|
||||||
|
|
||||||
|
x509.subjectdn:
|
||||||
|
description:
|
||||||
|
- For OpenID-Connect clients, subject which will be used to authenticate the client.
|
||||||
|
type: str
|
||||||
|
version_added: 9.5.0
|
||||||
|
|
||||||
|
x509.allow.regex.pattern.comparison:
|
||||||
|
description:
|
||||||
|
- For OpenID-Connect clients, boolean specifying whether to allow C(x509.subjectdn) as regular expression.
|
||||||
|
type: bool
|
||||||
|
version_added: 9.5.0
|
||||||
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- middleware_automation.keycloak.keycloak
|
- middleware_automation.keycloak.keycloak
|
||||||
- middleware_automation.keycloak.attributes
|
- middleware_automation.keycloak.attributes
|
||||||
|
|
||||||
author:
|
author:
|
||||||
- Eike Frost (@eikef)
|
- Eike Frost (@eikef)
|
||||||
|
@ -587,6 +636,22 @@ EXAMPLES = '''
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|
||||||
|
- name: Create or update a Keycloak client (minimal example), with x509 authentication
|
||||||
|
middleware_automation.keycloak.keycloak_client:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
realm: master
|
||||||
|
state: present
|
||||||
|
client_id: test
|
||||||
|
client_authenticator_type: client-x509
|
||||||
|
attributes:
|
||||||
|
x509.subjectdn: "CN=client"
|
||||||
|
x509.allow.regex.pattern.comparison: false
|
||||||
|
|
||||||
|
|
||||||
- name: Create or update a Keycloak client (with all the bells and whistles)
|
- name: Create or update a Keycloak client (with all the bells and whistles)
|
||||||
middleware_automation.keycloak.keycloak_client:
|
middleware_automation.keycloak.keycloak_client:
|
||||||
auth_client_id: admin-cli
|
auth_client_id: admin-cli
|
||||||
|
@ -637,7 +702,7 @@ EXAMPLES = '''
|
||||||
- test01
|
- test01
|
||||||
- test02
|
- test02
|
||||||
authentication_flow_binding_overrides:
|
authentication_flow_binding_overrides:
|
||||||
browser: 4c90336b-bf1d-4b87-916d-3677ba4e5fbb
|
browser: 4c90336b-bf1d-4b87-916d-3677ba4e5fbb
|
||||||
protocol_mappers:
|
protocol_mappers:
|
||||||
- config:
|
- config:
|
||||||
access.token.claim: true
|
access.token.claim: true
|
||||||
|
@ -717,11 +782,17 @@ end_state:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||||
keycloak_argument_spec, get_token, KeycloakError
|
keycloak_argument_spec, get_token, KeycloakError, is_struct_included
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
|
||||||
|
PROTOCOL_OPENID_CONNECT = 'openid-connect'
|
||||||
|
PROTOCOL_SAML = 'saml'
|
||||||
|
PROTOCOL_DOCKER_V2 = 'docker-v2'
|
||||||
|
CLIENT_META_DATA = ['authorizationServicesEnabled']
|
||||||
|
|
||||||
|
|
||||||
def normalise_cr(clientrep, remove_ids=False):
|
def normalise_cr(clientrep, remove_ids=False):
|
||||||
""" Re-sorts any properties where the order so that diff's is minimised, and adds default values where appropriate so that the
|
""" Re-sorts any properties where the order so that diff's is minimised, and adds default values where appropriate so that the
|
||||||
the change detection is more effective.
|
the change detection is more effective.
|
||||||
|
@ -737,6 +808,12 @@ def normalise_cr(clientrep, remove_ids=False):
|
||||||
if 'attributes' in clientrep:
|
if 'attributes' in clientrep:
|
||||||
clientrep['attributes'] = list(sorted(clientrep['attributes']))
|
clientrep['attributes'] = list(sorted(clientrep['attributes']))
|
||||||
|
|
||||||
|
if 'defaultClientScopes' in clientrep:
|
||||||
|
clientrep['defaultClientScopes'] = list(sorted(clientrep['defaultClientScopes']))
|
||||||
|
|
||||||
|
if 'optionalClientScopes' in clientrep:
|
||||||
|
clientrep['optionalClientScopes'] = list(sorted(clientrep['optionalClientScopes']))
|
||||||
|
|
||||||
if 'redirectUris' in clientrep:
|
if 'redirectUris' in clientrep:
|
||||||
clientrep['redirectUris'] = list(sorted(clientrep['redirectUris']))
|
clientrep['redirectUris'] = list(sorted(clientrep['redirectUris']))
|
||||||
|
|
||||||
|
@ -762,11 +839,70 @@ def sanitize_cr(clientrep):
|
||||||
if 'secret' in result:
|
if 'secret' in result:
|
||||||
result['secret'] = 'no_log'
|
result['secret'] = 'no_log'
|
||||||
if 'attributes' in result:
|
if 'attributes' in result:
|
||||||
if 'saml.signing.private.key' in result['attributes']:
|
attributes = result['attributes']
|
||||||
result['attributes']['saml.signing.private.key'] = 'no_log'
|
if isinstance(attributes, dict) and 'saml.signing.private.key' in attributes:
|
||||||
|
attributes['saml.signing.private.key'] = 'no_log'
|
||||||
return normalise_cr(result)
|
return normalise_cr(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_authentication_flow_id(flow_name, realm, kc):
|
||||||
|
""" Get the authentication flow ID based on the flow name, realm, and Keycloak client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flow_name (str): The name of the authentication flow.
|
||||||
|
realm (str): The name of the realm.
|
||||||
|
kc (KeycloakClient): The Keycloak client instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The ID of the authentication flow.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||||
|
"""
|
||||||
|
flow = kc.get_authentication_flow_by_alias(flow_name, realm)
|
||||||
|
if flow:
|
||||||
|
return flow["id"]
|
||||||
|
kc.module.fail_json(msg='Authentification flow %s not found in realm %s' % (flow_name, realm))
|
||||||
|
|
||||||
|
|
||||||
|
def flow_binding_from_dict_to_model(newClientFlowBinding, realm, kc):
|
||||||
|
""" Convert a dictionary representing client flow bindings to a model representation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
newClientFlowBinding (dict): A dictionary containing client flow bindings.
|
||||||
|
realm (str): The name of the realm.
|
||||||
|
kc (KeycloakClient): An instance of the KeycloakClient class.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary representing the model flow bindings. The dictionary has two keys:
|
||||||
|
- "browser" (str or None): The ID of the browser authentication flow binding, or None if not provided.
|
||||||
|
- "direct_grant" (str or None): The ID of the direct grant authentication flow binding, or None if not provided.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
modelFlow = {
|
||||||
|
"browser": None,
|
||||||
|
"direct_grant": None
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in newClientFlowBinding.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == "browser":
|
||||||
|
modelFlow["browser"] = v
|
||||||
|
elif k == "browser_name":
|
||||||
|
modelFlow["browser"] = get_authentication_flow_id(v, realm, kc)
|
||||||
|
elif k == "direct_grant":
|
||||||
|
modelFlow["direct_grant"] = v
|
||||||
|
elif k == "direct_grant_name":
|
||||||
|
modelFlow["direct_grant"] = get_authentication_flow_id(v, realm, kc)
|
||||||
|
|
||||||
|
return modelFlow
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Module execution
|
Module execution
|
||||||
|
@ -780,11 +916,18 @@ def main():
|
||||||
consentText=dict(type='str'),
|
consentText=dict(type='str'),
|
||||||
id=dict(type='str'),
|
id=dict(type='str'),
|
||||||
name=dict(type='str'),
|
name=dict(type='str'),
|
||||||
protocol=dict(type='str', choices=['openid-connect', 'saml']),
|
protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]),
|
||||||
protocolMapper=dict(type='str'),
|
protocolMapper=dict(type='str'),
|
||||||
config=dict(type='dict'),
|
config=dict(type='dict'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
authentication_flow_spec = dict(
|
||||||
|
browser=dict(type='str'),
|
||||||
|
browser_name=dict(type='str', aliases=['browserName']),
|
||||||
|
direct_grant=dict(type='str', aliases=['directGrant']),
|
||||||
|
direct_grant_name=dict(type='str', aliases=['directGrantName']),
|
||||||
|
)
|
||||||
|
|
||||||
meta_args = dict(
|
meta_args = dict(
|
||||||
state=dict(default='present', choices=['present', 'absent']),
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
realm=dict(type='str', default='master'),
|
realm=dict(type='str', default='master'),
|
||||||
|
@ -798,7 +941,7 @@ def main():
|
||||||
base_url=dict(type='str', aliases=['baseUrl']),
|
base_url=dict(type='str', aliases=['baseUrl']),
|
||||||
surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']),
|
surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']),
|
||||||
enabled=dict(type='bool'),
|
enabled=dict(type='bool'),
|
||||||
client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt'], aliases=['clientAuthenticatorType']),
|
client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt', 'client-x509'], aliases=['clientAuthenticatorType']),
|
||||||
secret=dict(type='str', no_log=True),
|
secret=dict(type='str', no_log=True),
|
||||||
registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True),
|
registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True),
|
||||||
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
|
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
|
||||||
|
@ -814,7 +957,7 @@ def main():
|
||||||
authorization_services_enabled=dict(type='bool', aliases=['authorizationServicesEnabled']),
|
authorization_services_enabled=dict(type='bool', aliases=['authorizationServicesEnabled']),
|
||||||
public_client=dict(type='bool', aliases=['publicClient']),
|
public_client=dict(type='bool', aliases=['publicClient']),
|
||||||
frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']),
|
frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']),
|
||||||
protocol=dict(type='str', choices=['openid-connect', 'saml']),
|
protocol=dict(type='str', choices=[PROTOCOL_OPENID_CONNECT, PROTOCOL_SAML, PROTOCOL_DOCKER_V2]),
|
||||||
attributes=dict(type='dict'),
|
attributes=dict(type='dict'),
|
||||||
full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']),
|
full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']),
|
||||||
node_re_registration_timeout=dict(type='int', aliases=['nodeReRegistrationTimeout']),
|
node_re_registration_timeout=dict(type='int', aliases=['nodeReRegistrationTimeout']),
|
||||||
|
@ -824,7 +967,13 @@ def main():
|
||||||
use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
|
use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
|
||||||
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
|
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
|
||||||
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
|
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
|
||||||
authentication_flow_binding_overrides=dict(type='dict', aliases=['authenticationFlowBindingOverrides']),
|
authentication_flow_binding_overrides=dict(
|
||||||
|
type='dict',
|
||||||
|
aliases=['authenticationFlowBindingOverrides'],
|
||||||
|
options=authentication_flow_spec,
|
||||||
|
required_one_of=[['browser', 'direct_grant', 'browser_name', 'direct_grant_name']],
|
||||||
|
mutually_exclusive=[['browser', 'browser_name'], ['direct_grant', 'direct_grant_name']],
|
||||||
|
),
|
||||||
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
|
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
|
||||||
authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
|
authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
|
||||||
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
|
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
|
||||||
|
@ -885,7 +1034,9 @@ def main():
|
||||||
# Unfortunately, the ansible argument spec checker introduces variables with null values when
|
# Unfortunately, the ansible argument spec checker introduces variables with null values when
|
||||||
# they are not specified
|
# they are not specified
|
||||||
if client_param == 'protocol_mappers':
|
if client_param == 'protocol_mappers':
|
||||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
|
||||||
|
elif client_param == 'authentication_flow_binding_overrides':
|
||||||
|
new_param_value = flow_binding_from_dict_to_model(new_param_value, realm, kc)
|
||||||
|
|
||||||
changeset[camel(client_param)] = new_param_value
|
changeset[camel(client_param)] = new_param_value
|
||||||
|
|
||||||
|
@ -912,6 +1063,8 @@ def main():
|
||||||
|
|
||||||
if 'clientId' not in desired_client:
|
if 'clientId' not in desired_client:
|
||||||
module.fail_json(msg='client_id needs to be specified when creating a new client')
|
module.fail_json(msg='client_id needs to be specified when creating a new client')
|
||||||
|
if 'protocol' not in desired_client:
|
||||||
|
desired_client['protocol'] = PROTOCOL_OPENID_CONNECT
|
||||||
|
|
||||||
if module._diff:
|
if module._diff:
|
||||||
result['diff'] = dict(before='', after=sanitize_cr(desired_client))
|
result['diff'] = dict(before='', after=sanitize_cr(desired_client))
|
||||||
|
@ -940,7 +1093,7 @@ def main():
|
||||||
if module._diff:
|
if module._diff:
|
||||||
result['diff'] = dict(before=sanitize_cr(before_norm),
|
result['diff'] = dict(before=sanitize_cr(before_norm),
|
||||||
after=sanitize_cr(desired_norm))
|
after=sanitize_cr(desired_norm))
|
||||||
result['changed'] = (before_norm != desired_norm)
|
result['changed'] = not is_struct_included(desired_norm, before_norm, CLIENT_META_DATA)
|
||||||
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
848
plugins/modules/keycloak_realm.py
Normal file
848
plugins/modules/keycloak_realm.py
Normal file
|
@ -0,0 +1,848 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
|
||||||
|
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: keycloak_realm
|
||||||
|
|
||||||
|
short_description: Allows administration of Keycloak realm via Keycloak API
|
||||||
|
|
||||||
|
version_added: 3.0.0
|
||||||
|
|
||||||
|
description:
|
||||||
|
- This module allows the administration of Keycloak realm via the Keycloak REST API. It
|
||||||
|
requires access to the REST API via OpenID Connect; the user connecting and the realm being
|
||||||
|
used must have the requisite access rights. In a default Keycloak installation, admin-cli
|
||||||
|
and an admin user would work, as would a separate realm definition with the scope tailored
|
||||||
|
to your needs and a user having the expected roles.
|
||||||
|
|
||||||
|
- The names of module options are snake_cased versions of the camelCase ones found in the
|
||||||
|
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
|
||||||
|
Aliases are provided so camelCased versions can be used as well.
|
||||||
|
|
||||||
|
- The Keycloak API does not always sanity check inputs e.g. you can set
|
||||||
|
SAML-specific settings on an OpenID Connect client for instance and vice versa. Be careful.
|
||||||
|
If you do not specify a setting, usually a sensible default is chosen.
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: full
|
||||||
|
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the realm.
|
||||||
|
- On V(present), the realm will be created (or updated if it exists already).
|
||||||
|
- On V(absent), the realm will be removed if it exists.
|
||||||
|
choices: ['present', 'absent']
|
||||||
|
default: 'present'
|
||||||
|
type: str
|
||||||
|
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- The realm to create.
|
||||||
|
type: str
|
||||||
|
realm:
|
||||||
|
description:
|
||||||
|
- The realm name.
|
||||||
|
type: str
|
||||||
|
access_code_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm access code lifespan.
|
||||||
|
aliases:
|
||||||
|
- accessCodeLifespan
|
||||||
|
type: int
|
||||||
|
access_code_lifespan_login:
|
||||||
|
description:
|
||||||
|
- The realm access code lifespan login.
|
||||||
|
aliases:
|
||||||
|
- accessCodeLifespanLogin
|
||||||
|
type: int
|
||||||
|
access_code_lifespan_user_action:
|
||||||
|
description:
|
||||||
|
- The realm access code lifespan user action.
|
||||||
|
aliases:
|
||||||
|
- accessCodeLifespanUserAction
|
||||||
|
type: int
|
||||||
|
access_token_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm access token lifespan.
|
||||||
|
aliases:
|
||||||
|
- accessTokenLifespan
|
||||||
|
type: int
|
||||||
|
access_token_lifespan_for_implicit_flow:
|
||||||
|
description:
|
||||||
|
- The realm access token lifespan for implicit flow.
|
||||||
|
aliases:
|
||||||
|
- accessTokenLifespanForImplicitFlow
|
||||||
|
type: int
|
||||||
|
account_theme:
|
||||||
|
description:
|
||||||
|
- The realm account theme.
|
||||||
|
aliases:
|
||||||
|
- accountTheme
|
||||||
|
type: str
|
||||||
|
action_token_generated_by_admin_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm action token generated by admin lifespan.
|
||||||
|
aliases:
|
||||||
|
- actionTokenGeneratedByAdminLifespan
|
||||||
|
type: int
|
||||||
|
action_token_generated_by_user_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm action token generated by user lifespan.
|
||||||
|
aliases:
|
||||||
|
- actionTokenGeneratedByUserLifespan
|
||||||
|
type: int
|
||||||
|
admin_events_details_enabled:
|
||||||
|
description:
|
||||||
|
- The realm admin events details enabled.
|
||||||
|
aliases:
|
||||||
|
- adminEventsDetailsEnabled
|
||||||
|
type: bool
|
||||||
|
admin_events_enabled:
|
||||||
|
description:
|
||||||
|
- The realm admin events enabled.
|
||||||
|
aliases:
|
||||||
|
- adminEventsEnabled
|
||||||
|
type: bool
|
||||||
|
admin_theme:
|
||||||
|
description:
|
||||||
|
- The realm admin theme.
|
||||||
|
aliases:
|
||||||
|
- adminTheme
|
||||||
|
type: str
|
||||||
|
attributes:
|
||||||
|
description:
|
||||||
|
- The realm attributes.
|
||||||
|
type: dict
|
||||||
|
browser_flow:
|
||||||
|
description:
|
||||||
|
- The realm browser flow.
|
||||||
|
aliases:
|
||||||
|
- browserFlow
|
||||||
|
type: str
|
||||||
|
browser_security_headers:
|
||||||
|
description:
|
||||||
|
- The realm browser security headers.
|
||||||
|
aliases:
|
||||||
|
- browserSecurityHeaders
|
||||||
|
type: dict
|
||||||
|
brute_force_protected:
|
||||||
|
description:
|
||||||
|
- The realm brute force protected.
|
||||||
|
aliases:
|
||||||
|
- bruteForceProtected
|
||||||
|
type: bool
|
||||||
|
client_authentication_flow:
|
||||||
|
description:
|
||||||
|
- The realm client authentication flow.
|
||||||
|
aliases:
|
||||||
|
- clientAuthenticationFlow
|
||||||
|
type: str
|
||||||
|
client_scope_mappings:
|
||||||
|
description:
|
||||||
|
- The realm client scope mappings.
|
||||||
|
aliases:
|
||||||
|
- clientScopeMappings
|
||||||
|
type: dict
|
||||||
|
default_default_client_scopes:
|
||||||
|
description:
|
||||||
|
- The realm default default client scopes.
|
||||||
|
aliases:
|
||||||
|
- defaultDefaultClientScopes
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default_groups:
|
||||||
|
description:
|
||||||
|
- The realm default groups.
|
||||||
|
aliases:
|
||||||
|
- defaultGroups
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default_locale:
|
||||||
|
description:
|
||||||
|
- The realm default locale.
|
||||||
|
aliases:
|
||||||
|
- defaultLocale
|
||||||
|
type: str
|
||||||
|
default_optional_client_scopes:
|
||||||
|
description:
|
||||||
|
- The realm default optional client scopes.
|
||||||
|
aliases:
|
||||||
|
- defaultOptionalClientScopes
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default_roles:
|
||||||
|
description:
|
||||||
|
- The realm default roles.
|
||||||
|
aliases:
|
||||||
|
- defaultRoles
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
default_signature_algorithm:
|
||||||
|
description:
|
||||||
|
- The realm default signature algorithm.
|
||||||
|
aliases:
|
||||||
|
- defaultSignatureAlgorithm
|
||||||
|
type: str
|
||||||
|
direct_grant_flow:
|
||||||
|
description:
|
||||||
|
- The realm direct grant flow.
|
||||||
|
aliases:
|
||||||
|
- directGrantFlow
|
||||||
|
type: str
|
||||||
|
display_name:
|
||||||
|
description:
|
||||||
|
- The realm display name.
|
||||||
|
aliases:
|
||||||
|
- displayName
|
||||||
|
type: str
|
||||||
|
display_name_html:
|
||||||
|
description:
|
||||||
|
- The realm display name HTML.
|
||||||
|
aliases:
|
||||||
|
- displayNameHtml
|
||||||
|
type: str
|
||||||
|
docker_authentication_flow:
|
||||||
|
description:
|
||||||
|
- The realm docker authentication flow.
|
||||||
|
aliases:
|
||||||
|
- dockerAuthenticationFlow
|
||||||
|
type: str
|
||||||
|
duplicate_emails_allowed:
|
||||||
|
description:
|
||||||
|
- The realm duplicate emails allowed option.
|
||||||
|
aliases:
|
||||||
|
- duplicateEmailsAllowed
|
||||||
|
type: bool
|
||||||
|
edit_username_allowed:
|
||||||
|
description:
|
||||||
|
- The realm edit username allowed option.
|
||||||
|
aliases:
|
||||||
|
- editUsernameAllowed
|
||||||
|
type: bool
|
||||||
|
email_theme:
|
||||||
|
description:
|
||||||
|
- The realm email theme.
|
||||||
|
aliases:
|
||||||
|
- emailTheme
|
||||||
|
type: str
|
||||||
|
enabled:
|
||||||
|
description:
|
||||||
|
- The realm enabled option.
|
||||||
|
type: bool
|
||||||
|
enabled_event_types:
|
||||||
|
description:
|
||||||
|
- The realm enabled event types.
|
||||||
|
aliases:
|
||||||
|
- enabledEventTypes
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
events_enabled:
|
||||||
|
description:
|
||||||
|
- Enables or disables login events for this realm.
|
||||||
|
aliases:
|
||||||
|
- eventsEnabled
|
||||||
|
type: bool
|
||||||
|
version_added: 3.6.0
|
||||||
|
events_expiration:
|
||||||
|
description:
|
||||||
|
- The realm events expiration.
|
||||||
|
aliases:
|
||||||
|
- eventsExpiration
|
||||||
|
type: int
|
||||||
|
events_listeners:
|
||||||
|
description:
|
||||||
|
- The realm events listeners.
|
||||||
|
aliases:
|
||||||
|
- eventsListeners
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
failure_factor:
|
||||||
|
description:
|
||||||
|
- The realm failure factor.
|
||||||
|
aliases:
|
||||||
|
- failureFactor
|
||||||
|
type: int
|
||||||
|
internationalization_enabled:
|
||||||
|
description:
|
||||||
|
- The realm internationalization enabled option.
|
||||||
|
aliases:
|
||||||
|
- internationalizationEnabled
|
||||||
|
type: bool
|
||||||
|
login_theme:
|
||||||
|
description:
|
||||||
|
- The realm login theme.
|
||||||
|
aliases:
|
||||||
|
- loginTheme
|
||||||
|
type: str
|
||||||
|
login_with_email_allowed:
|
||||||
|
description:
|
||||||
|
- The realm login with email allowed option.
|
||||||
|
aliases:
|
||||||
|
- loginWithEmailAllowed
|
||||||
|
type: bool
|
||||||
|
max_delta_time_seconds:
|
||||||
|
description:
|
||||||
|
- The realm max delta time in seconds.
|
||||||
|
aliases:
|
||||||
|
- maxDeltaTimeSeconds
|
||||||
|
type: int
|
||||||
|
max_failure_wait_seconds:
|
||||||
|
description:
|
||||||
|
- The realm max failure wait in seconds.
|
||||||
|
aliases:
|
||||||
|
- maxFailureWaitSeconds
|
||||||
|
type: int
|
||||||
|
minimum_quick_login_wait_seconds:
|
||||||
|
description:
|
||||||
|
- The realm minimum quick login wait in seconds.
|
||||||
|
aliases:
|
||||||
|
- minimumQuickLoginWaitSeconds
|
||||||
|
type: int
|
||||||
|
not_before:
|
||||||
|
description:
|
||||||
|
- The realm not before.
|
||||||
|
aliases:
|
||||||
|
- notBefore
|
||||||
|
type: int
|
||||||
|
offline_session_idle_timeout:
|
||||||
|
description:
|
||||||
|
- The realm offline session idle timeout.
|
||||||
|
aliases:
|
||||||
|
- offlineSessionIdleTimeout
|
||||||
|
type: int
|
||||||
|
offline_session_max_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm offline session max lifespan.
|
||||||
|
aliases:
|
||||||
|
- offlineSessionMaxLifespan
|
||||||
|
type: int
|
||||||
|
offline_session_max_lifespan_enabled:
|
||||||
|
description:
|
||||||
|
- The realm offline session max lifespan enabled option.
|
||||||
|
aliases:
|
||||||
|
- offlineSessionMaxLifespanEnabled
|
||||||
|
type: bool
|
||||||
|
otp_policy_algorithm:
|
||||||
|
description:
|
||||||
|
- The realm otp policy algorithm.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyAlgorithm
|
||||||
|
type: str
|
||||||
|
otp_policy_digits:
|
||||||
|
description:
|
||||||
|
- The realm otp policy digits.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyDigits
|
||||||
|
type: int
|
||||||
|
otp_policy_initial_counter:
|
||||||
|
description:
|
||||||
|
- The realm otp policy initial counter.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyInitialCounter
|
||||||
|
type: int
|
||||||
|
otp_policy_look_ahead_window:
|
||||||
|
description:
|
||||||
|
- The realm otp policy look ahead window.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyLookAheadWindow
|
||||||
|
type: int
|
||||||
|
otp_policy_period:
|
||||||
|
description:
|
||||||
|
- The realm otp policy period.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyPeriod
|
||||||
|
type: int
|
||||||
|
otp_policy_type:
|
||||||
|
description:
|
||||||
|
- The realm otp policy type.
|
||||||
|
aliases:
|
||||||
|
- otpPolicyType
|
||||||
|
type: str
|
||||||
|
otp_supported_applications:
|
||||||
|
description:
|
||||||
|
- The realm otp supported applications.
|
||||||
|
aliases:
|
||||||
|
- otpSupportedApplications
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
password_policy:
|
||||||
|
description:
|
||||||
|
- The realm password policy.
|
||||||
|
aliases:
|
||||||
|
- passwordPolicy
|
||||||
|
type: str
|
||||||
|
permanent_lockout:
|
||||||
|
description:
|
||||||
|
- The realm permanent lockout.
|
||||||
|
aliases:
|
||||||
|
- permanentLockout
|
||||||
|
type: bool
|
||||||
|
quick_login_check_milli_seconds:
|
||||||
|
description:
|
||||||
|
- The realm quick login check in milliseconds.
|
||||||
|
aliases:
|
||||||
|
- quickLoginCheckMilliSeconds
|
||||||
|
type: int
|
||||||
|
refresh_token_max_reuse:
|
||||||
|
description:
|
||||||
|
- The realm refresh token max reuse.
|
||||||
|
aliases:
|
||||||
|
- refreshTokenMaxReuse
|
||||||
|
type: int
|
||||||
|
registration_allowed:
|
||||||
|
description:
|
||||||
|
- The realm registration allowed option.
|
||||||
|
aliases:
|
||||||
|
- registrationAllowed
|
||||||
|
type: bool
|
||||||
|
registration_email_as_username:
|
||||||
|
description:
|
||||||
|
- The realm registration email as username option.
|
||||||
|
aliases:
|
||||||
|
- registrationEmailAsUsername
|
||||||
|
type: bool
|
||||||
|
registration_flow:
|
||||||
|
description:
|
||||||
|
- The realm registration flow.
|
||||||
|
aliases:
|
||||||
|
- registrationFlow
|
||||||
|
type: str
|
||||||
|
remember_me:
|
||||||
|
description:
|
||||||
|
- The realm remember me option.
|
||||||
|
aliases:
|
||||||
|
- rememberMe
|
||||||
|
type: bool
|
||||||
|
reset_credentials_flow:
|
||||||
|
description:
|
||||||
|
- The realm reset credentials flow.
|
||||||
|
aliases:
|
||||||
|
- resetCredentialsFlow
|
||||||
|
type: str
|
||||||
|
reset_password_allowed:
|
||||||
|
description:
|
||||||
|
- The realm reset password allowed option.
|
||||||
|
aliases:
|
||||||
|
- resetPasswordAllowed
|
||||||
|
type: bool
|
||||||
|
revoke_refresh_token:
|
||||||
|
description:
|
||||||
|
- The realm revoke refresh token option.
|
||||||
|
aliases:
|
||||||
|
- revokeRefreshToken
|
||||||
|
type: bool
|
||||||
|
smtp_server:
|
||||||
|
description:
|
||||||
|
- The realm smtp server.
|
||||||
|
aliases:
|
||||||
|
- smtpServer
|
||||||
|
type: dict
|
||||||
|
ssl_required:
|
||||||
|
description:
|
||||||
|
- The realm ssl required option.
|
||||||
|
choices: ['all', 'external', 'none']
|
||||||
|
aliases:
|
||||||
|
- sslRequired
|
||||||
|
type: str
|
||||||
|
sso_session_idle_timeout:
|
||||||
|
description:
|
||||||
|
- The realm sso session idle timeout.
|
||||||
|
aliases:
|
||||||
|
- ssoSessionIdleTimeout
|
||||||
|
type: int
|
||||||
|
sso_session_idle_timeout_remember_me:
|
||||||
|
description:
|
||||||
|
- The realm sso session idle timeout remember me.
|
||||||
|
aliases:
|
||||||
|
- ssoSessionIdleTimeoutRememberMe
|
||||||
|
type: int
|
||||||
|
sso_session_max_lifespan:
|
||||||
|
description:
|
||||||
|
- The realm sso session max lifespan.
|
||||||
|
aliases:
|
||||||
|
- ssoSessionMaxLifespan
|
||||||
|
type: int
|
||||||
|
sso_session_max_lifespan_remember_me:
|
||||||
|
description:
|
||||||
|
- The realm sso session max lifespan remember me.
|
||||||
|
aliases:
|
||||||
|
- ssoSessionMaxLifespanRememberMe
|
||||||
|
type: int
|
||||||
|
supported_locales:
|
||||||
|
description:
|
||||||
|
- The realm supported locales.
|
||||||
|
aliases:
|
||||||
|
- supportedLocales
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
user_managed_access_allowed:
|
||||||
|
description:
|
||||||
|
- The realm user managed access allowed option.
|
||||||
|
aliases:
|
||||||
|
- userManagedAccessAllowed
|
||||||
|
type: bool
|
||||||
|
verify_email:
|
||||||
|
description:
|
||||||
|
- The realm verify email option.
|
||||||
|
aliases:
|
||||||
|
- verifyEmail
|
||||||
|
type: bool
|
||||||
|
wait_increment_seconds:
|
||||||
|
description:
|
||||||
|
- The realm wait increment in seconds.
|
||||||
|
aliases:
|
||||||
|
- waitIncrementSeconds
|
||||||
|
type: int
|
||||||
|
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- middleware_automation.keycloak.keycloak
|
||||||
|
- middleware_automation.keycloak.attributes
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Christophe Gilles (@kris2kris)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create or update Keycloak realm (minimal example)
|
||||||
|
middleware_automation.keycloak.keycloak_realm:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
id: realm
|
||||||
|
realm: realm
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Delete a Keycloak realm
|
||||||
|
middleware_automation.keycloak.keycloak_realm:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
id: test
|
||||||
|
state: absent
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
msg:
|
||||||
|
description: Message as to what action was taken.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: "Realm testrealm has been updated"
|
||||||
|
|
||||||
|
proposed:
|
||||||
|
description: Representation of proposed realm.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
id: "test"
|
||||||
|
}
|
||||||
|
|
||||||
|
existing:
|
||||||
|
description: Representation of existing realm (sample is truncated).
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"adminUrl": "http://www.example.com/admin_url",
|
||||||
|
"attributes": {
|
||||||
|
"request.object.signature.alg": "RS256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end_state:
|
||||||
|
description: Representation of realm after module execution (sample is truncated).
|
||||||
|
returned: on success
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"adminUrl": "http://www.example.com/admin_url",
|
||||||
|
"attributes": {
|
||||||
|
"request.object.signature.alg": "RS256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||||
|
keycloak_argument_spec, get_token, KeycloakError
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
|
||||||
|
def normalise_cr(realmrep):
|
||||||
|
""" Re-sorts any properties where the order is important so that diff's is minimised and the change detection is more effective.
|
||||||
|
|
||||||
|
:param realmrep: the realmrep dict to be sanitized
|
||||||
|
:return: normalised realmrep dict
|
||||||
|
"""
|
||||||
|
# Avoid the dict passed in to be modified
|
||||||
|
realmrep = realmrep.copy()
|
||||||
|
|
||||||
|
if 'enabledEventTypes' in realmrep:
|
||||||
|
realmrep['enabledEventTypes'] = list(sorted(realmrep['enabledEventTypes']))
|
||||||
|
|
||||||
|
if 'otpSupportedApplications' in realmrep:
|
||||||
|
realmrep['otpSupportedApplications'] = list(sorted(realmrep['otpSupportedApplications']))
|
||||||
|
|
||||||
|
if 'supportedLocales' in realmrep:
|
||||||
|
realmrep['supportedLocales'] = list(sorted(realmrep['supportedLocales']))
|
||||||
|
|
||||||
|
return realmrep
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_cr(realmrep):
|
||||||
|
""" Removes probably sensitive details from a realm representation.
|
||||||
|
|
||||||
|
:param realmrep: the realmrep dict to be sanitized
|
||||||
|
:return: sanitized realmrep dict
|
||||||
|
"""
|
||||||
|
result = realmrep.copy()
|
||||||
|
if 'secret' in result:
|
||||||
|
result['secret'] = '********'
|
||||||
|
if 'attributes' in result:
|
||||||
|
if 'saml.signing.private.key' in result['attributes']:
|
||||||
|
result['attributes'] = result['attributes'].copy()
|
||||||
|
result['attributes']['saml.signing.private.key'] = '********'
|
||||||
|
return normalise_cr(result)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Module execution
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
argument_spec = keycloak_argument_spec()
|
||||||
|
|
||||||
|
meta_args = dict(
|
||||||
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
|
|
||||||
|
id=dict(type='str'),
|
||||||
|
realm=dict(type='str'),
|
||||||
|
access_code_lifespan=dict(type='int', aliases=['accessCodeLifespan']),
|
||||||
|
access_code_lifespan_login=dict(type='int', aliases=['accessCodeLifespanLogin']),
|
||||||
|
access_code_lifespan_user_action=dict(type='int', aliases=['accessCodeLifespanUserAction']),
|
||||||
|
access_token_lifespan=dict(type='int', aliases=['accessTokenLifespan'], no_log=False),
|
||||||
|
access_token_lifespan_for_implicit_flow=dict(type='int', aliases=['accessTokenLifespanForImplicitFlow'], no_log=False),
|
||||||
|
account_theme=dict(type='str', aliases=['accountTheme']),
|
||||||
|
action_token_generated_by_admin_lifespan=dict(type='int', aliases=['actionTokenGeneratedByAdminLifespan'], no_log=False),
|
||||||
|
action_token_generated_by_user_lifespan=dict(type='int', aliases=['actionTokenGeneratedByUserLifespan'], no_log=False),
|
||||||
|
admin_events_details_enabled=dict(type='bool', aliases=['adminEventsDetailsEnabled']),
|
||||||
|
admin_events_enabled=dict(type='bool', aliases=['adminEventsEnabled']),
|
||||||
|
admin_theme=dict(type='str', aliases=['adminTheme']),
|
||||||
|
attributes=dict(type='dict'),
|
||||||
|
browser_flow=dict(type='str', aliases=['browserFlow']),
|
||||||
|
browser_security_headers=dict(type='dict', aliases=['browserSecurityHeaders']),
|
||||||
|
brute_force_protected=dict(type='bool', aliases=['bruteForceProtected']),
|
||||||
|
client_authentication_flow=dict(type='str', aliases=['clientAuthenticationFlow']),
|
||||||
|
client_scope_mappings=dict(type='dict', aliases=['clientScopeMappings']),
|
||||||
|
default_default_client_scopes=dict(type='list', elements='str', aliases=['defaultDefaultClientScopes']),
|
||||||
|
default_groups=dict(type='list', elements='str', aliases=['defaultGroups']),
|
||||||
|
default_locale=dict(type='str', aliases=['defaultLocale']),
|
||||||
|
default_optional_client_scopes=dict(type='list', elements='str', aliases=['defaultOptionalClientScopes']),
|
||||||
|
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
|
||||||
|
default_signature_algorithm=dict(type='str', aliases=['defaultSignatureAlgorithm']),
|
||||||
|
direct_grant_flow=dict(type='str', aliases=['directGrantFlow']),
|
||||||
|
display_name=dict(type='str', aliases=['displayName']),
|
||||||
|
display_name_html=dict(type='str', aliases=['displayNameHtml']),
|
||||||
|
docker_authentication_flow=dict(type='str', aliases=['dockerAuthenticationFlow']),
|
||||||
|
duplicate_emails_allowed=dict(type='bool', aliases=['duplicateEmailsAllowed']),
|
||||||
|
edit_username_allowed=dict(type='bool', aliases=['editUsernameAllowed']),
|
||||||
|
email_theme=dict(type='str', aliases=['emailTheme']),
|
||||||
|
enabled=dict(type='bool'),
|
||||||
|
enabled_event_types=dict(type='list', elements='str', aliases=['enabledEventTypes']),
|
||||||
|
events_enabled=dict(type='bool', aliases=['eventsEnabled']),
|
||||||
|
events_expiration=dict(type='int', aliases=['eventsExpiration']),
|
||||||
|
events_listeners=dict(type='list', elements='str', aliases=['eventsListeners']),
|
||||||
|
failure_factor=dict(type='int', aliases=['failureFactor']),
|
||||||
|
internationalization_enabled=dict(type='bool', aliases=['internationalizationEnabled']),
|
||||||
|
login_theme=dict(type='str', aliases=['loginTheme']),
|
||||||
|
login_with_email_allowed=dict(type='bool', aliases=['loginWithEmailAllowed']),
|
||||||
|
max_delta_time_seconds=dict(type='int', aliases=['maxDeltaTimeSeconds']),
|
||||||
|
max_failure_wait_seconds=dict(type='int', aliases=['maxFailureWaitSeconds']),
|
||||||
|
minimum_quick_login_wait_seconds=dict(type='int', aliases=['minimumQuickLoginWaitSeconds']),
|
||||||
|
not_before=dict(type='int', aliases=['notBefore']),
|
||||||
|
offline_session_idle_timeout=dict(type='int', aliases=['offlineSessionIdleTimeout']),
|
||||||
|
offline_session_max_lifespan=dict(type='int', aliases=['offlineSessionMaxLifespan']),
|
||||||
|
offline_session_max_lifespan_enabled=dict(type='bool', aliases=['offlineSessionMaxLifespanEnabled']),
|
||||||
|
otp_policy_algorithm=dict(type='str', aliases=['otpPolicyAlgorithm']),
|
||||||
|
otp_policy_digits=dict(type='int', aliases=['otpPolicyDigits']),
|
||||||
|
otp_policy_initial_counter=dict(type='int', aliases=['otpPolicyInitialCounter']),
|
||||||
|
otp_policy_look_ahead_window=dict(type='int', aliases=['otpPolicyLookAheadWindow']),
|
||||||
|
otp_policy_period=dict(type='int', aliases=['otpPolicyPeriod']),
|
||||||
|
otp_policy_type=dict(type='str', aliases=['otpPolicyType']),
|
||||||
|
otp_supported_applications=dict(type='list', elements='str', aliases=['otpSupportedApplications']),
|
||||||
|
password_policy=dict(type='str', aliases=['passwordPolicy'], no_log=False),
|
||||||
|
permanent_lockout=dict(type='bool', aliases=['permanentLockout']),
|
||||||
|
quick_login_check_milli_seconds=dict(type='int', aliases=['quickLoginCheckMilliSeconds']),
|
||||||
|
refresh_token_max_reuse=dict(type='int', aliases=['refreshTokenMaxReuse'], no_log=False),
|
||||||
|
registration_allowed=dict(type='bool', aliases=['registrationAllowed']),
|
||||||
|
registration_email_as_username=dict(type='bool', aliases=['registrationEmailAsUsername']),
|
||||||
|
registration_flow=dict(type='str', aliases=['registrationFlow']),
|
||||||
|
remember_me=dict(type='bool', aliases=['rememberMe']),
|
||||||
|
reset_credentials_flow=dict(type='str', aliases=['resetCredentialsFlow']),
|
||||||
|
reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed'], no_log=False),
|
||||||
|
revoke_refresh_token=dict(type='bool', aliases=['revokeRefreshToken']),
|
||||||
|
smtp_server=dict(type='dict', aliases=['smtpServer']),
|
||||||
|
ssl_required=dict(choices=["external", "all", "none"], aliases=['sslRequired']),
|
||||||
|
sso_session_idle_timeout=dict(type='int', aliases=['ssoSessionIdleTimeout']),
|
||||||
|
sso_session_idle_timeout_remember_me=dict(type='int', aliases=['ssoSessionIdleTimeoutRememberMe']),
|
||||||
|
sso_session_max_lifespan=dict(type='int', aliases=['ssoSessionMaxLifespan']),
|
||||||
|
sso_session_max_lifespan_remember_me=dict(type='int', aliases=['ssoSessionMaxLifespanRememberMe']),
|
||||||
|
supported_locales=dict(type='list', elements='str', aliases=['supportedLocales']),
|
||||||
|
user_managed_access_allowed=dict(type='bool', aliases=['userManagedAccessAllowed']),
|
||||||
|
verify_email=dict(type='bool', aliases=['verifyEmail']),
|
||||||
|
wait_increment_seconds=dict(type='int', aliases=['waitIncrementSeconds']),
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(meta_args)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=([['id', 'realm', 'enabled'],
|
||||||
|
['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||||
|
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||||
|
|
||||||
|
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
|
||||||
|
|
||||||
|
# Obtain access token, initialize API
|
||||||
|
try:
|
||||||
|
connection_header = get_token(module.params)
|
||||||
|
except KeycloakError as e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
kc = KeycloakAPI(module, connection_header)
|
||||||
|
|
||||||
|
realm = module.params.get('realm')
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
# convert module parameters to realm representation parameters (if they belong in there)
|
||||||
|
params_to_ignore = list(keycloak_argument_spec().keys()) + ['state']
|
||||||
|
|
||||||
|
# Filter and map the parameters names that apply to the role
|
||||||
|
realm_params = [x for x in module.params
|
||||||
|
if x not in params_to_ignore and
|
||||||
|
module.params.get(x) is not None]
|
||||||
|
|
||||||
|
# See whether the realm already exists in Keycloak
|
||||||
|
before_realm = kc.get_realm_by_id(realm=realm)
|
||||||
|
|
||||||
|
if before_realm is None:
|
||||||
|
before_realm = {}
|
||||||
|
|
||||||
|
# Build a proposed changeset from parameters given to this module
|
||||||
|
changeset = {}
|
||||||
|
|
||||||
|
for realm_param in realm_params:
|
||||||
|
new_param_value = module.params.get(realm_param)
|
||||||
|
changeset[camel(realm_param)] = new_param_value
|
||||||
|
|
||||||
|
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||||
|
desired_realm = before_realm.copy()
|
||||||
|
desired_realm.update(changeset)
|
||||||
|
|
||||||
|
result['proposed'] = sanitize_cr(changeset)
|
||||||
|
before_realm_sanitized = sanitize_cr(before_realm)
|
||||||
|
result['existing'] = before_realm_sanitized
|
||||||
|
|
||||||
|
# Cater for when it doesn't exist (an empty dict)
|
||||||
|
if not before_realm:
|
||||||
|
if state == 'absent':
|
||||||
|
# Do nothing and exit
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after='')
|
||||||
|
result['changed'] = False
|
||||||
|
result['end_state'] = {}
|
||||||
|
result['msg'] = 'Realm does not exist, doing nothing.'
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# Process a creation
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
if 'id' not in desired_realm:
|
||||||
|
module.fail_json(msg='id needs to be specified when creating a new realm')
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after=sanitize_cr(desired_realm))
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# create it
|
||||||
|
kc.create_realm(desired_realm)
|
||||||
|
after_realm = kc.get_realm_by_id(desired_realm['id'])
|
||||||
|
|
||||||
|
result['end_state'] = sanitize_cr(after_realm)
|
||||||
|
|
||||||
|
result['msg'] = 'Realm %s has been created.' % desired_realm['id']
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if state == 'present':
|
||||||
|
# Process an update
|
||||||
|
|
||||||
|
# doing an update
|
||||||
|
result['changed'] = True
|
||||||
|
if module.check_mode:
|
||||||
|
# We can only compare the current realm with the proposed updates we have
|
||||||
|
before_norm = normalise_cr(before_realm)
|
||||||
|
desired_norm = normalise_cr(desired_realm)
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=sanitize_cr(before_norm),
|
||||||
|
after=sanitize_cr(desired_norm))
|
||||||
|
result['changed'] = (before_norm != desired_norm)
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# do the update
|
||||||
|
kc.update_realm(desired_realm, realm=realm)
|
||||||
|
|
||||||
|
after_realm = kc.get_realm_by_id(realm=realm)
|
||||||
|
|
||||||
|
if before_realm == after_realm:
|
||||||
|
result['changed'] = False
|
||||||
|
|
||||||
|
result['end_state'] = sanitize_cr(after_realm)
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=before_realm_sanitized,
|
||||||
|
after=sanitize_cr(after_realm))
|
||||||
|
|
||||||
|
result['msg'] = 'Realm %s has been updated.' % desired_realm['id']
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Process a deletion (because state was not 'present')
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=before_realm_sanitized, after='')
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# delete it
|
||||||
|
kc.delete_realm(realm=realm)
|
||||||
|
|
||||||
|
result['proposed'] = {}
|
||||||
|
result['end_state'] = {}
|
||||||
|
|
||||||
|
result['msg'] = 'Realm %s has been deleted.' % before_realm['id']
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -40,8 +40,8 @@ options:
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- State of the role.
|
- State of the role.
|
||||||
- On C(present), the role will be created if it does not yet exist, or updated with the parameters you provide.
|
- On V(present), the role will be created if it does not yet exist, or updated with the parameters you provide.
|
||||||
- On C(absent), the role will be removed if it exists.
|
- On V(absent), the role will be removed if it exists.
|
||||||
default: 'present'
|
default: 'present'
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
|
@ -77,6 +77,42 @@ options:
|
||||||
description:
|
description:
|
||||||
- A dict of key/value pairs to set as custom attributes for the role.
|
- A dict of key/value pairs to set as custom attributes for the role.
|
||||||
- Values may be single values (e.g. a string) or a list of strings.
|
- Values may be single values (e.g. a string) or a list of strings.
|
||||||
|
composite:
|
||||||
|
description:
|
||||||
|
- If V(true), the role is a composition of other realm and/or client role.
|
||||||
|
default: false
|
||||||
|
type: bool
|
||||||
|
version_added: 7.1.0
|
||||||
|
composites:
|
||||||
|
description:
|
||||||
|
- List of roles to include to the composite realm role.
|
||||||
|
- If the composite role is a client role, the C(clientId) (not ID of the client) must be specified.
|
||||||
|
default: []
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
version_added: 7.1.0
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the role. This can be the name of a REALM role or a client role.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
client_id:
|
||||||
|
description:
|
||||||
|
- Client ID if the role is a client role. Do not include this option for a REALM role.
|
||||||
|
- Use the client ID you can see in the Keycloak console, not the technical ID of the client.
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
aliases:
|
||||||
|
- clientId
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- Create the composite if present, remove it if absent.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- present
|
||||||
|
- absent
|
||||||
|
default: present
|
||||||
|
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- middleware_automation.keycloak.keycloak
|
- middleware_automation.keycloak.keycloak
|
||||||
|
@ -142,14 +178,14 @@ EXAMPLES = '''
|
||||||
auth_password: PASSWORD
|
auth_password: PASSWORD
|
||||||
name: my-new-role
|
name: my-new-role
|
||||||
attributes:
|
attributes:
|
||||||
attrib1: value1
|
attrib1: value1
|
||||||
attrib2: value2
|
attrib2: value2
|
||||||
attrib3:
|
attrib3:
|
||||||
- with
|
- with
|
||||||
- numerous
|
- numerous
|
||||||
- individual
|
- individual
|
||||||
- list
|
- list
|
||||||
- items
|
- items
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -198,8 +234,9 @@ end_state:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||||
keycloak_argument_spec, get_token, KeycloakError
|
keycloak_argument_spec, get_token, KeycloakError, is_struct_included
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -210,6 +247,12 @@ def main():
|
||||||
"""
|
"""
|
||||||
argument_spec = keycloak_argument_spec()
|
argument_spec = keycloak_argument_spec()
|
||||||
|
|
||||||
|
composites_spec = dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
client_id=dict(type='str', aliases=['clientId'], required=False),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent'])
|
||||||
|
)
|
||||||
|
|
||||||
meta_args = dict(
|
meta_args = dict(
|
||||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
name=dict(type='str', required=True),
|
name=dict(type='str', required=True),
|
||||||
|
@ -217,6 +260,8 @@ def main():
|
||||||
realm=dict(type='str', default='master'),
|
realm=dict(type='str', default='master'),
|
||||||
client_id=dict(type='str'),
|
client_id=dict(type='str'),
|
||||||
attributes=dict(type='dict'),
|
attributes=dict(type='dict'),
|
||||||
|
composites=dict(type='list', default=[], options=composites_spec, elements='dict'),
|
||||||
|
composite=dict(type='bool', default=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
argument_spec.update(meta_args)
|
argument_spec.update(meta_args)
|
||||||
|
@ -250,7 +295,7 @@ def main():
|
||||||
|
|
||||||
# Filter and map the parameters names that apply to the role
|
# Filter and map the parameters names that apply to the role
|
||||||
role_params = [x for x in module.params
|
role_params = [x for x in module.params
|
||||||
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'client_id', 'composites'] and
|
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'client_id'] and
|
||||||
module.params.get(x) is not None]
|
module.params.get(x) is not None]
|
||||||
|
|
||||||
# See if it already exists in Keycloak
|
# See if it already exists in Keycloak
|
||||||
|
@ -269,10 +314,10 @@ def main():
|
||||||
new_param_value = module.params.get(param)
|
new_param_value = module.params.get(param)
|
||||||
old_value = before_role[param] if param in before_role else None
|
old_value = before_role[param] if param in before_role else None
|
||||||
if new_param_value != old_value:
|
if new_param_value != old_value:
|
||||||
changeset[camel(param)] = new_param_value
|
changeset[camel(param)] = copy.deepcopy(new_param_value)
|
||||||
|
|
||||||
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||||
desired_role = before_role.copy()
|
desired_role = copy.deepcopy(before_role)
|
||||||
desired_role.update(changeset)
|
desired_role.update(changeset)
|
||||||
|
|
||||||
result['proposed'] = changeset
|
result['proposed'] = changeset
|
||||||
|
@ -309,6 +354,9 @@ def main():
|
||||||
kc.create_client_role(desired_role, clientid, realm)
|
kc.create_client_role(desired_role, clientid, realm)
|
||||||
after_role = kc.get_client_role(name, clientid, realm)
|
after_role = kc.get_client_role(name, clientid, realm)
|
||||||
|
|
||||||
|
if after_role['composite']:
|
||||||
|
after_role['composites'] = kc.get_role_composites(rolerep=after_role, clientid=clientid, realm=realm)
|
||||||
|
|
||||||
result['end_state'] = after_role
|
result['end_state'] = after_role
|
||||||
|
|
||||||
result['msg'] = 'Role {name} has been created'.format(name=name)
|
result['msg'] = 'Role {name} has been created'.format(name=name)
|
||||||
|
@ -316,10 +364,25 @@ def main():
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
|
compare_exclude = []
|
||||||
|
if 'composites' in desired_role and isinstance(desired_role['composites'], list) and len(desired_role['composites']) > 0:
|
||||||
|
composites = kc.get_role_composites(rolerep=before_role, clientid=clientid, realm=realm)
|
||||||
|
before_role['composites'] = []
|
||||||
|
for composite in composites:
|
||||||
|
before_composite = {}
|
||||||
|
if composite['clientRole']:
|
||||||
|
composite_client = kc.get_client_by_id(id=composite['containerId'], realm=realm)
|
||||||
|
before_composite['client_id'] = composite_client['clientId']
|
||||||
|
else:
|
||||||
|
before_composite['client_id'] = None
|
||||||
|
before_composite['name'] = composite['name']
|
||||||
|
before_composite['state'] = 'present'
|
||||||
|
before_role['composites'].append(before_composite)
|
||||||
|
else:
|
||||||
|
compare_exclude.append('composites')
|
||||||
# Process an update
|
# Process an update
|
||||||
|
|
||||||
# no changes
|
# no changes
|
||||||
if desired_role == before_role:
|
if is_struct_included(desired_role, before_role, exclude=compare_exclude):
|
||||||
result['changed'] = False
|
result['changed'] = False
|
||||||
result['end_state'] = desired_role
|
result['end_state'] = desired_role
|
||||||
result['msg'] = "No changes required to role {name}.".format(name=name)
|
result['msg'] = "No changes required to role {name}.".format(name=name)
|
||||||
|
@ -341,6 +404,8 @@ def main():
|
||||||
else:
|
else:
|
||||||
kc.update_client_role(desired_role, clientid, realm)
|
kc.update_client_role(desired_role, clientid, realm)
|
||||||
after_role = kc.get_client_role(name, clientid, realm)
|
after_role = kc.get_client_role(name, clientid, realm)
|
||||||
|
if after_role['composite']:
|
||||||
|
after_role['composites'] = kc.get_role_composites(rolerep=after_role, clientid=clientid, realm=realm)
|
||||||
|
|
||||||
result['end_state'] = after_role
|
result['end_state'] = after_role
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ options:
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- State of the user federation.
|
- State of the user federation.
|
||||||
- On C(present), the user federation will be created if it does not yet exist, or updated with
|
- On V(present), the user federation will be created if it does not yet exist, or updated with
|
||||||
the parameters you provide.
|
the parameters you provide.
|
||||||
- On C(absent), the user federation will be removed if it exists.
|
- On V(absent), the user federation will be removed if it exists.
|
||||||
default: 'present'
|
default: 'present'
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
|
@ -54,7 +54,7 @@ options:
|
||||||
id:
|
id:
|
||||||
description:
|
description:
|
||||||
- The unique ID for this user federation. If left empty, the user federation will be searched
|
- The unique ID for this user federation. If left empty, the user federation will be searched
|
||||||
by its I(name).
|
by its O(name).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
name:
|
name:
|
||||||
|
@ -64,18 +64,15 @@ options:
|
||||||
|
|
||||||
provider_id:
|
provider_id:
|
||||||
description:
|
description:
|
||||||
- Provider for this user federation.
|
- Provider for this user federation. Built-in providers are V(ldap), V(kerberos), and V(sssd).
|
||||||
|
Custom user storage providers can also be used.
|
||||||
aliases:
|
aliases:
|
||||||
- providerId
|
- providerId
|
||||||
type: str
|
type: str
|
||||||
choices:
|
|
||||||
- ldap
|
|
||||||
- kerberos
|
|
||||||
- sssd
|
|
||||||
|
|
||||||
provider_type:
|
provider_type:
|
||||||
description:
|
description:
|
||||||
- Component type for user federation (only supported value is C(org.keycloak.storage.UserStorageProvider)).
|
- Component type for user federation (only supported value is V(org.keycloak.storage.UserStorageProvider)).
|
||||||
aliases:
|
aliases:
|
||||||
- providerType
|
- providerType
|
||||||
default: org.keycloak.storage.UserStorageProvider
|
default: org.keycloak.storage.UserStorageProvider
|
||||||
|
@ -88,13 +85,37 @@ options:
|
||||||
- parentId
|
- parentId
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
remove_unspecified_mappers:
|
||||||
|
description:
|
||||||
|
- Remove mappers that are not specified in the configuration for this federation.
|
||||||
|
- Set to V(false) to keep mappers that are not listed in O(mappers).
|
||||||
|
type: bool
|
||||||
|
default: true
|
||||||
|
|
||||||
|
bind_credential_update_mode:
|
||||||
|
description:
|
||||||
|
- The value of the config parameter O(config.bindCredential) is redacted in the Keycloak responses.
|
||||||
|
Comparing the redacted value with the desired value always evaluates to not equal. This means
|
||||||
|
the before and desired states are never equal if the parameter is set.
|
||||||
|
- Set to V(always) to include O(config.bindCredential) in the comparison of before and desired state.
|
||||||
|
Because of the redacted value returned by Keycloak the module will always detect a change
|
||||||
|
and make an update if a O(config.bindCredential) value is set.
|
||||||
|
- Set to V(only_indirect) to exclude O(config.bindCredential) when comparing the before state with the
|
||||||
|
desired state. The value of O(config.bindCredential) will only be updated if there are other changes
|
||||||
|
to the user federation that require an update.
|
||||||
|
type: str
|
||||||
|
default: always
|
||||||
|
choices:
|
||||||
|
- always
|
||||||
|
- only_indirect
|
||||||
|
|
||||||
config:
|
config:
|
||||||
description:
|
description:
|
||||||
- Dict specifying the configuration options for the provider; the contents differ depending on
|
- Dict specifying the configuration options for the provider; the contents differ depending on
|
||||||
the value of I(provider_id). Examples are given below for C(ldap), C(kerberos) and C(sssd).
|
the value of O(provider_id). Examples are given below for V(ldap), V(kerberos) and V(sssd).
|
||||||
It is easiest to obtain valid config values by dumping an already-existing user federation
|
It is easiest to obtain valid config values by dumping an already-existing user federation
|
||||||
configuration through check-mode in the I(existing) field.
|
configuration through check-mode in the RV(existing) field.
|
||||||
- The value C(sssd) has been supported since middleware_automation.keycloak 1.0.0.
|
- The value V(sssd) has been supported since middleware_automation.keycloak 2.0.0.
|
||||||
type: dict
|
type: dict
|
||||||
suboptions:
|
suboptions:
|
||||||
enabled:
|
enabled:
|
||||||
|
@ -111,15 +132,15 @@ options:
|
||||||
|
|
||||||
importEnabled:
|
importEnabled:
|
||||||
description:
|
description:
|
||||||
- If C(true), LDAP users will be imported into Keycloak DB and synced by the configured
|
- If V(true), LDAP users will be imported into Keycloak DB and synced by the configured
|
||||||
sync policies.
|
sync policies.
|
||||||
default: true
|
default: true
|
||||||
type: bool
|
type: bool
|
||||||
|
|
||||||
editMode:
|
editMode:
|
||||||
description:
|
description:
|
||||||
- C(READ_ONLY) is a read-only LDAP store. C(WRITABLE) means data will be synced back to LDAP
|
- V(READ_ONLY) is a read-only LDAP store. V(WRITABLE) means data will be synced back to LDAP
|
||||||
on demand. C(UNSYNCED) means user data will be imported, but not synced back to LDAP.
|
on demand. V(UNSYNCED) means user data will be imported, but not synced back to LDAP.
|
||||||
type: str
|
type: str
|
||||||
choices:
|
choices:
|
||||||
- READ_ONLY
|
- READ_ONLY
|
||||||
|
@ -136,13 +157,13 @@ options:
|
||||||
vendor:
|
vendor:
|
||||||
description:
|
description:
|
||||||
- LDAP vendor (provider).
|
- LDAP vendor (provider).
|
||||||
- Use short name. For instance, write C(rhds) for "Red Hat Directory Server".
|
- Use short name. For instance, write V(rhds) for "Red Hat Directory Server".
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
usernameLDAPAttribute:
|
usernameLDAPAttribute:
|
||||||
description:
|
description:
|
||||||
- Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server
|
- Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server
|
||||||
vendors it can be C(uid). For Active directory it can be C(sAMAccountName) or C(cn).
|
vendors it can be V(uid). For Active directory it can be V(sAMAccountName) or V(cn).
|
||||||
The attribute should be filled for all LDAP user records you want to import from
|
The attribute should be filled for all LDAP user records you want to import from
|
||||||
LDAP to Keycloak.
|
LDAP to Keycloak.
|
||||||
type: str
|
type: str
|
||||||
|
@ -151,15 +172,15 @@ options:
|
||||||
description:
|
description:
|
||||||
- Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN.
|
- Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN.
|
||||||
Usually it's the same as Username LDAP attribute, however it is not required. For
|
Usually it's the same as Username LDAP attribute, however it is not required. For
|
||||||
example for Active directory, it is common to use C(cn) as RDN attribute when
|
example for Active directory, it is common to use V(cn) as RDN attribute when
|
||||||
username attribute might be C(sAMAccountName).
|
username attribute might be V(sAMAccountName).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
uuidLDAPAttribute:
|
uuidLDAPAttribute:
|
||||||
description:
|
description:
|
||||||
- Name of LDAP attribute, which is used as unique object identifier (UUID) for objects
|
- Name of LDAP attribute, which is used as unique object identifier (UUID) for objects
|
||||||
in LDAP. For many LDAP server vendors, it is C(entryUUID); however some are different.
|
in LDAP. For many LDAP server vendors, it is V(entryUUID); however some are different.
|
||||||
For example for Active directory it should be C(objectGUID). If your LDAP server does
|
For example for Active directory it should be V(objectGUID). If your LDAP server does
|
||||||
not support the notion of UUID, you can use any other attribute that is supposed to
|
not support the notion of UUID, you can use any other attribute that is supposed to
|
||||||
be unique among LDAP users in tree.
|
be unique among LDAP users in tree.
|
||||||
type: str
|
type: str
|
||||||
|
@ -167,7 +188,7 @@ options:
|
||||||
userObjectClasses:
|
userObjectClasses:
|
||||||
description:
|
description:
|
||||||
- All values of LDAP objectClass attribute for users in LDAP divided by comma.
|
- All values of LDAP objectClass attribute for users in LDAP divided by comma.
|
||||||
For example C(inetOrgPerson, organizationalPerson). Newly created Keycloak users
|
For example V(inetOrgPerson, organizationalPerson). Newly created Keycloak users
|
||||||
will be written to LDAP with all those object classes and existing LDAP user records
|
will be written to LDAP with all those object classes and existing LDAP user records
|
||||||
are found just if they contain all those object classes.
|
are found just if they contain all those object classes.
|
||||||
type: str
|
type: str
|
||||||
|
@ -251,8 +272,8 @@ options:
|
||||||
useTruststoreSpi:
|
useTruststoreSpi:
|
||||||
description:
|
description:
|
||||||
- Specifies whether LDAP connection will use the truststore SPI with the truststore
|
- Specifies whether LDAP connection will use the truststore SPI with the truststore
|
||||||
configured in standalone.xml/domain.xml. C(Always) means that it will always use it.
|
configured in standalone.xml/domain.xml. V(always) means that it will always use it.
|
||||||
C(Never) means that it will not use it. C(Only for ldaps) means that it will use if
|
V(never) means that it will not use it. V(ldapsOnly) means that it will use if
|
||||||
your connection URL use ldaps. Note even if standalone.xml/domain.xml is not
|
your connection URL use ldaps. Note even if standalone.xml/domain.xml is not
|
||||||
configured, the default Java cacerts or certificate specified by
|
configured, the default Java cacerts or certificate specified by
|
||||||
C(javax.net.ssl.trustStore) property will be used.
|
C(javax.net.ssl.trustStore) property will be used.
|
||||||
|
@ -297,7 +318,7 @@ options:
|
||||||
connectionPoolingDebug:
|
connectionPoolingDebug:
|
||||||
description:
|
description:
|
||||||
- A string that indicates the level of debug output to produce. Example valid values are
|
- A string that indicates the level of debug output to produce. Example valid values are
|
||||||
C(fine) (trace connection creation and removal) and C(all) (all debugging information).
|
V(fine) (trace connection creation and removal) and V(all) (all debugging information).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
connectionPoolingInitSize:
|
connectionPoolingInitSize:
|
||||||
|
@ -321,7 +342,7 @@ options:
|
||||||
connectionPoolingProtocol:
|
connectionPoolingProtocol:
|
||||||
description:
|
description:
|
||||||
- A list of space-separated protocol types of connections that may be pooled.
|
- A list of space-separated protocol types of connections that may be pooled.
|
||||||
Valid types are C(plain) and C(ssl).
|
Valid types are V(plain) and V(ssl).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
connectionPoolingTimeout:
|
connectionPoolingTimeout:
|
||||||
|
@ -342,17 +363,26 @@ options:
|
||||||
- Name of kerberos realm.
|
- Name of kerberos realm.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
krbPrincipalAttribute:
|
||||||
|
description:
|
||||||
|
- Name of the LDAP attribute, which refers to Kerberos principal.
|
||||||
|
This is used to lookup appropriate LDAP user after successful Kerberos/SPNEGO authentication in Keycloak.
|
||||||
|
When this is empty, the LDAP user will be looked based on LDAP username corresponding
|
||||||
|
to the first part of his Kerberos principal. For instance, for principal C(john@KEYCLOAK.ORG),
|
||||||
|
it will assume that LDAP username is V(john).
|
||||||
|
type: str
|
||||||
|
|
||||||
serverPrincipal:
|
serverPrincipal:
|
||||||
description:
|
description:
|
||||||
- Full name of server principal for HTTP service including server and domain name. For
|
- Full name of server principal for HTTP service including server and domain name. For
|
||||||
example C(HTTP/host.foo.org@FOO.ORG). Use C(*) to accept any service principal in the
|
example V(HTTP/host.foo.org@FOO.ORG). Use V(*) to accept any service principal in the
|
||||||
KeyTab file.
|
KeyTab file.
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
keyTab:
|
keyTab:
|
||||||
description:
|
description:
|
||||||
- Location of Kerberos KeyTab file containing the credentials of server principal. For
|
- Location of Kerberos KeyTab file containing the credentials of server principal. For
|
||||||
example C(/etc/krb5.keytab).
|
example V(/etc/krb5.keytab).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
|
@ -427,6 +457,16 @@ options:
|
||||||
- Max lifespan of cache entry in milliseconds.
|
- Max lifespan of cache entry in milliseconds.
|
||||||
type: int
|
type: int
|
||||||
|
|
||||||
|
referral:
|
||||||
|
description:
|
||||||
|
- Specifies if LDAP referrals should be followed or ignored. Please note that enabling
|
||||||
|
referrals can slow down authentication as it allows the LDAP server to decide which other
|
||||||
|
LDAP servers to use. This could potentially include untrusted servers.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- ignore
|
||||||
|
- follow
|
||||||
|
|
||||||
mappers:
|
mappers:
|
||||||
description:
|
description:
|
||||||
- A list of dicts defining mappers associated with this Identity Provider.
|
- A list of dicts defining mappers associated with this Identity Provider.
|
||||||
|
@ -451,7 +491,7 @@ options:
|
||||||
|
|
||||||
providerId:
|
providerId:
|
||||||
description:
|
description:
|
||||||
- The mapper type for this mapper (for instance C(user-attribute-ldap-mapper)).
|
- The mapper type for this mapper (for instance V(user-attribute-ldap-mapper)).
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
providerType:
|
providerType:
|
||||||
|
@ -534,14 +574,14 @@ EXAMPLES = '''
|
||||||
provider_id: kerberos
|
provider_id: kerberos
|
||||||
provider_type: org.keycloak.storage.UserStorageProvider
|
provider_type: org.keycloak.storage.UserStorageProvider
|
||||||
config:
|
config:
|
||||||
priority: 0
|
priority: 0
|
||||||
enabled: true
|
enabled: true
|
||||||
cachePolicy: DEFAULT
|
cachePolicy: DEFAULT
|
||||||
kerberosRealm: EXAMPLE.COM
|
kerberosRealm: EXAMPLE.COM
|
||||||
serverPrincipal: HTTP/host.example.com@EXAMPLE.COM
|
serverPrincipal: HTTP/host.example.com@EXAMPLE.COM
|
||||||
keyTab: keytab
|
keyTab: keytab
|
||||||
allowPasswordAuthentication: false
|
allowPasswordAuthentication: false
|
||||||
updateProfileFirstLogin: false
|
updateProfileFirstLogin: false
|
||||||
|
|
||||||
- name: Create sssd user federation
|
- name: Create sssd user federation
|
||||||
middleware_automation.keycloak.keycloak_user_federation:
|
middleware_automation.keycloak.keycloak_user_federation:
|
||||||
|
@ -704,16 +744,27 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_kc_comp(comp):
|
||||||
|
if 'config' in comp:
|
||||||
|
# kc completely removes the parameter `krbPrincipalAttribute` if it is set to `''`; the unset kc parameter is equivalent to `''`;
|
||||||
|
# to make change detection and diff more accurate we set it again in the kc responses
|
||||||
|
if 'krbPrincipalAttribute' not in comp['config']:
|
||||||
|
comp['config']['krbPrincipalAttribute'] = ['']
|
||||||
|
|
||||||
|
# kc stores a timestamp of the last sync in `lastSync` to time the periodic sync, it is removed to minimize diff/changes
|
||||||
|
comp['config'].pop('lastSync', None)
|
||||||
|
|
||||||
|
|
||||||
def sanitize(comp):
|
def sanitize(comp):
|
||||||
compcopy = deepcopy(comp)
|
compcopy = deepcopy(comp)
|
||||||
if 'config' in compcopy:
|
if 'config' in compcopy:
|
||||||
compcopy['config'] = dict((k, v[0]) for k, v in compcopy['config'].items())
|
compcopy['config'] = {k: v[0] for k, v in compcopy['config'].items()}
|
||||||
if 'bindCredential' in compcopy['config']:
|
if 'bindCredential' in compcopy['config']:
|
||||||
compcopy['config']['bindCredential'] = '**********'
|
compcopy['config']['bindCredential'] = '**********'
|
||||||
if 'mappers' in compcopy:
|
if 'mappers' in compcopy:
|
||||||
for mapper in compcopy['mappers']:
|
for mapper in compcopy['mappers']:
|
||||||
if 'config' in mapper:
|
if 'config' in mapper:
|
||||||
mapper['config'] = dict((k, v[0]) for k, v in mapper['config'].items())
|
mapper['config'] = {k: v[0] for k, v in mapper['config'].items()}
|
||||||
return compcopy
|
return compcopy
|
||||||
|
|
||||||
|
|
||||||
|
@ -760,8 +811,10 @@ def main():
|
||||||
priority=dict(type='int', default=0),
|
priority=dict(type='int', default=0),
|
||||||
rdnLDAPAttribute=dict(type='str'),
|
rdnLDAPAttribute=dict(type='str'),
|
||||||
readTimeout=dict(type='int'),
|
readTimeout=dict(type='int'),
|
||||||
|
referral=dict(type='str', choices=['ignore', 'follow']),
|
||||||
searchScope=dict(type='str', choices=['1', '2'], default='1'),
|
searchScope=dict(type='str', choices=['1', '2'], default='1'),
|
||||||
serverPrincipal=dict(type='str'),
|
serverPrincipal=dict(type='str'),
|
||||||
|
krbPrincipalAttribute=dict(type='str'),
|
||||||
startTls=dict(type='bool', default=False),
|
startTls=dict(type='bool', default=False),
|
||||||
syncRegistrations=dict(type='bool', default=False),
|
syncRegistrations=dict(type='bool', default=False),
|
||||||
trustEmail=dict(type='bool', default=False),
|
trustEmail=dict(type='bool', default=False),
|
||||||
|
@ -792,9 +845,11 @@ def main():
|
||||||
realm=dict(type='str', default='master'),
|
realm=dict(type='str', default='master'),
|
||||||
id=dict(type='str'),
|
id=dict(type='str'),
|
||||||
name=dict(type='str'),
|
name=dict(type='str'),
|
||||||
provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos', 'sssd']),
|
provider_id=dict(type='str', aliases=['providerId']),
|
||||||
provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
|
provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'),
|
||||||
parent_id=dict(type='str', aliases=['parentId']),
|
parent_id=dict(type='str', aliases=['parentId']),
|
||||||
|
remove_unspecified_mappers=dict(type='bool', default=True),
|
||||||
|
bind_credential_update_mode=dict(type='str', default='always', choices=['always', 'only_indirect']),
|
||||||
mappers=dict(type='list', elements='dict', options=mapper_spec),
|
mappers=dict(type='list', elements='dict', options=mapper_spec),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -825,19 +880,26 @@ def main():
|
||||||
|
|
||||||
# Keycloak API expects config parameters to be arrays containing a single string element
|
# Keycloak API expects config parameters to be arrays containing a single string element
|
||||||
if config is not None:
|
if config is not None:
|
||||||
module.params['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v])
|
module.params['config'] = {
|
||||||
for k, v in config.items() if config[k] is not None)
|
k: [str(v).lower() if not isinstance(v, str) else v]
|
||||||
|
for k, v in config.items()
|
||||||
|
if config[k] is not None
|
||||||
|
}
|
||||||
|
|
||||||
if mappers is not None:
|
if mappers is not None:
|
||||||
for mapper in mappers:
|
for mapper in mappers:
|
||||||
if mapper.get('config') is not None:
|
if mapper.get('config') is not None:
|
||||||
mapper['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v])
|
mapper['config'] = {
|
||||||
for k, v in mapper['config'].items() if mapper['config'][k] is not None)
|
k: [str(v).lower() if not isinstance(v, str) else v]
|
||||||
|
for k, v in mapper['config'].items()
|
||||||
|
if mapper['config'][k] is not None
|
||||||
|
}
|
||||||
|
|
||||||
# Filter and map the parameters names that apply
|
# Filter and map the parameters names that apply
|
||||||
comp_params = [x for x in module.params
|
comp_params = [x for x in module.params
|
||||||
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'mappers'] and
|
if x not in list(keycloak_argument_spec().keys())
|
||||||
module.params.get(x) is not None]
|
+ ['state', 'realm', 'mappers', 'remove_unspecified_mappers', 'bind_credential_update_mode']
|
||||||
|
and module.params.get(x) is not None]
|
||||||
|
|
||||||
# See if it already exists in Keycloak
|
# See if it already exists in Keycloak
|
||||||
if cid is None:
|
if cid is None:
|
||||||
|
@ -855,7 +917,9 @@ def main():
|
||||||
|
|
||||||
# if user federation exists, get associated mappers
|
# if user federation exists, get associated mappers
|
||||||
if cid is not None and before_comp:
|
if cid is not None and before_comp:
|
||||||
before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name'))
|
before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
|
||||||
|
|
||||||
|
normalize_kc_comp(before_comp)
|
||||||
|
|
||||||
# Build a proposed changeset from parameters given to this module
|
# Build a proposed changeset from parameters given to this module
|
||||||
changeset = {}
|
changeset = {}
|
||||||
|
@ -864,7 +928,7 @@ def main():
|
||||||
new_param_value = module.params.get(param)
|
new_param_value = module.params.get(param)
|
||||||
old_value = before_comp[camel(param)] if camel(param) in before_comp else None
|
old_value = before_comp[camel(param)] if camel(param) in before_comp else None
|
||||||
if param == 'mappers':
|
if param == 'mappers':
|
||||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
new_param_value = [{k: v for k, v in x.items() if v is not None} for x in new_param_value]
|
||||||
if new_param_value != old_value:
|
if new_param_value != old_value:
|
||||||
changeset[camel(param)] = new_param_value
|
changeset[camel(param)] = new_param_value
|
||||||
|
|
||||||
|
@ -873,17 +937,17 @@ def main():
|
||||||
if module.params['provider_id'] in ['kerberos', 'sssd']:
|
if module.params['provider_id'] in ['kerberos', 'sssd']:
|
||||||
module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
|
module.fail_json(msg='Cannot configure mappers for {type} provider.'.format(type=module.params['provider_id']))
|
||||||
for change in module.params['mappers']:
|
for change in module.params['mappers']:
|
||||||
change = dict((k, v) for k, v in change.items() if change[k] is not None)
|
change = {k: v for k, v in change.items() if v is not None}
|
||||||
if change.get('id') is None and change.get('name') is None:
|
if change.get('id') is None and change.get('name') is None:
|
||||||
module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.')
|
module.fail_json(msg='Either `name` or `id` has to be specified on each mapper.')
|
||||||
if cid is None:
|
if cid is None:
|
||||||
old_mapper = {}
|
old_mapper = {}
|
||||||
elif change.get('id') is not None:
|
elif change.get('id') is not None:
|
||||||
old_mapper = kc.get_component(change['id'], realm)
|
old_mapper = next((before_mapper for before_mapper in before_comp.get('mappers', []) if before_mapper["id"] == change['id']), None)
|
||||||
if old_mapper is None:
|
if old_mapper is None:
|
||||||
old_mapper = {}
|
old_mapper = {}
|
||||||
else:
|
else:
|
||||||
found = kc.get_components(urlencode(dict(parent=cid, name=change['name'])), realm)
|
found = [before_mapper for before_mapper in before_comp.get('mappers', []) if before_mapper['name'] == change['name']]
|
||||||
if len(found) > 1:
|
if len(found) > 1:
|
||||||
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=change['name']))
|
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=change['name']))
|
||||||
if len(found) == 1:
|
if len(found) == 1:
|
||||||
|
@ -892,10 +956,16 @@ def main():
|
||||||
old_mapper = {}
|
old_mapper = {}
|
||||||
new_mapper = old_mapper.copy()
|
new_mapper = old_mapper.copy()
|
||||||
new_mapper.update(change)
|
new_mapper.update(change)
|
||||||
if new_mapper != old_mapper:
|
# changeset contains all desired mappers: those existing, to update or to create
|
||||||
if changeset.get('mappers') is None:
|
if changeset.get('mappers') is None:
|
||||||
changeset['mappers'] = list()
|
changeset['mappers'] = list()
|
||||||
changeset['mappers'].append(new_mapper)
|
changeset['mappers'].append(new_mapper)
|
||||||
|
changeset['mappers'] = sorted(changeset['mappers'], key=lambda x: x.get('name') or '')
|
||||||
|
|
||||||
|
# to keep unspecified existing mappers we add them to the desired mappers list, unless they're already present
|
||||||
|
if not module.params['remove_unspecified_mappers'] and 'mappers' in before_comp:
|
||||||
|
changeset_mapper_ids = [mapper['id'] for mapper in changeset['mappers'] if 'id' in mapper]
|
||||||
|
changeset['mappers'].extend([mapper for mapper in before_comp['mappers'] if mapper['id'] not in changeset_mapper_ids])
|
||||||
|
|
||||||
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||||
desired_comp = before_comp.copy()
|
desired_comp = before_comp.copy()
|
||||||
|
@ -918,50 +988,68 @@ def main():
|
||||||
# Process a creation
|
# Process a creation
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
|
|
||||||
if module._diff:
|
|
||||||
result['diff'] = dict(before='', after=sanitize(desired_comp))
|
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after=sanitize(desired_comp))
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
# create it
|
# create it
|
||||||
desired_comp = desired_comp.copy()
|
desired_mappers = desired_comp.pop('mappers', [])
|
||||||
updated_mappers = desired_comp.pop('mappers', [])
|
|
||||||
after_comp = kc.create_component(desired_comp, realm)
|
after_comp = kc.create_component(desired_comp, realm)
|
||||||
|
|
||||||
cid = after_comp['id']
|
cid = after_comp['id']
|
||||||
|
updated_mappers = []
|
||||||
|
# when creating a user federation, keycloak automatically creates default mappers
|
||||||
|
default_mappers = kc.get_components(urlencode(dict(parent=cid)), realm)
|
||||||
|
|
||||||
for mapper in updated_mappers:
|
# create new mappers or update existing default mappers
|
||||||
found = kc.get_components(urlencode(dict(parent=cid, name=mapper['name'])), realm)
|
for desired_mapper in desired_mappers:
|
||||||
|
found = [default_mapper for default_mapper in default_mappers if default_mapper['name'] == desired_mapper['name']]
|
||||||
if len(found) > 1:
|
if len(found) > 1:
|
||||||
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=mapper['name']))
|
module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=desired_mapper['name']))
|
||||||
if len(found) == 1:
|
if len(found) == 1:
|
||||||
old_mapper = found[0]
|
old_mapper = found[0]
|
||||||
else:
|
else:
|
||||||
old_mapper = {}
|
old_mapper = {}
|
||||||
|
|
||||||
new_mapper = old_mapper.copy()
|
new_mapper = old_mapper.copy()
|
||||||
new_mapper.update(mapper)
|
new_mapper.update(desired_mapper)
|
||||||
|
|
||||||
if new_mapper.get('id') is not None:
|
if new_mapper.get('id') is not None:
|
||||||
kc.update_component(new_mapper, realm)
|
kc.update_component(new_mapper, realm)
|
||||||
|
updated_mappers.append(new_mapper)
|
||||||
else:
|
else:
|
||||||
if new_mapper.get('parentId') is None:
|
if new_mapper.get('parentId') is None:
|
||||||
new_mapper['parentId'] = after_comp['id']
|
new_mapper['parentId'] = cid
|
||||||
mapper = kc.create_component(new_mapper, realm)
|
updated_mappers.append(kc.create_component(new_mapper, realm))
|
||||||
|
|
||||||
after_comp['mappers'] = updated_mappers
|
if module.params['remove_unspecified_mappers']:
|
||||||
|
# we remove all unwanted default mappers
|
||||||
|
# we use ids so we dont accidently remove one of the previously updated default mapper
|
||||||
|
for default_mapper in default_mappers:
|
||||||
|
if not default_mapper['id'] in [x['id'] for x in updated_mappers]:
|
||||||
|
kc.delete_component(default_mapper['id'], realm)
|
||||||
|
|
||||||
|
after_comp['mappers'] = kc.get_components(urlencode(dict(parent=cid)), realm)
|
||||||
|
normalize_kc_comp(after_comp)
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after=sanitize(after_comp))
|
||||||
result['end_state'] = sanitize(after_comp)
|
result['end_state'] = sanitize(after_comp)
|
||||||
|
result['msg'] = "User federation {id} has been created".format(id=cid)
|
||||||
result['msg'] = "User federation {id} has been created".format(id=after_comp['id'])
|
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if state == 'present':
|
if state == 'present':
|
||||||
# Process an update
|
# Process an update
|
||||||
|
|
||||||
|
desired_copy = deepcopy(desired_comp)
|
||||||
|
before_copy = deepcopy(before_comp)
|
||||||
|
# exclude bindCredential when checking wether an update is required, therefore
|
||||||
|
# updating it only if there are other changes
|
||||||
|
if module.params['bind_credential_update_mode'] == 'only_indirect':
|
||||||
|
desired_copy.get('config', []).pop('bindCredential', None)
|
||||||
|
before_copy.get('config', []).pop('bindCredential', None)
|
||||||
# no changes
|
# no changes
|
||||||
if desired_comp == before_comp:
|
if desired_copy == before_copy:
|
||||||
result['changed'] = False
|
result['changed'] = False
|
||||||
result['end_state'] = sanitize(desired_comp)
|
result['end_state'] = sanitize(desired_comp)
|
||||||
result['msg'] = "No changes required to user federation {id}.".format(id=cid)
|
result['msg'] = "No changes required to user federation {id}.".format(id=cid)
|
||||||
|
@ -977,22 +1065,33 @@ def main():
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
# do the update
|
# do the update
|
||||||
desired_comp = desired_comp.copy()
|
desired_mappers = desired_comp.pop('mappers', [])
|
||||||
updated_mappers = desired_comp.pop('mappers', [])
|
|
||||||
kc.update_component(desired_comp, realm)
|
kc.update_component(desired_comp, realm)
|
||||||
after_comp = kc.get_component(cid, realm)
|
|
||||||
|
|
||||||
for mapper in updated_mappers:
|
for before_mapper in before_comp.get('mappers', []):
|
||||||
|
# remove unwanted existing mappers that will not be updated
|
||||||
|
if not before_mapper['id'] in [x['id'] for x in desired_mappers if 'id' in x]:
|
||||||
|
kc.delete_component(before_mapper['id'], realm)
|
||||||
|
|
||||||
|
for mapper in desired_mappers:
|
||||||
|
if mapper in before_comp.get('mappers', []):
|
||||||
|
continue
|
||||||
if mapper.get('id') is not None:
|
if mapper.get('id') is not None:
|
||||||
kc.update_component(mapper, realm)
|
kc.update_component(mapper, realm)
|
||||||
else:
|
else:
|
||||||
if mapper.get('parentId') is None:
|
if mapper.get('parentId') is None:
|
||||||
mapper['parentId'] = desired_comp['id']
|
mapper['parentId'] = desired_comp['id']
|
||||||
mapper = kc.create_component(mapper, realm)
|
kc.create_component(mapper, realm)
|
||||||
|
|
||||||
after_comp['mappers'] = updated_mappers
|
|
||||||
result['end_state'] = sanitize(after_comp)
|
|
||||||
|
|
||||||
|
after_comp = kc.get_component(cid, realm)
|
||||||
|
after_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name') or '')
|
||||||
|
normalize_kc_comp(after_comp)
|
||||||
|
after_comp_sanitized = sanitize(after_comp)
|
||||||
|
before_comp_sanitized = sanitize(before_comp)
|
||||||
|
result['end_state'] = after_comp_sanitized
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=before_comp_sanitized, after=after_comp_sanitized)
|
||||||
|
result['changed'] = before_comp_sanitized != after_comp_sanitized
|
||||||
result['msg'] = "User federation {id} has been updated".format(id=cid)
|
result['msg'] = "User federation {id} has been updated".format(id=cid)
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
- name: Ensure required params for CLI have been provided
|
- name: Ensure required params for CLI have been provided
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- query is defined
|
- cli_query is defined
|
||||||
fail_msg: "Missing required parameters to execute CLI."
|
fail_msg: "Missing required parameters to execute CLI."
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: "Execute CLI query: {{ query }}"
|
- name: "Execute CLI query: {{ cli_query }}"
|
||||||
ansible.builtin.command: >
|
ansible.builtin.command: >
|
||||||
{{ keycloak.cli_path }} --connect --command='{{ query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }}
|
{{ keycloak.cli_path }} --connect --command='{{ cli_query }}' --controller={{ keycloak_host }}:{{ keycloak_management_http_port }}
|
||||||
changed_when: false
|
changed_when: false
|
||||||
register: cli_result
|
register: cli_result
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
- name: "Check installed patches"
|
- name: "Check installed patches"
|
||||||
ansible.builtin.include_tasks: rhsso_cli.yml
|
ansible.builtin.include_tasks: rhsso_cli.yml
|
||||||
vars:
|
vars:
|
||||||
query: "patch info"
|
cli_query: "patch info"
|
||||||
args:
|
args:
|
||||||
apply:
|
apply:
|
||||||
become: true
|
become: true
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
- name: "Apply patch {{ patch_version }} to server"
|
- name: "Apply patch {{ patch_version }} to server"
|
||||||
ansible.builtin.include_tasks: rhsso_cli.yml
|
ansible.builtin.include_tasks: rhsso_cli.yml
|
||||||
vars:
|
vars:
|
||||||
query: "patch apply {{ patch_archive }}"
|
cli_query: "patch apply {{ patch_archive }}"
|
||||||
args:
|
args:
|
||||||
apply:
|
apply:
|
||||||
become: true
|
become: true
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
- name: "Restart server to ensure patch content is running"
|
- name: "Restart server to ensure patch content is running"
|
||||||
ansible.builtin.include_tasks: rhsso_cli.yml
|
ansible.builtin.include_tasks: rhsso_cli.yml
|
||||||
vars:
|
vars:
|
||||||
query: "shutdown --restart"
|
cli_query: "shutdown --restart"
|
||||||
when:
|
when:
|
||||||
- cli_result.rc == 0
|
- cli_result.rc == 0
|
||||||
args:
|
args:
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
- name: "Query installed patch after restart"
|
- name: "Query installed patch after restart"
|
||||||
ansible.builtin.include_tasks: rhsso_cli.yml
|
ansible.builtin.include_tasks: rhsso_cli.yml
|
||||||
vars:
|
vars:
|
||||||
query: "patch info"
|
cli_query: "patch info"
|
||||||
args:
|
args:
|
||||||
apply:
|
apply:
|
||||||
become: true
|
become: true
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
keycloak_quarkus
|
keycloak_quarkus
|
||||||
================
|
================
|
||||||
|
<!--start description -->
|
||||||
Install [keycloak](https://keycloak.org/) >= 20.0.0 (quarkus) server configurations.
|
Install [keycloak](https://keycloak.org/) >= 20.0.0 (quarkus) server configurations.
|
||||||
|
<!--end description -->
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
@ -33,40 +33,47 @@ Role Defaults
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|:---------|:------------|:--------|
|
|:---------|:------------|:--------|
|
||||||
|`keycloak_quarkus_version`| keycloak.org package version | `24.0.4` |
|
|`keycloak_quarkus_version`| keycloak.org package version | `26.0.7` |
|
||||||
|`keycloak_quarkus_offline_install` | Perform an offline install | `False`|
|
|`keycloak_quarkus_offline_install` | Perform an offline install | `False`|
|
||||||
|`keycloak_quarkus_dest`| Installation root path | `/opt/keycloak` |
|
|`keycloak_quarkus_dest`| Installation root path | `/opt/keycloak` |
|
||||||
|`keycloak_quarkus_download_url` | Download URL for keycloak | `https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}` |
|
|`keycloak_quarkus_download_url` | Download URL for keycloak | `https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}` |
|
||||||
|
|`keycloak_quarkus_download_path`| Path local to controller for offline/download of install archives | `{{ lookup('env', 'PWD') }}` |
|
||||||
|
|
||||||
|
|
||||||
#### Service configuration
|
#### Service configuration
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|:---------|:------------|:--------|
|
|:---------|:------------|:--------|
|
||||||
|`keycloak_quarkus_admin_user`| Administration console user account | `admin` |
|
|`keycloak_quarkus_bootstrap_admin_user`| Administration console user account | `admin` |
|
||||||
|
|`keycloak_quarkus_admin_user`| Deprecated, use `keycloak_quarkus_bootstrap_admin_user` instead. | |
|
||||||
|`keycloak_quarkus_bind_address`| Address for binding service ports | `0.0.0.0` |
|
|`keycloak_quarkus_bind_address`| Address for binding service ports | `0.0.0.0` |
|
||||||
|`keycloak_quarkus_host`| Hostname for the Keycloak server | `localhost` |
|
|`keycloak_quarkus_host`| Deprecated, use `keycloak_quarkus_hostname` instead. | |
|
||||||
|`keycloak_quarkus_port`| The port used by the proxy when exposing the hostname | `-1` |
|
|`keycloak_quarkus_port`| Deprecated, use `keycloak_quarkus_hostname` instead. | |
|
||||||
|`keycloak_quarkus_path`| This should be set if proxy uses a different context-path for Keycloak | |
|
|`keycloak_quarkus_path`| Deprecated, use `keycloak_quarkus_hostname` instead. | |
|
||||||
|`keycloak_quarkus_http_port`| HTTP listening port | `8080` |
|
|`keycloak_quarkus_http_port`| HTTP listening port | `8080` |
|
||||||
|`keycloak_quarkus_https_port`| TLS HTTP listening port | `8443` |
|
|`keycloak_quarkus_https_port`| TLS HTTP listening port | `8443` |
|
||||||
|
|`keycloak_quarkus_http_management_port`| Port of the management interface. Relevant only when something is exposed on the management interface - see the guide for details. | `9000` |
|
||||||
|`keycloak_quarkus_ajp_port`| AJP port | `8009` |
|
|`keycloak_quarkus_ajp_port`| AJP port | `8009` |
|
||||||
|`keycloak_quarkus_service_user`| Posix account username | `keycloak` |
|
|`keycloak_quarkus_service_user`| Posix account username | `keycloak` |
|
||||||
|`keycloak_quarkus_service_group`| Posix account group | `keycloak` |
|
|`keycloak_quarkus_service_group`| Posix account group | `keycloak` |
|
||||||
|`keycloak_quarkus_service_restart_always`| systemd restart always behavior activation | `False` |
|
|`keycloak_quarkus_service_restart_always`| systemd restart always behavior activation | `False` |
|
||||||
|`keycloak_quarkus_service_restart_on_failure`| systemd restart on-failure behavior activation | `False` |
|
|`keycloak_quarkus_service_restart_on_failure`| systemd restart on-failure behavior activation | `False` |
|
||||||
|`keycloak_quarkus_service_restartsec`| systemd RestartSec | `10s` |
|
|`keycloak_quarkus_service_restartsec`| systemd RestartSec | `10s` |
|
||||||
|`keycloak_quarkus_jvm_package`| RHEL java package runtime | `java-17-openjdk-headless` |
|
|`keycloak_quarkus_jvm_package`| RHEL java package runtime | `java-21-openjdk-headless` |
|
||||||
|`keycloak_quarkus_java_home`| JAVA_HOME of installed JRE, leave empty for using specified keycloak_quarkus_jvm_package RPM path | `None` |
|
|`keycloak_quarkus_java_home`| JAVA_HOME of installed JRE, leave empty for using specified keycloak_quarkus_jvm_package RPM path | `None` |
|
||||||
|`keycloak_quarkus_java_heap_opts`| Heap memory JVM setting | `-Xms1024m -Xmx2048m` |
|
|`keycloak_quarkus_java_heap_opts`| Heap memory JVM setting | `-Xms1024m -Xmx2048m` |
|
||||||
|`keycloak_quarkus_java_jvm_opts`| Other JVM settings | same as keycloak |
|
|`keycloak_quarkus_java_jvm_opts`| Other JVM settings | same as keycloak |
|
||||||
|`keycloak_quarkus_java_opts`| JVM arguments; if overridden, it takes precedence over `keycloak_quarkus_java_*` | `{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}` |
|
|`keycloak_quarkus_java_opts`| JVM arguments; if overridden, it takes precedence over `keycloak_quarkus_java_*` | `{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}` |
|
||||||
|`keycloak_quarkus_additional_env_vars` | List of additional env variables of { key: str, value: str} to be put in sysconfig file | `[]` |
|
|`keycloak_quarkus_additional_env_vars` | List of additional env variables of { key: str, value: str} to be put in sysconfig file | `[]` |
|
||||||
|`keycloak_quarkus_frontend_url`| Set the base URL for frontend URLs, including scheme, host, port and path | |
|
|`keycloak_quarkus_hostname`| Address at which is the server exposed. Can be a full URL, or just a hostname. When only hostname is provided, scheme, port and context path are resolved from the request. | |
|
||||||
|`keycloak_quarkus_admin_url`| Set the base URL for accessing the administration console, including scheme, host, port and path | |
|
|`keycloak_quarkus_frontend_url`| Deprecated, use `keycloak_quarkus_hostname` instead. | |
|
||||||
|
|`keycloak_quarkus_admin`| Set the base URL for accessing the administration console, including scheme, host, port and path | |
|
||||||
|
|`keycloak_quarkus_admin_url`| Deprecated, use `keycloak_quarkus_admin` instead. | |
|
||||||
|`keycloak_quarkus_http_relative_path` | Set the path relative to / for serving resources. The path must start with a / | `/` |
|
|`keycloak_quarkus_http_relative_path` | Set the path relative to / for serving resources. The path must start with a / | `/` |
|
||||||
|
|`keycloak_quarkus_http_management_relative_path` | Set the path relative to / for serving resources from management interface. The path must start with a /. If not given, the value is inherited from HTTP options. Relevant only when something is exposed on the management interface - see the guide for details. | `/` |
|
||||||
|`keycloak_quarkus_http_enabled`| Enable listener on HTTP port | `True` |
|
|`keycloak_quarkus_http_enabled`| Enable listener on HTTP port | `True` |
|
||||||
|`keycloak_quarkus_health_check_url_path`| Path to the health check endpoint; scheme, host and keycloak_quarkus_http_relative_path will be prepended automatically | `realms/master/.well-known/openid-configuration` |
|
|`keycloak_quarkus_health_check_url`| Full URL (including scheme, host, path, fragment etc.) used for health check endpoint; keycloak_quarkus_hostname will NOT be prepended; helpful when health checks should happen against http port, but keycloak_quarkus_hostname uses https scheme per default | `` |
|
||||||
|
|`keycloak_quarkus_health_check_url_path`| Path to the health check endpoint; keycloak_quarkus_hostname will be prepended automatically; Note that keycloak_quarkus_health_check_url takes precedence over this property | `realms/master/.well-known/openid-configuration` |
|
||||||
|`keycloak_quarkus_https_key_file_enabled`| Enable listener on HTTPS port | `False` |
|
|`keycloak_quarkus_https_key_file_enabled`| Enable listener on HTTPS port | `False` |
|
||||||
|`keycloak_quarkus_key_file_copy_enabled`| Enable copy of key file to target host | `False` |
|
|`keycloak_quarkus_key_file_copy_enabled`| Enable copy of key file to target host | `False` |
|
||||||
|`keycloak_quarkus_key_content`| Content of the TLS private key. Use `"{{ lookup('file', 'server.key.pem') }}"` to lookup a file. | `""` |
|
|`keycloak_quarkus_key_content`| Content of the TLS private key. Use `"{{ lookup('file', 'server.key.pem') }}"` to lookup a file. | `""` |
|
||||||
|
@ -98,6 +105,7 @@ Role Defaults
|
||||||
|`keycloak_quarkus_db_enabled`| Enable auto configuration for database backend | `True` if `keycloak_quarkus_ha_enabled` is True, else `False` |
|
|`keycloak_quarkus_db_enabled`| Enable auto configuration for database backend | `True` if `keycloak_quarkus_ha_enabled` is True, else `False` |
|
||||||
|`keycloak_quarkus_jgroups_port`| jgroups cluster tcp port | `7800` |
|
|`keycloak_quarkus_jgroups_port`| jgroups cluster tcp port | `7800` |
|
||||||
|`keycloak_quarkus_systemd_wait_for_port` | Whether systemd unit should wait for keycloak port before returning | `{{ keycloak_quarkus_ha_enabled }}` |
|
|`keycloak_quarkus_systemd_wait_for_port` | Whether systemd unit should wait for keycloak port before returning | `{{ keycloak_quarkus_ha_enabled }}` |
|
||||||
|
|`keycloak_quarkus_systemd_wait_for_port_number`| Which port the systemd unit should wait for | `{{ keycloak_quarkus_https_port }}` |
|
||||||
|`keycloak_quarkus_systemd_wait_for_log` | Whether systemd unit should wait for service to be up in logs | `false` |
|
|`keycloak_quarkus_systemd_wait_for_log` | Whether systemd unit should wait for service to be up in logs | `false` |
|
||||||
|`keycloak_quarkus_systemd_wait_for_timeout`| How long to wait for service to be alive (seconds) | `60` |
|
|`keycloak_quarkus_systemd_wait_for_timeout`| How long to wait for service to be alive (seconds) | `60` |
|
||||||
|`keycloak_quarkus_systemd_wait_for_delay`| Activation delay for service systemd unit (seconds) | `10` |
|
|`keycloak_quarkus_systemd_wait_for_delay`| Activation delay for service systemd unit (seconds) | `10` |
|
||||||
|
@ -114,7 +122,8 @@ Role Defaults
|
||||||
|:---------|:------------|:--------|
|
|:---------|:------------|:--------|
|
||||||
|`keycloak_quarkus_http_relative_path`| Set the path relative to / for serving resources. The path must start with a / | `/` |
|
|`keycloak_quarkus_http_relative_path`| Set the path relative to / for serving resources. The path must start with a / | `/` |
|
||||||
|`keycloak_quarkus_hostname_strict`| Disables dynamically resolving the hostname from request headers | `true` |
|
|`keycloak_quarkus_hostname_strict`| Disables dynamically resolving the hostname from request headers | `true` |
|
||||||
|`keycloak_quarkus_hostname_strict_backchannel`| By default backchannel URLs are dynamically resolved from request headers to allow internal and external applications. If all applications use the public URL this option should be enabled. | `false` |
|
|`keycloak_quarkus_hostname_backchannel_dynamic`| Enables dynamic resolving of backchannel URLs, including hostname, scheme, port and context path. Set to true if your application accesses Keycloak via a private network. If set to true, hostname option needs to be specified as a full URL. | `false` |
|
||||||
|
|`keycloak_quarkus_hostname_strict_backchannel`| Deprecated, use (the inverted!)`keycloak_quarkus_hostname_backchannel_dynamic` instead. | |
|
||||||
|
|
||||||
|
|
||||||
#### Database configuration
|
#### Database configuration
|
||||||
|
@ -146,7 +155,7 @@ Role Defaults
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|:---------|:------------|:--------|
|
|:---------|:------------|:--------|
|
||||||
|`keycloak_quarkus_metrics_enabled`| Whether to enable metrics | `False` |
|
|`keycloak_quarkus_metrics_enabled`| Whether to enable metrics | `False` |
|
||||||
|`keycloak_quarkus_health_enabled`| If the server should expose health check endpoints | `True` |
|
|`keycloak_quarkus_health_enabled`| If the server should expose health check endpoints on the management interface | `True` |
|
||||||
|`keycloak_quarkus_archive` | keycloak install archive filename | `keycloak-{{ keycloak_quarkus_version }}.zip` |
|
|`keycloak_quarkus_archive` | keycloak install archive filename | `keycloak-{{ keycloak_quarkus_version }}.zip` |
|
||||||
|`keycloak_quarkus_installdir` | Installation path | `{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}` |
|
|`keycloak_quarkus_installdir` | Installation path | `{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}` |
|
||||||
|`keycloak_quarkus_home` | Installation work directory | `{{ keycloak_quarkus_installdir }}` |
|
|`keycloak_quarkus_home` | Installation work directory | `{{ keycloak_quarkus_installdir }}` |
|
||||||
|
@ -154,7 +163,6 @@ Role Defaults
|
||||||
|`keycloak_quarkus_master_realm` | Name for rest authentication realm | `master` |
|
|`keycloak_quarkus_master_realm` | Name for rest authentication realm | `master` |
|
||||||
|`keycloak_auth_client` | Authentication client for configuration REST calls | `admin-cli` |
|
|`keycloak_auth_client` | Authentication client for configuration REST calls | `admin-cli` |
|
||||||
|`keycloak_force_install` | Remove pre-existing versions of service | `False` |
|
|`keycloak_force_install` | Remove pre-existing versions of service | `False` |
|
||||||
|`keycloak_url` | URL for configuration rest calls | `http://{{ keycloak_quarkus_host }}:{{ keycloak_http_port }}` |
|
|
||||||
|`keycloak_quarkus_log`| Enable one or more log handlers in a comma-separated list | `file` |
|
|`keycloak_quarkus_log`| Enable one or more log handlers in a comma-separated list | `file` |
|
||||||
|`keycloak_quarkus_log_level`| The log level of the root category or a comma-separated list of individual categories and their levels | `info` |
|
|`keycloak_quarkus_log_level`| The log level of the root category or a comma-separated list of individual categories and their levels | `info` |
|
||||||
|`keycloak_quarkus_log_file`| Set the log file path and filename relative to keycloak home | `data/log/keycloak.log` |
|
|`keycloak_quarkus_log_file`| Set the log file path and filename relative to keycloak home | `data/log/keycloak.log` |
|
||||||
|
@ -198,14 +206,14 @@ keycloak_quarkus_providers:
|
||||||
- id: http-client # required; "{{ id }}.jar" identifies the file name on RHBK
|
- id: http-client # required; "{{ id }}.jar" identifies the file name on RHBK
|
||||||
spi: connections # required if neither url, local_path nor maven are specified; required for setting properties
|
spi: connections # required if neither url, local_path nor maven are specified; required for setting properties
|
||||||
default: true # optional, whether to set default for spi, default false
|
default: true # optional, whether to set default for spi, default false
|
||||||
restart: true # optional, whether to restart, default true
|
restart: true # optional, whether to rebuild config and restart the service after deploying, default true
|
||||||
url: https://.../.../custom_spi.jar # optional, url for download via http
|
url: https://.../.../custom_spi.jar # optional, url for download via http
|
||||||
local_path: my_theme_spi.jar # optional, path on local controller for SPI to be uploaded
|
local_path: my_theme_spi.jar # optional, path on local controller for SPI to be uploaded
|
||||||
maven: # optional, for download using maven
|
maven: # optional, for download using maven
|
||||||
repository_url: https://maven.pkg.github.com/OWNER/REPOSITORY # optional, maven repo url
|
repository_url: https://maven.pkg.github.com/OWNER/REPOSITORY # optional, maven repo url
|
||||||
group_id: my.group # optional, maven group id
|
group_id: my.group # optional, maven group id
|
||||||
artifact_id: artifact # optional, maven artifact id
|
artifact_id: artifact # optional, maven artifact id
|
||||||
version: 24.0.4 # optional, defaults to latest
|
version: 24.0.5 # optional, defaults to latest
|
||||||
username: user # optional, cf. https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry#authenticating-to-github-packages
|
username: user # optional, cf. https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-apache-maven-registry#authenticating-to-github-packages
|
||||||
password: pat # optional, provide a PAT for accessing Github's Apache Maven registry
|
password: pat # optional, provide a PAT for accessing Github's Apache Maven registry
|
||||||
properties: # optional, list of key-values
|
properties: # optional, list of key-values
|
||||||
|
@ -241,7 +249,8 @@ Role Variables
|
||||||
|
|
||||||
| Variable | Description | Required |
|
| Variable | Description | Required |
|
||||||
|:---------|:------------|----------|
|
|:---------|:------------|----------|
|
||||||
|`keycloak_quarkus_admin_pass`| Password of console admin account | `yes` |
|
|`keycloak_quarkus_bootstrap_admin_password`| Password of console admin account | `yes` |
|
||||||
|
|`keycloak_quarkus_admin_pass`| Deprecated, use `keycloak_quarkus_bootstrap_admin_password` instead. | |
|
||||||
|`keycloak_quarkus_frontend_url`| Base URL for frontend URLs, including scheme, host, port and path | `no` |
|
|`keycloak_quarkus_frontend_url`| Base URL for frontend URLs, including scheme, host, port and path | `no` |
|
||||||
|`keycloak_quarkus_admin_url`| Base URL for accessing the administration console, including scheme, host, port and path | `no` |
|
|`keycloak_quarkus_admin_url`| Base URL for accessing the administration console, including scheme, host, port and path | `no` |
|
||||||
|`keycloak_quarkus_ks_vault_pass`| The password for accessing the keystore vault SPI | `no` |
|
|`keycloak_quarkus_ks_vault_pass`| The password for accessing the keystore vault SPI | `no` |
|
||||||
|
@ -263,7 +272,7 @@ The role uses the following [custom facts](https://docs.ansible.com/ansible/late
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|:---------|:------------|
|
|:---------|:------------|
|
||||||
|`general.bootstrapped` | A custom fact indicating whether this role has been used for bootstrapping keycloak on the respective host before; set to `false` (e.g., when starting off with a new, empty database) ensures that the initial admin user as defined by `keycloak_quarkus_admin_user[_pass]` gets created |
|
|`general.bootstrapped` | A custom fact indicating whether this role has been used for bootstrapping keycloak on the respective host before; set to `false` (e.g., when starting off with a new, empty database) ensures that the initial admin user as defined by `keycloak_quarkus_bootstrap_admin_user[_password]` gets created |
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
### Configuration specific to keycloak
|
### Configuration specific to keycloak
|
||||||
keycloak_quarkus_version: 24.0.4
|
keycloak_quarkus_version: 26.0.7
|
||||||
keycloak_quarkus_archive: "keycloak-{{ keycloak_quarkus_version }}.zip"
|
keycloak_quarkus_archive: "keycloak-{{ keycloak_quarkus_version }}.zip"
|
||||||
keycloak_quarkus_download_url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}"
|
keycloak_quarkus_download_url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}"
|
||||||
keycloak_quarkus_installdir: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}"
|
keycloak_quarkus_installdir: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}"
|
||||||
|
@ -15,6 +15,7 @@ keycloak_quarkus_java_home:
|
||||||
keycloak_quarkus_dest: /opt/keycloak
|
keycloak_quarkus_dest: /opt/keycloak
|
||||||
keycloak_quarkus_home: "{{ keycloak_quarkus_installdir }}"
|
keycloak_quarkus_home: "{{ keycloak_quarkus_installdir }}"
|
||||||
keycloak_quarkus_config_dir: "{{ keycloak_quarkus_home }}/conf"
|
keycloak_quarkus_config_dir: "{{ keycloak_quarkus_home }}/conf"
|
||||||
|
keycloak_quarkus_download_path: "{{ lookup('env', 'PWD') }}"
|
||||||
keycloak_quarkus_start_dev: false
|
keycloak_quarkus_start_dev: false
|
||||||
keycloak_quarkus_service_user: keycloak
|
keycloak_quarkus_service_user: keycloak
|
||||||
keycloak_quarkus_service_group: keycloak
|
keycloak_quarkus_service_group: keycloak
|
||||||
|
@ -26,18 +27,16 @@ keycloak_quarkus_configure_firewalld: false
|
||||||
keycloak_quarkus_configure_iptables: false
|
keycloak_quarkus_configure_iptables: false
|
||||||
|
|
||||||
### administrator console password
|
### administrator console password
|
||||||
keycloak_quarkus_admin_user: admin
|
keycloak_quarkus_bootstrap_admin_user: admin
|
||||||
keycloak_quarkus_admin_pass:
|
keycloak_quarkus_bootstrap_admin_password:
|
||||||
keycloak_quarkus_master_realm: master
|
keycloak_quarkus_master_realm: master
|
||||||
|
|
||||||
### Configuration settings
|
### Configuration settings
|
||||||
keycloak_quarkus_bind_address: 0.0.0.0
|
keycloak_quarkus_bind_address: 0.0.0.0
|
||||||
keycloak_quarkus_host: localhost
|
|
||||||
keycloak_quarkus_port: -1
|
|
||||||
keycloak_quarkus_path:
|
|
||||||
keycloak_quarkus_http_enabled: true
|
keycloak_quarkus_http_enabled: true
|
||||||
keycloak_quarkus_http_port: 8080
|
keycloak_quarkus_http_port: 8080
|
||||||
keycloak_quarkus_https_port: 8443
|
keycloak_quarkus_https_port: 8443
|
||||||
|
keycloak_quarkus_http_management_port: 9000
|
||||||
keycloak_quarkus_ajp_port: 8009
|
keycloak_quarkus_ajp_port: 8009
|
||||||
keycloak_quarkus_jgroups_port: 7800
|
keycloak_quarkus_jgroups_port: 7800
|
||||||
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx2048m"
|
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx2048m"
|
||||||
|
@ -74,13 +73,14 @@ keycloak_quarkus_ha_discovery: "TCPPING"
|
||||||
### Enable database configuration, must be enabled when HA is configured
|
### Enable database configuration, must be enabled when HA is configured
|
||||||
keycloak_quarkus_db_enabled: "{{ keycloak_quarkus_ha_enabled }}"
|
keycloak_quarkus_db_enabled: "{{ keycloak_quarkus_ha_enabled }}"
|
||||||
keycloak_quarkus_systemd_wait_for_port: "{{ keycloak_quarkus_ha_enabled }}"
|
keycloak_quarkus_systemd_wait_for_port: "{{ keycloak_quarkus_ha_enabled }}"
|
||||||
|
keycloak_quarkus_systemd_wait_for_port_number: "{{ keycloak_quarkus_https_port }}"
|
||||||
keycloak_quarkus_systemd_wait_for_log: false
|
keycloak_quarkus_systemd_wait_for_log: false
|
||||||
keycloak_quarkus_systemd_wait_for_timeout: 60
|
keycloak_quarkus_systemd_wait_for_timeout: 60
|
||||||
keycloak_quarkus_systemd_wait_for_delay: 10
|
keycloak_quarkus_systemd_wait_for_delay: 10
|
||||||
|
|
||||||
### keycloak frontend url
|
### keycloak frontend url
|
||||||
keycloak_quarkus_frontend_url:
|
keycloak_quarkus_hostname:
|
||||||
keycloak_quarkus_admin_url:
|
keycloak_quarkus_admin:
|
||||||
|
|
||||||
### Set the path relative to / for serving resources. The path must start with a /
|
### Set the path relative to / for serving resources. The path must start with a /
|
||||||
### (set to `/auth` for retrocompatibility with pre-quarkus releases)
|
### (set to `/auth` for retrocompatibility with pre-quarkus releases)
|
||||||
|
@ -89,9 +89,9 @@ keycloak_quarkus_http_relative_path: /
|
||||||
# Disables dynamically resolving the hostname from request headers.
|
# Disables dynamically resolving the hostname from request headers.
|
||||||
# Should always be set to true in production, unless proxy verifies the Host header.
|
# Should always be set to true in production, unless proxy verifies the Host header.
|
||||||
keycloak_quarkus_hostname_strict: true
|
keycloak_quarkus_hostname_strict: true
|
||||||
# By default backchannel URLs are dynamically resolved from request headers to allow internal and external applications.
|
# Enables dynamic resolving of backchannel URLs, including hostname, scheme, port and context path.
|
||||||
# If all applications use the public URL this option should be enabled.
|
# Set to true if your application accesses Keycloak via a private network. If set to true, keycloak_quarkus_hostname option needs to be specified as a full URL.
|
||||||
keycloak_quarkus_hostname_strict_backchannel: false
|
keycloak_quarkus_hostname_backchannel_dynamic: false
|
||||||
|
|
||||||
# The proxy headers that should be accepted by the server. ['', 'forwarded', 'xforwarded']
|
# The proxy headers that should be accepted by the server. ['', 'forwarded', 'xforwarded']
|
||||||
keycloak_quarkus_proxy_headers: ""
|
keycloak_quarkus_proxy_headers: ""
|
||||||
|
@ -136,9 +136,9 @@ keycloak_quarkus_default_jdbc:
|
||||||
version: 2.7.4
|
version: 2.7.4
|
||||||
mssql:
|
mssql:
|
||||||
url: 'jdbc:sqlserver://localhost:1433;databaseName=keycloak;'
|
url: 'jdbc:sqlserver://localhost:1433;databaseName=keycloak;'
|
||||||
version: 12.4.2
|
version: 12.8.1
|
||||||
driver_jar_url: "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/12.4.2.jre11/mssql-jdbc-12.4.2.jre11.jar"
|
driver_jar_url: "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/12.8.1.jre11/mssql-jdbc-12.8.1.jre11.jar"
|
||||||
# cf. https://access.redhat.com/documentation/en-us/red_hat_build_of_keycloak/24.0/html/server_guide/db-#db-installing-the-microsoft-sql-server-driver
|
# cf. 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
|
||||||
### logging configuration
|
### logging configuration
|
||||||
keycloak_quarkus_log: file
|
keycloak_quarkus_log: file
|
||||||
keycloak_quarkus_log_level: info
|
keycloak_quarkus_log_level: info
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
---
|
---
|
||||||
|
- name: "Invalidate {{ keycloak.service_name }} theme cache"
|
||||||
|
ansible.builtin.include_tasks: invalidate_theme_cache.yml
|
||||||
|
listen: "invalidate keycloak theme cache"
|
||||||
# handler should be invoked anytime a [build configuration](https://www.keycloak.org/server/all-config?f=build) changes
|
# handler should be invoked anytime a [build configuration](https://www.keycloak.org/server/all-config?f=build) changes
|
||||||
- name: "Rebuild {{ keycloak.service_name }} config"
|
- name: "Rebuild {{ keycloak.service_name }} config"
|
||||||
ansible.builtin.include_tasks: rebuild_config.yml
|
ansible.builtin.include_tasks: rebuild_config.yml
|
||||||
|
@ -10,7 +13,7 @@
|
||||||
ansible.builtin.include_tasks:
|
ansible.builtin.include_tasks:
|
||||||
file: "{{ keycloak_quarkus_restart_strategy if keycloak_quarkus_ha_enabled else 'restart.yml' }}"
|
file: "{{ keycloak_quarkus_restart_strategy if keycloak_quarkus_ha_enabled else 'restart.yml' }}"
|
||||||
listen: "restart keycloak"
|
listen: "restart keycloak"
|
||||||
- name: "Print deprecation warning"
|
- name: "Display deprecation warning"
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
msg: "Deprecation warning: you are using the deprecated variable '{{ deprecated_variable | d('NotSet') }}', check docs on how to upgrade."
|
msg: "Deprecation warning: you are using the deprecated variable '{{ deprecated_variable | d('NotSet') }}', check docs on how to upgrade."
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
|
@ -2,7 +2,7 @@ argument_specs:
|
||||||
main:
|
main:
|
||||||
options:
|
options:
|
||||||
keycloak_quarkus_version:
|
keycloak_quarkus_version:
|
||||||
default: "24.0.4"
|
default: "26.0.7"
|
||||||
description: "keycloak.org package version"
|
description: "keycloak.org package version"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_archive:
|
keycloak_quarkus_archive:
|
||||||
|
@ -22,7 +22,7 @@ argument_specs:
|
||||||
description: "Perform an offline install"
|
description: "Perform an offline install"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_quarkus_jvm_package:
|
keycloak_quarkus_jvm_package:
|
||||||
default: "java-11-openjdk-headless"
|
default: "java-21-openjdk-headless"
|
||||||
description: "RHEL java package runtime"
|
description: "RHEL java package runtime"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_java_home:
|
keycloak_quarkus_java_home:
|
||||||
|
@ -56,25 +56,25 @@ argument_specs:
|
||||||
default: false
|
default: false
|
||||||
description: "Ensure firewalld is running and configure keycloak ports"
|
description: "Ensure firewalld is running and configure keycloak ports"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_service_restart_always:
|
keycloak_quarkus_service_restart_always:
|
||||||
default: false
|
default: false
|
||||||
description: "systemd restart always behavior of service; takes precedence over keycloak_service_restart_on_failure if true"
|
description: "systemd restart always behavior of service; takes precedence over keycloak_service_restart_on_failure if true"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_service_restart_on_failure:
|
keycloak_quarkus_service_restart_on_failure:
|
||||||
default: false
|
default: false
|
||||||
description: "systemd restart on-failure behavior of service"
|
description: "systemd restart on-failure behavior of service"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_service_restartsec:
|
keycloak_quarkus_service_restartsec:
|
||||||
default: "10s"
|
default: "10s"
|
||||||
description: "systemd RestartSec for service"
|
description: "systemd RestartSec for service"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_admin_user:
|
keycloak_quarkus_bootstrap_admin_user:
|
||||||
default: "admin"
|
default: "admin"
|
||||||
description: "Administration console user account"
|
description: "Administration user account, only for bootstrapping"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_admin_pass:
|
keycloak_quarkus_bootstrap_admin_password:
|
||||||
required: true
|
required: true
|
||||||
description: "Password of console admin account"
|
description: "Password of admin account, only for bootstrapping"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_master_realm:
|
keycloak_quarkus_master_realm:
|
||||||
default: "master"
|
default: "master"
|
||||||
|
@ -84,17 +84,19 @@ argument_specs:
|
||||||
default: "0.0.0.0"
|
default: "0.0.0.0"
|
||||||
description: "Address for binding service ports"
|
description: "Address for binding service ports"
|
||||||
type: "str"
|
type: "str"
|
||||||
|
keycloak_quarkus_hostname:
|
||||||
|
description: >-
|
||||||
|
Address at which is the server exposed.
|
||||||
|
Can be a full URL, or just a hostname. When only hostname is provided, scheme, port and context path are resolved from the request.
|
||||||
|
type: "str"
|
||||||
keycloak_quarkus_host:
|
keycloak_quarkus_host:
|
||||||
default: "localhost"
|
description: "Deprecated in v26, use keycloak_quarkus_hostname instead."
|
||||||
description: "Hostname for the Keycloak server"
|
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_port:
|
keycloak_quarkus_port:
|
||||||
default: -1
|
description: "Deprecated in v26, use keycloak_quarkus_hostname instead."
|
||||||
description: "The port used by the proxy when exposing the hostname"
|
|
||||||
type: "int"
|
type: "int"
|
||||||
keycloak_quarkus_path:
|
keycloak_quarkus_path:
|
||||||
required: false
|
description: "Deprecated in v26, use keycloak_quarkus_hostname instead."
|
||||||
description: "This should be set if proxy uses a different context-path for Keycloak"
|
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_http_enabled:
|
keycloak_quarkus_http_enabled:
|
||||||
default: true
|
default: true
|
||||||
|
@ -104,9 +106,12 @@ argument_specs:
|
||||||
default: 8080
|
default: 8080
|
||||||
description: "HTTP port"
|
description: "HTTP port"
|
||||||
type: "int"
|
type: "int"
|
||||||
|
keycloak_quarkus_health_check_url:
|
||||||
|
description: "Full URL (including scheme, host, path, fragment etc.) used for health check endpoint; keycloak_quarkus_hostname will NOT be prepended; helpful when health checks should happen against http port, but keycloak_quarkus_hostname uses https scheme per default"
|
||||||
|
type: "str"
|
||||||
keycloak_quarkus_health_check_url_path:
|
keycloak_quarkus_health_check_url_path:
|
||||||
default: "realms/master/.well-known/openid-configuration"
|
default: "realms/master/.well-known/openid-configuration"
|
||||||
description: "Path to the health check endpoint; scheme, host and keycloak_quarkus_http_relative_path will be prepended automatically"
|
description: "Path to the health check endpoint; keycloak_quarkus_hostname will be prepended automatically; Note that keycloak_quarkus_health_check_url takes precedence over this property"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_https_key_file_enabled:
|
keycloak_quarkus_https_key_file_enabled:
|
||||||
default: false
|
default: false
|
||||||
|
@ -182,6 +187,10 @@ argument_specs:
|
||||||
default: 8443
|
default: 8443
|
||||||
description: "HTTPS port"
|
description: "HTTPS port"
|
||||||
type: "int"
|
type: "int"
|
||||||
|
keycloak_quarkus_http_management_port:
|
||||||
|
default: 9000
|
||||||
|
description: "Port of the management interface. Relevant only when something is exposed on the management interface - see the guide for details."
|
||||||
|
type: "int"
|
||||||
keycloak_quarkus_ajp_port:
|
keycloak_quarkus_ajp_port:
|
||||||
default: 8009
|
default: 8009
|
||||||
description: "AJP port"
|
description: "AJP port"
|
||||||
|
@ -226,13 +235,21 @@ argument_specs:
|
||||||
default: /
|
default: /
|
||||||
description: "Set the path relative to / for serving resources. The path must start with a /"
|
description: "Set the path relative to / for serving resources. The path must start with a /"
|
||||||
type: "str"
|
type: "str"
|
||||||
|
keycloak_quarkus_http_management_relative_path:
|
||||||
|
required: false
|
||||||
|
description: "Set the path relative to / for serving resources from management interface. The path must start with a /. If not given, the value is inherited from HTTP options. Relevant only when something is exposed on the management interface - see the guide for details."
|
||||||
|
type: "str"
|
||||||
keycloak_quarkus_frontend_url:
|
keycloak_quarkus_frontend_url:
|
||||||
required: false
|
required: false
|
||||||
description: "Service public URL"
|
description: "Deprecated in v26, use keycloak_quarkus_hostname instead."
|
||||||
|
type: "str"
|
||||||
|
keycloak_quarkus_admin:
|
||||||
|
required: false
|
||||||
|
description: "Service URL for the admin console"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_admin_url:
|
keycloak_quarkus_admin_url:
|
||||||
required: false
|
required: false
|
||||||
description: "Service URL for the admin console"
|
description: "Deprecated in v26, use keycloak_quarkus_admin instead."
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_quarkus_metrics_enabled:
|
keycloak_quarkus_metrics_enabled:
|
||||||
default: false
|
default: false
|
||||||
|
@ -240,7 +257,7 @@ argument_specs:
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_quarkus_health_enabled:
|
keycloak_quarkus_health_enabled:
|
||||||
default: true
|
default: true
|
||||||
description: "If the server should expose health check endpoints"
|
description: "If the server should expose health check endpoints on the management interface"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_quarkus_ispn_user:
|
keycloak_quarkus_ispn_user:
|
||||||
default: "supervisor"
|
default: "supervisor"
|
||||||
|
@ -348,24 +365,18 @@ argument_specs:
|
||||||
description: >
|
description: >
|
||||||
Disables dynamically resolving the hostname from request headers. Should always be set to true in production, unless
|
Disables dynamically resolving the hostname from request headers. Should always be set to true in production, unless
|
||||||
proxy verifies the Host header.
|
proxy verifies the Host header.
|
||||||
keycloak_quarkus_hostname_strict_backchannel:
|
keycloak_quarkus_hostname_backchannel_dynamic:
|
||||||
default: false
|
default: false
|
||||||
type: "bool"
|
type: "bool"
|
||||||
description: >
|
description: >
|
||||||
By default backchannel URLs are dynamically resolved from request headers to allow internal and external applications. If all
|
Enables dynamic resolving of backchannel URLs, including hostname, scheme, port and context path.
|
||||||
applications use the public URL this option should be enabled.
|
Set to true if your application accesses Keycloak via a private network. If set to true, hostname option needs to be specified as a full URL.
|
||||||
keycloak_quarkus_spi_sticky_session_encoder_infinispan_should_attach_route:
|
keycloak_quarkus_spi_sticky_session_encoder_infinispan_should_attach_route:
|
||||||
default: true
|
default: true
|
||||||
type: "bool"
|
type: "bool"
|
||||||
description: >
|
description: >
|
||||||
If the route should be attached to cookies to reflect the node that owns a particular session. If false, route is not attached to cookies
|
If the route should be attached to cookies to reflect the node that owns a particular session. If false, route is not attached to cookies
|
||||||
and we rely on the session affinity capabilities from reverse proxy
|
and we rely on the session affinity capabilities from reverse proxy
|
||||||
keycloak_quarkus_hostname_strict_https:
|
|
||||||
type: "bool"
|
|
||||||
required: false
|
|
||||||
description: >
|
|
||||||
By default, Keycloak requires running using TLS/HTTPS. If the service MUST run without TLS/HTTPS, then set
|
|
||||||
this option to "true"
|
|
||||||
keycloak_quarkus_ks_vault_enabled:
|
keycloak_quarkus_ks_vault_enabled:
|
||||||
default: false
|
default: false
|
||||||
type: "bool"
|
type: "bool"
|
||||||
|
@ -386,6 +397,10 @@ argument_specs:
|
||||||
description: 'Whether systemd unit should wait for keycloak port before returning'
|
description: 'Whether systemd unit should wait for keycloak port before returning'
|
||||||
default: "{{ keycloak_quarkus_ha_enabled }}"
|
default: "{{ keycloak_quarkus_ha_enabled }}"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
|
keycloak_quarkus_systemd_wait_for_port_number:
|
||||||
|
default: "{{ keycloak_quarkus_https_port }}"
|
||||||
|
description: "The port the systemd unit should wait for, by default the https port"
|
||||||
|
type: "int"
|
||||||
keycloak_quarkus_systemd_wait_for_log:
|
keycloak_quarkus_systemd_wait_for_log:
|
||||||
description: 'Whether systemd unit should wait for service to be up in logs'
|
description: 'Whether systemd unit should wait for service to be up in logs'
|
||||||
default: false
|
default: false
|
||||||
|
@ -453,10 +468,18 @@ argument_specs:
|
||||||
description: "Number of attempts for successful health check before failing"
|
description: "Number of attempts for successful health check before failing"
|
||||||
default: 25
|
default: 25
|
||||||
type: 'int'
|
type: 'int'
|
||||||
|
keycloak_quarkus_show_deprecation_warnings:
|
||||||
|
default: true
|
||||||
|
description: "Whether or not deprecation warnings should be shown"
|
||||||
|
type: "bool"
|
||||||
|
keycloak_quarkus_download_path:
|
||||||
|
description: "Path local to controller for offline/download of install archives"
|
||||||
|
default: "{{ lookup('env', 'PWD') }}"
|
||||||
|
type: "str"
|
||||||
downstream:
|
downstream:
|
||||||
options:
|
options:
|
||||||
rhbk_version:
|
rhbk_version:
|
||||||
default: "24.0.3"
|
default: "26.0.7"
|
||||||
description: "Red Hat Build of Keycloak version"
|
description: "Red Hat Build of Keycloak version"
|
||||||
type: "str"
|
type: "str"
|
||||||
rhbk_archive:
|
rhbk_archive:
|
||||||
|
@ -483,10 +506,6 @@ argument_specs:
|
||||||
default: false
|
default: false
|
||||||
description: "Perform an offline install"
|
description: "Perform an offline install"
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_quarkus_show_deprecation_warnings:
|
|
||||||
default: true
|
|
||||||
description: "Whether deprecation warnings should be shown"
|
|
||||||
type: "bool"
|
|
||||||
rhbk_service_name:
|
rhbk_service_name:
|
||||||
default: "rhbk"
|
default: "rhbk"
|
||||||
description: "systemd service name for Red Hat Build of Keycloak"
|
description: "systemd service name for Red Hat Build of Keycloak"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
- name: Write ansible custom facts
|
- name: Save ansible custom facts
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: keycloak.fact.j2
|
src: keycloak.fact.j2
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
vars:
|
vars:
|
||||||
bootstrapped: true
|
bootstrapped: true
|
||||||
|
|
||||||
- name: Re-read custom facts
|
- name: Refresh custom facts
|
||||||
ansible.builtin.setup:
|
ansible.builtin.setup:
|
||||||
filter: ansible_local
|
filter: ansible_local
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
- name: "Initialize empty configuration key store"
|
- name: "Initialize empty configuration key store"
|
||||||
become: true
|
become: true
|
||||||
# keytool doesn't allow creating an empty key store, so this is a hacky way around it
|
# keytool doesn't allow creating an empty key store, so this is a hacky way around it
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
|
||||||
set -o nounset # abort on unbound variable
|
set -o nounset # abort on unbound variable
|
||||||
set -o pipefail # do not hide errors within pipes
|
set -o pipefail # do not hide errors within pipes
|
||||||
set -o errexit # abort on nonzero exit status
|
set -o errexit # abort on nonzero exit status
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
creates: "{{ keycloak_quarkus_config_key_store_file }}"
|
creates: "{{ keycloak_quarkus_config_key_store_file }}"
|
||||||
|
|
||||||
- name: "Set configuration key store using keytool"
|
- name: "Set configuration key store using keytool"
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
|
||||||
set -o nounset # abort on unbound variable
|
set -o nounset # abort on unbound variable
|
||||||
set -o pipefail # do not hide errors within pipes
|
set -o pipefail # do not hide errors within pipes
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo {{ item.value | quote }} | keytool -noprompt -importpass -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }} -storetype PKCS12
|
echo {{ item.value | quote }} | keytool -noprompt -importpass -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }} -storetype PKCS12
|
||||||
with_items: "{{ store_items }}"
|
loop: "{{ store_items }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
become: true
|
become: true
|
||||||
changed_when: true
|
changed_when: true
|
||||||
|
|
|
@ -49,5 +49,101 @@
|
||||||
notify:
|
notify:
|
||||||
- print deprecation warning
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
|
||||||
|
- name: Check deprecation of keycloak_quarkus_frontend_url -> keycloak_quarkus_hostname
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_hostname is not defined
|
||||||
|
- keycloak_quarkus_frontend_url is defined
|
||||||
|
- keycloak_quarkus_frontend_url != ''
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_hostname: "{{ keycloak_quarkus_frontend_url }}"
|
||||||
|
deprecated_variable: "keycloak_quarkus_frontend_url" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
|
||||||
|
- name: Check deprecation of keycloak_quarkus_hostname_strict_https + keycloak_quarkus_host + keycloak_quarkus_port + keycloak_quarkus_path -> keycloak_quarkus_hostname
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_hostname is not defined
|
||||||
|
- keycloak_quarkus_hostname_strict_https is defined or keycloak_quarkus_frontend_url is defined or keycloak_quarkus_port is defined or keycloak_quarkus_path is defined
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_hostname: >-
|
||||||
|
{% set protocol = '' %}
|
||||||
|
{% if keycloak_quarkus_hostname_strict_https %}
|
||||||
|
{% set protocol = 'https://' %}
|
||||||
|
{% elif keycloak_quarkus_hostname_strict_https is defined and keycloak_quarkus_hostname_strict_https is False %}
|
||||||
|
{% set protocol = 'http://' %}
|
||||||
|
{% endif %}
|
||||||
|
{{ protocol }}{{ keycloak_quarkus_host }}:{{ keycloak_quarkus_port }}/{{ keycloak_quarkus_path }}
|
||||||
|
deprecated_variable: "keycloak_quarkus_hostname_strict_https or keycloak_quarkus_frontend_url or keycloak_quarkus_frontend_url or keycloak_quarkus_hostname" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
|
||||||
|
- name: Check deprecation of keycloak_quarkus_admin_url -> keycloak_quarkus_admin
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_admin is not defined
|
||||||
|
- keycloak_quarkus_admin_url is defined
|
||||||
|
- keycloak_quarkus_admin_url != ''
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_admin: "{{ keycloak_quarkus_admin_url }}"
|
||||||
|
deprecated_variable: "keycloak_quarkus_admin_url" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/26.0/html-single/upgrading_guide/index#new_hostname_options
|
||||||
|
- name: Check deprecation of keycloak_quarkus_hostname_strict_backchannel -> keycloak_quarkus_hostname_backchannel_dynamic
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_hostname_backchannel_dynamic is not defined
|
||||||
|
- keycloak_quarkus_hostname_strict_backchannel is defined
|
||||||
|
- keycloak_quarkus_hostname_strict_backchannel != ''
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_hostname_backchannel_dynamic: "{{ keycloak_quarkus_hostname_strict_backchannel == False }}"
|
||||||
|
deprecated_variable: "keycloak_quarkus_hostname_backchannel_dynamic" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://github.com/keycloak/keycloak/issues/30009
|
||||||
|
- name: Check deprecation of keycloak_quarkus_admin_user -> keycloak_quarkus_bootstrap_admin_user
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_bootstrap_admin_user is not defined
|
||||||
|
- keycloak_quarkus_admin_user is defined
|
||||||
|
- keycloak_quarkus_admin_user != ''
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_user }}"
|
||||||
|
deprecated_variable: "keycloak_quarkus_admin_user" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
|
# https://github.com/keycloak/keycloak/issues/30009
|
||||||
|
- name: Check deprecation of keycloak_quarkus_admin_pass -> keycloak_quarkus_bootstrap_admin_password
|
||||||
|
when:
|
||||||
|
- keycloak_quarkus_bootstrap_admin_password is not defined
|
||||||
|
- keycloak_quarkus_admin_pass is defined
|
||||||
|
- keycloak_quarkus_admin_pass != ''
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
changed_when: keycloak_quarkus_show_deprecation_warnings
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
keycloak_quarkus_bootstrap_admin_user: "{{ keycloak_quarkus_admin_pass }}"
|
||||||
|
deprecated_variable: "keycloak_quarkus_admin_pass" # read in deprecation handler
|
||||||
|
notify:
|
||||||
|
- print deprecation warning
|
||||||
|
|
||||||
- name: Flush handlers
|
- name: Flush handlers
|
||||||
ansible.builtin.meta: flush_handlers
|
ansible.builtin.meta: flush_handlers
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
enabled: true
|
enabled: true
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
- name: "Configure firewall for {{ keycloak.service_name }} ports"
|
- name: "Configure firewall for {{ keycloak.service_name }} http port"
|
||||||
become: true
|
become: true
|
||||||
ansible.posix.firewalld:
|
ansible.posix.firewalld:
|
||||||
port: "{{ item }}"
|
port: "{{ item }}"
|
||||||
|
@ -21,5 +21,16 @@
|
||||||
immediate: true
|
immediate: true
|
||||||
loop:
|
loop:
|
||||||
- "{{ keycloak_quarkus_http_port }}/tcp"
|
- "{{ keycloak_quarkus_http_port }}/tcp"
|
||||||
|
when: keycloak_quarkus_http_enabled | bool
|
||||||
|
|
||||||
|
- name: "Configure firewall for {{ keycloak.service_name }} ports"
|
||||||
|
become: true
|
||||||
|
ansible.posix.firewalld:
|
||||||
|
port: "{{ item }}"
|
||||||
|
permanent: true
|
||||||
|
state: enabled
|
||||||
|
immediate: true
|
||||||
|
loop:
|
||||||
- "{{ keycloak_quarkus_https_port }}/tcp"
|
- "{{ keycloak_quarkus_https_port }}/tcp"
|
||||||
|
- "{{ keycloak_quarkus_http_management_port }}/tcp"
|
||||||
- "{{ keycloak_quarkus_jgroups_port }}/tcp"
|
- "{{ keycloak_quarkus_jgroups_port }}/tcp"
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
- keycloak_quarkus_archive is defined
|
- keycloak_quarkus_archive is defined
|
||||||
- keycloak_quarkus_download_url is defined
|
- keycloak_quarkus_download_url is defined
|
||||||
- keycloak_quarkus_version is defined
|
- keycloak_quarkus_version is defined
|
||||||
|
- local_path is defined
|
||||||
quiet: true
|
quiet: true
|
||||||
|
|
||||||
- name: Check for an existing deployment
|
- name: Check for an existing deployment
|
||||||
|
@ -52,14 +53,6 @@
|
||||||
register: archive_path
|
register: archive_path
|
||||||
|
|
||||||
## download to controller
|
## download to controller
|
||||||
- name: Check local download archive path
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ lookup('env', 'PWD') }}"
|
|
||||||
register: local_path
|
|
||||||
delegate_to: localhost
|
|
||||||
run_once: true
|
|
||||||
become: false
|
|
||||||
|
|
||||||
- name: Download keycloak archive
|
- name: Download keycloak archive
|
||||||
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
|
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
|
||||||
url: "{{ keycloak_quarkus_download_url }}"
|
url: "{{ keycloak_quarkus_download_url }}"
|
||||||
|
@ -225,7 +218,7 @@
|
||||||
become: true
|
become: true
|
||||||
loop: "{{ keycloak_quarkus_providers }}"
|
loop: "{{ keycloak_quarkus_providers }}"
|
||||||
when: item.url is defined and item.url | length > 0
|
when: item.url is defined and item.url | length > 0
|
||||||
notify: "{{ ['rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or not item.restart else [] }}"
|
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
|
||||||
|
|
||||||
# this requires the `lxml` package to be installed; we redirect this step to localhost such that we do need to install it on the remote hosts
|
# this requires the `lxml` package to be installed; we redirect this step to localhost such that we do need to install it on the remote hosts
|
||||||
- name: "Download custom providers to localhost using maven"
|
- name: "Download custom providers to localhost using maven"
|
||||||
|
@ -242,9 +235,9 @@
|
||||||
loop: "{{ keycloak_quarkus_providers }}"
|
loop: "{{ keycloak_quarkus_providers }}"
|
||||||
when: item.maven is defined
|
when: item.maven is defined
|
||||||
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
|
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
|
||||||
notify: "{{ ['rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or not item.restart else [] }}"
|
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
|
||||||
|
|
||||||
- name: "Upload local maven providers"
|
- name: "Copy maven providers"
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: "{{ local_path.stat.path }}/{{ item.id }}.jar"
|
src: "{{ local_path.stat.path }}/{{ item.id }}.jar"
|
||||||
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
|
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
|
||||||
|
@ -256,7 +249,7 @@
|
||||||
when: item.maven is defined
|
when: item.maven is defined
|
||||||
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
|
no_log: "{{ item.maven.password is defined and item.maven.password | length > 0 | default(false) }}"
|
||||||
|
|
||||||
- name: "Upload local providers"
|
- name: "Copy local providers"
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: "{{ item.local_path }}"
|
src: "{{ item.local_path }}"
|
||||||
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
|
dest: "{{ keycloak.home }}/providers/{{ item.id }}.jar"
|
||||||
|
@ -266,6 +259,7 @@
|
||||||
become: true
|
become: true
|
||||||
loop: "{{ keycloak_quarkus_providers }}"
|
loop: "{{ keycloak_quarkus_providers }}"
|
||||||
when: item.local_path is defined
|
when: item.local_path is defined
|
||||||
|
notify: "{{ ['invalidate keycloak theme cache', 'rebuild keycloak config', 'restart keycloak'] if not item.restart is defined or item.restart else [] }}"
|
||||||
|
|
||||||
- name: Ensure required folder structure for policies exists
|
- name: Ensure required folder structure for policies exists
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|
11
roles/keycloak_quarkus/tasks/invalidate_theme_cache.yml
Normal file
11
roles/keycloak_quarkus/tasks/invalidate_theme_cache.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
# From https://docs.redhat.com/en/documentation/red_hat_build_of_keycloak/24.0/html/server_developer_guide/themes#creating_a_theme:
|
||||||
|
# If you want to manually delete the content of the themes cache,
|
||||||
|
# you can do so by deleting the data/tmp/kc-gzip-cache directory of the server distribution
|
||||||
|
# It can be useful for instance if you redeployed custom providers or custom themes without
|
||||||
|
# disabling themes caching in the previous server executions.
|
||||||
|
- name: "Delete {{ keycloak.service_name }} theme cache directory"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ keycloak.home }}/data/tmp/kc-gzip-cache"
|
||||||
|
state: absent
|
||||||
|
become: true
|
|
@ -91,7 +91,7 @@
|
||||||
register: keycloak_service_status
|
register: keycloak_service_status
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
- name: "Trigger bootstrapped notification: remove `keycloak_quarkus_admin_user[_pass]` env vars"
|
- name: "Notify to remove `keycloak_quarkus_bootstrap_admin_user[_password]` env vars"
|
||||||
when:
|
when:
|
||||||
- not ansible_local.keycloak.general.bootstrapped | default(false) | bool # it was not bootstrapped prior to the current role's execution
|
- not ansible_local.keycloak.general.bootstrapped | default(false) | bool # it was not bootstrapped prior to the current role's execution
|
||||||
- keycloak_service_status.status.ActiveState == "active" # but it is now
|
- keycloak_service_status.status.ActiveState == "active" # but it is now
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
- name: Validate admin console password
|
- name: Validate admin console password
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- keycloak_quarkus_admin_pass | length > 12
|
- keycloak_quarkus_bootstrap_admin_password | length > 12
|
||||||
quiet: true
|
quiet: true
|
||||||
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_admin_pass to a 12+ char long string"
|
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_bootstrap_admin_password to a 12+ char long string"
|
||||||
success_msg: "{{ 'Console administrator password OK' }}"
|
success_msg: "{{ 'Console administrator password OK' }}"
|
||||||
|
|
||||||
- name: Validate relative path
|
- name: Validate http_relative_path
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- keycloak_quarkus_http_relative_path is regex('^/.*')
|
- keycloak_quarkus_http_relative_path is regex('^/.*')
|
||||||
|
@ -15,6 +15,15 @@
|
||||||
fail_msg: "The relative path for keycloak_quarkus_http_relative_path must begin with /"
|
fail_msg: "The relative path for keycloak_quarkus_http_relative_path must begin with /"
|
||||||
success_msg: "{{ 'Relative path OK' }}"
|
success_msg: "{{ 'Relative path OK' }}"
|
||||||
|
|
||||||
|
- name: Validate http_management_relative_path
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- keycloak_quarkus_http_management_relative_path is regex('^/.*')
|
||||||
|
quiet: true
|
||||||
|
fail_msg: "The relative path for keycloak_quarkus_http_management_relative_path must begin with /"
|
||||||
|
success_msg: "{{ 'Relative mgmt path OK' }}"
|
||||||
|
when: keycloak_quarkus_http_management_relative_path is defined
|
||||||
|
|
||||||
- name: Validate configuration
|
- name: Validate configuration
|
||||||
ansible.builtin.assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
|
@ -43,10 +52,50 @@
|
||||||
vars:
|
vars:
|
||||||
packages_list: "{{ keycloak_quarkus_prereq_package_list }}"
|
packages_list: "{{ keycloak_quarkus_prereq_package_list }}"
|
||||||
|
|
||||||
|
- name: Check local download archive path
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ keycloak_quarkus_download_path }}"
|
||||||
|
register: local_path
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
become: false
|
||||||
|
|
||||||
|
- name: Validate local download path
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- local_path.stat.exists
|
||||||
|
- local_path.stat.readable
|
||||||
|
- keycloak_quarkus_offline_install or local_path.stat.writeable
|
||||||
|
quiet: true
|
||||||
|
fail_msg: "Defined controller path for downloading resources is incorrect or unreadable: {{ keycloak_quarkus_download_path }}"
|
||||||
|
success_msg: "Will download resource to controller path: {{ keycloak_quarkus_download_path }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
|
||||||
|
- name: Check downloaded archive if offline
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ local_path.stat.path }}/{{ keycloak.bundle }}"
|
||||||
|
when: keycloak_quarkus_offline_install
|
||||||
|
register: local_archive_path_check
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
|
||||||
|
- name: Validate local downloaded archive if offline
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- local_archive_path_check.stat.exists
|
||||||
|
- local_archive_path_check.stat.readable
|
||||||
|
quiet: true
|
||||||
|
fail_msg: "Configured for offline install but install archive not found at: {{ local_path.stat.path }}/{{ keycloak.bundle }}"
|
||||||
|
success_msg: "Will install offline with expected archive: {{ local_path.stat.path }}/{{ keycloak.bundle }}"
|
||||||
|
when: keycloak_quarkus_offline_install
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
|
||||||
- name: "Validate keytool"
|
- name: "Validate keytool"
|
||||||
when: keycloak_quarkus_config_key_store_password | length > 0
|
when: keycloak_quarkus_config_key_store_password | length > 0
|
||||||
block:
|
block:
|
||||||
- name: "Attempt to run keytool"
|
- name: "Check run keytool"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
ansible.builtin.command: keytool -help
|
ansible.builtin.command: keytool -help
|
||||||
register: keytool_check
|
register: keytool_check
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
# cf. https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup
|
# cf. https://www.keycloak.org/server/configuration#_optimize_the_keycloak_startup
|
||||||
- name: "Rebuild {{ keycloak.service_name }} config"
|
- name: "Rebuild {{ keycloak.service_name }} config"
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: | # noqa blocked_modules shell is necessary here
|
||||||
{{ keycloak.home }}/bin/kc.sh build
|
{{ keycloak.home }}/bin/kc.sh build
|
||||||
environment:
|
environment:
|
||||||
PATH: "{{ keycloak_quarkus_java_home | default(keycloak_quarkus_pkg_java_home, true) }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
PATH: "{{ keycloak_quarkus_java_home | default(keycloak_quarkus_pkg_java_home, true) }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
delay: "{{ keycloak_quarkus_restart_health_check_delay }}"
|
delay: "{{ keycloak_quarkus_restart_health_check_delay }}"
|
||||||
when: internal_force_health_check | default(keycloak_quarkus_restart_health_check)
|
when: internal_force_health_check | default(keycloak_quarkus_restart_health_check)
|
||||||
|
|
||||||
- name: Pause to give distributed ispn caches time to (re-)replicate back onto first host
|
- name: Wait to give distributed ispn caches time to (re-)replicate back onto first host
|
||||||
ansible.builtin.pause:
|
ansible.builtin.pause:
|
||||||
seconds: "{{ keycloak_quarkus_restart_pause }}"
|
seconds: "{{ keycloak_quarkus_restart_pause }}"
|
||||||
when:
|
when:
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
<infinispan
|
<infinispan
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="urn:infinispan:config:14.0 http://www.infinispan.org/schemas/infinispan-config-14.0.xsd"
|
xsi:schemaLocation="urn:infinispan:config:15.0 http://www.infinispan.org/schemas/infinispan-config-15.0.xsd"
|
||||||
xmlns="urn:infinispan:config:14.0">
|
xmlns="urn:infinispan:config:15.0">
|
||||||
|
|
||||||
{% set stack_expression='' %}
|
{% set stack_expression='' %}
|
||||||
{% if keycloak_quarkus_ha_enabled and keycloak_quarkus_ha_discovery == 'TCPPING' %}
|
{% if keycloak_quarkus_ha_enabled and keycloak_quarkus_ha_discovery == 'TCPPING' %}
|
||||||
|
@ -55,18 +55,22 @@
|
||||||
</local-cache>
|
</local-cache>
|
||||||
<distributed-cache name="sessions" owners="2">
|
<distributed-cache name="sessions" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
|
<memory max-count="10000"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
<distributed-cache name="authenticationSessions" owners="2">
|
<distributed-cache name="authenticationSessions" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
<distributed-cache name="offlineSessions" owners="2">
|
<distributed-cache name="offlineSessions" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
|
<memory max-count="10000"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
<distributed-cache name="clientSessions" owners="2">
|
<distributed-cache name="clientSessions" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
|
<memory max-count="10000"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
<distributed-cache name="offlineClientSessions" owners="2">
|
<distributed-cache name="offlineClientSessions" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
|
<memory max-count="10000"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
<distributed-cache name="loginFailures" owners="2">
|
<distributed-cache name="loginFailures" owners="2">
|
||||||
<expiration lifespan="-1"/>
|
<expiration lifespan="-1"/>
|
||||||
|
@ -98,4 +102,4 @@
|
||||||
<memory max-count="-1"/>
|
<memory max-count="-1"/>
|
||||||
</distributed-cache>
|
</distributed-cache>
|
||||||
</cache-container>
|
</cache-container>
|
||||||
</infinispan>
|
</infinispan>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{ ansible_managed | comment }}
|
{{ ansible_managed | comment }}
|
||||||
{% if not ansible_local.keycloak.general.bootstrapped | default(false) | bool %}
|
{% if not ansible_local.keycloak.general.bootstrapped | default(false) | bool %}
|
||||||
KEYCLOAK_ADMIN={{ keycloak_quarkus_admin_user }}
|
KC_BOOTSTRAP_ADMIN_USERNAME={{ keycloak_quarkus_bootstrap_admin_user }}
|
||||||
KEYCLOAK_ADMIN_PASSWORD='{{ keycloak_quarkus_admin_pass }}'
|
KC_BOOTSTRAP_ADMIN_PASSWORD='{{ keycloak_quarkus_bootstrap_admin_password }}'
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ keycloak.bootstrap_mnemonic }}
|
{{ keycloak.bootstrap_mnemonic }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -10,18 +10,10 @@ db-password={{ keycloak_quarkus_db_pass }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if keycloak_quarkus_hostname_strict_https is defined and keycloak_quarkus_hostname_strict_https is sameas true -%}
|
|
||||||
hostname-strict-https=true
|
|
||||||
{% endif -%}
|
|
||||||
{% if keycloak_quarkus_hostname_strict_https is defined and keycloak_quarkus_hostname_strict_https is sameas false -%}
|
|
||||||
hostname-strict-https=false
|
|
||||||
{% endif -%}
|
|
||||||
|
|
||||||
{% if keycloak.config_key_store_enabled %}
|
{% if keycloak.config_key_store_enabled %}
|
||||||
# Config store
|
# Config store
|
||||||
config-keystore={{ keycloak_quarkus_config_key_store_file }}
|
config-keystore={{ keycloak_quarkus_config_key_store_file }}
|
||||||
config-keystore-password={{ keycloak_quarkus_config_key_store_password }}
|
config-keystore-password={{ keycloak_quarkus_config_key_store_password }}
|
||||||
config-keystore-type=PKCS12
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Observability
|
# Observability
|
||||||
|
@ -30,9 +22,17 @@ health-enabled={{ keycloak_quarkus_health_enabled | lower }}
|
||||||
|
|
||||||
# HTTP
|
# HTTP
|
||||||
http-enabled={{ keycloak_quarkus_http_enabled | lower }}
|
http-enabled={{ keycloak_quarkus_http_enabled | lower }}
|
||||||
|
{% if keycloak_quarkus_http_enabled %}
|
||||||
http-port={{ keycloak_quarkus_http_port }}
|
http-port={{ keycloak_quarkus_http_port }}
|
||||||
|
{% endif %}
|
||||||
http-relative-path={{ keycloak_quarkus_http_relative_path }}
|
http-relative-path={{ keycloak_quarkus_http_relative_path }}
|
||||||
|
|
||||||
|
# Management
|
||||||
|
http-management-port={{ keycloak_quarkus_http_management_port }}
|
||||||
|
{% if keycloak_quarkus_http_management_relative_path is defined and keycloak_quarkus_http_management_relative_path | length > 0 %}
|
||||||
|
http-management-relative-path={{ keycloak_quarkus_http_management_relative_path }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# HTTPS
|
# HTTPS
|
||||||
https-port={{ keycloak_quarkus_https_port }}
|
https-port={{ keycloak_quarkus_https_port }}
|
||||||
{% if keycloak_quarkus_https_key_file_enabled %}
|
{% if keycloak_quarkus_https_key_file_enabled %}
|
||||||
|
@ -49,16 +49,10 @@ https-trust-store-password={{ keycloak_quarkus_https_trust_store_password }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Client URL configuration
|
# Client URL configuration
|
||||||
{% if keycloak_quarkus_frontend_url %}
|
hostname={{ keycloak_quarkus_hostname }}
|
||||||
hostname-url={{ keycloak_quarkus_frontend_url }}
|
hostname-admin={{ keycloak_quarkus_admin }}
|
||||||
{% else %}
|
|
||||||
hostname={{ keycloak_quarkus_host }}
|
|
||||||
hostname-port={{ keycloak_quarkus_port }}
|
|
||||||
hostname-path={{ keycloak_quarkus_path }}
|
|
||||||
{% endif %}
|
|
||||||
hostname-admin-url={{ keycloak_quarkus_admin_url }}
|
|
||||||
hostname-strict={{ keycloak_quarkus_hostname_strict | lower }}
|
hostname-strict={{ keycloak_quarkus_hostname_strict | lower }}
|
||||||
hostname-strict-backchannel={{ keycloak_quarkus_hostname_strict_backchannel | lower }}
|
hostname-backchannel-dynamic={{ keycloak_quarkus_hostname_backchannel_dynamic | lower }}
|
||||||
|
|
||||||
# Cluster
|
# Cluster
|
||||||
{% if keycloak_quarkus_ha_enabled %}
|
{% if keycloak_quarkus_ha_enabled %}
|
||||||
|
|
|
@ -23,7 +23,7 @@ RestartSec={{ keycloak_quarkus_service_restartsec }}
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if keycloak_quarkus_systemd_wait_for_port %}
|
{% if keycloak_quarkus_systemd_wait_for_port %}
|
||||||
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'while ! ss -H -t -l -n sport = :{{ keycloak_quarkus_https_port }} | grep -q "^LISTEN.*:{{ keycloak_quarkus_https_port }}"; do sleep 1; done && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'while ! ss -H -t -l -n sport = :{{ keycloak_quarkus_systemd_wait_for_port_number }} | grep -q "^LISTEN.*:{{ keycloak_quarkus_systemd_wait_for_port_number }}"; do sleep 1; done && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if keycloak_quarkus_systemd_wait_for_log %}
|
{% if keycloak_quarkus_systemd_wait_for_log %}
|
||||||
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'cat {{ keycloak.log.file }} | sed "/Profile.*activated/ q" && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'cat {{ keycloak.log.file }} | sed "/Profile.*activated/ q" && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
||||||
|
|
|
@ -4,8 +4,7 @@ keycloak: # noqa var-naming this is an internal dict of interpolated values
|
||||||
config_dir: "{{ keycloak_quarkus_config_dir }}"
|
config_dir: "{{ keycloak_quarkus_config_dir }}"
|
||||||
bundle: "{{ keycloak_quarkus_archive }}"
|
bundle: "{{ keycloak_quarkus_archive }}"
|
||||||
service_name: "keycloak"
|
service_name: "keycloak"
|
||||||
health_url: "{{ 'https' if keycloak_quarkus_http_enabled == False else 'http' }}://{{ keycloak_quarkus_host }}:{{ keycloak_quarkus_https_port if keycloak_quarkus_http_enabled == False else keycloak_quarkus_http_port }}{{ keycloak_quarkus_http_relative_path }}{{ '/' \
|
health_url: "{{ keycloak_quarkus_health_check_url | default(keycloak_quarkus_hostname ~ '/' ~ (keycloak_quarkus_health_check_url_path | default('realms/master/.well-known/openid-configuration'))) }}"
|
||||||
if keycloak_quarkus_http_relative_path | length > 1 else '' }}{{ keycloak_quarkus_health_check_url_path | default('realms/master/.well-known/openid-configuration') }}"
|
|
||||||
cli_path: "{{ keycloak_quarkus_home }}/bin/kcadm.sh"
|
cli_path: "{{ keycloak_quarkus_home }}/bin/kcadm.sh"
|
||||||
service_user: "{{ keycloak_quarkus_service_user }}"
|
service_user: "{{ keycloak_quarkus_service_user }}"
|
||||||
service_group: "{{ keycloak_quarkus_service_group }}"
|
service_group: "{{ keycloak_quarkus_service_group }}"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
keycloak_quarkus_varjvm_package: "{{ keycloak_quarkus_jvm_package | default('java-17-openjdk-headless') }}"
|
keycloak_quarkus_varjvm_package: "{{ keycloak_quarkus_jvm_package | default('java-21-openjdk-headless') }}"
|
||||||
keycloak_quarkus_prereq_package_list:
|
keycloak_quarkus_prereq_package_list:
|
||||||
- "{{ keycloak_quarkus_varjvm_package }}"
|
- "{{ keycloak_quarkus_varjvm_package }}"
|
||||||
- unzip
|
- unzip
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
keycloak_realm
|
keycloak_realm
|
||||||
==============
|
==============
|
||||||
|
<!--start description_realm -->
|
||||||
|
|
||||||
Create realms and clients in [keycloak](https://keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) services.
|
Create realms and clients in [keycloak](https://keycloak.org/) or [Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on) services.
|
||||||
|
<!--end description_realm -->
|
||||||
|
|
||||||
Role Defaults
|
Role Defaults
|
||||||
-------------
|
-------------
|
||||||
|
@ -18,7 +19,7 @@ Role Defaults
|
||||||
|`keycloak_management_http_port`| Management port | `9990` |
|
|`keycloak_management_http_port`| Management port | `9990` |
|
||||||
|`keycloak_auth_client`| Authentication client for configuration REST calls | `admin-cli` |
|
|`keycloak_auth_client`| Authentication client for configuration REST calls | `admin-cli` |
|
||||||
|`keycloak_client_public`| Configure a public realm client | `True` |
|
|`keycloak_client_public`| Configure a public realm client | `True` |
|
||||||
|`keycloak_client_web_origins`| Web origins for realm client | `+` |
|
|`keycloak_client_web_origins`| Web origins for realm client | `/*` |
|
||||||
|`keycloak_url`| URL for configuration rest calls | `http://{{ keycloak_host }}:{{ keycloak_http_port }}` |
|
|`keycloak_url`| URL for configuration rest calls | `http://{{ keycloak_host }}:{{ keycloak_http_port }}` |
|
||||||
|`keycloak_management_url`| URL for management console rest calls | `http://{{ keycloak_host }}:{{ keycloak_management_http_port }}` |
|
|`keycloak_management_url`| URL for management console rest calls | `http://{{ keycloak_host }}:{{ keycloak_management_http_port }}` |
|
||||||
|
|
||||||
|
@ -136,4 +137,4 @@ Author Information
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
* [Guido Grazioli](https://github.com/guidograzioli)
|
* [Guido Grazioli](https://github.com/guidograzioli)
|
||||||
* [Romain Pelisse](https://github.com/rpelisse)
|
* [Romain Pelisse](https://github.com/rpelisse)
|
||||||
|
|
|
@ -43,7 +43,7 @@ keycloak_client_default_roles: []
|
||||||
keycloak_client_public: true
|
keycloak_client_public: true
|
||||||
|
|
||||||
# allowed web origins for the client
|
# allowed web origins for the client
|
||||||
keycloak_client_web_origins: '+'
|
keycloak_client_web_origins: '/*'
|
||||||
|
|
||||||
# list of user and role mappings to create in the client
|
# list of user and role mappings to create in the client
|
||||||
# Each user has the form:
|
# Each user has the form:
|
||||||
|
|
|
@ -53,7 +53,7 @@ argument_specs:
|
||||||
type: "bool"
|
type: "bool"
|
||||||
keycloak_client_web_origins:
|
keycloak_client_web_origins:
|
||||||
# line 42 of keycloak_realm/defaults/main.yml
|
# line 42 of keycloak_realm/defaults/main.yml
|
||||||
default: "+"
|
default: "/*"
|
||||||
description: "Web origins for realm client"
|
description: "Web origins for realm client"
|
||||||
type: "str"
|
type: "str"
|
||||||
keycloak_client_users:
|
keycloak_client_users:
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ keycloak_realm }}"
|
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ keycloak_realm }}"
|
||||||
method: GET
|
method: GET
|
||||||
|
validate_certs: false
|
||||||
status_code:
|
status_code:
|
||||||
- 200
|
- 200
|
||||||
- 404
|
- 404
|
||||||
|
@ -82,7 +83,7 @@
|
||||||
base_url: "{{ item.base_url | default('') }}"
|
base_url: "{{ item.base_url | default('') }}"
|
||||||
enabled: "{{ item.enabled | default(True) }}"
|
enabled: "{{ item.enabled | default(True) }}"
|
||||||
redirect_uris: "{{ item.redirect_uris | default(omit) }}"
|
redirect_uris: "{{ item.redirect_uris | default(omit) }}"
|
||||||
web_origins: "{{ item.web_origins | default('+') }}"
|
web_origins: "{{ item.web_origins | default(omit) }}"
|
||||||
bearer_only: "{{ item.bearer_only | default(omit) }}"
|
bearer_only: "{{ item.bearer_only | default(omit) }}"
|
||||||
standard_flow_enabled: "{{ item.standard_flow_enabled | default(omit) }}"
|
standard_flow_enabled: "{{ item.standard_flow_enabled | default(omit) }}"
|
||||||
implicit_flow_enabled: "{{ item.implicit_flow_enabled | default(omit) }}"
|
implicit_flow_enabled: "{{ item.implicit_flow_enabled | default(omit) }}"
|
||||||
|
@ -92,7 +93,7 @@
|
||||||
protocol: "{{ item.protocol | default(omit) }}"
|
protocol: "{{ item.protocol | default(omit) }}"
|
||||||
attributes: "{{ item.attributes | default(omit) }}"
|
attributes: "{{ item.attributes | default(omit) }}"
|
||||||
state: present
|
state: present
|
||||||
no_log: "{{ keycloak_no_log | default('True') }}"
|
no_log: "{{ keycloak_no_log | default('false') }}"
|
||||||
register: create_client_result
|
register: create_client_result
|
||||||
loop: "{{ keycloak_clients | flatten }}"
|
loop: "{{ keycloak_clients | flatten }}"
|
||||||
when: (item.name is defined and item.client_id is defined) or (item.name is defined and item.id is defined)
|
when: (item.name is defined and item.client_id is defined) or (item.name is defined and item.id is defined)
|
||||||
|
@ -110,3 +111,6 @@
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: client
|
loop_var: client
|
||||||
when: "'users' in client"
|
when: "'users' in client"
|
||||||
|
|
||||||
|
- name: Provide Access token lifespan
|
||||||
|
ansible.builtin.include_tasks: manage_token_lifespan.yml
|
||||||
|
|
14
roles/keycloak_realm/tasks/manage_token_lifespan.yml
Normal file
14
roles/keycloak_realm/tasks/manage_token_lifespan.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
- name: "Update Access token lifespan"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ keycloak_realm }}"
|
||||||
|
method: PUT
|
||||||
|
body:
|
||||||
|
accessTokenLifespan: 300
|
||||||
|
validate_certs: false
|
||||||
|
body_format: json
|
||||||
|
status_code:
|
||||||
|
- 200
|
||||||
|
- 204
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ keycloak_auth_response.json.access_token }}"
|
|
@ -3,6 +3,7 @@
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm | default(keycloak_realm) }}"
|
url: "{{ keycloak_url }}{{ keycloak_context }}/admin/realms/{{ client_role.realm | default(keycloak_realm) }}"
|
||||||
method: GET
|
method: GET
|
||||||
|
validate_certs: false
|
||||||
status_code:
|
status_code:
|
||||||
- 200
|
- 200
|
||||||
headers:
|
headers:
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
default(keycloak_realm) }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | \
|
default(keycloak_realm) }}/users/{{ (keycloak_user.json | first).id }}/role-mappings/clients/{{ (create_client_result.results | \
|
||||||
selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}/available"
|
selectattr('end_state.clientId', 'equalto', client_role.client) | list | first).end_state.id }}/available"
|
||||||
method: GET
|
method: GET
|
||||||
|
validate_certs: false
|
||||||
status_code:
|
status_code:
|
||||||
- 200
|
- 200
|
||||||
headers:
|
headers:
|
||||||
|
|
Loading…
Add table
Reference in a new issue