From 18e0446c9c545f396d7737b406e6e207748e7926 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Thu, 22 Dec 2022 13:01:30 +0100 Subject: move acmetool to new x509 subdir --- roles/acmetool/base/defaults/main.yml | 26 ---- roles/acmetool/base/tasks/main.yml | 65 --------- roles/acmetool/base/tasks/selfsigned.yml | 145 --------------------- roles/acmetool/base/templates/acme-reload.j2 | 7 - roles/acmetool/base/templates/responses.j2 | 15 --- .../base/templates/systemd-override.conf.j2 | 19 --- roles/acmetool/cert/defaults/main.yml | 2 - roles/acmetool/cert/filter_plugins/acme_certs.py | 24 ---- roles/acmetool/cert/handlers/main.yml | 6 - roles/acmetool/cert/tasks/main.yml | 10 -- roles/apps/coturn/tasks/main.yml | 2 +- roles/apps/mumble/tasks/main.yml | 2 +- roles/nginx/vhost/tasks/acme.yml | 2 +- roles/x509/acmetool/base/defaults/main.yml | 26 ++++ roles/x509/acmetool/base/tasks/main.yml | 65 +++++++++ roles/x509/acmetool/base/tasks/selfsigned.yml | 145 +++++++++++++++++++++ roles/x509/acmetool/base/templates/acme-reload.j2 | 7 + roles/x509/acmetool/base/templates/responses.j2 | 15 +++ .../base/templates/systemd-override.conf.j2 | 19 +++ roles/x509/acmetool/cert/defaults/main.yml | 2 + .../acmetool/cert/filter_plugins/acme_certs.py | 24 ++++ roles/x509/acmetool/cert/handlers/main.yml | 6 + roles/x509/acmetool/cert/tasks/main.yml | 10 ++ 23 files changed, 322 insertions(+), 322 deletions(-) delete mode 100644 roles/acmetool/base/defaults/main.yml delete mode 100644 roles/acmetool/base/tasks/main.yml delete mode 100644 roles/acmetool/base/tasks/selfsigned.yml delete mode 100644 roles/acmetool/base/templates/acme-reload.j2 delete mode 100644 roles/acmetool/base/templates/responses.j2 delete mode 100644 roles/acmetool/base/templates/systemd-override.conf.j2 delete mode 100644 roles/acmetool/cert/defaults/main.yml delete mode 100644 roles/acmetool/cert/filter_plugins/acme_certs.py delete mode 100644 roles/acmetool/cert/handlers/main.yml delete mode 100644 roles/acmetool/cert/tasks/main.yml create mode 100644 roles/x509/acmetool/base/defaults/main.yml create mode 100644 roles/x509/acmetool/base/tasks/main.yml create mode 100644 roles/x509/acmetool/base/tasks/selfsigned.yml create mode 100644 roles/x509/acmetool/base/templates/acme-reload.j2 create mode 100644 roles/x509/acmetool/base/templates/responses.j2 create mode 100644 roles/x509/acmetool/base/templates/systemd-override.conf.j2 create mode 100644 roles/x509/acmetool/cert/defaults/main.yml create mode 100644 roles/x509/acmetool/cert/filter_plugins/acme_certs.py create mode 100644 roles/x509/acmetool/cert/handlers/main.yml create mode 100644 roles/x509/acmetool/cert/tasks/main.yml (limited to 'roles') diff --git a/roles/acmetool/base/defaults/main.yml b/roles/acmetool/base/defaults/main.yml deleted file mode 100644 index df82d26c..00000000 --- a/roles/acmetool/base/defaults/main.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -acmetool_directory_server_le_live_v1: "https://acme-v01.api.letsencrypt.org/directory" -acmetool_directory_server_le_staging_v1: "https://acme-staging.api.letsencrypt.org/directory" - -acmetool_directory_server_le_live_v2: "https://acme-v02.api.letsencrypt.org/directory" -acmetool_directory_server_le_staging_v2: "https://acme-staging-v02.api.letsencrypt.org/directory" - -## this can't be changed after the account as been created (aka after the first run) -## and it's not recommended to keep this empty so we don't define it here which will lead to an error -# acmetool_account_email: -acmetool_directory_server: "{{ acmetool_directory_server_le_staging_v2 }}" - -#### optionally set http(s)_proxy -# acmetool_http_proxy: -# acmetool_https_proxy: - -acmetool_default_key_type: rsa -acmetool_default_rsa_key_size: 4096 -acmetool_default_ecdsa_curve: nistp256 - -### this defaults to '/var/run/acme/acme-challenge' -# acmetool_challenge_webroot_path: "/path/to/acme-challenge" - -### by default a number of daemons are tried to be reloaded -### an empty list disables reloading of any service -# acmetool_reload_services: [] diff --git a/roles/acmetool/base/tasks/main.yml b/roles/acmetool/base/tasks/main.yml deleted file mode 100644 index 5f2ae4ab..00000000 --- a/roles/acmetool/base/tasks/main.yml +++ /dev/null @@ -1,65 +0,0 @@ ---- -- name: check if acmetool package is new enough - ansible.builtin.debug: - msg: "Check distribution_release" - failed_when: (ansible_distribution == 'Debian' and (ansible_distribution_major_version | int) < 9) or (ansible_distribution == 'Ubuntu' and (ansible_distribution_major_version | int) < 17) or (ansible_distribution != 'Debian' and ansible_distribution != 'Ubuntu') - -- name: install needed packages - ansible.builtin.apt: - name: - - acmetool - - "{{ python_basename }}-openssl" - state: present - -- name: create initial directory structure - ansible.builtin.command: acmetool --batch - args: - creates: /var/lib/acme/conf - -- name: create acmetool response file - ansible.builtin.template: - src: responses.j2 - dest: /var/lib/acme/conf/responses - -- name: create non-standard acmetool webroot path - ansible.builtin.file: - name: "{{ acmetool_challenge_webroot_path }}" - state: directory - when: acmetool_challenge_webroot_path is defined - -- name: run quickstart to create account and default target configuration - ansible.builtin.command: acmetool --batch quickstart - environment: - http_proxy: "{{ acmetool_http_proxy | default('') }}" - https_proxy: "{{ acmetool_https_proxy | default('') }}" - args: - creates: /var/lib/acme/conf/target - -- name: generate selfsigned interim certificate - ansible.builtin.include_tasks: selfsigned.yml - -- name: install service reload configuration - ansible.builtin.template: - src: acme-reload.j2 - dest: /etc/default/acme-reload - owner: root - group: root - mode: 0644 - when: acmetool_reload_services is defined - -- name: create system unit snippet directory - ansible.builtin.file: - path: /etc/systemd/system/acmetool.service.d/ - state: directory - -- name: install systemd unit snippet - ansible.builtin.template: - src: systemd-override.conf.j2 - dest: /etc/systemd/system/acmetool.service.d/override.conf - -- name: enable/start systemd timer for acmetool - ansible.builtin.systemd: - name: acmetool.timer - state: started - enabled: yes - daemon_reload: yes diff --git a/roles/acmetool/base/tasks/selfsigned.yml b/roles/acmetool/base/tasks/selfsigned.yml deleted file mode 100644 index 449fbdb9..00000000 --- a/roles/acmetool/base/tasks/selfsigned.yml +++ /dev/null @@ -1,145 +0,0 @@ ---- -- name: get id of existing selfsigned interim certificate - ansible.builtin.shell: cat /var/lib/acme/.selfsigned-interim-cert || true - changed_when: false - check_mode: false - register: existing_selfsigned_interim_cert_id - -- name: set existing_selfsigned_interim_cert_id variable - ansible.builtin.set_fact: - existing_selfsigned_interim_cert_id: "{{ existing_selfsigned_interim_cert_id.stdout }}" - -- name: check if selfsigned interim certificate does exist - ansible.builtin.stat: - path: "/var/lib/acme/certs/{{ existing_selfsigned_interim_cert_id }}" - register: existing_selfsigned_interim_cert_stat - -- name: create selfsigned interim certificate - when: not existing_selfsigned_interim_cert_id or not existing_selfsigned_interim_cert_stat.stat.exists - block: - - name: create temporary directory - ansible.builtin.tempfile: - path: /var/lib/acme/tmp - prefix: selfsigned-interim-cert- - state: directory - register: tmpdir - - - name: set tmpdir variable - ansible.builtin.set_fact: - tmpdir: "{{ tmpdir.path }}" - - - name: generate private key for selfsigned interim certificate - ansible.builtin.openssl_privatekey: - path: "{{ tmpdir }}/privkey" - mode: 0600 - - - name: generate csr for selfsigned interim certificate - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ tmpdir }}/privkey" - common_name: "{{ ansible_fqdn }}" - register: selfsigned_interim_cert_req - - - ### this is needed because strftime filter in ansible is exceptionally stupid - ### see: https://github.com/ansible/ansible/issues/39835 - - name: get remote date-time 10s ago - ansible.builtin.command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_10sago - - - name: get remote date-time now - ansible.builtin.command: date -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_now - - - name: generate selfsigned interim certificate - community.crypto.x509_certificate_pipe: - privatekey_path: "{{ tmpdir }}/privkey" - csr_content: "{{ selfsigned_interim_cert_req.csr }}" - provider: selfsigned - ## make sure the certificate is not valid anymore to force acmetool to create a new cert - selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" - selfsigned_not_after: "{{ remote_datetime_now.stdout }}" - register: selfsigned_interim_cert - - - name: install selfsigned interim certificate and fullchain - loop: - - cert - - fullchain - ansible.builtin.copy: - content: "{{ selfsigned_interim_cert.certificate }}" - dest: "{{ tmpdir }}/{{ item }}" - - - name: create additional empty files - loop: - - chain - - selfsigned - ansible.builtin.copy: - content: "" - dest: "{{ tmpdir }}/{{ item }}" - - ### TODO: remove this once acmetool respects it's own storage layout - ### see: https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md#temporary-use-of-self-signed-certificates - - name: generate fake url file - ansible.builtin.copy: - content: "https://acme.example.com/acme/cert/self-signed\n" - dest: "{{ tmpdir }}/url" - - - name: get key id - ansible.builtin.shell: "openssl x509 -in '{{ tmpdir }}/cert' -noout -pubkey | openssl enc -base64 -d | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" - register: selfsigned_interim_key_id - - - name: set selfsigned_interim_key_id variable - ansible.builtin.set_fact: - selfsigned_interim_key_id: "{{ selfsigned_interim_key_id.stdout }}" - - - name: create directory for private key of selfsigned interim certificate - ansible.builtin.file: - path: "/var/lib/acme/keys/{{ selfsigned_interim_key_id }}" - state: directory - mode: 0700 - - - name: move private key to its directory - ansible.builtin.command: "mv '{{ tmpdir }}/privkey' '/var/lib/acme/keys/{{ selfsigned_interim_key_id }}/privkey'" - - - name: create symlink to privkey - ansible.builtin.file: - src: "../../keys/{{ selfsigned_interim_key_id }}/privkey" - dest: "{{ tmpdir }}/privkey" - state: link - - # - name: get certificate id - # ansible.builtin.shell: "openssl x509 -in '{{ tmpdir }}/cert' -outform der | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" - # register: selfsigned_interim_cert_id - - # - name: set selfsigned_interim_cert_id variable - # ansible.builtin.set_fact: - # selfsigned_interim_cert_id: "selfsigned-{{ selfsigned_interim_cert_id.stdout }}" - - ### TODO: replace with the above once acmetool respects it's own storage layout - ### see: https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md#temporary-use-of-self-signed-certificates - - name: get certificate id - ansible.builtin.shell: "cat '{{ tmpdir }}/url' | tr -d '\n' | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" - register: selfsigned_interim_cert_id - - - name: set selfsigned_interim_cert_id variable - ansible.builtin.set_fact: - selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" - - - name: set permissions for selfsigned interim certificate directory - ansible.builtin.file: - path: "{{ tmpdir }}" - mode: 0755 - state: directory - - - name: move selfsigned interim certificate directory into place - ansible.builtin.command: "mv '{{ tmpdir }}' '/var/lib/acme/certs/{{ selfsigned_interim_cert_id }}'" - - - name: write cert-id of selfsigned interim certificate to state directory - ansible.builtin.copy: - content: "{{ selfsigned_interim_cert_id }}" - dest: /var/lib/acme/.selfsigned-interim-cert - - rescue: - - name: remove temporary directory for selfsigned interim certificate - ansible.builtin.file: - path: "{{ tmpdir }}" - state: absent diff --git a/roles/acmetool/base/templates/acme-reload.j2 b/roles/acmetool/base/templates/acme-reload.j2 deleted file mode 100644 index a679bc7d..00000000 --- a/roles/acmetool/base/templates/acme-reload.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# This should contain a space-seperated list of services to be -# reloaded after new certificates are generated. An empty list -# disables reloading of any service -# -# example: SERVICES="apache2 nginx postfix" - -SERVICES="{{ acmetool_reload_services | join(' ') }}" diff --git a/roles/acmetool/base/templates/responses.j2 b/roles/acmetool/base/templates/responses.j2 deleted file mode 100644 index 981eba90..00000000 --- a/roles/acmetool/base/templates/responses.j2 +++ /dev/null @@ -1,15 +0,0 @@ -"acme-enter-email": "{{ acmetool_account_email }}" -"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf": true -"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017-w-v1.3-notice.pdf": true -"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-August-10-2022.pdf": true -"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf": true -"acmetool-quickstart-choose-server": {{ acmetool_directory_server }} -"acmetool-quickstart-choose-method": webroot -"acmetool-quickstart-webroot-path": "{{ acmetool_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}" -"acmetool-quickstart-complete": true -"acmetool-quickstart-install-cronjob": false -"acmetool-quickstart-install-haproxy-script": true -"acmetool-quickstart-install-redirector-systemd": false -"acmetool-quickstart-key-type": {{ acmetool_default_key_type }} -"acmetool-quickstart-rsa-key-size": {{ acmetool_default_rsa_key_size }} -"acmetool-quickstart-ecdsa-curve": {{ acmetool_default_ecdsa_curve }} diff --git a/roles/acmetool/base/templates/systemd-override.conf.j2 b/roles/acmetool/base/templates/systemd-override.conf.j2 deleted file mode 100644 index 5de58bdd..00000000 --- a/roles/acmetool/base/templates/systemd-override.conf.j2 +++ /dev/null @@ -1,19 +0,0 @@ -{% if acmetool_challenge_webroot_path is defined %} -[Unit] -# In case the webroot path is hosted by a filesystem that is -# using a systemd automount unit the ReadWritePaths= below does -# prevent the unit from being loaded when the filesystem is -# not yet mounted. -RequiresMountsFor={{ acmetool_challenge_webroot_path }} - -{% endif %} -[Service] -{% if acmetool_http_proxy is defined %} -Environment=http_proxy={{ acmetool_http_proxy }} -{% endif %} -{% if acmetool_https_proxy is defined %} -Environment=https_proxy={{ acmetool_https_proxy }} -{% endif %} -{% if acmetool_challenge_webroot_path is defined %} -ReadWritePaths={{ acmetool_challenge_webroot_path }} -{% endif %} diff --git a/roles/acmetool/cert/defaults/main.yml b/roles/acmetool/cert/defaults/main.yml deleted file mode 100644 index ab0afaa3..00000000 --- a/roles/acmetool/cert/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -acmetool_reconcile_disabled: false diff --git a/roles/acmetool/cert/filter_plugins/acme_certs.py b/roles/acmetool/cert/filter_plugins/acme_certs.py deleted file mode 100644 index 179f71e9..00000000 --- a/roles/acmetool/cert/filter_plugins/acme_certs.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -from functools import partial - -from ansible import errors - - -def acme_cert_nonexistent(data, hostnames): - try: - return [hostnames[i] for i, d in enumerate(data) if d['stat']['exists'] == False] - except Exception as e: - raise errors.AnsibleFilterError("acme_cert_nonexistent(): %s" % str(e)) - - -class FilterModule(object): - - ''' acme certificate filters ''' - filter_map = { - 'acme_cert_nonexistent': acme_cert_nonexistent, - } - - def filters(self): - return self.filter_map diff --git a/roles/acmetool/cert/handlers/main.yml b/roles/acmetool/cert/handlers/main.yml deleted file mode 100644 index 08892c18..00000000 --- a/roles/acmetool/cert/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: reconcile acmetool - when: not acmetool_reconcile_disabled - ansible.builtin.systemd: - name: acmetool.service - state: started diff --git a/roles/acmetool/cert/tasks/main.yml b/roles/acmetool/cert/tasks/main.yml deleted file mode 100644 index e97aab84..00000000 --- a/roles/acmetool/cert/tasks/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -- name: add acmetool desired file - vars: - acmetool_cert_satisfy: - satisfy: - names: "{{ acmetool_cert_hostnames | default([acmetool_cert_name]) }}" - ansible.builtin.copy: - content: "{{ acmetool_cert_config | default({}) | combine(acmetool_cert_satisfy) | to_nice_yaml }}" - dest: "/var/lib/acme/desired/{{ acmetool_cert_name }}" - notify: reconcile acmetool diff --git a/roles/apps/coturn/tasks/main.yml b/roles/apps/coturn/tasks/main.yml index 838385ea..42ccd2b3 100644 --- a/roles/apps/coturn/tasks/main.yml +++ b/roles/apps/coturn/tasks/main.yml @@ -72,7 +72,7 @@ - name: get certificate using acmetool when: not coturn_install_nginx_vhost import_role: - name: acmetool/cert + name: x509/acmetool/cert vars: acmetool_cert_name: "coturn-{{ coturn_realm }}" acmetool_cert_hostnames: "{{ coturn_hostnames }}" diff --git a/roles/apps/mumble/tasks/main.yml b/roles/apps/mumble/tasks/main.yml index 0e16e54b..91932b05 100644 --- a/roles/apps/mumble/tasks/main.yml +++ b/roles/apps/mumble/tasks/main.yml @@ -60,7 +60,7 @@ - name: get certificate using acmetool import_role: - name: acmetool/cert + name: x509/acmetool/cert vars: acmetool_cert_name: "mumble-{{ mumble_instance }}" acmetool_cert_hostnames: "{{ mumble_hostnames }}" diff --git a/roles/nginx/vhost/tasks/acme.yml b/roles/nginx/vhost/tasks/acme.yml index 21ab8695..8a6cddb7 100644 --- a/roles/nginx/vhost/tasks/acme.yml +++ b/roles/nginx/vhost/tasks/acme.yml @@ -38,7 +38,7 @@ - name: get certificate using acmetool import_role: - name: acmetool/cert + name: x509/acmetool/cert vars: acmetool_cert_name: "{{ nginx_vhost.name }}" acmetool_cert_hostnames: "{{ nginx_vhost.hostnames }}" diff --git a/roles/x509/acmetool/base/defaults/main.yml b/roles/x509/acmetool/base/defaults/main.yml new file mode 100644 index 00000000..df82d26c --- /dev/null +++ b/roles/x509/acmetool/base/defaults/main.yml @@ -0,0 +1,26 @@ +--- +acmetool_directory_server_le_live_v1: "https://acme-v01.api.letsencrypt.org/directory" +acmetool_directory_server_le_staging_v1: "https://acme-staging.api.letsencrypt.org/directory" + +acmetool_directory_server_le_live_v2: "https://acme-v02.api.letsencrypt.org/directory" +acmetool_directory_server_le_staging_v2: "https://acme-staging-v02.api.letsencrypt.org/directory" + +## this can't be changed after the account as been created (aka after the first run) +## and it's not recommended to keep this empty so we don't define it here which will lead to an error +# acmetool_account_email: +acmetool_directory_server: "{{ acmetool_directory_server_le_staging_v2 }}" + +#### optionally set http(s)_proxy +# acmetool_http_proxy: +# acmetool_https_proxy: + +acmetool_default_key_type: rsa +acmetool_default_rsa_key_size: 4096 +acmetool_default_ecdsa_curve: nistp256 + +### this defaults to '/var/run/acme/acme-challenge' +# acmetool_challenge_webroot_path: "/path/to/acme-challenge" + +### by default a number of daemons are tried to be reloaded +### an empty list disables reloading of any service +# acmetool_reload_services: [] diff --git a/roles/x509/acmetool/base/tasks/main.yml b/roles/x509/acmetool/base/tasks/main.yml new file mode 100644 index 00000000..5f2ae4ab --- /dev/null +++ b/roles/x509/acmetool/base/tasks/main.yml @@ -0,0 +1,65 @@ +--- +- name: check if acmetool package is new enough + ansible.builtin.debug: + msg: "Check distribution_release" + failed_when: (ansible_distribution == 'Debian' and (ansible_distribution_major_version | int) < 9) or (ansible_distribution == 'Ubuntu' and (ansible_distribution_major_version | int) < 17) or (ansible_distribution != 'Debian' and ansible_distribution != 'Ubuntu') + +- name: install needed packages + ansible.builtin.apt: + name: + - acmetool + - "{{ python_basename }}-openssl" + state: present + +- name: create initial directory structure + ansible.builtin.command: acmetool --batch + args: + creates: /var/lib/acme/conf + +- name: create acmetool response file + ansible.builtin.template: + src: responses.j2 + dest: /var/lib/acme/conf/responses + +- name: create non-standard acmetool webroot path + ansible.builtin.file: + name: "{{ acmetool_challenge_webroot_path }}" + state: directory + when: acmetool_challenge_webroot_path is defined + +- name: run quickstart to create account and default target configuration + ansible.builtin.command: acmetool --batch quickstart + environment: + http_proxy: "{{ acmetool_http_proxy | default('') }}" + https_proxy: "{{ acmetool_https_proxy | default('') }}" + args: + creates: /var/lib/acme/conf/target + +- name: generate selfsigned interim certificate + ansible.builtin.include_tasks: selfsigned.yml + +- name: install service reload configuration + ansible.builtin.template: + src: acme-reload.j2 + dest: /etc/default/acme-reload + owner: root + group: root + mode: 0644 + when: acmetool_reload_services is defined + +- name: create system unit snippet directory + ansible.builtin.file: + path: /etc/systemd/system/acmetool.service.d/ + state: directory + +- name: install systemd unit snippet + ansible.builtin.template: + src: systemd-override.conf.j2 + dest: /etc/systemd/system/acmetool.service.d/override.conf + +- name: enable/start systemd timer for acmetool + ansible.builtin.systemd: + name: acmetool.timer + state: started + enabled: yes + daemon_reload: yes diff --git a/roles/x509/acmetool/base/tasks/selfsigned.yml b/roles/x509/acmetool/base/tasks/selfsigned.yml new file mode 100644 index 00000000..449fbdb9 --- /dev/null +++ b/roles/x509/acmetool/base/tasks/selfsigned.yml @@ -0,0 +1,145 @@ +--- +- name: get id of existing selfsigned interim certificate + ansible.builtin.shell: cat /var/lib/acme/.selfsigned-interim-cert || true + changed_when: false + check_mode: false + register: existing_selfsigned_interim_cert_id + +- name: set existing_selfsigned_interim_cert_id variable + ansible.builtin.set_fact: + existing_selfsigned_interim_cert_id: "{{ existing_selfsigned_interim_cert_id.stdout }}" + +- name: check if selfsigned interim certificate does exist + ansible.builtin.stat: + path: "/var/lib/acme/certs/{{ existing_selfsigned_interim_cert_id }}" + register: existing_selfsigned_interim_cert_stat + +- name: create selfsigned interim certificate + when: not existing_selfsigned_interim_cert_id or not existing_selfsigned_interim_cert_stat.stat.exists + block: + - name: create temporary directory + ansible.builtin.tempfile: + path: /var/lib/acme/tmp + prefix: selfsigned-interim-cert- + state: directory + register: tmpdir + + - name: set tmpdir variable + ansible.builtin.set_fact: + tmpdir: "{{ tmpdir.path }}" + + - name: generate private key for selfsigned interim certificate + ansible.builtin.openssl_privatekey: + path: "{{ tmpdir }}/privkey" + mode: 0600 + + - name: generate csr for selfsigned interim certificate + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ tmpdir }}/privkey" + common_name: "{{ ansible_fqdn }}" + register: selfsigned_interim_cert_req + + + ### this is needed because strftime filter in ansible is exceptionally stupid + ### see: https://github.com/ansible/ansible/issues/39835 + - name: get remote date-time 10s ago + ansible.builtin.command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_10sago + + - name: get remote date-time now + ansible.builtin.command: date -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_now + + - name: generate selfsigned interim certificate + community.crypto.x509_certificate_pipe: + privatekey_path: "{{ tmpdir }}/privkey" + csr_content: "{{ selfsigned_interim_cert_req.csr }}" + provider: selfsigned + ## make sure the certificate is not valid anymore to force acmetool to create a new cert + selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" + selfsigned_not_after: "{{ remote_datetime_now.stdout }}" + register: selfsigned_interim_cert + + - name: install selfsigned interim certificate and fullchain + loop: + - cert + - fullchain + ansible.builtin.copy: + content: "{{ selfsigned_interim_cert.certificate }}" + dest: "{{ tmpdir }}/{{ item }}" + + - name: create additional empty files + loop: + - chain + - selfsigned + ansible.builtin.copy: + content: "" + dest: "{{ tmpdir }}/{{ item }}" + + ### TODO: remove this once acmetool respects it's own storage layout + ### see: https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md#temporary-use-of-self-signed-certificates + - name: generate fake url file + ansible.builtin.copy: + content: "https://acme.example.com/acme/cert/self-signed\n" + dest: "{{ tmpdir }}/url" + + - name: get key id + ansible.builtin.shell: "openssl x509 -in '{{ tmpdir }}/cert' -noout -pubkey | openssl enc -base64 -d | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" + register: selfsigned_interim_key_id + + - name: set selfsigned_interim_key_id variable + ansible.builtin.set_fact: + selfsigned_interim_key_id: "{{ selfsigned_interim_key_id.stdout }}" + + - name: create directory for private key of selfsigned interim certificate + ansible.builtin.file: + path: "/var/lib/acme/keys/{{ selfsigned_interim_key_id }}" + state: directory + mode: 0700 + + - name: move private key to its directory + ansible.builtin.command: "mv '{{ tmpdir }}/privkey' '/var/lib/acme/keys/{{ selfsigned_interim_key_id }}/privkey'" + + - name: create symlink to privkey + ansible.builtin.file: + src: "../../keys/{{ selfsigned_interim_key_id }}/privkey" + dest: "{{ tmpdir }}/privkey" + state: link + + # - name: get certificate id + # ansible.builtin.shell: "openssl x509 -in '{{ tmpdir }}/cert' -outform der | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" + # register: selfsigned_interim_cert_id + + # - name: set selfsigned_interim_cert_id variable + # ansible.builtin.set_fact: + # selfsigned_interim_cert_id: "selfsigned-{{ selfsigned_interim_cert_id.stdout }}" + + ### TODO: replace with the above once acmetool respects it's own storage layout + ### see: https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md#temporary-use-of-self-signed-certificates + - name: get certificate id + ansible.builtin.shell: "cat '{{ tmpdir }}/url' | tr -d '\n' | openssl sha256 -binary | base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'" + register: selfsigned_interim_cert_id + + - name: set selfsigned_interim_cert_id variable + ansible.builtin.set_fact: + selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" + + - name: set permissions for selfsigned interim certificate directory + ansible.builtin.file: + path: "{{ tmpdir }}" + mode: 0755 + state: directory + + - name: move selfsigned interim certificate directory into place + ansible.builtin.command: "mv '{{ tmpdir }}' '/var/lib/acme/certs/{{ selfsigned_interim_cert_id }}'" + + - name: write cert-id of selfsigned interim certificate to state directory + ansible.builtin.copy: + content: "{{ selfsigned_interim_cert_id }}" + dest: /var/lib/acme/.selfsigned-interim-cert + + rescue: + - name: remove temporary directory for selfsigned interim certificate + ansible.builtin.file: + path: "{{ tmpdir }}" + state: absent diff --git a/roles/x509/acmetool/base/templates/acme-reload.j2 b/roles/x509/acmetool/base/templates/acme-reload.j2 new file mode 100644 index 00000000..a679bc7d --- /dev/null +++ b/roles/x509/acmetool/base/templates/acme-reload.j2 @@ -0,0 +1,7 @@ +# This should contain a space-seperated list of services to be +# reloaded after new certificates are generated. An empty list +# disables reloading of any service +# +# example: SERVICES="apache2 nginx postfix" + +SERVICES="{{ acmetool_reload_services | join(' ') }}" diff --git a/roles/x509/acmetool/base/templates/responses.j2 b/roles/x509/acmetool/base/templates/responses.j2 new file mode 100644 index 00000000..981eba90 --- /dev/null +++ b/roles/x509/acmetool/base/templates/responses.j2 @@ -0,0 +1,15 @@ +"acme-enter-email": "{{ acmetool_account_email }}" +"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf": true +"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017-w-v1.3-notice.pdf": true +"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-August-10-2022.pdf": true +"acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf": true +"acmetool-quickstart-choose-server": {{ acmetool_directory_server }} +"acmetool-quickstart-choose-method": webroot +"acmetool-quickstart-webroot-path": "{{ acmetool_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}" +"acmetool-quickstart-complete": true +"acmetool-quickstart-install-cronjob": false +"acmetool-quickstart-install-haproxy-script": true +"acmetool-quickstart-install-redirector-systemd": false +"acmetool-quickstart-key-type": {{ acmetool_default_key_type }} +"acmetool-quickstart-rsa-key-size": {{ acmetool_default_rsa_key_size }} +"acmetool-quickstart-ecdsa-curve": {{ acmetool_default_ecdsa_curve }} diff --git a/roles/x509/acmetool/base/templates/systemd-override.conf.j2 b/roles/x509/acmetool/base/templates/systemd-override.conf.j2 new file mode 100644 index 00000000..5de58bdd --- /dev/null +++ b/roles/x509/acmetool/base/templates/systemd-override.conf.j2 @@ -0,0 +1,19 @@ +{% if acmetool_challenge_webroot_path is defined %} +[Unit] +# In case the webroot path is hosted by a filesystem that is +# using a systemd automount unit the ReadWritePaths= below does +# prevent the unit from being loaded when the filesystem is +# not yet mounted. +RequiresMountsFor={{ acmetool_challenge_webroot_path }} + +{% endif %} +[Service] +{% if acmetool_http_proxy is defined %} +Environment=http_proxy={{ acmetool_http_proxy }} +{% endif %} +{% if acmetool_https_proxy is defined %} +Environment=https_proxy={{ acmetool_https_proxy }} +{% endif %} +{% if acmetool_challenge_webroot_path is defined %} +ReadWritePaths={{ acmetool_challenge_webroot_path }} +{% endif %} diff --git a/roles/x509/acmetool/cert/defaults/main.yml b/roles/x509/acmetool/cert/defaults/main.yml new file mode 100644 index 00000000..ab0afaa3 --- /dev/null +++ b/roles/x509/acmetool/cert/defaults/main.yml @@ -0,0 +1,2 @@ +--- +acmetool_reconcile_disabled: false diff --git a/roles/x509/acmetool/cert/filter_plugins/acme_certs.py b/roles/x509/acmetool/cert/filter_plugins/acme_certs.py new file mode 100644 index 00000000..179f71e9 --- /dev/null +++ b/roles/x509/acmetool/cert/filter_plugins/acme_certs.py @@ -0,0 +1,24 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from functools import partial + +from ansible import errors + + +def acme_cert_nonexistent(data, hostnames): + try: + return [hostnames[i] for i, d in enumerate(data) if d['stat']['exists'] == False] + except Exception as e: + raise errors.AnsibleFilterError("acme_cert_nonexistent(): %s" % str(e)) + + +class FilterModule(object): + + ''' acme certificate filters ''' + filter_map = { + 'acme_cert_nonexistent': acme_cert_nonexistent, + } + + def filters(self): + return self.filter_map diff --git a/roles/x509/acmetool/cert/handlers/main.yml b/roles/x509/acmetool/cert/handlers/main.yml new file mode 100644 index 00000000..08892c18 --- /dev/null +++ b/roles/x509/acmetool/cert/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: reconcile acmetool + when: not acmetool_reconcile_disabled + ansible.builtin.systemd: + name: acmetool.service + state: started diff --git a/roles/x509/acmetool/cert/tasks/main.yml b/roles/x509/acmetool/cert/tasks/main.yml new file mode 100644 index 00000000..e97aab84 --- /dev/null +++ b/roles/x509/acmetool/cert/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: add acmetool desired file + vars: + acmetool_cert_satisfy: + satisfy: + names: "{{ acmetool_cert_hostnames | default([acmetool_cert_name]) }}" + ansible.builtin.copy: + content: "{{ acmetool_cert_config | default({}) | combine(acmetool_cert_satisfy) | to_nice_yaml }}" + dest: "/var/lib/acme/desired/{{ acmetool_cert_name }}" + notify: reconcile acmetool -- cgit v1.2.3