From 9db35595976db3f4e202f3df75c3dbf6a06b404c Mon Sep 17 00:00:00 2001 From: Tom Whitwell Date: Tue, 9 Aug 2022 12:37:40 +0100 Subject: [PATCH] Implement a post-renewal hook script Sometimes, after a certificate is renewed, you may want to do something with the new certificate. For example, you may want to change the ownership of the certificate or key files, use these files for some other cryptographic purpose (eg. creating a `.p12` file) or some other action. This change allows for a renewal script to be created by specifying commands with the `step_acme_cert_post_renewal_commands` variable. An example of this is for provisioning a certificate for UniFi's Controller. The following configuration will update unifi's jks and restart the service after the certificate is renewed: ```yaml step_acme_cert_post_renewal_commands: - openssl pkcs12 -export -in "${CERT_FILE}" -inkey "${KEY_FILE}" -out /etc/ssl/cert.p12 -name unifi -password pass:aircontrolenterprise - keytool -importkeystore -deststorepass aircontrolenterprise -destkeypass aircontrolenterprise -destkeystore /usr/lib/unifi/data/keystore -srckeystore /etc/ssl/cert.p12 -srcstoretype PKCS12 -srcstorepass aircontrolenterprise -alias unifi - systemctl restart unifi ``` In the example above, I am using `systemctl restart unifi` as the last command, because I have experienced issues with `systemctl try-reload-or-restart` for this specific service. For a more 'normal' service, the following should work: ```yaml step_acme_cert_post_renewal_commands: - do_something ${CERT_FILE} ${KEY_FILE} step_acme_cert_renewal_reload_services: - some_service ``` The variables `${STEP_CLI}`, `${CERT_FILE}`, and `${KEY_FILE}` are all exported in the script by default, and are available for use in the commands. `systemctl try-reload-or-restart {{step_acme_cert_renewal_reload_services}}` has been removed from the `ExecStart` command in the systemd unit file, and is appended to the end of this post-renewal hook script. If no `step_acme_cert_renewal_reload_services` or `step_acme_cert_post_renewal_commands` are provided, the post-renew-hook script is not created. This change is backwards compatible, and will not break existing configurations. Signed-off-by: Tom Whitwell --- roles/step_acme_cert/README.md | 12 ++++++++++++ roles/step_acme_cert/defaults/main.yml | 2 ++ roles/step_acme_cert/meta/argument_specs.yml | 15 +++++++++++++++ roles/step_acme_cert/tasks/renewal.yml | 8 ++++++++ .../templates/step-post-renew-hook.sh.j2 | 12 ++++++++++++ .../templates/step-renew.service.j2 | 2 +- 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 roles/step_acme_cert/templates/step-post-renew-hook.sh.j2 diff --git a/roles/step_acme_cert/README.md b/roles/step_acme_cert/README.md index d9ea73f9..3353d45f 100644 --- a/roles/step_acme_cert/README.md +++ b/roles/step_acme_cert/README.md @@ -78,6 +78,18 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode. - Renew the cert when its remaining valid time crosses this threshold - Default: undefined (uses the smallstep default: 1/3 of the certificates valid duration, i.e. 8 hours for a 24h cert) +##### `step_acme_cert_post_renewal_shell` +- Shell with which to run `step_acme_cert_post_renewal_commands` +- Must be a valid shell +- Default: `/bin/sh` + +##### `step_acme_cert_post_renewal_commands` +- Run these commands after a cert renewal +- Must be a list of commands executable with the shell specified in `step_acme_cert_post_renewal_shell` +- `${STEP_CLI}`, `${CERT_FILE}` and `${KEY_FILE}` are available for use. All are absolute paths to files. +- Example: `["cp ${CERT_FILE} /path/to/somewhere/", "chmod 400 ${KEY_FILE}"]` +- Default: `[]` + ##### `step_acme_cert_renewal_reload_services` - Reload or restart these systemd services after a cert renewal - Must be a list of systemd units diff --git a/roles/step_acme_cert/defaults/main.yml b/roles/step_acme_cert/defaults/main.yml index c440f106..cd81489d 100644 --- a/roles/step_acme_cert/defaults/main.yml +++ b/roles/step_acme_cert/defaults/main.yml @@ -25,4 +25,6 @@ step_acme_cert_keyfile_defaults: step_acme_cert_renewal_service: step-renew #step_acme_cert_renewal_when: 8h +step_acme_cert_post_renewal_shell: "/bin/sh" +step_acme_cert_post_renewal_commands: [] step_acme_cert_renewal_reload_services: [] diff --git a/roles/step_acme_cert/meta/argument_specs.yml b/roles/step_acme_cert/meta/argument_specs.yml index 6a1175f3..d0fa29d5 100644 --- a/roles/step_acme_cert/meta/argument_specs.yml +++ b/roles/step_acme_cert/meta/argument_specs.yml @@ -111,6 +111,21 @@ argument_specs: description: - Renew the cert when its remaining valid time crosses this threshold - Uses the smallstep default (1/3 of the certs valid duration) if left undefined + step_acme_cert_post_renewal_shell: + type: str + default: /bin/sh + description: + - Shell with which to run commands specified in "step_acme_cert_post_renewal_commands" + - Must be a valid shell + step_acme_cert_post_renewal_commands: + type: list + elements: str + default: [] + description: + - Run these commands after a cert renewal + - Must be a list of commands executable with the shell specified in "step_acme_cert_post_renewal_shell" + - "${STEP_CLI}, ${CERT_FILE} and ${KEY_FILE} are available for use. All will be absolute paths to files." + - "Example: C(['cp ${CERT_FILE} /path/to/somewhere/', 'chmod 400 ${KEY_FILE}'])" step_acme_cert_renewal_reload_services: type: list elements: str diff --git a/roles/step_acme_cert/tasks/renewal.yml b/roles/step_acme_cert/tasks/renewal.yml index a0cf31c4..fcaf8009 100644 --- a/roles/step_acme_cert/tasks/renewal.yml +++ b/roles/step_acme_cert/tasks/renewal.yml @@ -6,6 +6,14 @@ changed_when: no check_mode: no +- name: Post renewal hook script is present + template: + src: step-post-renew-hook.sh.j2 + dest: "{{step_cli_steppath}}/{{ step_acme_cert_renewal_service }}_post.sh" + owner: root + group: root + mode: 0744 + - name: Renewal service is installed template: src: step-renew.service.j2 diff --git a/roles/step_acme_cert/templates/step-post-renew-hook.sh.j2 b/roles/step_acme_cert/templates/step-post-renew-hook.sh.j2 new file mode 100644 index 00000000..c371430e --- /dev/null +++ b/roles/step_acme_cert/templates/step-post-renew-hook.sh.j2 @@ -0,0 +1,12 @@ +#!{{ step_acme_cert_post_renewal_shell }} +####### added by ansible: maxhoesel.smallstep.step_acme_cert - changes will be overwritten ####### +set -eu +export STEP_CLI="{{ step_cli_executable_absolute.stdout }}" +export CERT_FILE="{{ step_acme_cert_certfile_full.path }}" +export KEY_FILE="{{ step_acme_cert_keyfile_full.path }}" +{% for command in step_acme_cert_post_renewal_commands -%} +{{ command }} +{% endfor -%} +{% if step_acme_cert_renewal_reload_services -%} +systemctl try-reload-or-restart {{ step_acme_cert_renewal_reload_services | join(' ') }} +{% endif -%} diff --git a/roles/step_acme_cert/templates/step-renew.service.j2 b/roles/step_acme_cert/templates/step-renew.service.j2 index f8030ee9..8c9082f9 100644 --- a/roles/step_acme_cert/templates/step-renew.service.j2 +++ b/roles/step_acme_cert/templates/step-renew.service.j2 @@ -9,7 +9,7 @@ Type=simple Restart=always RestartSec=1 Environment=STEPPATH={{ step_cli_steppath }} -ExecStart={{ step_cli_executable_absolute.stdout }} ca renew {{ step_acme_cert_certfile_full.path }} {{ step_acme_cert_keyfile_full.path }} --daemon --force{% if step_acme_cert_renewal_when is defined %} --expires-in {{ step_acme_cert_renewal_when }}{% endif %}{% if step_acme_cert_renewal_reload_services %} --exec "systemctl try-reload-or-restart {{ step_acme_cert_renewal_reload_services | join(' ') }}"{% endif %} +ExecStart={{ step_cli_executable_absolute.stdout }} ca renew {{ step_acme_cert_certfile_full.path }} {{ step_acme_cert_keyfile_full.path }} --daemon --force{% if step_acme_cert_renewal_when is defined %} --expires-in {{ step_acme_cert_renewal_when }}{% endif %}{% if step_acme_cert_post_renewal_commands or step_acme_cert_renewal_reload_services %} --exec "{{ step_acme_cert_post_renewal_shell }} {{ step_cli_steppath }}/{{ step_acme_cert_renewal_service }}_post.sh"{% endif %} [Install] WantedBy=multi-user.target