From 0dd86bb03aeb6a84aaf8fd0c58e24824ec8df899 Mon Sep 17 00:00:00 2001 From: Helmut Wolf Date: Tue, 16 Apr 2024 09:39:03 +0200 Subject: [PATCH] keycloak_quarkus: allow setting "sensitive options" using a Java KeyStore file #188 --- roles/keycloak_quarkus/README.md | 3 + roles/keycloak_quarkus/defaults/main.yml | 3 + .../keycloak_quarkus/meta/argument_specs.yml | 8 +++ roles/keycloak_quarkus/tasks/config_store.yml | 64 +++++++++++++++++++ roles/keycloak_quarkus/tasks/main.yml | 6 ++ .../templates/keycloak.conf.j2 | 9 +++ roles/keycloak_quarkus/vars/main.yml | 1 + 7 files changed, 94 insertions(+) create mode 100644 roles/keycloak_quarkus/tasks/config_store.yml diff --git a/roles/keycloak_quarkus/README.md b/roles/keycloak_quarkus/README.md index a518ccaf..ce454111 100644 --- a/roles/keycloak_quarkus/README.md +++ b/roles/keycloak_quarkus/README.md @@ -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_password`| Password for the trust store | `""` | |`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 diff --git a/roles/keycloak_quarkus/defaults/main.yml b/roles/keycloak_quarkus/defaults/main.yml index 2f2fa463..929521b7 100644 --- a/roles/keycloak_quarkus/defaults/main.yml +++ b/roles/keycloak_quarkus/defaults/main.yml @@ -58,6 +58,9 @@ keycloak_quarkus_https_key_store_password: '' keycloak_quarkus_https_trust_store_enabled: false keycloak_quarkus_https_trust_store_file: "{{ keycloak.home }}/conf/trust_store.p12" 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 keycloak_quarkus_ha_enabled: false diff --git a/roles/keycloak_quarkus/meta/argument_specs.yml b/roles/keycloak_quarkus/meta/argument_specs.yml index 3dc31b39..eed8379d 100644 --- a/roles/keycloak_quarkus/meta/argument_specs.yml +++ b/roles/keycloak_quarkus/meta/argument_specs.yml @@ -152,6 +152,14 @@ argument_specs: default: "" description: "Password for the trust store" 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: default: 8443 description: "HTTPS port" diff --git a/roles/keycloak_quarkus/tasks/config_store.yml b/roles/keycloak_quarkus/tasks/config_store.yml new file mode 100644 index 00000000..69197056 --- /dev/null +++ b/roles/keycloak_quarkus/tasks/config_store.yml @@ -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 diff --git a/roles/keycloak_quarkus/tasks/main.yml b/roles/keycloak_quarkus/tasks/main.yml index c5444525..d64e123c 100644 --- a/roles/keycloak_quarkus/tasks/main.yml +++ b/roles/keycloak_quarkus/tasks/main.yml @@ -26,6 +26,12 @@ tags: - 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" ansible.builtin.template: src: keycloak.conf.j2 diff --git a/roles/keycloak_quarkus/templates/keycloak.conf.j2 b/roles/keycloak_quarkus/templates/keycloak.conf.j2 index d13a4cb1..6c9433e3 100644 --- a/roles/keycloak_quarkus/templates/keycloak.conf.j2 +++ b/roles/keycloak_quarkus/templates/keycloak.conf.j2 @@ -5,8 +5,17 @@ db={{ keycloak_quarkus_jdbc_engine }} db-url={{ keycloak_quarkus_jdbc_url }} db-username={{ keycloak_quarkus_db_user }} +{% if not keycloak.config_key_store_enabled %} db-password={{ keycloak_quarkus_db_pass }} {% 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 metrics-enabled={{ keycloak_quarkus_metrics_enabled | lower }} diff --git a/roles/keycloak_quarkus/vars/main.yml b/roles/keycloak_quarkus/vars/main.yml index 0ef68448..9fc37911 100644 --- a/roles/keycloak_quarkus/vars/main.yml +++ b/roles/keycloak_quarkus/vars/main.yml @@ -9,6 +9,7 @@ keycloak: service_user: "{{ keycloak_quarkus_service_user }}" service_group: "{{ keycloak_quarkus_service_group }}" offline_install: "{{ keycloak_quarkus_offline_install }}" + config_key_store_enabled: "{{ keycloak_quarkus_config_key_store_password != '' }}" log: file: "{{ keycloak_quarkus_home }}/{{ keycloak_quarkus_log_file }}" level: "{{ keycloak_quarkus_log_level }}"