From 706d2d84941fa8df1df1effe0f73741467dee093 Mon Sep 17 00:00:00 2001 From: Eric Engstrom Date: Wed, 6 Oct 2021 14:10:05 -0500 Subject: [PATCH] feat: add `step_certificate` role. This role can accommodate all different types of provisioners. For the currently implementation, only: - ACME - JWK If the requested provisioner is not supported, role will fail with appropriate message. If this role is adopted, suggest we deprecate `step_acme_cert`. FIXES: #127 --- README.md | 18 +-- roles/step_certificate/README.md | 132 +++++++++++++----- roles/step_certificate/defaults/main.yml | 31 ++-- roles/step_certificate/handlers/main.yml | 2 +- .../step_certificate/meta/argument_specs.yml | 62 ++++---- roles/step_certificate/tasks/cert.yml | 36 +++++ roles/step_certificate/tasks/check.yml | 8 +- roles/step_certificate/tasks/get_cert.yml | 26 ---- .../step_certificate/tasks/get_cert/acme.yml | 18 +++ .../tasks/get_cert/default.yml | 3 + roles/step_certificate/tasks/get_cert/jwk.yml | 42 ++++++ roles/step_certificate/tasks/main.yml | 26 +--- roles/step_certificate/tasks/renewal.yml | 12 +- roles/step_certificate/tasks/vars.yml | 6 + .../templates/step-renew.service.j2 | 2 +- 15 files changed, 284 insertions(+), 140 deletions(-) create mode 100644 roles/step_certificate/tasks/cert.yml delete mode 100644 roles/step_certificate/tasks/get_cert.yml create mode 100644 roles/step_certificate/tasks/get_cert/acme.yml create mode 100644 roles/step_certificate/tasks/get_cert/default.yml create mode 100644 roles/step_certificate/tasks/get_cert/jwk.yml create mode 100644 roles/step_certificate/tasks/vars.yml diff --git a/README.md b/README.md index d616cf68..3ba13e45 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ --- **NOTE** -This collection is stil under active development. While we try to preserve compatibility with previous versions, -breaking changes may ocurr between minor releases (such as 0.3, 0.4, etc.) if required. +This collection is still under active development. While we try to preserve compatibility with previous versions, +breaking changes may occur between minor releases (such as 0.3, 0.4, etc.) if required. --- @@ -18,7 +18,7 @@ and the [CLI tool](https://github.com/smallstep/cli). Possible uses for this col - Managing your `step-ca` server install (installation, configuration, provisioners) - Automated bootstrapping of hosts to trust your CA - Token or certificate creation from within your Ansible playbooks -- [Complete configuration of client certificates via ACME, including automatic renewal](roles/step_acme_cert/README.md) +- [Complete configuration of client certificates, including automatic renewal](roles/step_certificate/README.md) ## Components @@ -29,9 +29,9 @@ and the [CLI tool](https://github.com/smallstep/cli). Possible uses for this col |------|-------------| | [`step_ca`](roles/step_ca/README.md) | Install step-ca as an internal CA. | [`step_bootstrap_host`](roles/step_bootstrap_host/README.md) | Configure a client host to trust your CA using step-cli. -| [`step_acme_cert`](roles/step_acme_cert/README.md) | Set up a Let's Encrypt-style certificate on a host using your ca, including automatic renewal. | [`step_cli`](roles/step_cli/README.md) | Install step-cli and nothing else. Used by bootstrap_host and step_ca under the hood. - +| [`step_certificate`](roles/step_certificate/README.md) | Create a new host certificate from a CA, including automatic renewal. Supports multiple provisioners. +| [`step_acme_cert`](roles/step_acme_cert/README.md) | (**DEPRECATED**) Create a host certificate from a CA, including automatic renewal, via ACME provisioner. ### Standalone Modules @@ -48,10 +48,10 @@ To learn more about the differences between Online/Offline/Local-Only Modules, s | Module | Description | Online | Offline/Local | |---------|-------------|--------|---------------| -| `step_ca_bootstrap` | Initialize `step-cli` to trust a step-ca server | X | | +| `step_ca_bootstrap` | Initialize `step-cli` to trust a `step-ca` server | X | | | `step_ca_certificate` | Generate a new private key and certificate signed by the CA root certificate | X | `offline` parameter | | `step_ca_provisioner` | Manage provisioners on a `step-ca` server | | X | -| `step_ca_provisioner_claims` | Manage default or provisioner claims on a `step-ca server | | X | +| `step_ca_provisioner_claims` | Manage default or provisioner claims on a `step-ca` server | | X | | `step_ca_renew` | Renew a valid certificate | X | `offline` parameter | | `step_ca_revoke` | Revoke a Certificate | X | `offline` parameter | | `step_ca_token` | Generate an OTT granting access to the CA | X | `offline` parameter | @@ -169,7 +169,7 @@ You can take a look at the available modules to further configure your CA and ho ## Module Usage -This collection contains several modules for mamaging your smallstep environment. +This collection contains several modules for managing your smallstep environment. Most of them wrap around `step-cli` commands, so they usually support all the features of the respective command. If you'd like to know more about an individual module, you can view its documentation using `ansible-doc maxhoesel.smallstep.`. @@ -187,7 +187,7 @@ See [this table](#ca-modules) for details. In order to talk to your CA in online mode, `step-cli` needs to already trust it. You can achieve this by: - Running the module on a host that was configured with `step_bootstrap_host` as root (recommended). -- Pasing the `ca_url` and `root` parameters to the module. +- Passing the `ca_url` and `root` parameters to the module. For offline mode, you need to: - Provide `step-cli` with the path to your CA config (`$STEPPATH/config/ca.json` by default). diff --git a/roles/step_certificate/README.md b/roles/step_certificate/README.md index 129ef1a3..ac1b3443 100644 --- a/roles/step_certificate/README.md +++ b/roles/step_certificate/README.md @@ -1,4 +1,4 @@ -# maxhoesel.smallstep.step_acme_cert +# maxhoesel.smallstep.step_certificate Get a certificate from a CA with ACME and setup automatic renewal using `step-cli renew`. @@ -30,36 +30,51 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode. ### CA -##### `step_acme_cert_ca_provisioner` -- Name of the provisioner on the CA that will issue the ACME cert +##### `step_cert_ca_provisioner_type` +- Type of provisioner on the CA that will issue the certificate - Required: Yes -##### `step_acme_cert_webroot_path` -- If set, this role will use `step-cli`s webroot mode to get a new certificate. -- If empty, this role will use the standalone mode instead, causing `step-cli` to bind itself to port 80. Make sure that no other services are listening on this port. +##### `step_cert_ca_provisioner_name` +- Name of the provisioner on the CA that will issue the certificate +- Required: Yes + + +### Provisioner + +#### ACME + +##### `step_cert_acme_webroot_path` +- If set, the ACME provisioner will use `step-cli`s webroot mode to get a new certificate. +- If empty, the ACME provisioner will use the standalone mode instead, causing `step-cli` to bind itself to port 80. Make sure that no other services are listening on this port. Note that `step-cli` only needs to bind to this port when getting a *new* certificate. It does not need to bind if it is only *renewing* a certificate. - Default: "" +#### JWK + +##### `step_cert_ca_jwk_password_file` +- Path to the file on the client system containing the password used to decrypt the one-time token generating key from a JWK provisioner on the CA. +- Required: Yes, if using a JWK provisioner. + ### Certificate -##### `step_acme_cert_name` +##### `step_cert_name` - The subject name that the certificate will be issued for - Default: `{{ ansible_fqdn }}` -##### `step_acme_cert_san` +##### `step_cert_san` - Subject Alternate Names to add to the cert - Must be a list of valid SANs - Default: `[]` -##### `step_acme_cert_duration` +##### `step_cert_duration` - Valid duration of the certificate - Default: undefined (uses the default for the given provisioner, typically 24h) -##### `step_acme_cert_contact` +##### `step_cert_contact` - Contact email for the CA for important notifications - Default: `root@localhost` -##### `step_acme_cert_certfile`/`step_acme_cert_keyfile` +##### `step_cert_certfile`/`step_cert_keyfile` - Details about the cert/key files on disk - Is a dict with the following elements: - `path`: Absolute path to the cert/key file. Defaults to `/etc/ssl/step.crt|step.key`. The directory must already exist. @@ -68,30 +83,26 @@ before setting up a renewal service using `step-cli ca renew`s `--daemon` mode. ### Renewal -##### `step_acme_cert_renewal_service` -- Name of the systemd service that will handle cert renewals -- If you have multiple cert/key pairs on one system, you will have to set a unique service name for each pair. If you only have one, then you can leave this as is. +##### `step_cert_renewal_service` +- Name of the `systemd` service that will handle cert renewals +- If you have multiple cert/key pairs on one system, you will have to set a unique service name for each pair. - Default: `step-renew` -##### `step_acme_cert_renewal_when` +##### `step_cert_renewal_when` - 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) +- Default: undefined (uses the smallstep default: 1/3 of the certificates valid duration, e.g. 8 hours for a 24h cert) -##### `step_acme_cert_renewal_reload_services` -- Reload or restart these systemd services after a cert renewal -- Must be a list of systemd units +##### `step_cert_renewal_reload_services` +- Reload or restart these `systemd` services after a cert renewal +- Must be a list of `systemd` units - Example: `["nginx", "mysqld"]` - Default: `[]` ## Example Playbooks ---- -**NOTE** +### ACME -Make sure that you are familiar with the way ACME works. You will need a functioning DNS environment at the very least -to make use of ACME certs. - ---- +Make sure that you are familiar with the way ACME certificate generation works. Minimally, to make use of the ACME provisioners, you will need open network ports (e.g. `:80`) between the client and the server, and a functioning DNS environment. ```yaml # Configure your CA to include an ACME provisioner @@ -110,7 +121,8 @@ to make use of ACME certs. name: step-ca state: reloaded -- hosts: clients +# Create a certificate using an ACME provisioner +- hosts: step_clients tasks: # Bootstrap the host to trust the CA - role: maxhoesel.smallstep.step_bootstrap_host @@ -119,13 +131,67 @@ to make use of ACME certs. step_bootstrap_fingerprint: your CAs fingerprint become: yes - # This will download a certificate to /etc/step/ that you can then use in other applications. - # See the step_acme_cert README for more options - - name: Configure an ACME cert + renewal - include_role: - name: maxhoesel.smallstep.step_acme_cert - vars: - step_acme_cert_ca_provisioner: ACME + # Configure an ACME provisioned cert + renewal in /etc/step + - role: maxhoesel.smallstep.step_certificate + vars: + step_cert_ca_provisioner_type: ACME + step_cert_ca_provisioner_name: ACME + become: yes +``` + +### JWK + +When using a JWK provisioner, you will need a shared secret between the CA server and the CA clients. This file must be placed on the system before you attempt to generate a certificate. + +```yaml +# Configure your CA to include a JWK provisioner +- hosts: step_ca + become: yes + tasks: + - name: step-ca | deploy CA JWK provisioner password + copy: + dest: "{{ step_jwk_provisioner_password_file }}" + content: "SUPER SECRET {{ step_provisioner_token_password_secret_thing }}" + owner: step-ca + group: step-ca + mode: 0600 + + - name: step-ca | configure JWK provisioner on the CA + maxhoesel.smallstep.step_ca_provisioner: + type: JWK + name: "JWK@{{ ansible_domain }}" + jwk_password_file: "{{ step_jwk_provisioner_password_file }}" + become_user: step-ca + notify: reload step-ca + + handlers: + - name: reload step-ca + systemd: + name: step-ca + state: reloaded + +# Create a certificate using a JWK provisioner +- hosts: step_clients + tasks: + # Bootstrap the host to trust the CA + - role: maxhoesel.smallstep.step_bootstrap_host + vars: + step_bootstrap_ca_url: https://myca.localdomain + step_bootstrap_fingerprint: your CAs fingerprint become: yes + - name: step-client | deploy CA JWK provisioner password + copy: + dest: "{{ step_jwk_provisioner_password_file }}" + content: "SUPER SECRET {{ step_provisioner_token_password_secret_thing }}" + mode: 0600 + owner: "{{ system_root_user }}" + group: "{{ system_root_group }}" + + # Configure a JWK provisioned cert + renewal in /etc/step + - role: maxhoesel.smallstep.step_certificate + vars: + step_cert_ca_provisioner_type: JWK + step_cert_ca_provisioner_name: "JWK@{{ ansible_domain }}" + step_cert_ca_jwk_password_file: "{{ step_jwk_provisioner_password_file }}" ``` diff --git a/roles/step_certificate/defaults/main.yml b/roles/step_certificate/defaults/main.yml index bb7d0fb6..03946630 100644 --- a/roles/step_certificate/defaults/main.yml +++ b/roles/step_certificate/defaults/main.yml @@ -2,28 +2,31 @@ step_cli_executable: step-cli step_cli_steppath: /root/.step -#step_acme_cert_ca_url: -#step_acme_cert_ca_provisioner: -step_acme_cert_webroot_path: "" +# Required parameters - role will error if either is not provided +#step_cert_ca_provisioner_type: +#step_cert_ca_provisioner_name: -step_acme_cert_name: "{{ ansible_fqdn }}" -step_acme_cert_san: [] -#step_acme_cert_duration: 24h -step_acme_cert_contact: root@localhost +# path to ACME provisioner web root for HTTP-01 challenge. +step_cert_acme_webroot_path: "" -step_acme_cert_certfile: "{{ step_acme_cert_certfile_defaults }}" -step_acme_cert_certfile_defaults: +step_cert_name: "{{ ansible_fqdn }}" +step_cert_san: [] +step_cert_contact: "root@{{ ansible_fqdn }}" +#step_cert_duration: 24h + +step_cert_certfile: "{{ step_cert_certfile_defaults }}" +step_cert_certfile_defaults: path: /etc/ssl/step.crt mode: "644" owner: root group: root -step_acme_cert_keyfile: "{{ step_acme_cert_keyfile_defaults }}" -step_acme_cert_keyfile_defaults: +step_cert_keyfile: "{{ step_cert_keyfile_defaults }}" +step_cert_keyfile_defaults: path: /etc/ssl/step.key mode: "600" owner: root group: root -step_acme_cert_renewal_service: step-renew -#step_acme_cert_renewal_when: 8h -step_acme_cert_renewal_reload_services: [] +step_cert_renewal_service: step-renew +#step_cert_renewal_when: 8h +step_cert_renewal_reload_services: [] diff --git a/roles/step_certificate/handlers/main.yml b/roles/step_certificate/handlers/main.yml index fd481c77..263d79ef 100644 --- a/roles/step_certificate/handlers/main.yml +++ b/roles/step_certificate/handlers/main.yml @@ -1,4 +1,4 @@ - name: restart renewal service service: - name: '{{ step_acme_cert_renewal_service }}' + name: '{{ step_cert_renewal_service }}' state: restarted diff --git a/roles/step_certificate/meta/argument_specs.yml b/roles/step_certificate/meta/argument_specs.yml index 305c7bd8..c034cd15 100644 --- a/roles/step_certificate/meta/argument_specs.yml +++ b/roles/step_certificate/meta/argument_specs.yml @@ -6,52 +6,62 @@ argument_specs: type: path description: Path or name of the step-cli executable to use for executing commands in this role default: step-cli - step_acme_cert_ca_provisioner: + # Provisioner Selection + step_cert_ca_provisioner_type: type: str required: yes - description: Name of the provisioner on the CA that will issue the ACME cert - step_acme_cert_webroot_path: + description: Type of provisioner on the CA that will issue the certificate + step_cert_ca_provisioner_name: + type: str + required: yes + description: Name of the provisioner on the CA that will issue the certificate + # Provisioner options + step_cert_acme_webroot_path: + type: str + default: "" + description: Path to the webroot, for ACME, or empty for standalone mode + step_cert_ca_jwk_password_file: type: str default: "" - description: Path to the webroot for ACME, or empty for standalone mode + description: Path to the file on the client system containing the password used to decrypt the one-time token generating key from a JWK provisioner on the CA # Certificate Options - step_acme_cert_name: + step_cert_name: type: str default: '{{ ansible_fqdn }}' - description: The subject name that the certificate will be issued for - step_acme_cert_san: + description: The subject name for which the certificate will be issued + step_cert_san: type: list elements: str default: [] - description: Subject Alternate Names to add to the cert - step_acme_cert_duration: + description: Subject Alternate Names to add to the certificate + step_cert_duration: type: str description: Valid duration of the certificate - step_acme_cert_contact: + step_cert_contact: type: str - default: root@localhost + default: 'root@{{ ansible_fqdn }}' description: Contact email for the CA for important notifications - step_acme_cert_certfile: + step_cert_certfile: type: dict - description: Details about the cert file on disk + description: Details about the certificate and key files on disk options: path: type: path default: /etc/ssl/step.crt - description: Absolute path to the cert file + description: Absolute path to the certificate file mode: type: str default: '644' - description: File mode for the cert file + description: File mode for the certificate file owner: type: str default: root - description: Owner of the file + description: Owner of the certificate file group: type: str default: root - description: Group of the file - step_acme_cert_keyfile: + description: Group of the certificate file + step_cert_keyfile: type: dict description: Details about the key file on disk options: @@ -66,21 +76,21 @@ argument_specs: owner: type: str default: root - description: Owner of the file + description: Owner of the key file group: type: str default: root - description: Group of the file + description: Group of the key file # Renewal Options - step_acme_cert_renewal_service: + step_cert_renewal_service: type: str default: step-renew - description: Name of the systemd service that will handle cert renewals - step_acme_cert_renewal_when: + description: Name of the `systemd` service that will handle certificate renewals + step_cert_renewal_when: type: str - description: Renew the cert when its remaining valid time crosses this threshold - step_acme_cert_renewal_reload_services: + description: Renew the certificate when its remaining valid time crosses this threshold + step_cert_renewal_reload_services: type: list elements: str default: [] - description: Reload or restart these systemd services after a cert renewal + description: Reload or restart these systemd services after a certificate renewal diff --git a/roles/step_certificate/tasks/cert.yml b/roles/step_certificate/tasks/cert.yml new file mode 100644 index 00000000..c2018fce --- /dev/null +++ b/roles/step_certificate/tasks/cert.yml @@ -0,0 +1,36 @@ +# create, if needed, a new certificate +--- +# This will fail with non-zero r.c. certificate does not exist, +# or if the cert validity check fails. +- name: Check if expected certificate file is valid + command: "{{ step_cli_executable }} certificate verify {{ step_cert_certfile_full.path }}" + check_mode: no # Always run, even in --check mode + changed_when: no + failed_when: no + ignore_errors: true + register: _step_cert_check + +- name: Include certificate generation tasks, by certificate type + include_tasks: "{{ _cert_tasks_file }}" + vars: + _cert_tasks_paths: + - "get_cert/{{ step_cert_ca_provisioner_type | d('UNDEFINED') }}.yml" + - "get_cert/default.yml" + _cert_tasks_file: "{{ lookup('first_found', _cert_tasks_paths, errors='ignore') }}" + when: (_step_cert_check.rc | d(1)) != 0 + +- name: Look for existing certificate file + stat: + path: "{{ step_cert_certfile_full.path }}" + register: _step_cert_stat + +- name: Cert and key permissions are set + file: + path: "{{ item.path }}" + mode: "{{ item.mode }}" + owner: "{{ item.owner }}" + group: "{{ item.group }}" + loop: + - "{{ step_cert_keyfile_full }}" + - "{{ step_cert_certfile_full }}" + when: _step_cert_stat.stat.exists diff --git a/roles/step_certificate/tasks/check.yml b/roles/step_certificate/tasks/check.yml index 5a73809c..fbcdd03d 100644 --- a/roles/step_certificate/tasks/check.yml +++ b/roles/step_certificate/tasks/check.yml @@ -1,8 +1,11 @@ +--- - name: Verify that required parameters are set assert: that: - - step_acme_cert_ca_provisioner is defined - - step_acme_cert_ca_provisioner | length > 0 + - step_cert_ca_provisioner_type is defined + - step_cert_ca_provisioner_type | length > 0 + - step_cert_ca_provisioner_name is defined + - step_cert_ca_provisioner_name | length > 0 when: ansible_version.string is version('2.11.1', '<') - name: Look for step_cli_executable # noqa command-instead-of-shell @@ -13,6 +16,7 @@ failed_when: step_cli_install.rc not in [0,1,127] changed_when: no check_mode: no + - name: Verify that `step-cli` is installed assert: that: step_cli_install.rc == 0 diff --git a/roles/step_certificate/tasks/get_cert.yml b/roles/step_certificate/tasks/get_cert.yml deleted file mode 100644 index 477b805c..00000000 --- a/roles/step_certificate/tasks/get_cert.yml +++ /dev/null @@ -1,26 +0,0 @@ -- name: Get certificate from CA - maxhoesel.smallstep.step_ca_certificate: - provisioner: "{{ step_acme_cert_ca_provisioner }}" - contact: "{{ step_acme_cert_contact }}" - crt_file: "{{ step_acme_cert_certfile_full.path }}" - key_file: "{{ step_acme_cert_keyfile_full.path }}" - force: yes - name: "{{ step_acme_cert_name }}" - not_after: "{{ step_acme_cert_duration|default(omit) }}" - san: "{{ step_acme_cert_san }}" - standalone: "{{ step_acme_cert_webroot_path | bool }}" - step_cli_executable: "{{ step_cli_executable }}" - webroot: "{{ step_acme_cert_webroot_path }}" - become: yes - environment: - STEPPATH: "{{ step_cli_steppath }}" - -- name: Cert and key permissions are set - file: - path: "{{ item.path }}" - mode: "{{ item.mode }}" - owner: "{{ item.owner }}" - group: "{{ item.group }}" - loop: - - "{{ step_acme_cert_keyfile_full }}" - - "{{ step_acme_cert_certfile_full }}" diff --git a/roles/step_certificate/tasks/get_cert/acme.yml b/roles/step_certificate/tasks/get_cert/acme.yml new file mode 100644 index 00000000..1075d00c --- /dev/null +++ b/roles/step_certificate/tasks/get_cert/acme.yml @@ -0,0 +1,18 @@ +--- +- name: Get certificate from CA via ACME provisioner + maxhoesel.smallstep.step_ca_certificate: + provisioner: "{{ step_cert_ca_provisioner_name }}" + name: "{{ step_cert_name }}" + san: "{{ step_cert_san }}" + contact: "{{ step_cert_contact }}" + not_after: "{{ step_cert_duration|default(omit) }}" + crt_file: "{{ step_cert_certfile_full.path }}" + key_file: "{{ step_cert_keyfile_full.path }}" + webroot: "{{ step_cert_acme_webroot_path }}" + standalone: "{{ step_cert_acme_webroot_path | bool }}" + force: yes + step_cli_executable: "{{ step_cli_executable }}" + become: yes + environment: + STEPPATH: "{{ step_cli_steppath }}" + register: _step_certificate diff --git a/roles/step_certificate/tasks/get_cert/default.yml b/roles/step_certificate/tasks/get_cert/default.yml new file mode 100644 index 00000000..59c45894 --- /dev/null +++ b/roles/step_certificate/tasks/get_cert/default.yml @@ -0,0 +1,3 @@ +- name: default certificate provisioner failure + fail: + msg: No known or no support (yet) for type '{{ step_cert_ca_provisioner_type }}' provisioners diff --git a/roles/step_certificate/tasks/get_cert/jwk.yml b/roles/step_certificate/tasks/get_cert/jwk.yml new file mode 100644 index 00000000..81df84c6 --- /dev/null +++ b/roles/step_certificate/tasks/get_cert/jwk.yml @@ -0,0 +1,42 @@ +--- + +# CARGS="--ca-url https://hecate.sift.net --root /etc/step/certs/root_ca.crt" +# HOST=testing +# TOKEN=$(step-cli ca token $HOST.sift.net $CARGS) +# step-cli ca certificate $HOST.sift.net $HOST.crt $HOST.key --token $TOKEN $CARGS +# openssl x509 -noout -dates <$HOST.crt +# step-cli ca renew $HOST.crt $HOST.key $CARGS +# openssl x509 -noout -dates <$HOST.crt +# step-cli ca certificate $HOST.sift.net $HOST.crt $HOST.key --token $TOKEN $CARGS + +- name: Generate JWK certificate token + maxhoesel.smallstep.step_ca_token: + name: '{{ step_cert_name }}' + san: "{{ step_cert_san }}" + provisioner: "{{ step_cert_ca_provisioner_name }}" + provisioner_password_file: "{{ step_cert_ca_jwk_password_file }}" + not_after: "{{ step_cert_duration | d(omit) }}" + return_token: true + step_cli_executable: "{{ step_cli_executable }}" + register: _step_cert_token + +# - debug: +# var: _step_cert_token +# changed_when: true + +- name: Get certificate from CA via JWK provisioner + maxhoesel.smallstep.step_ca_certificate: + provisioner: "{{ step_cert_ca_provisioner_name }}" + token: "{{ _step_cert_token.token }}" + name: '{{ step_cert_name }}' + san: "{{ step_cert_san }}" + contact: "{{ step_cert_contact }}" + not_after: "{{ step_cert_duration | d(omit) }}" + crt_file: "{{ step_cert_certfile_full.path }}" + key_file: "{{ step_cert_keyfile_full.path }}" + force: yes # stupid, but required flag to allow overwriting existing files + step_cli_executable: "{{ step_cli_executable }}" + become: yes + environment: + STEPPATH: "{{ step_cli_steppath }}" + register: _step_certificate diff --git a/roles/step_certificate/tasks/main.yml b/roles/step_certificate/tasks/main.yml index 3303d495..ee859c93 100644 --- a/roles/step_certificate/tasks/main.yml +++ b/roles/step_certificate/tasks/main.yml @@ -1,27 +1,9 @@ +# tasks file for step_certificate --- -# tasks file for step_acme_cert - include: check.yml +- include: vars.yml +- include: cert.yml -- name: Update cert/keyfile dicts with defaults - set_fact: - # Role params take precedence over set_fact, so we need to declare a new private variable - step_acme_cert_keyfile_full: "{{ step_acme_cert_keyfile_defaults | combine(step_acme_cert_keyfile) }}" - step_acme_cert_certfile_full: "{{ step_acme_cert_certfile_defaults | combine(step_acme_cert_certfile) }}" - -- name: Look for existing certificate - stat: - path: "{{ step_acme_cert_certfile_full.path }}" - register: step_acme_cert_current_cert - -- name: Check if certificate is valid - command: "step-cli certificate verify {{ step_acme_cert_certfile_full.path }}" - changed_when: no - check_mode: no - ignore_errors: true - register: _step_acme_cert_validity - when: step_acme_cert_current_cert.stat.exists - -- include: get_cert.yml - when: 'not step_acme_cert_current_cert.stat.exists or "failed to verify certificate" in _step_acme_cert_validity.stderr' +# - meta: end_host - include: renewal.yml diff --git a/roles/step_certificate/tasks/renewal.yml b/roles/step_certificate/tasks/renewal.yml index a0cf31c4..81141437 100644 --- a/roles/step_certificate/tasks/renewal.yml +++ b/roles/step_certificate/tasks/renewal.yml @@ -1,23 +1,23 @@ # shell is required due to "command" being a shell builtin - name: Get absolute step command path # noqa command-instead-of-shell shell: "command -v {{ step_cli_executable }}" - register: step_cli_executable_absolute become: yes - changed_when: no check_mode: no + changed_when: no + register: _step_cli_executable_absolute -- name: Renewal service is installed +- name: Configure certificate renewal service template: src: step-renew.service.j2 - dest: "/etc/systemd/system/{{ step_acme_cert_renewal_service }}.service" + dest: "/etc/systemd/system/{{ step_cert_renewal_service }}.service" owner: root group: root mode: 0644 notify: restart renewal service -- name: Renewal service is enabled and running +- name: Ensure certificate renewal service is enabled and running systemd: daemon_reload: yes - name: "{{ step_acme_cert_renewal_service }}" + name: "{{ step_cert_renewal_service }}" state: started enabled: yes diff --git a/roles/step_certificate/tasks/vars.yml b/roles/step_certificate/tasks/vars.yml new file mode 100644 index 00000000..fb56d226 --- /dev/null +++ b/roles/step_certificate/tasks/vars.yml @@ -0,0 +1,6 @@ +--- +- name: Update cert/keyfile dicts with defaults + set_fact: + # Role params take precedence over set_fact, so we need to declare a new private variable + step_cert_keyfile_full: "{{ step_cert_keyfile_defaults | combine(step_cert_keyfile) }}" + step_cert_certfile_full: "{{ step_cert_certfile_defaults | combine(step_cert_certfile) }}" diff --git a/roles/step_certificate/templates/step-renew.service.j2 b/roles/step_certificate/templates/step-renew.service.j2 index ff9607ec..a07bb1af 100644 --- a/roles/step_certificate/templates/step-renew.service.j2 +++ b/roles/step_certificate/templates/step-renew.service.j2 @@ -8,7 +8,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_cert_certfile_full.path }} {{ step_cert_keyfile_full.path }} --daemon --force{% if step_cert_renewal_when is defined %} --expires-in {{ step_cert_renewal_when }}{% endif %}{% if step_cert_renewal_reload_services %} --exec "systemctl try-reload-or-restart {{ step_cert_renewal_reload_services | join(' ') }}"{% endif %} [Install] WantedBy=multi-user.target