From c88d501f72874f4b945a40b7bc4b5cc8b7f35445 Mon Sep 17 00:00:00 2001 From: Eric Engstrom Date: Thu, 30 Sep 2021 09:15:53 -0500 Subject: [PATCH] dev: copy `step_acme_cert` to seed genric `step_certificate` --- roles/step_certificate/README.md | 131 ++++++++++++++++++ roles/step_certificate/defaults/main.yml | 29 ++++ roles/step_certificate/handlers/main.yml | 4 + .../step_certificate/meta/argument_specs.yml | 86 ++++++++++++ .../molecule/default/converge.yml | 68 +++++++++ .../molecule/default/files/existing.crt | 10 ++ .../molecule/default/files/existing.key | 5 + .../molecule/default/files/nginx.conf | 55 ++++++++ .../molecule/default/files/nginx_site.conf | 20 +++ .../molecule/default/molecule.yml | 128 +++++++++++++++++ .../molecule/default/prepare.yml | 52 +++++++ .../molecule/default/verify.yml | 17 +++ roles/step_certificate/tasks/check.yml | 19 +++ roles/step_certificate/tasks/get_cert.yml | 26 ++++ roles/step_certificate/tasks/main.yml | 27 ++++ roles/step_certificate/tasks/renewal.yml | 23 +++ .../templates/step-renew.service.j2 | 14 ++ 17 files changed, 714 insertions(+) create mode 100644 roles/step_certificate/README.md create mode 100644 roles/step_certificate/defaults/main.yml create mode 100644 roles/step_certificate/handlers/main.yml create mode 100644 roles/step_certificate/meta/argument_specs.yml create mode 100644 roles/step_certificate/molecule/default/converge.yml create mode 100644 roles/step_certificate/molecule/default/files/existing.crt create mode 100644 roles/step_certificate/molecule/default/files/existing.key create mode 100644 roles/step_certificate/molecule/default/files/nginx.conf create mode 100644 roles/step_certificate/molecule/default/files/nginx_site.conf create mode 100644 roles/step_certificate/molecule/default/molecule.yml create mode 100644 roles/step_certificate/molecule/default/prepare.yml create mode 100644 roles/step_certificate/molecule/default/verify.yml create mode 100644 roles/step_certificate/tasks/check.yml create mode 100644 roles/step_certificate/tasks/get_cert.yml create mode 100644 roles/step_certificate/tasks/main.yml create mode 100644 roles/step_certificate/tasks/renewal.yml create mode 100644 roles/step_certificate/templates/step-renew.service.j2 diff --git a/roles/step_certificate/README.md b/roles/step_certificate/README.md new file mode 100644 index 00000000..129ef1a3 --- /dev/null +++ b/roles/step_certificate/README.md @@ -0,0 +1,131 @@ +# maxhoesel.smallstep.step_acme_cert + +Get a certificate from a CA with ACME and setup automatic renewal using `step-cli renew`. + +This role uses `step-cli` to request and save a certificate from the configured CA, +before setting up a renewal service using `step-cli ca renew`s `--daemon` mode. + +## Requirements + +- The following distributions are currently supported: + - Ubuntu 18.04 LTS or newer + - Debian 10 or newer + - CentOS 8 or newer +- This role requires root access. Make sure to run this role with `become: yes` or equivalent +- The host must be bootstrapped with `step_bootstrap_host` and the root user must be able to access the CA. + +## Role Variables + +### General + +##### `step_cli_executable` +- Path or name of the step-cli executable to use for executing commands in this role +- Can be an absolute path or a command (make sure the executable is in $PATH) for all users +- Default: `step-cli` + +##### `step_cli_steppath` +- Optionally set a custom `$STEPPATH` from which to read the step config +- Example: `/etc/step-cli` +- Default: `/root/.step/` + +### CA + +##### `step_acme_cert_ca_provisioner` +- Name of the provisioner on the CA that will issue the ACME cert +- 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. + 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: "" + +### Certificate + +##### `step_acme_cert_name` +- The subject name that the certificate will be issued for +- Default: `{{ ansible_fqdn }}` + +##### `step_acme_cert_san` +- Subject Alternate Names to add to the cert +- Must be a list of valid SANs +- Default: `[]` + +##### `step_acme_cert_duration` +- Valid duration of the certificate +- Default: undefined (uses the default for the given provisioner, typically 24h) + +##### `step_acme_cert_contact` +- Contact email for the CA for important notifications +- Default: `root@localhost` + +##### `step_acme_cert_certfile`/`step_acme_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. + - `mode`: File mode for the cert/key file. Defaults to `644` for the cert and `600` for the key + - `owner`/`group`: Owner and group of the file. Defaults to root. + +### 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. +- Default: `step-renew` + +##### `step_acme_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) + +##### `step_acme_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** + +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. + +--- + +```yaml +# Configure your CA to include an ACME provisioner +- hosts: step_ca + become: yes + tasks: + - name: Add an ACME provisioner to the CA + maxhoesel.smallstep.step_ca_provisioner: + name: ACME + type: ACME + become_user: step-ca + notify: reload step-ca + handlers: + - name: reload step-ca + systemd: + name: step-ca + state: reloaded + +- hosts: 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 + + # 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 + become: yes + +``` diff --git a/roles/step_certificate/defaults/main.yml b/roles/step_certificate/defaults/main.yml new file mode 100644 index 00000000..bb7d0fb6 --- /dev/null +++ b/roles/step_certificate/defaults/main.yml @@ -0,0 +1,29 @@ +--- +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: "" + +step_acme_cert_name: "{{ ansible_fqdn }}" +step_acme_cert_san: [] +#step_acme_cert_duration: 24h +step_acme_cert_contact: root@localhost + +step_acme_cert_certfile: "{{ step_acme_cert_certfile_defaults }}" +step_acme_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: + 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: [] diff --git a/roles/step_certificate/handlers/main.yml b/roles/step_certificate/handlers/main.yml new file mode 100644 index 00000000..fd481c77 --- /dev/null +++ b/roles/step_certificate/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart renewal service + service: + name: '{{ step_acme_cert_renewal_service }}' + state: restarted diff --git a/roles/step_certificate/meta/argument_specs.yml b/roles/step_certificate/meta/argument_specs.yml new file mode 100644 index 00000000..305c7bd8 --- /dev/null +++ b/roles/step_certificate/meta/argument_specs.yml @@ -0,0 +1,86 @@ +argument_specs: + main: + short_description: Main entrypoint + options: + step_cli_executable: + 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: + type: str + required: yes + description: Name of the provisioner on the CA that will issue the ACME cert + step_acme_cert_webroot_path: + type: str + default: "" + description: Path to the webroot for ACME, or empty for standalone mode + # Certificate Options + step_acme_cert_name: + type: str + default: '{{ ansible_fqdn }}' + description: The subject name that the certificate will be issued for + step_acme_cert_san: + type: list + elements: str + default: [] + description: Subject Alternate Names to add to the cert + step_acme_cert_duration: + type: str + description: Valid duration of the certificate + step_acme_cert_contact: + type: str + default: root@localhost + description: Contact email for the CA for important notifications + step_acme_cert_certfile: + type: dict + description: Details about the cert file on disk + options: + path: + type: path + default: /etc/ssl/step.crt + description: Absolute path to the cert file + mode: + type: str + default: '644' + description: File mode for the cert file + owner: + type: str + default: root + description: Owner of the file + group: + type: str + default: root + description: Group of the file + step_acme_cert_keyfile: + type: dict + description: Details about the key file on disk + options: + path: + type: path + default: /etc/ssl/step.key + description: Absolute path to the key file + mode: + type: str + default: '600' + description: File mode for the key file + owner: + type: str + default: root + description: Owner of the file + group: + type: str + default: root + description: Group of the file + # Renewal Options + step_acme_cert_renewal_service: + type: str + default: step-renew + description: Name of the systemd service that will handle cert renewals + step_acme_cert_renewal_when: + type: str + description: Renew the cert when its remaining valid time crosses this threshold + step_acme_cert_renewal_reload_services: + type: list + elements: str + default: [] + description: Reload or restart these systemd services after a cert renewal diff --git a/roles/step_certificate/molecule/default/converge.yml b/roles/step_certificate/molecule/default/converge.yml new file mode 100644 index 00000000..f911262d --- /dev/null +++ b/roles/step_certificate/molecule/default/converge.yml @@ -0,0 +1,68 @@ +--- +- name: Converge + hosts: clients + vars: + webroots: + Debian: /var/www/html + RedHat: /usr/share/nginx/html + webgroup: + Debian: www-data + RedHat: nginx + tasks: + - name: Get standalone cert + include_role: + name: "step_acme_cert" + vars: + step_cli_steppath: /etc/step-cli-molecule + + - name: Start nginx + systemd: + name: nginx + state: started + + - name: Get cert via webroot + include_role: + name: "step_acme_cert" + vars: + step_service_user: step + step_acme_cert_webroot_path: "{{ webroots[ansible_os_family] }}" + step_acme_cert_duration: 1h + step_acme_cert_certfile: + # Lazy evaluation testing + #path: /etc/ssl/step.crt + mode: "644" + owner: root + group: "{{ webgroup[ansible_os_family] }}" + step_acme_cert_keyfile: + #path: /etc/ssl/step.key + mode: "640" + owner: root + group: "{{ webgroup[ansible_os_family] }}" + step_acme_cert_renewal_service: step-renew-webroot + step_acme_cert_renewal_when: 59m # force renewal to happen every minute + step_acme_cert_renewal_reload_services: [nginx] + step_cli_steppath: /etc/step-cli-molecule + + - name: Install Nginx site [Debian] + copy: + src: nginx_site.conf + dest: /etc/nginx/sites-enabled/default + owner: root + group: root + mode: "644" + notify: restart nginx + when: ansible_os_family == "Debian" + - name: Install Nginx config [RedHat] + copy: + src: nginx.conf + dest: /etc/nginx/nginx.conf + owner: root + group: root + mode: "644" + notify: restart nginx + when: ansible_os_family == "RedHat" + handlers: + - name: restart nginx + systemd: + name: nginx + state: restarted diff --git a/roles/step_certificate/molecule/default/files/existing.crt b/roles/step_certificate/molecule/default/files/existing.crt new file mode 100644 index 00000000..43830023 --- /dev/null +++ b/roles/step_certificate/molecule/default/files/existing.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBXzCCAQWgAwIBAgIQBz1USaMKt+hX3E7NcG+nCDAKBggqhkjOPQQDAjAOMQww +CgYDVQQDEwNmb28wHhcNMjEwMzA5MTkzOTUzWhcNMzEwMzA3MTkzOTUzWjAOMQww +CgYDVQQDEwNmb28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARb1UAYfvY5Tl4u +Oj5/4aNzK5H0Z0qn/o1S9QtFrev9x6W6DRtFo0LSrNDwb3Z57f79k4BEQoe6YQbR +bKlYSXYoo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd +BgNVHQ4EFgQUpUnhM8zYfky4yoU/rV6uDIM0vDkwCgYIKoZIzj0EAwIDSAAwRQIg +KU0olBNzqkJ91MtvvGR7twX2mieyjqfdCRFNc6L6YFoCIQDAAXAW+ncWnaI6HAR9 +irL8wNYy84tnZiLg48j7HP5vLQ== +-----END CERTIFICATE----- diff --git a/roles/step_certificate/molecule/default/files/existing.key b/roles/step_certificate/molecule/default/files/existing.key new file mode 100644 index 00000000..8725a4db --- /dev/null +++ b/roles/step_certificate/molecule/default/files/existing.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIuXoMWikPEYrOG2+ATCw0FEgaVew89g4ys3K363z4MDoAoGCCqGSM49 +AwEHoUQDQgAEW9VAGH72OU5eLjo+f+GjcyuR9GdKp/6NUvULRa3r/celug0bRaNC +0qzQ8G92ee3+/ZOAREKHumEG0WypWEl2KA== +-----END EC PRIVATE KEY----- diff --git a/roles/step_certificate/molecule/default/files/nginx.conf b/roles/step_certificate/molecule/default/files/nginx.conf new file mode 100644 index 00000000..eb4186e2 --- /dev/null +++ b/roles/step_certificate/molecule/default/files/nginx.conf @@ -0,0 +1,55 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + include /etc/nginx/conf.d/*.conf; + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + root /usr/share/nginx/html; + + include /etc/nginx/default.d/*.conf; + + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + ssl_certificate /etc/ssl/step.crt; + ssl_certificate_key /etc/ssl/step.key; + + location / { + } + + error_page 404 /404.html; + location = /40x.html { + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + } + } +} diff --git a/roles/step_certificate/molecule/default/files/nginx_site.conf b/roles/step_certificate/molecule/default/files/nginx_site.conf new file mode 100644 index 00000000..f772aa6c --- /dev/null +++ b/roles/step_certificate/molecule/default/files/nginx_site.conf @@ -0,0 +1,20 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + root /var/www/html; + + ssl_certificate /etc/ssl/step.crt; + ssl_certificate_key /etc/ssl/step.key; + + index index.html index.htm index.nginx-debian.html; + + server_name _; + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/roles/step_certificate/molecule/default/molecule.yml b/roles/step_certificate/molecule/default/molecule.yml new file mode 100644 index 00000000..203b016b --- /dev/null +++ b/roles/step_certificate/molecule/default/molecule.yml @@ -0,0 +1,128 @@ +--- +dependency: + name: galaxy +driver: + name: docker + network: + - name: smallstep_acme_cert + driver: bridge +platforms: + # Note on containers: + # - We use the images provided by geerlingguy, as they provide out-of-the-box + # support for Ansible and systemd (needed to test service management). + # - The containers run as privileged containers so that we can + # use systemd functionality. This *should* be possible with unpriliged + # containers as well, but is quite the headache. + # - they are connected to a shared network to allow simulating a remote CA + - name: step-ca + hostname: step-ca.localdomain + groups: + - ca + - ubuntu + image: "geerlingguy/docker-ubuntu2004-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + + - name: step-cert-ubuntu-20 + hostname: step-cert-ubuntu-20.localdomain + groups: + - clients + - ubuntu + image: "geerlingguy/docker-ubuntu2004-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + + - name: step-cert-ubuntu-18 + hostname: step-cert-ubuntu-18.localdomain + groups: + - clients + - ubuntu + image: "geerlingguy/docker-ubuntu1804-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + + - name: step-cert-debian-11 + hostname: step-cert-debian-11.localdomain + groups: + - clients + - debian + image: "geerlingguy/docker-debian11-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + + - name: step-cert-debian-10 + hostname: step-cert-debian-10.localdomain + groups: + - clients + - debian + image: "geerlingguy/docker-debian10-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + + - name: step-cert-centos-8 + hostname: step-cert-centos-8.localdomain + groups: + - clients + - centos + image: "geerlingguy/docker-centos8-ansible" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + privileged: true + override_command: false + pre_build_image: true + networks: + - name: smallstep_acme_cert + +provisioner: + name: ansible + config_options: + ssh_connection: + pipelining: yes + defaults: + forks: 15 + inventory: + links: + group_vars: ../../../../tests/molecule/group_vars + +scenario: + test_sequence: + - lint + - destroy + - dependency + - syntax + - create + - prepare + - converge + - idempotence + - check # also run check mode in regular tests + - side_effect + - verify + - destroy + +verifier: + name: ansible diff --git a/roles/step_certificate/molecule/default/prepare.yml b/roles/step_certificate/molecule/default/prepare.yml new file mode 100644 index 00000000..7e3e5f56 --- /dev/null +++ b/roles/step_certificate/molecule/default/prepare.yml @@ -0,0 +1,52 @@ +- hosts: ubuntu:debian + tasks: + - name: Update apt + apt: + update_cache: yes + +- hosts: ca + tasks: + - name: Copy existing ca cert/key to nodes + copy: + src: "{{ item }}" + dest: /tmp/ + owner: root + group: root + mode: 0755 + loop: + - "existing.crt" + - "existing.key" + + - name: Install step-ca + include_role: + name: step_ca + + - name: Add ACME provisioner + maxhoesel.smallstep.step_ca_provisioner: + name: ACME + type: ACME + become: yes + become_user: "{{ step_ca_user }}" + + - name: Reload step-ca + systemd: + name: step-ca + state: reloaded + +- hosts: clients + tasks: + - name: Install nginx + package: + name: nginx + - name: Stop nginx + systemd: + name: nginx + state: stopped + enabled: no + - name: Bootstrap host + include_role: + name: maxhoesel.smallstep.step_bootstrap_host + vars: + step_bootstrap_ca_url: https://step-ca.localdomain + step_bootstrap_fingerprint: 345bf77397642dc9211a3820af0b1816c4afa430ad249ae705f1456b4bafa046 + step_cli_steppath: /etc/step-cli-molecule diff --git a/roles/step_certificate/molecule/default/verify.yml b/roles/step_certificate/molecule/default/verify.yml new file mode 100644 index 00000000..28a1710e --- /dev/null +++ b/roles/step_certificate/molecule/default/verify.yml @@ -0,0 +1,17 @@ +--- +- name: Verify + hosts: clients + tasks: + - name: Get service facts + service_facts: + + - name: Verify that nginx and renew services are running + assert: + that: + - ansible_facts.services["nginx.service"]["state"] == "running" + - ansible_facts.services["step-renew.service"]["state"] == "running" + - ansible_facts.services["step-renew-webroot.service"]["state"] == "running" + + - name: Try to access the locally hosted site over HTTPS + uri: + url: "https://{{ ansible_fqdn }}" diff --git a/roles/step_certificate/tasks/check.yml b/roles/step_certificate/tasks/check.yml new file mode 100644 index 00000000..5a73809c --- /dev/null +++ b/roles/step_certificate/tasks/check.yml @@ -0,0 +1,19 @@ +- name: Verify that required parameters are set + assert: + that: + - step_acme_cert_ca_provisioner is defined + - step_acme_cert_ca_provisioner | length > 0 + when: ansible_version.string is version('2.11.1', '<') + +- name: Look for step_cli_executable # noqa command-instead-of-shell + #"command" is a shell builtin, hence the need for the shell module + shell: "command -v {{ step_cli_executable }}" + register: step_cli_install + # dash (Debian sh shell) uses 127 instead of 1 for not found errors + 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 + fail_msg: "Could not find step-cli at path '{{ step_cli_executable }}'. Please install step-cli via maxhoesel.smallstep.step_cli or step_bootstrap_host" diff --git a/roles/step_certificate/tasks/get_cert.yml b/roles/step_certificate/tasks/get_cert.yml new file mode 100644 index 00000000..477b805c --- /dev/null +++ b/roles/step_certificate/tasks/get_cert.yml @@ -0,0 +1,26 @@ +- 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/main.yml b/roles/step_certificate/tasks/main.yml new file mode 100644 index 00000000..3303d495 --- /dev/null +++ b/roles/step_certificate/tasks/main.yml @@ -0,0 +1,27 @@ +--- +# tasks file for step_acme_cert +- include: check.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' + +- include: renewal.yml diff --git a/roles/step_certificate/tasks/renewal.yml b/roles/step_certificate/tasks/renewal.yml new file mode 100644 index 00000000..a0cf31c4 --- /dev/null +++ b/roles/step_certificate/tasks/renewal.yml @@ -0,0 +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 + +- name: Renewal service is installed + template: + src: step-renew.service.j2 + dest: "/etc/systemd/system/{{ step_acme_cert_renewal_service }}.service" + owner: root + group: root + mode: 0644 + notify: restart renewal service + +- name: Renewal service is enabled and running + systemd: + daemon_reload: yes + name: "{{ step_acme_cert_renewal_service }}" + state: started + enabled: yes diff --git a/roles/step_certificate/templates/step-renew.service.j2 b/roles/step_certificate/templates/step-renew.service.j2 new file mode 100644 index 00000000..ff9607ec --- /dev/null +++ b/roles/step_certificate/templates/step-renew.service.j2 @@ -0,0 +1,14 @@ +[Unit] +Description=Step TLS Renewer +After=network.target +StartLimitIntervalSec=0 + +[Service] +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 %} + +[Install] +WantedBy=multi-user.target