#188: keycloak_quarkus: allow setting "sensitive options" using a Java KeyStore file #188

This commit is contained in:
Helmut Wolf 2024-04-16 09:39:03 +02:00
parent 60ca798e1a
commit 0ee29eb483
7 changed files with 94 additions and 0 deletions

View file

@ -57,6 +57,9 @@ Role Defaults
|`keycloak_quarkus_https_trust_store_file`| The file path to the trust store | `{{ keycloak.home }}/conf/trust_store.p12` | |`keycloak_quarkus_https_trust_store_file`| The file path to the trust store | `{{ keycloak.home }}/conf/trust_store.p12` |
|`keycloak_quarkus_https_trust_store_password`| Password for the trust store | `""` | |`keycloak_quarkus_https_trust_store_password`| Password for the trust store | `""` |
|`keycloak_quarkus_proxy_headers`| Parse reverse proxy headers (`forwarded` or `xforwardedPassword`) | `""` | |`keycloak_quarkus_proxy_headers`| Parse reverse proxy headers (`forwarded` or `xforwardedPassword`) | `""` |
|`keycloak_quarkus_config_key_store_file`| Path to the configuration key store; only used if `keycloak_quarkus_keystore_password` is not empty | `{{ keycloak.home }}/conf/conf_store.p12` if `keycloak_quarkus_keystore_password`!='', else '' |
|`keycloak_quarkus_config_key_store_password`| Password of the configuration key store; if non-empty, `keycloak_quarkus_db_pass` will be saved to the key store at `keycloak_quarkus_config_key_store_file` (instead of being written to the configuration file in clear text | `""` |
* Hostname configuration * Hostname configuration

View file

@ -58,6 +58,9 @@ keycloak_quarkus_https_key_store_password: ''
keycloak_quarkus_https_trust_store_enabled: false keycloak_quarkus_https_trust_store_enabled: false
keycloak_quarkus_https_trust_store_file: "{{ keycloak.home }}/conf/trust_store.p12" keycloak_quarkus_https_trust_store_file: "{{ keycloak.home }}/conf/trust_store.p12"
keycloak_quarkus_https_trust_store_password: '' keycloak_quarkus_https_trust_store_password: ''
### configuration key store configuration
keycloak_quarkus_config_key_store_file: "{{ keycloak.home }}/conf/conf_store.p12"
keycloak_quarkus_config_key_store_password: ''
### Enable configuration for database backend, clustering and remote caches on infinispan ### Enable configuration for database backend, clustering and remote caches on infinispan
keycloak_quarkus_ha_enabled: false keycloak_quarkus_ha_enabled: false

View file

@ -152,6 +152,14 @@ argument_specs:
default: "" default: ""
description: "Password for the trust store" description: "Password for the trust store"
type: "str" type: "str"
keycloak_quarkus_config_key_store_file:
default: "{{ keycloak.home }}/conf/conf_store.p12"
description: "Path to the configuration key store; only used if `keycloak_quarkus_keystore_password` is not empty"
type: "str"
keycloak_quarkus_config_key_store_password:
default: ""
description: "Password of the configuration key store; if non-empty, `keycloak_quarkus_db_pass` will be saved to the key store at `keycloak_quarkus_config_key_store_file` (instead of being written to the configuration file in clear text"
type: "str"
keycloak_quarkus_https_port: keycloak_quarkus_https_port:
default: 8443 default: 8443
description: "HTTPS port" description: "HTTPS port"

View file

@ -0,0 +1,64 @@
---
- name: "Check if keytool exists in path"
block:
- name: "Attempt to run keytool"
ansible.builtin.command: keytool -help
register: keytool_check
ignore_errors: true
- name: "Fail when no keytool found"
when: keytool_check.rc != 0
ansible.builtin.fail:
msg: "keytool NOT found in the PATH, but is required for setting up the configuration key store"
- name: "Initialize configuration key store variables to be written"
ansible.builtin.set_fact:
store_items:
- key: "kc.db-password"
value: "{{ keycloak_quarkus_db_pass }}"
- name: "Initialize empty configuration key store"
become: true
# keytool doesn't allow creating an empty key store, so this is a hacky way around it
ansible.builtin.shell: |
set -o nounset # abort on unbound variable
set -o pipefail # do not hide errors within pipes
set -o errexit # abort on nonzero exit status
echo dummy | keytool -noprompt -importpass -alias dummy -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }} -storetype PKCS12
keytool -delete -alias dummy -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
args:
creates: "{{ keycloak_quarkus_config_key_store_file }}"
- name: "Set configuration key store using keytool"
ansible.builtin.shell: |
set -o nounset # abort on unbound variable
set -o pipefail # do not hide errors within pipes
keytool -list -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
retVal=$?
set -o errexit # abort on nonzero exit status
if [ $retVal -eq 0 ]; then
# value is already in keystore, but keytool has no replace function: delete and re-create instead
# note that we can not read whether the value has changed either[^1], so we need to override it
# [^1]: https://stackoverflow.com/a/37491400
keytool -delete -alias {{ item.key | quote }} -keystore {{ keycloak_quarkus_config_key_store_file | quote }} -storepass {{ keycloak_quarkus_config_key_store_password | quote }}
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
with_items: "{{ store_items }}"
no_log: true
become: true
changed_when: true
notify:
- restart keycloak
- name: "Set owner of configuration key store {{ keycloak_quarkus_config_key_store_file }}"
ansible.builtin.file:
path: "{{ keycloak_quarkus_config_key_store_file }}"
owner: "{{ keycloak.service_user }}"
group: "{{ keycloak.service_group }}"
mode: '0400'
become: true

