diff --git a/roles/keycloak_quarkus/README.md b/roles/keycloak_quarkus/README.md index d6fa46d..e5bb5bf 100644 --- a/roles/keycloak_quarkus/README.md +++ b/roles/keycloak_quarkus/README.md @@ -38,7 +38,7 @@ Role Defaults |`keycloak_quarkus_service_pidfile`| Pid file path for service | `/run/keycloak.pid` | |`keycloak_quarkus_jvm_package`| RHEL java package runtime | `java-17-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_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_opts`| JVM arguments; if overriden, it takes precedence over `keycloak_quarkus_java_*` | `{{ keycloak_quarkus_java_heap_opts + ' ' + keycloak_quarkus_java_jvm_opts }}` | |`keycloak_quarkus_frontend_url`| Set the base URL for frontend URLs, including scheme, host, port and path | | @@ -49,12 +49,17 @@ Role Defaults |`keycloak_quarkus_key_file`| The file path to a private key in PEM format | `{{ keycloak.home }}/conf/server.key.pem` | |`keycloak_quarkus_cert_file`| The file path to a server certificate or certificate chain in PEM format | `{{ keycloak.home }}/conf/server.crt.pem` | |`keycloak_quarkus_https_key_store_enabled`| Enable configuration of HTTPS via a key store | `False` | -|`keycloak_quarkus_key_store_file`| The file pat to the key store | `{{ keycloak.home }}/conf/key_store.p12` | -|`keycloak_quarkus_key_store_password`| Password for the key store | `""` | -|`keycloak_quarkus_https_trust_store_enabled`| Enalbe confiugration of a trust store | `False` | -|`keycloak_quarkus_trust_store_file`| The file pat to the trust store | `{{ keycloak.home }}/conf/trust_store.p12` | -|`keycloak_quarkus_trust_store_password`| Password for the trust store | `""` | -|`keycloak_quarkus_proxy_headers`| Parse reverse proxy headers (`forwarded` or `xforwardedPassword`) | `""` | +|`keycloak_quarkus_key_store_file`| Deprecated, use `keycloak_quarkus_https_key_store_file` instead. || +|`keycloak_quarkus_key_store_password`| Deprecated, use `keycloak_quarkus_https_key_store_password` instead.|| +|`keycloak_quarkus_https_key_store_file`| The file path to the key store | `{{ keycloak.home }}/conf/key_store.p12` | +|`keycloak_quarkus_https_key_store_password`| Password for the key store | `""` | +|`keycloak_quarkus_https_trust_store_enabled`| Enable configuration of the https trust store | `False` | +|`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 `xforwarded`) | `""` | +|`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 5821aca..37d7fb6 100644 --- a/roles/keycloak_quarkus/defaults/main.yml +++ b/roles/keycloak_quarkus/defaults/main.yml @@ -52,12 +52,15 @@ keycloak_quarkus_key_file: "{{ keycloak.home }}/conf/server.key.pem" keycloak_quarkus_cert_file: "{{ keycloak.home }}/conf/server.crt.pem" #### key store configuration keycloak_quarkus_https_key_store_enabled: false -keycloak_quarkus_key_store_file: "{{ keycloak.home }}/conf/key_store.p12" -keycloak_quarkus_key_store_password: '' +keycloak_quarkus_https_key_store_file: "{{ keycloak.home }}/conf/key_store.p12" +keycloak_quarkus_https_key_store_password: '' ##### trust store configuration keycloak_quarkus_https_trust_store_enabled: false -keycloak_quarkus_trust_store_file: "{{ keycloak.home }}/conf/trust_store.p12" -keycloak_quarkus_trust_store_password: '' +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/handlers/main.yml b/roles/keycloak_quarkus/handlers/main.yml index 82e229b..bbdf61c 100644 --- a/roles/keycloak_quarkus/handlers/main.yml +++ b/roles/keycloak_quarkus/handlers/main.yml @@ -6,3 +6,8 @@ - name: "Restart {{ keycloak.service_name }}" ansible.builtin.include_tasks: restart.yml listen: "restart keycloak" +- name: "Print deprecation warning" + ansible.builtin.fail: + msg: "Deprecation warning: you are using the deprecated variable '{{ deprecated_variable | d('NotSet') }}', check docs on how to upgrade." + ignore_errors: True + listen: "print deprecation warning" diff --git a/roles/keycloak_quarkus/meta/argument_specs.yml b/roles/keycloak_quarkus/meta/argument_specs.yml index 57dec30..bdc0615 100644 --- a/roles/keycloak_quarkus/meta/argument_specs.yml +++ b/roles/keycloak_quarkus/meta/argument_specs.yml @@ -125,25 +125,41 @@ argument_specs: description: "Enable configuration of HTTPS via a key store" type: "bool" keycloak_quarkus_key_store_file: + default: "" + description: "Deprecated, use `keycloak_quarkus_https_key_store_file` instead." + type: "str" + keycloak_quarkus_key_store_password: + default: "" + description: "Deprecated, use `keycloak_quarkus_https_key_store_password` instead." + type: "str" + keycloak_quarkus_https_key_store_file: default: "{{ keycloak.home }}/conf/key_store.p12" description: "The file path to the key store" type: "str" - keycloak_quarkus_key_store_password: + keycloak_quarkus_https_key_store_password: default: "" description: "Password for the key store" type: "str" keycloak_quarkus_https_trust_store_enabled: default: false - description: "Enalbe confiugration of a trust store" + description: "Enable configuration of the https trust store" type: "bool" - keycloak_quarkus_trust_store_file: + keycloak_quarkus_https_trust_store_file: default: "{{ keycloak.home }}/conf/trust_store.p12" description: "The file path to the trust store" type: "str" - keycloak_quarkus_trust_store_password: + keycloak_quarkus_https_trust_store_password: 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" @@ -295,7 +311,7 @@ argument_specs: keycloak_quarkus_proxy_headers: default: "" type: "str" - description: "Parse reverse proxy headers (`forwarded` or `xforwardedPassword`), overrides the deprecated keycloak_quarkus_proxy_mode argument" + description: "Parse reverse proxy headers (`forwarded` or `xforwarded`), overrides the deprecated keycloak_quarkus_proxy_mode argument" keycloak_quarkus_start_dev: default: false type: "bool" diff --git a/roles/keycloak_quarkus/tasks/config_store.yml b/roles/keycloak_quarkus/tasks/config_store.yml new file mode 100644 index 0000000..40acc65 --- /dev/null +++ b/roles/keycloak_quarkus/tasks/config_store.yml @@ -0,0 +1,52 @@ +--- +- 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/deprecations.yml b/roles/keycloak_quarkus/tasks/deprecations.yml new file mode 100644 index 0000000..a81c808 --- /dev/null +++ b/roles/keycloak_quarkus/tasks/deprecations.yml @@ -0,0 +1,36 @@ +--- +- name: Check deprecation keycloak_quarkus_key_store -> keycloak_quarkus_http_key_store + delegate_to: localhost + run_once: true + when: + - keycloak_quarkus_https_key_store_enabled + block: + - name: Ensure backward compatibility for `keycloak_quarkus_key_store_file`, superseded by `keycloak_quarkus_https_key_store_file` + when: + - keycloak_quarkus_key_store_file is defined + - keycloak_quarkus_key_store_file != '' + - keycloak_quarkus_https_key_store_file == keycloak.home + "/conf/key_store.p12" # default value + changed_when: true + ansible.builtin.set_fact: + keycloak_quarkus_https_key_store_file: "{{ keycloak_quarkus_key_store_file }}" + deprecated_variable: "keycloak_quarkus_key_store_file" # read in deprecation handler + notify: + - print deprecation warning + + - name: Flush handlers + ansible.builtin.meta: flush_handlers + + - name: Ensure backward compatibility for `keycloak_quarkus_key_store_password`, superseded by `keycloak_quarkus_https_key_store_password` + when: + - keycloak_quarkus_key_store_password is defined + - keycloak_quarkus_key_store_password != '' + - keycloak_quarkus_https_key_store_password == "" # default value + changed_when: true + ansible.builtin.set_fact: + keycloak_quarkus_https_key_store_password: "{{ keycloak_quarkus_key_store_password }}" + deprecated_variable: "keycloak_quarkus_key_store_password" # read in deprecation handler + notify: + - print deprecation warning + + - name: Flush handlers + ansible.builtin.meta: flush_handlers diff --git a/roles/keycloak_quarkus/tasks/main.yml b/roles/keycloak_quarkus/tasks/main.yml index 44fd3d1..8ca24cc 100644 --- a/roles/keycloak_quarkus/tasks/main.yml +++ b/roles/keycloak_quarkus/tasks/main.yml @@ -6,6 +6,11 @@ - prereqs - always +- name: Check for deprecations + ansible.builtin.include_tasks: deprecations.yml + tags: + - always + - name: Distro specific tasks ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml" tags: @@ -21,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/tasks/prereqs.yml b/roles/keycloak_quarkus/tasks/prereqs.yml index d344e98..d96c09e 100644 --- a/roles/keycloak_quarkus/tasks/prereqs.yml +++ b/roles/keycloak_quarkus/tasks/prereqs.yml @@ -42,3 +42,17 @@ ansible.builtin.include_tasks: fastpackages.yml vars: packages_list: "{{ keycloak_quarkus_prereq_package_list }}" + +- name: "Validate keytool" + when: keycloak.config_key_store_enabled + block: + - name: "Attempt to run keytool" + changed_when: false + 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" diff --git a/roles/keycloak_quarkus/templates/keycloak.conf.j2 b/roles/keycloak_quarkus/templates/keycloak.conf.j2 index 20d3f7f..6c9433e 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 }} @@ -24,12 +33,12 @@ https-certificate-file={{ keycloak_quarkus_cert_file}} https-certificate-key-file={{ keycloak_quarkus_key_file }} {% endif %} {% if keycloak_quarkus_https_key_store_enabled %} -https-key-store-file={{ keycloak_quarkus_key_store_file }} -https-key-store-password={{ keycloak_quarkus_key_store_password }} +https-key-store-file={{ keycloak_quarkus_https_key_store_file }} +https-key-store-password={{ keycloak_quarkus_https_key_store_password }} {% endif %} {% if keycloak_quarkus_https_trust_store_enabled %} -https-trust-store-file={{ keycloak_quarkus_trust_store_file }} -https-trust-store-password={{ keycloak_quarkus_trust_store_password }} +https-trust-store-file={{ keycloak_quarkus_https_trust_store_file }} +https-trust-store-password={{ keycloak_quarkus_https_trust_store_password }} {% endif %} # Client URL configuration diff --git a/roles/keycloak_quarkus/vars/main.yml b/roles/keycloak_quarkus/vars/main.yml index 6f92f4f..84fbeaa 100644 --- a/roles/keycloak_quarkus/vars/main.yml +++ b/roles/keycloak_quarkus/vars/main.yml @@ -10,6 +10,7 @@ keycloak: # noqa var-naming this is an internal dict of interpolated values 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 }}"