From 5b08c3fb96e54e0ae8ae1d650658b27dcdfd78de Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Tue, 8 Aug 2023 00:42:56 +0200 Subject: make acmetool cert role more generic (WIP - needs more testing) --- chaos-at-home/ch-http-proxy.yml | 4 ++ chaos-at-home/ch-imap-proxy.yml | 11 ++++-- dan/k8s-emc.yml | 3 +- dan/sk-testvm.yml | 7 +++- .../collabora/code/templates/nginx-vhost.conf.j2 | 4 +- roles/apps/coturn/templates/nginx-vhost.conf.j2 | 4 +- .../etherpad-lite/templates/nginx-vhost.conf.j2 | 4 +- roles/gitolite/http/templates/nginx-vhost.conf.j2 | 4 +- roles/nginx/vhost/tasks/acme.yml | 44 ---------------------- roles/nginx/vhost/tasks/main.yml | 19 +++++++++- roles/nginx/vhost/templates/generic.conf.j2 | 4 +- roles/x509/acmetool/cert/defaults/main.yml | 2 - .../acmetool/cert/filter_plugins/acme_certs.py | 24 ------------ .../x509/acmetool/cert/finalize/defaults/main.yml | 2 + .../x509/acmetool/cert/finalize/handlers/main.yml | 6 +++ roles/x509/acmetool/cert/finalize/tasks/main.yml | 10 +++++ roles/x509/acmetool/cert/handlers/main.yml | 6 --- roles/x509/acmetool/cert/meta/main.yml | 3 ++ .../cert/prepare/filter_plugins/acme_certs.py | 24 ++++++++++++ roles/x509/acmetool/cert/prepare/tasks/main.yml | 41 ++++++++++++++++++++ roles/x509/acmetool/cert/tasks/main.yml | 10 ----- 21 files changed, 134 insertions(+), 102 deletions(-) delete mode 100644 roles/nginx/vhost/tasks/acme.yml delete mode 100644 roles/x509/acmetool/cert/defaults/main.yml delete mode 100644 roles/x509/acmetool/cert/filter_plugins/acme_certs.py create mode 100644 roles/x509/acmetool/cert/finalize/defaults/main.yml create mode 100644 roles/x509/acmetool/cert/finalize/handlers/main.yml create mode 100644 roles/x509/acmetool/cert/finalize/tasks/main.yml delete mode 100644 roles/x509/acmetool/cert/handlers/main.yml create mode 100644 roles/x509/acmetool/cert/meta/main.yml create mode 100644 roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py create mode 100644 roles/x509/acmetool/cert/prepare/tasks/main.yml delete mode 100644 roles/x509/acmetool/cert/tasks/main.yml diff --git a/chaos-at-home/ch-http-proxy.yml b/chaos-at-home/ch-http-proxy.yml index 0376fcd5..67e3521a 100644 --- a/chaos-at-home/ch-http-proxy.yml +++ b/chaos-at-home/ch-http-proxy.yml @@ -6,6 +6,10 @@ - role: core/base - role: core/sshd/base - role: core/zsh + +- name: Payload Setup + hosts: ch-http-proxy + roles: - role: apt-repo/spreadspace - role: x509/acmetool/base - role: nginx/base diff --git a/chaos-at-home/ch-imap-proxy.yml b/chaos-at-home/ch-imap-proxy.yml index 936140bc..1c05f28b 100644 --- a/chaos-at-home/ch-imap-proxy.yml +++ b/chaos-at-home/ch-imap-proxy.yml @@ -6,10 +6,15 @@ - role: core/base - role: core/sshd/base - role: core/zsh + +- name: Payload Setup + hosts: ch-imap-proxy + roles: - role: apt-repo/spreadspace - role: x509/acmetool/base - role: x509/acmetool/cert - acmetool_cert_name: "imap.chaos-at-home.org" + acmetool_cert_hostnames: + - "imap.chaos-at-home.org" acmetool_cert_config: request: challenge: @@ -25,8 +30,8 @@ dest: /etc/stunnel/imap.conf content: | pid = /var/run/stunnel-imap.pid - cert = /var/lib/acme/live/imap.chaos-at-home.org/fullchain - key = /var/lib/acme/live/imap.chaos-at-home.org/privkey + cert = {{ x509_certificate_path_fullchain }} + key = {{ x509_certificate_path_key }} [imap] client = yes diff --git a/dan/k8s-emc.yml b/dan/k8s-emc.yml index 86fea6c5..b8358fee 100644 --- a/dan/k8s-emc.yml +++ b/dan/k8s-emc.yml @@ -16,7 +16,8 @@ roles: - role: x509/acmetool/base - role: x509/acmetool/cert - acmetool_cert_name: "{{ host_name }}.{{ host_domain }}" + acmetool_cert_hostnames: + - "{{ host_name }}.{{ host_domain }}" - role: storage/lvm/volume lvm_volume: vg: storage diff --git a/dan/sk-testvm.yml b/dan/sk-testvm.yml index e349a3c3..93e20776 100644 --- a/dan/sk-testvm.yml +++ b/dan/sk-testvm.yml @@ -7,5 +7,10 @@ - role: core/sshd/base - role: core/zsh - role: core/ntp - - role: x509/uacme/base + +- name: Payload Setup + hosts: sk-testvm + roles: +# - role: x509/acmetool/base +# - role: x509/uacme/base - role: nginx/base diff --git a/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 b/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 index d56d77a0..04358976 100644 --- a/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 +++ b/roles/apps/collabora/code/templates/nginx-vhost.conf.j2 @@ -17,8 +17,8 @@ server { include snippets/acmetool.conf; include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ item.value.hostname }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ item.value.hostname }}/privkey; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; include snippets/hsts.conf; diff --git a/roles/apps/coturn/templates/nginx-vhost.conf.j2 b/roles/apps/coturn/templates/nginx-vhost.conf.j2 index 0639fbe1..577c33b4 100644 --- a/roles/apps/coturn/templates/nginx-vhost.conf.j2 +++ b/roles/apps/coturn/templates/nginx-vhost.conf.j2 @@ -17,8 +17,8 @@ server { include snippets/acmetool.conf; include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ coturn_hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ coturn_hostnames[0] }}/privkey; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; include snippets/hsts.conf; location / { diff --git a/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 b/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 index b59701fc..0ac9d0f0 100644 --- a/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 +++ b/roles/apps/etherpad-lite/templates/nginx-vhost.conf.j2 @@ -17,8 +17,8 @@ server { include snippets/acmetool.conf; include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ item.value.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ item.value.hostnames[0] }}/privkey; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; include snippets/hsts.conf; location / { diff --git a/roles/gitolite/http/templates/nginx-vhost.conf.j2 b/roles/gitolite/http/templates/nginx-vhost.conf.j2 index add7a719..3386d956 100644 --- a/roles/gitolite/http/templates/nginx-vhost.conf.j2 +++ b/roles/gitolite/http/templates/nginx-vhost.conf.j2 @@ -23,8 +23,8 @@ server { include snippets/acmetool.conf; include snippets/tls.conf; - ssl_certificate /var/lib/acme/live/{{ gitolite_instances[gitolite_instance].http.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ gitolite_instances[gitolite_instance].http.hostnames[0] }}/privkey; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; include snippets/hsts.conf; location = / { diff --git a/roles/nginx/vhost/tasks/acme.yml b/roles/nginx/vhost/tasks/acme.yml deleted file mode 100644 index 8a6cddb7..00000000 --- a/roles/nginx/vhost/tasks/acme.yml +++ /dev/null @@ -1,44 +0,0 @@ ---- -- name: check if acme certs already exist - loop: "{{ nginx_vhost.hostnames }}" - loop_control: - loop_var: acme_hostname - stat: - path: "/var/lib/acme/live/{{ acme_hostname }}" - register: acme_cert_stat - -- name: set acmecert_missing_hostnames variable - set_fact: - acmecert_missing_hostnames: "{{ acme_cert_stat.results | acme_cert_nonexistent(nginx_vhost.hostnames) }}" - -- name: link nonexistent hostnames to self-signed interim cert - when: acmecert_missing_hostnames | length > 0 - block: - - name: get id of existing selfsigned interim certificate - command: cat /var/lib/acme/.selfsigned-interim-cert - changed_when: false - check_mode: false - register: selfsigned_interim_cert_id - - - name: set selfsigned_interim_cert_id variable - set_fact: - selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" - - - name: link to snakeoil cert for nonexistent hostnames - loop: "{{ acmecert_missing_hostnames }}" - loop_control: - loop_var: acme_missing_hostname - file: - src: "../certs/{{ selfsigned_interim_cert_id }}" - dest: "/var/lib/acme/live/{{ acme_missing_hostname }}" - state: link - -- name: make sure nginx config has been (re)loaded - meta: flush_handlers - -- name: get certificate using acmetool - import_role: - name: x509/acmetool/cert - vars: - acmetool_cert_name: "{{ nginx_vhost.name }}" - acmetool_cert_hostnames: "{{ nginx_vhost.hostnames }}" diff --git a/roles/nginx/vhost/tasks/main.yml b/roles/nginx/vhost/tasks/main.yml index 1b5e3392..424c86a0 100644 --- a/roles/nginx/vhost/tasks/main.yml +++ b/roles/nginx/vhost/tasks/main.yml @@ -1,4 +1,12 @@ --- +- name: ensure certificate exists (fake it, until you make it) + when: "'acme' in nginx_vhost and nginx_vhost.acme" + import_role: + name: x509/acmetool/cert/prepare + vars: + acmetool_cert_name: "{{ nginx_vhost.name }}" + acmetool_cert_hostnames: "{{ nginx_vhost.hostnames }}" + - name: install nginx configs from template when: "'template' in nginx_vhost" template: @@ -24,4 +32,13 @@ - name: generate acme certificate when: "'acme' in nginx_vhost and nginx_vhost.acme" - include_tasks: acme.yml + block: + - name: make sure nginx config has been (re)loaded + meta: flush_handlers + + - name: actually request the certificate + import_role: + name: x509/acmetool/cert/finalize + vars: + acmetool_cert_name: "{{ nginx_vhost.name }}" + acmetool_cert_hostnames: "{{ nginx_vhost.hostnames }}" diff --git a/roles/nginx/vhost/templates/generic.conf.j2 b/roles/nginx/vhost/templates/generic.conf.j2 index 08bf7a60..5c7576e7 100644 --- a/roles/nginx/vhost/templates/generic.conf.j2 +++ b/roles/nginx/vhost/templates/generic.conf.j2 @@ -18,8 +18,8 @@ server { include snippets/acmetool.conf; include snippets/tls{% if 'tls_variant' in nginx_vhost %}-{{ nginx_vhost.tls_variant }}{% endif %}.conf; - ssl_certificate /var/lib/acme/live/{{ nginx_vhost.hostnames[0] }}/fullchain; - ssl_certificate_key /var/lib/acme/live/{{ nginx_vhost.hostnames[0] }}/privkey; + ssl_certificate {{ x509_certificate_path_fullchain }}; + ssl_certificate_key {{ x509_certificate_path_key }}; include snippets/hsts.conf; {% endif %} diff --git a/roles/x509/acmetool/cert/defaults/main.yml b/roles/x509/acmetool/cert/defaults/main.yml deleted file mode 100644 index ab0afaa3..00000000 --- a/roles/x509/acmetool/cert/defaults/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -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 deleted file mode 100644 index 179f71e9..00000000 --- a/roles/x509/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/x509/acmetool/cert/finalize/defaults/main.yml b/roles/x509/acmetool/cert/finalize/defaults/main.yml new file mode 100644 index 00000000..ab0afaa3 --- /dev/null +++ b/roles/x509/acmetool/cert/finalize/defaults/main.yml @@ -0,0 +1,2 @@ +--- +acmetool_reconcile_disabled: false diff --git a/roles/x509/acmetool/cert/finalize/handlers/main.yml b/roles/x509/acmetool/cert/finalize/handlers/main.yml new file mode 100644 index 00000000..a7fc43ed --- /dev/null +++ b/roles/x509/acmetool/cert/finalize/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: reconcile acmetool + when: not acmetool_reconcile_disabled + systemd: + name: acmetool.service + state: started diff --git a/roles/x509/acmetool/cert/finalize/tasks/main.yml b/roles/x509/acmetool/cert/finalize/tasks/main.yml new file mode 100644 index 00000000..91bf5157 --- /dev/null +++ b/roles/x509/acmetool/cert/finalize/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: add acmetool desired file + vars: + acmetool_cert_satisfy: + satisfy: + names: "{{ acmetool_cert_hostnames }}" + copy: + content: "{{ acmetool_cert_config | default({}) | combine(acmetool_cert_satisfy) | to_nice_yaml }}" + dest: "/var/lib/acme/desired/{{ acmetool_cert_name | default(acmetool_cert_hostnames[0]) }}" + notify: reconcile acmetool diff --git a/roles/x509/acmetool/cert/handlers/main.yml b/roles/x509/acmetool/cert/handlers/main.yml deleted file mode 100644 index a7fc43ed..00000000 --- a/roles/x509/acmetool/cert/handlers/main.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -- name: reconcile acmetool - when: not acmetool_reconcile_disabled - systemd: - name: acmetool.service - state: started diff --git a/roles/x509/acmetool/cert/meta/main.yml b/roles/x509/acmetool/cert/meta/main.yml new file mode 100644 index 00000000..8e6ac88d --- /dev/null +++ b/roles/x509/acmetool/cert/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: x509/acmetool/cert/prepare + - role: x509/acmetool/cert/finalize diff --git a/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py b/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py new file mode 100644 index 00000000..179f71e9 --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/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/prepare/tasks/main.yml b/roles/x509/acmetool/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..1f7dc724 --- /dev/null +++ b/roles/x509/acmetool/cert/prepare/tasks/main.yml @@ -0,0 +1,41 @@ +--- +- name: check if acme certs already exist + loop: "{{ acmetool_cert_hostnames }}" + loop_control: + loop_var: acme_hostname + stat: + path: "/var/lib/acme/live/{{ acme_hostname }}" + register: acme_cert_stat + +- name: set acmecert_missing_hostnames variable + set_fact: + acmecert_missing_hostnames: "{{ acme_cert_stat.results | acme_cert_nonexistent(acmetool_cert_hostnames) }}" + +- name: link nonexistent hostnames to self-signed interim cert + when: acmecert_missing_hostnames | length > 0 + block: + - name: get id of existing selfsigned interim certificate + command: cat /var/lib/acme/.selfsigned-interim-cert + changed_when: false + check_mode: false + register: selfsigned_interim_cert_id + + - name: set selfsigned_interim_cert_id variable + set_fact: + selfsigned_interim_cert_id: "{{ selfsigned_interim_cert_id.stdout }}" + + - name: link to snakeoil cert for nonexistent hostnames + loop: "{{ acmecert_missing_hostnames }}" + loop_control: + loop_var: acme_missing_hostname + file: + src: "../certs/{{ selfsigned_interim_cert_id }}" + dest: "/var/lib/acme/live/{{ acme_missing_hostname }}" + state: link + +- name: export paths to certificate files + set_fact: + x509_certificate_path_key: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/privkey" + x509_certificate_path_fullchain: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/fullchain" + x509_certificate_path_cert: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/cert" + x509_certificate_path_chain: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/chain" diff --git a/roles/x509/acmetool/cert/tasks/main.yml b/roles/x509/acmetool/cert/tasks/main.yml deleted file mode 100644 index 09980dad..00000000 --- a/roles/x509/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]) }}" - 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