View file

@ -26,6 +26,12 @@
tags: tags:
- systemd - systemd
- name: Include configuration key store tasks
when: keycloak.config_key_store_enabled
ansible.builtin.include_tasks: config_store.yml
tags:
- install
- name: "Configure config for keycloak service" - name: "Configure config for keycloak service"
ansible.builtin.template: ansible.builtin.template:
src: keycloak.conf.j2 src: keycloak.conf.j2

View file

@ -5,8 +5,17 @@
db={{ keycloak_quarkus_jdbc_engine }} db={{ keycloak_quarkus_jdbc_engine }}
db-url={{ keycloak_quarkus_jdbc_url }} db-url={{ keycloak_quarkus_jdbc_url }}
db-username={{ keycloak_quarkus_db_user }} db-username={{ keycloak_quarkus_db_user }}
{% if not keycloak.config_key_store_enabled %}
db-password={{ keycloak_quarkus_db_pass }} db-password={{ keycloak_quarkus_db_pass }}
{% endif %} {% endif %}
{% endif %}
{% if keycloak.config_key_store_enabled %}
# Config store
config-keystore={{ keycloak_quarkus_config_key_store_file }}
config-keystore-password={{ keycloak_quarkus_config_key_store_password }}
config-keystore-type=PKCS12
{% endif %}
# Observability # Observability
metrics-enabled={{ keycloak_quarkus_metrics_enabled | lower }} metrics-enabled={{ keycloak_quarkus_metrics_enabled | lower }}

View file

@ -10,6 +10,7 @@ keycloak: # noqa var-naming this is an internal dict of interpolated values
service_user: "{{ keycloak_quarkus_service_user }}" service_user: "{{ keycloak_quarkus_service_user }}"
service_group: "{{ keycloak_quarkus_service_group }}" service_group: "{{ keycloak_quarkus_service_group }}"
offline_install: "{{ keycloak_quarkus_offline_install }}" offline_install: "{{ keycloak_quarkus_offline_install }}"
config_key_store_enabled: "{{ keycloak_quarkus_config_key_store_password != '' }}"
log: log:
file: "{{ keycloak_quarkus_home }}/{{ keycloak_quarkus_log_file }}" file: "{{ keycloak_quarkus_home }}/{{ keycloak_quarkus_log_file }}"
level: "{{ keycloak_quarkus_log_level }}" level: "{{ keycloak_quarkus_log_level }}"