From 5756978238ad7b7f2fe8dc46d511cfbd5245c0c3 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Wed, 16 Aug 2023 23:38:07 +0200 Subject: uacme roles almost done --- roles/nginx/vhost/templates/generic.conf.j2 | 8 +- roles/x509/selfsigned/cert/prepare/tasks/main.yml | 2 +- roles/x509/uacme/base/defaults/main.yml | 2 + roles/x509/uacme/base/tasks/main.yml | 11 ++- roles/x509/uacme/base/tasks/selfsigned.yml | 47 ------------ .../uacme/base/templates/uacme-reconcile.sh.j2 | 32 ++++++++ roles/x509/uacme/cert/finalize/defaults/main.yml | 3 + roles/x509/uacme/cert/finalize/tasks/main.yml | 5 ++ roles/x509/uacme/cert/meta/main.yml | 4 + roles/x509/uacme/cert/prepare/defaults/main.yml | 15 ++++ roles/x509/uacme/cert/prepare/tasks/main.yml | 88 ++++++++++++++++++++++ 11 files changed, 161 insertions(+), 56 deletions(-) delete mode 100644 roles/x509/uacme/base/tasks/selfsigned.yml create mode 100644 roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 create mode 100644 roles/x509/uacme/cert/finalize/defaults/main.yml create mode 100644 roles/x509/uacme/cert/finalize/tasks/main.yml create mode 100644 roles/x509/uacme/cert/meta/main.yml create mode 100644 roles/x509/uacme/cert/prepare/defaults/main.yml create mode 100644 roles/x509/uacme/cert/prepare/tasks/main.yml (limited to 'roles') diff --git a/roles/nginx/vhost/templates/generic.conf.j2 b/roles/nginx/vhost/templates/generic.conf.j2 index 64569a5e..dae84a2f 100644 --- a/roles/nginx/vhost/templates/generic.conf.j2 +++ b/roles/nginx/vhost/templates/generic.conf.j2 @@ -13,8 +13,8 @@ server { {% endif %} {% if 'tls' in nginx_vhost %} -{% if nginx_vhost.tls.certificate_provider == 'acmetool' %} - include snippets/acmetool.conf; +{% if nginx_vhost.tls.certificate_provider == 'acmetool' or nginx_vhost.tls.certificate_provider == 'uacme' %} + include snippets/{{ nginx_vhost.tls.certificate_provider }}.conf; {% endif %} location / { @@ -36,8 +36,8 @@ server { {% endif %} {% endif %} -{% if nginx_vhost.tls.certificate_provider == 'acmetool' %} - include snippets/acmetool.conf; +{% if nginx_vhost.tls.certificate_provider == 'acmetool' or nginx_vhost.tls.certificate_provider == 'uacme' %} + include snippets/{{ nginx_vhost.tls.certificate_provider }}.conf; {% endif %} include snippets/tls{% if 'variant' in nginx_vhost.tls %}-{{ nginx_vhost.tls.variant }}{% endif %}.conf; ssl_certificate {{ x509_certificate_path_fullchain }}; diff --git a/roles/x509/selfsigned/cert/prepare/tasks/main.yml b/roles/x509/selfsigned/cert/prepare/tasks/main.yml index c089d420..1af6ef5e 100644 --- a/roles/x509/selfsigned/cert/prepare/tasks/main.yml +++ b/roles/x509/selfsigned/cert/prepare/tasks/main.yml @@ -25,7 +25,7 @@ - name: generate csr for selfsigned certificate community.crypto.openssl_csr: path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-csr.pem" - mode: "{{ selfsigned_cert_config.cert.mode | default('0600') }}" + mode: "{{ selfsigned_cert_config.cert.mode | default('0644') }}" owner: "{{ selfsigned_cert_config.cert.owner | default(omit) }}" group: "{{ selfsigned_cert_config.cert.group | default(omit) }}" privatekey_path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem" diff --git a/roles/x509/uacme/base/defaults/main.yml b/roles/x509/uacme/base/defaults/main.yml index 50ac8019..264bc2d9 100644 --- a/roles/x509/uacme/base/defaults/main.yml +++ b/roles/x509/uacme/base/defaults/main.yml @@ -4,3 +4,5 @@ uacme_directory_server: "{{ acme_directory_server }}" ### this defaults to '/var/run/acme/acme-challenge' # uacme_challenge_webroot_path: "/path/to/acme-challenge" + +# uacme_eab: :base64() diff --git a/roles/x509/uacme/base/tasks/main.yml b/roles/x509/uacme/base/tasks/main.yml index 3d1c8404..9d67e0e7 100644 --- a/roles/x509/uacme/base/tasks/main.yml +++ b/roles/x509/uacme/base/tasks/main.yml @@ -7,7 +7,7 @@ state: present - name: create acme account key - command: "uacme -c /var/lib/uacme.d -a '{{ uacme_directory_server }}' -y new '{{ uacme_account_email }}'" + command: "uacme -c /var/lib/uacme.d -a '{{ uacme_directory_server }}' -y{% if uacme_eab is defined %} -e {{ uacme_eab }}{% endif %} new '{{ uacme_account_email }}'" args: creates: /var/lib/uacme.d/private/key.pem @@ -44,7 +44,10 @@ alias {{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}/; } -- name: generate selfsigned interim certificate - include_tasks: selfsigned.yml +- name: install reconcile script + template: + src: uacme-reconcile.sh.j2 + dest: /usr/local/bin/uacme-reconcile.sh + mode: 0755 -## TODO: add global automatic refresher? +## TODO: add systemd units for automatic refreshing diff --git a/roles/x509/uacme/base/tasks/selfsigned.yml b/roles/x509/uacme/base/tasks/selfsigned.yml deleted file mode 100644 index fff77d42..00000000 --- a/roles/x509/uacme/base/tasks/selfsigned.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -- name: create directories for selfsigned interim certificate - loop: - - path: private/.self-signed - mode: "0700" - - path: .self-signed - mode: "0755" - loop_control: - label: "{{ item.path }}" - file: - path: "/var/lib/uacme.d/{{ item.path }}" - state: directory - mode: "{{ item.mode }}" - -- name: generate private key for selfsigned interim certificate - openssl_privatekey: - path: /var/lib/uacme.d/private/.self-signed/key.pem - mode: 0600 - -- name: generate csr for selfsigned interim certificate - community.crypto.openssl_csr_pipe: - privatekey_path: /var/lib/uacme.d/private/.self-signed/key.pem - common_name: "{{ ansible_fqdn }}" - register: selfsigned_interim_cert_req - changed_when: false - -### 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 - command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_10sago - changed_when: false - -- name: get remote date-time now - command: date -u '+%Y%m%d%H%M%SZ' - register: remote_datetime_now - changed_when: false - -- name: generate selfsigned interim certificate - community.crypto.x509_certificate: - path: /var/lib/uacme.d/.self-signed/cert.pem - privatekey_path: /var/lib/uacme.d/private/.self-signed/key.pem - csr_content: "{{ selfsigned_interim_cert_req.csr }}" - provider: selfsigned - ## make sure the certificate is not valid anymore to force uacme to create a new cert - selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" - selfsigned_not_after: "{{ remote_datetime_now.stdout }}" diff --git a/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 new file mode 100644 index 00000000..73a7f4a3 --- /dev/null +++ b/roles/x509/uacme/base/templates/uacme-reconcile.sh.j2 @@ -0,0 +1,32 @@ +#!/bin/bash + +declare -a csr_files +if [ -n "$1" ]; then + csr_files+=("/var/lib/uacme.d/$1/$1.csr") +else + readarray -d '' csr_files < <(find /var/lib/uacme.d -name "*.csr" -print0) +fi + +export UACME_CHALLENGE_PATH="{{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}" + +failed=0 +for csr_file in "${csr_files[@]}"; do + id=$(basename -s .csr "$csr_file") + uacme -c /var/lib/uacme.d -a "{{ uacme_directory_server }}" -h /usr/share/uacme/uacme.sh -n issue "$csr_file" + case $? in + 0) + echo "$id successfully (re)issued." + awk '{if(length($0) > 0) print} /-----END CERTIFICATE-----/ { exit }' "/var/lib/uacme.d/$id/$id-cert.pem" > "/var/lib/uacme.d/$id/crt.pem" + awk '(show==1) {if(length($0) > 0) print} /-----END CERTIFICATE-----/ { show=1 }' "/var/lib/uacme.d/$id/$id-cert.pem" > "/var/lib/uacme.d/$id/chain.pem" + ## TODO: reload services + ;; + 1) + echo "$id not updated." + ;; + *) + failed=1 + ;; + esac +done + +exit $failed diff --git a/roles/x509/uacme/cert/finalize/defaults/main.yml b/roles/x509/uacme/cert/finalize/defaults/main.yml new file mode 100644 index 00000000..611dc6fc --- /dev/null +++ b/roles/x509/uacme/cert/finalize/defaults/main.yml @@ -0,0 +1,3 @@ +--- +uacme_cert_hostnames: "{{ x509_certificate_hostnames }}" +uacme_cert_name: "{{ x509_certificate_name | default(uacme_cert_hostnames[0]) }}" diff --git a/roles/x509/uacme/cert/finalize/tasks/main.yml b/roles/x509/uacme/cert/finalize/tasks/main.yml new file mode 100644 index 00000000..6578c418 --- /dev/null +++ b/roles/x509/uacme/cert/finalize/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- name: running uacme issue command + command: "/usr/local/bin/uacme-reconcile.sh '{{ uacme_cert_name }}'" + register: uacme_reconcile + changed_when: "'not updated.' not in uacme_reconcile.stdout" diff --git a/roles/x509/uacme/cert/meta/main.yml b/roles/x509/uacme/cert/meta/main.yml new file mode 100644 index 00000000..5106342c --- /dev/null +++ b/roles/x509/uacme/cert/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - role: x509/uacme/cert/prepare + - role: x509/uacme/cert/finalize diff --git a/roles/x509/uacme/cert/prepare/defaults/main.yml b/roles/x509/uacme/cert/prepare/defaults/main.yml new file mode 100644 index 00000000..b15c1e44 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/defaults/main.yml @@ -0,0 +1,15 @@ +--- +uacme_cert_hostnames: "{{ x509_certificate_hostnames }}" +uacme_cert_name: "{{ x509_certificate_name | default(uacme_cert_hostnames[0]) }}" + +# uacme_cert_config: +# key: +# mode: "0640" +# owner: root +# group: www-data +# type: RSA +# size: 4096 +# cert: +# mode: "0644" +# owner: root +# group: www-data diff --git a/roles/x509/uacme/cert/prepare/tasks/main.yml b/roles/x509/uacme/cert/prepare/tasks/main.yml new file mode 100644 index 00000000..06b9f146 --- /dev/null +++ b/roles/x509/uacme/cert/prepare/tasks/main.yml @@ -0,0 +1,88 @@ +--- +- name: create directory for uacme-controlled certificate + file: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}" + state: directory + +- name: generate key for uacme-controlled certificate + openssl_privatekey: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + mode: "{{ uacme_cert_config.key.mode | default('0600') }}" + owner: "{{ uacme_cert_config.key.owner | default(omit) }}" + group: "{{ uacme_cert_config.key.group | default(omit) }}" + type: "{{ uacme_cert_config.key.type | default(omit) }}" + size: "{{ uacme_cert_config.key.size | default(omit) }}" + notify: "{{ x509_notify_on_change | default(omit) }}" + +- name: generate csr for uacme-controlled certificate + community.crypto.openssl_csr: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}.csr" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + privatekey_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + common_name: "{{ uacme_cert_hostnames[0] }}" + subject_alt_name: "{{ ['DNS:'] | product(uacme_cert_hostnames) | map('join') | list }}" + subject_alt_name_critical: yes + use_common_name_for_san: no + +- name: test if uacme-controlled certificate already exists + stat: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" + register: uacme_cert_file + +- name: generate selfsigned interim certificate + when: not uacme_cert_file.stat.exists + block: + ### 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 + command: date -d '10 seconds ago' -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_10sago + changed_when: false + + - name: get remote date-time now + command: date -u '+%Y%m%d%H%M%SZ' + register: remote_datetime_now + changed_when: false + + - name: generate selfsigned interim certificate + community.crypto.x509_certificate: + path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + privatekey_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + csr_path: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}.csr" + provider: selfsigned + ## make sure the certificate is not valid anymore to force uacme to create a new cert + selfsigned_not_before: "{{ remote_datetime_10sago.stdout }}" + selfsigned_not_after: "{{ remote_datetime_now.stdout }}" + return_content: yes + register: uacme_cert_selfsigned + notify: "{{ x509_notify_on_change | default(omit) }}" + + - name: make sure cert-only file exists + copy: + content: "{{ uacme_cert_selfsigned.certificate }}" + dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/crt.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + notify: "{{ x509_notify_on_change | default(omit) }}" + + - name: make sure the chain file exists + copy: + content: "" + dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/chain.pem" + mode: "{{ uacme_cert_config.cert.mode | default('0644') }}" + owner: "{{ uacme_cert_config.cert.owner | default(omit) }}" + group: "{{ uacme_cert_config.cert.group | default(omit) }}" + notify: "{{ x509_notify_on_change | default(omit) }}" + +- name: export paths to certificate files + set_fact: + x509_certificate_path_key: "/var/lib/uacme.d/{{ uacme_cert_name }}/key.pem" + x509_certificate_path_cert: "/var/lib/uacme.d/{{ uacme_cert_name }}/crt.pem" + x509_certificate_path_chain: "/var/lib/uacme.d/{{ uacme_cert_name }}/chain.pem" + x509_certificate_path_fullchain: "/var/lib/uacme.d/{{ uacme_cert_name }}/{{ uacme_cert_name }}-cert.pem" -- cgit v1.2.3