Skip to content

Commit

Permalink
Implement a post-renewal hook script
Browse files Browse the repository at this point in the history
Sometimes, after a certificate is renewed, you may want to do something
with the new certificate. For example, you may want to restart a web
server or reload a configuration file.

This change allows for a renewal script to be created by specifying
commands in 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
```

`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. 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.

This change is backwards compatible, and will not break existing
configurations.

Signed-off-by: Tom Whitwell <[email protected]>
  • Loading branch information
whi-tw committed Sep 14, 2022
1 parent 7113e87 commit e0d024a
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 1 deletion.
12 changes: 12 additions & 0 deletions roles/step_acme_cert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions roles/step_acme_cert/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
15 changes: 15 additions & 0 deletions roles/step_acme_cert/meta/argument_specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions roles/step_acme_cert/tasks/renewal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions roles/step_acme_cert/templates/step-post-renew-hook.sh.j2
Original file line number Diff line number Diff line change
@@ -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 -%}
2 changes: 1 addition & 1 deletion roles/step_acme_cert/templates/step-renew.service.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit e0d024a

Please sign in to comment.