summaryrefslogtreecommitdiff
path: root/roles/x509
diff options
context:
space:
mode:
authorChristian Pointner <equinox@spreadspace.org>2023-08-22 19:53:49 +0200
committerChristian Pointner <equinox@spreadspace.org>2023-08-22 19:53:49 +0200
commitfc5d0657bfcba53ace230ff2ada64b7fcf9b97a3 (patch)
tree350a8d401e0113bff7d78aee4d8547cddf06b8f7 /roles/x509
parentfix docker for debian bookworm+ (diff)
parentsome more cleanup for acme specific variables (diff)
Merge branch 'topic/uacme'
Diffstat (limited to 'roles/x509')
-rw-r--r--roles/x509/acmetool/cert/defaults/main.yml2
-rw-r--r--roles/x509/acmetool/cert/finalize/defaults/main.yml5
-rw-r--r--roles/x509/acmetool/cert/finalize/handlers/main.yml (renamed from roles/x509/acmetool/cert/handlers/main.yml)1
-rw-r--r--roles/x509/acmetool/cert/finalize/tasks/main.yml (renamed from roles/x509/acmetool/cert/tasks/main.yml)2
-rw-r--r--roles/x509/acmetool/cert/meta/main.yml4
-rw-r--r--roles/x509/acmetool/cert/prepare/defaults/main.yml2
-rw-r--r--roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py (renamed from roles/x509/acmetool/cert/filter_plugins/acme_certs.py)0
-rw-r--r--roles/x509/acmetool/cert/prepare/handlers/main.yml10
-rw-r--r--roles/x509/acmetool/cert/prepare/tasks/main.yml79
-rw-r--r--roles/x509/acmetool/cert/prepare/templates/reload.sh.j231
-rw-r--r--roles/x509/selfsigned/base/tasks/main.yml5
-rw-r--r--roles/x509/selfsigned/cert/finalize/tasks/main.yml2
-rw-r--r--roles/x509/selfsigned/cert/meta/main.yml4
-rw-r--r--roles/x509/selfsigned/cert/prepare/defaults/main.yml41
-rw-r--r--roles/x509/selfsigned/cert/prepare/handlers/main.yml6
-rw-r--r--roles/x509/selfsigned/cert/prepare/tasks/main.yml69
-rw-r--r--roles/x509/static/base/tasks/main.yml2
-rw-r--r--roles/x509/static/cert/finalize/tasks/main.yml2
-rw-r--r--roles/x509/static/cert/meta/main.yml4
-rw-r--r--roles/x509/static/cert/prepare/defaults/main.yml35
-rw-r--r--roles/x509/static/cert/prepare/handlers/main.yml6
-rw-r--r--roles/x509/static/cert/prepare/tasks/main.yml81
-rw-r--r--roles/x509/uacme/base/defaults/main.yml2
-rw-r--r--roles/x509/uacme/base/tasks/main.yml29
-rw-r--r--roles/x509/uacme/base/tasks/selfsigned.yml47
-rw-r--r--roles/x509/uacme/base/templates/uacme-reconcile.service.j218
-rw-r--r--roles/x509/uacme/base/templates/uacme-reconcile.sh.j232
-rw-r--r--roles/x509/uacme/base/templates/uacme-reconcile.timer.j210
-rw-r--r--roles/x509/uacme/cert/finalize/defaults/main.yml3
-rw-r--r--roles/x509/uacme/cert/finalize/tasks/main.yml5
-rw-r--r--roles/x509/uacme/cert/meta/main.yml4
-rw-r--r--roles/x509/uacme/cert/prepare/defaults/main.yml15
-rw-r--r--roles/x509/uacme/cert/prepare/handlers/main.yml10
-rw-r--r--roles/x509/uacme/cert/prepare/tasks/main.yml112
-rw-r--r--roles/x509/uacme/cert/prepare/templates/updated.sh.j233
35 files changed, 659 insertions, 54 deletions
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/finalize/defaults/main.yml b/roles/x509/acmetool/cert/finalize/defaults/main.yml
new file mode 100644
index 00000000..b9a80136
--- /dev/null
+++ b/roles/x509/acmetool/cert/finalize/defaults/main.yml
@@ -0,0 +1,5 @@
+---
+acmetool_cert_hostnames: "{{ x509_certificate_hostnames }}"
+acmetool_cert_name: "{{ x509_certificate_name | default(acmetool_cert_hostnames[0]) }}"
+
+acmetool_reconcile_disabled: false
diff --git a/roles/x509/acmetool/cert/handlers/main.yml b/roles/x509/acmetool/cert/finalize/handlers/main.yml
index a7fc43ed..02ffa598 100644
--- a/roles/x509/acmetool/cert/handlers/main.yml
+++ b/roles/x509/acmetool/cert/finalize/handlers/main.yml
@@ -2,5 +2,6 @@
- name: reconcile acmetool
when: not acmetool_reconcile_disabled
systemd:
+ daemon_reload: yes
name: acmetool.service
state: started
diff --git a/roles/x509/acmetool/cert/tasks/main.yml b/roles/x509/acmetool/cert/finalize/tasks/main.yml
index 09980dad..abb2d4cb 100644
--- a/roles/x509/acmetool/cert/tasks/main.yml
+++ b/roles/x509/acmetool/cert/finalize/tasks/main.yml
@@ -3,7 +3,7 @@
vars:
acmetool_cert_satisfy:
satisfy:
- names: "{{ acmetool_cert_hostnames | default([acmetool_cert_name]) }}"
+ 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 }}"
diff --git a/roles/x509/acmetool/cert/meta/main.yml b/roles/x509/acmetool/cert/meta/main.yml
new file mode 100644
index 00000000..472f5a8c
--- /dev/null
+++ b/roles/x509/acmetool/cert/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - role: x509/acmetool/cert/prepare
+ - role: x509/acmetool/cert/finalize
diff --git a/roles/x509/acmetool/cert/prepare/defaults/main.yml b/roles/x509/acmetool/cert/prepare/defaults/main.yml
new file mode 100644
index 00000000..d4eb7c86
--- /dev/null
+++ b/roles/x509/acmetool/cert/prepare/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+acmetool_cert_hostnames: "{{ x509_certificate_hostnames }}"
diff --git a/roles/x509/acmetool/cert/filter_plugins/acme_certs.py b/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py
index 179f71e9..179f71e9 100644
--- a/roles/x509/acmetool/cert/filter_plugins/acme_certs.py
+++ b/roles/x509/acmetool/cert/prepare/filter_plugins/acme_certs.py
diff --git a/roles/x509/acmetool/cert/prepare/handlers/main.yml b/roles/x509/acmetool/cert/prepare/handlers/main.yml
new file mode 100644
index 00000000..330bcd11
--- /dev/null
+++ b/roles/x509/acmetool/cert/prepare/handlers/main.yml
@@ -0,0 +1,10 @@
+---
+- name: reload systemd
+ systemd:
+ daemon_reload: yes
+
+- name: reload services for x509 certificates
+ loop: "{{ x509_certificate_reload_services | default([]) }}"
+ service:
+ name: "{{ item }}"
+ state: reloaded
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..2db332b8
--- /dev/null
+++ b/roles/x509/acmetool/cert/prepare/tasks/main.yml
@@ -0,0 +1,79 @@
+---
+- 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
+ notify: reload services for x509 certificates
+
+- name: export paths to certificate files
+ set_fact:
+ x509_certificate_path_key: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/privkey"
+ 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"
+ x509_certificate_path_fullchain: "/var/lib/acme/live/{{ acmetool_cert_hostnames[0] }}/fullchain"
+
+- name: setup custom renewal script
+ when: x509_certificate_renewal is defined
+ block:
+ - name: install custom hook script
+ template:
+ src: reload.sh.j2
+ dest: "/etc/acme/hooks/{{ x509_certificate_name }}"
+ mode: 0755
+
+ - name: install acmetool systemd unit snippet
+ when: "'install' in x509_certificate_renewal"
+ copy:
+ dest: "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf"
+ content: |
+ [Service]
+ {% for path in (x509_certificate_renewal.install | map(attribute='dest') | map('dirname') | unique | list) %}
+ ReadWritePaths={{ path }}
+ {% endfor %}
+ notify: reload systemd
+
+ - name: remove acmetool systemd unit snippet
+ when: "'install' not in x509_certificate_renewal"
+ file:
+ path: "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf"
+ state: absent
+ notify: reload systemd
+
+- name: remove custom renewal script
+ when: x509_certificate_renewal is not defined
+ loop:
+ - "/etc/systemd/system/acmetool.service.d/{{ x509_certificate_name }}.conf"
+ - "/etc/acme/hooks/{{ x509_certificate_name }}"
+ file:
+ path: "{{ item }}"
+ state: absent
+ notify: reload systemd
diff --git a/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2 b/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2
new file mode 100644
index 00000000..f4b8259e
--- /dev/null
+++ b/roles/x509/acmetool/cert/prepare/templates/reload.sh.j2
@@ -0,0 +1,31 @@
+#!/bin/sh
+set -e
+EVENT_NAME="$1"
+[ "$EVENT_NAME" = "live-updated" ] || exit 42
+
+MAIN_HOSTNAME="{{ acmetool_cert_hostnames[0] }}"
+
+while read name; do
+ certdir="$ACME_STATE_DIR/live/$name"
+ if [ -z "$name" -o ! -e "$certdir" ]; then
+ continue
+ fi
+ if [ "$name" != "$MAIN_HOSTNAME" ]; then
+ continue
+ fi
+{% if 'install' in x509_certificate_renewal %}
+
+{% for file in x509_certificate_renewal.install %}
+ install{% if 'mode' in file %} -m {{ file.mode }}{% endif %}{% if 'owner' in file %} -o {{ file.owner }}{% endif %}{% if 'owner' in file %} -g {{ file.group }}{% endif %} /dev/null "{{ file.dest }}.new"
+{% for src in file.src %}
+ cat "{{ hostvars[inventory_hostname]['x509_certificate_path_' + src] }}" >> "{{ file.dest }}.new"
+ mv "{{ file.dest }}.new" "{{ file.dest }}"
+{% endfor %}
+{% endfor %}
+{% endif %}
+{% if 'reload' in x509_certificate_renewal %}
+
+ {{ x509_certificate_renewal.reload | trim | indent(2) }}
+{% endif %}
+ break
+done
diff --git a/roles/x509/selfsigned/base/tasks/main.yml b/roles/x509/selfsigned/base/tasks/main.yml
new file mode 100644
index 00000000..51397d67
--- /dev/null
+++ b/roles/x509/selfsigned/base/tasks/main.yml
@@ -0,0 +1,5 @@
+---
+- name: install needed packages
+ apt:
+ name: "{{ python_basename }}-openssl"
+ state: present
diff --git a/roles/x509/selfsigned/cert/finalize/tasks/main.yml b/roles/x509/selfsigned/cert/finalize/tasks/main.yml
new file mode 100644
index 00000000..c5b6cafe
--- /dev/null
+++ b/roles/x509/selfsigned/cert/finalize/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+# nothing to do here
diff --git a/roles/x509/selfsigned/cert/meta/main.yml b/roles/x509/selfsigned/cert/meta/main.yml
new file mode 100644
index 00000000..c7a30d00
--- /dev/null
+++ b/roles/x509/selfsigned/cert/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - role: x509/selfsigned/cert/prepare
+ - role: x509/selfsigned/cert/finalize
diff --git a/roles/x509/selfsigned/cert/prepare/defaults/main.yml b/roles/x509/selfsigned/cert/prepare/defaults/main.yml
new file mode 100644
index 00000000..53dc3b06
--- /dev/null
+++ b/roles/x509/selfsigned/cert/prepare/defaults/main.yml
@@ -0,0 +1,41 @@
+---
+selfsigned_cert_hostnames: "{{ x509_certificate_hostnames }}"
+selfsigned_cert_name: "{{ x509_certificate_name | default(selfsigned_cert_hostnames[0]) }}"
+
+selfsigned_cert_base_dir: "/etc/ssl"
+
+# selfsigned_cert_config:
+# path: "{{ selfsigned_cert_base_dir }}/{{ selfsigned_cert_name }}"
+# mode: "0750"
+# owner: root
+# group: www-data
+# key:
+# mode: "0640"
+# owner: root
+# group: www-data
+# type: RSA
+# size: 4096
+# cert:
+# mode: "0644"
+# owner: root
+# group: www-data
+# country_name: "AT"
+# locality_name: "Graz"
+# organization_name: "spreadspace"
+# organizational_unit_name: "ansible"
+# state_or_province_name: "Styria"
+# basic_constraints:
+# - "CA:TRUE"
+# - "pathLenConstraint:0"
+# basic_constraints_critical: no
+# key_usage:
+# - digitalSignature
+# - keyAgreement
+# key_usage_critical: yes
+# extended_key_usage:
+# - serverAuth
+# extended_key_usage_critical: yes
+# create_subject_key_identifier: yes
+# digest: SHA256
+# not_before: +0h
+# not_after: +520w
diff --git a/roles/x509/selfsigned/cert/prepare/handlers/main.yml b/roles/x509/selfsigned/cert/prepare/handlers/main.yml
new file mode 100644
index 00000000..b169d6ca
--- /dev/null
+++ b/roles/x509/selfsigned/cert/prepare/handlers/main.yml
@@ -0,0 +1,6 @@
+---
+- name: reload services for x509 certificates
+ loop: "{{ x509_certificate_reload_services | default([]) }}"
+ service:
+ name: "{{ item }}"
+ state: reloaded
diff --git a/roles/x509/selfsigned/cert/prepare/tasks/main.yml b/roles/x509/selfsigned/cert/prepare/tasks/main.yml
new file mode 100644
index 00000000..e7a47742
--- /dev/null
+++ b/roles/x509/selfsigned/cert/prepare/tasks/main.yml
@@ -0,0 +1,69 @@
+---
+- name: compute path to selfsigned certificate directory
+ set_fact:
+ selfsigned_cert_path: "{{ selfsigned_cert_config.path | default([selfsigned_cert_base_dir, selfsigned_cert_name] | path_join) }}"
+
+- name: create directory for selfsigned certificate
+ file:
+ path: "{{ selfsigned_cert_path }}"
+ state: directory
+ mode: "{{ selfsigned_cert_config.mode | default('0700') }}"
+ owner: "{{ selfsigned_cert_config.owner | default(omit) }}"
+ group: "{{ selfsigned_cert_config.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- name: generate key for selfsigned certificate
+ openssl_privatekey:
+ path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem"
+ mode: "{{ selfsigned_cert_config.key.mode | default('0600') }}"
+ owner: "{{ selfsigned_cert_config.key.owner | default(omit) }}"
+ group: "{{ selfsigned_cert_config.key.group | default(omit) }}"
+ type: "{{ selfsigned_cert_config.key.type | default(omit) }}"
+ size: "{{ selfsigned_cert_config.key.size | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- 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('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"
+ create_subject_key_identifier: "{{ selfsigned_cert_config.cert.create_subject_key_identifier | default(omit) }}"
+ digest: "{{ selfsigned_cert_config.cert.digest | default(omit) }}"
+ common_name: "{{ selfsigned_cert_name }}"
+ subject_alt_name: "{{ ['DNS:'] | product(selfsigned_cert_hostnames) | map('join') | list }}"
+ subject_alt_name_critical: yes
+ use_common_name_for_san: no
+ country_name: "{{ selfsigned_cert_config.cert.country_name | default(omit) }}"
+ locality_name: "{{ selfsigned_cert_config.cert.locality_name | default(omit) }}"
+ organization_name: "{{ selfsigned_cert_config.cert.organization_name | default(omit) }}"
+ organizational_unit_name: "{{ selfsigned_cert_config.cert.organizational_unit_name | default(omit) }}"
+ state_or_province_name: "{{ selfsigned_cert_config.cert.state_or_province_name | default(omit) }}"
+ basic_constraints: "{{ selfsigned_cert_config.cert.basic_constraints | default(omit) }}"
+ basic_constraints_critical: "{{ selfsigned_cert_config.cert.basic_constraints_critical | default(omit) }}"
+ key_usage: "{{ selfsigned_cert_config.cert.key_usage | default(omit) }}"
+ key_usage_critical: "{{ selfsigned_cert_config.cert.key_usage_critical | default(omit) }}"
+ extended_key_usage: "{{ selfsigned_cert_config.cert.extended_key_usage | default(omit) }}"
+ extended_key_usage_critical: "{{ selfsigned_cert_config.cert.extended_key_usage_critical | default(omit) }}"
+
+- name: generate selfsigned certificate
+ community.crypto.x509_certificate:
+ path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem"
+ 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"
+ csr_path: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-csr.pem"
+ provider: selfsigned
+ selfsigned_digest: "{{ selfsigned_cert_config.cert.digest | default(omit) }}"
+ selfsigned_not_before: "{{ selfsigned_cert_config.cert.not_before | default(omit) }}"
+ selfsigned_not_after: "{{ selfsigned_cert_config.cert.not_after | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- name: export paths to certificate files
+ set_fact:
+ x509_certificate_path_key: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-key.pem"
+ x509_certificate_path_cert: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem"
+ x509_certificate_path_chain: ""
+ x509_certificate_path_fullchain: "{{ selfsigned_cert_path }}/{{ selfsigned_cert_name }}-crt.pem"
diff --git a/roles/x509/static/base/tasks/main.yml b/roles/x509/static/base/tasks/main.yml
new file mode 100644
index 00000000..c5b6cafe
--- /dev/null
+++ b/roles/x509/static/base/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+# nothing to do here
diff --git a/roles/x509/static/cert/finalize/tasks/main.yml b/roles/x509/static/cert/finalize/tasks/main.yml
new file mode 100644
index 00000000..c5b6cafe
--- /dev/null
+++ b/roles/x509/static/cert/finalize/tasks/main.yml
@@ -0,0 +1,2 @@
+---
+# nothing to do here
diff --git a/roles/x509/static/cert/meta/main.yml b/roles/x509/static/cert/meta/main.yml
new file mode 100644
index 00000000..c619208c
--- /dev/null
+++ b/roles/x509/static/cert/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+ - role: x509/static/cert/prepare
+ - role: x509/static/cert/finalize
diff --git a/roles/x509/static/cert/prepare/defaults/main.yml b/roles/x509/static/cert/prepare/defaults/main.yml
new file mode 100644
index 00000000..d632a5de
--- /dev/null
+++ b/roles/x509/static/cert/prepare/defaults/main.yml
@@ -0,0 +1,35 @@
+---
+static_cert_hostnames: "{{ x509_certificate_hostnames }}"
+static_cert_name: "{{ x509_certificate_name | default(static_cert_hostnames[0]) }}"
+
+static_cert_base_dir: "/etc/ssl"
+
+# static_cert_config:
+# path: "{{ static_cert_base_dir }}/{{ static_cert_name }}"
+# mode: "0750"
+# owner: root
+# group: www-data
+# key:
+# mode: "0640"
+# owner: root
+# group: www-data
+# content: |
+# -----BEGIN RSA PRIVATE KEY-----
+# ...
+# -----END RSA PRIVATE KEY-----
+# cert:
+# mode: "0644"
+# owner: root
+# group: www-data
+# content: |
+# -----BEGIN CERTIFICATE-----
+# ...
+# -----END CERTIFICATE-----
+# chain:
+# mode: "0644"
+# owner: root
+# group: www-data
+# content: |
+# -----BEGIN CERTIFICATE-----
+# ...
+# -----END CERTIFICATE-----
diff --git a/roles/x509/static/cert/prepare/handlers/main.yml b/roles/x509/static/cert/prepare/handlers/main.yml
new file mode 100644
index 00000000..b169d6ca
--- /dev/null
+++ b/roles/x509/static/cert/prepare/handlers/main.yml
@@ -0,0 +1,6 @@
+---
+- name: reload services for x509 certificates
+ loop: "{{ x509_certificate_reload_services | default([]) }}"
+ service:
+ name: "{{ item }}"
+ state: reloaded
diff --git a/roles/x509/static/cert/prepare/tasks/main.yml b/roles/x509/static/cert/prepare/tasks/main.yml
new file mode 100644
index 00000000..03df7542
--- /dev/null
+++ b/roles/x509/static/cert/prepare/tasks/main.yml
@@ -0,0 +1,81 @@
+---
+- name: compute path to static certificate directory
+ set_fact:
+ static_cert_path: "{{ static_cert_config.path | default([static_cert_base_dir, static_cert_name] | path_join) }}"
+
+- name: create directory for static certificate
+ file:
+ path: "{{ static_cert_path }}"
+ state: directory
+ mode: "{{ static_cert_config.mode | default('0700') }}"
+ owner: "{{ static_cert_config.owner | default(omit) }}"
+ group: "{{ static_cert_config.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- name: install key for static certificate
+ copy:
+ content: "{{ static_cert_config.key.content }}"
+ dest: "{{ static_cert_path }}/{{ static_cert_name }}-key.pem"
+ mode: "{{ static_cert_config.key.mode | default('0600') }}"
+ owner: "{{ static_cert_config.key.owner | default(omit) }}"
+ group: "{{ static_cert_config.key.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- name: install static certificate
+ copy:
+ content: "{{ static_cert_config.cert.content }}"
+ dest: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem"
+ mode: "{{ static_cert_config.cert.mode | default('0644') }}"
+ owner: "{{ static_cert_config.cert.owner | default(omit) }}"
+ group: "{{ static_cert_config.cert.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+- name: export paths to basic certificate files
+ set_fact:
+ x509_certificate_path_key: "{{ static_cert_path }}/{{ static_cert_name }}-key.pem"
+ x509_certificate_path_fullchain: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem"
+ x509_certificate_path_cert: "{{ static_cert_path }}/{{ static_cert_name }}-crt.pem"
+
+- name: install chain and fullchain for static certificate
+ when: "'chain' in static_cert_config"
+ block:
+ - name: install chain for static certificate
+ copy:
+ content: "{{ static_cert_config.chain.content }}"
+ dest: "{{ static_cert_path }}/{{ static_cert_name }}-chain.pem"
+ mode: "{{ static_cert_config.chain.mode | default('0644') }}"
+ owner: "{{ static_cert_config.chain.owner | default(omit) }}"
+ group: "{{ static_cert_config.chain.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+ - name: install fullchain for static certificate
+ copy:
+ content: |
+ {{ static_cert_config.cert.content | trim }}
+ {{ static_cert_config.chain.content }}
+ dest: "{{ static_cert_path }}/{{ static_cert_name }}-fullchain.pem"
+ mode: "{{ static_cert_config.cert.mode | default('0644') }}"
+ owner: "{{ static_cert_config.cert.owner | default(omit) }}"
+ group: "{{ static_cert_config.cert.group | default(omit) }}"
+ notify: reload services for x509 certificates
+
+ - name: export paths to additional certificate files
+ set_fact:
+ x509_certificate_path_chain: "{{ static_cert_path }}/{{ static_cert_name }}-chain.pem"
+ x509_certificate_path_fullchain: "{{ static_cert_path }}/{{ static_cert_name }}-fullchain.pem"
+
+- name: make sure chain and fullchain files are removed
+ when: "'chain' not in static_cert_config"
+ block:
+ - name: remove chain/fullchain files
+ loop:
+ - chain
+ - fullchain
+ file:
+ path: "{{ static_cert_path }}/{{ static_cert_name }}-{{ item }}.pem"
+ state: absent
+ notify: reload services for x509 certificates
+
+ - name: make sure variable that points to the chain certificate file is unset
+ set_fact:
+ x509_certificate_path_chain: ""
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: <keyid>:base64(<key>)
diff --git a/roles/x509/uacme/base/tasks/main.yml b/roles/x509/uacme/base/tasks/main.yml
index 3d1c8404..3473d541 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,28 @@
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?
+- name: install systemd unit for automatic refresh
+ loop:
+ - service
+ - timer
+ template:
+ src: "uacme-reconcile.{{ item }}.j2"
+ dest: "/etc/systemd/system/uacme-reconcile.{{ item }}"
+
+- name: create system unit snippet directory
+ file:
+ path: /etc/systemd/system/uacme-reconcile.service.d/
+ state: directory
+
+- name: make sure systemd timer for automatic refresh is enabled and started
+ systemd:
+ daemon_reload: yes
+ name: uacme-reconcile.timer
+ state: started
+ enabled: yes
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.service.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.service.j2
new file mode 100644
index 00000000..c2fe917a
--- /dev/null
+++ b/roles/x509/uacme/base/templates/uacme-reconcile.service.j2
@@ -0,0 +1,18 @@
+[Unit]
+Description=Reconcile Let's Encrypt certificates using uacme
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/uacme-reconcile.sh
+TimeoutStartSec=5min
+CapabilityBoundingSet=CAP_CHOWN CAP_NET_BIND_SERVICE
+NoNewPrivileges=yes
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=strict
+ReadWritePaths=/var/lib/uacme.d {{ uacme_challenge_webroot_path | default('/var/run/acme/acme-challenge') }}
+ProtectHome=yes
+ProtectKernelTunables=yes
+ProtectControlGroups=yes
+RestrictRealtime=yes
+RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
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..ea02841d
--- /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."
+ if [ -x "/var/lib/uacme.d/$id/updated.sh" ]; then
+ /var/lib/uacme.d/$id/updated.sh
+ fi
+ ;;
+ 1)
+ echo "$id not updated."
+ ;;
+ *)
+ failed=1
+ ;;
+ esac
+done
+
+exit $failed
diff --git a/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2 b/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2
new file mode 100644
index 00000000..6d37a162
--- /dev/null
+++ b/roles/x509/uacme/base/templates/uacme-reconcile.timer.j2
@@ -0,0 +1,10 @@
+[Unit]
+Description=Reconcile Let's Encrypt certificates using uacme
+
+[Timer]
+OnCalendar=*-*-* 00,12:00:00
+RandomizedDelaySec=1h
+Persistent=yes
+
+[Install]
+WantedBy=timers.target
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/handlers/main.yml b/roles/x509/uacme/cert/prepare/handlers/main.yml
new file mode 100644
index 00000000..330bcd11
--- /dev/null
+++ b/roles/x509/uacme/cert/prepare/handlers/main.yml
@@ -0,0 +1,10 @@
+---
+- name: reload systemd
+ systemd:
+ daemon_reload: yes
+
+- name: reload services for x509 certificates
+ loop: "{{ x509_certificate_reload_services | default([]) }}"
+ service:
+ name: "{{ item }}"
+ state: reloaded
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..a83651b3
--- /dev/null
+++ b/roles/x509/uacme/cert/prepare/tasks/main.yml
@@ -0,0 +1,112 @@
+---
+- 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: reload services for x509 certificates
+
+- 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: reload services for x509 certificates
+
+ - 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: reload services for x509 certificates
+
+ - 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: reload services for x509 certificates
+
+- 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"
+
+- name: install script to be called when new certificate is generated
+ template:
+ src: updated.sh.j2
+ dest: "/var/lib/uacme.d/{{ uacme_cert_name }}/updated.sh"
+ mode: 0755
+
+- name: install systemd unit snippet
+ when: "x509_certificate_renewal is defined and 'install' in x509_certificate_renewal"
+ copy:
+ dest: "/etc/systemd/system/uacme-reconcile.service.d/{{ x509_certificate_name }}.conf"
+ content: |
+ [Service]
+ {% for path in (x509_certificate_renewal.install | map(attribute='dest') | map('dirname') | unique | list) %}
+ ReadWritePaths={{ path }}
+ {% endfor %}
+ notify: reload systemd
+
+- name: remove systemd unit snippet
+ when: "x509_certificate_renewal is undefined or 'install' not in x509_certificate_renewal"
+ file:
+ path: "/etc/systemd/system/uacme-reconcile.service.d/{{ x509_certificate_name }}.conf"
+ state: absent
+ notify: reload systemd
diff --git a/roles/x509/uacme/cert/prepare/templates/updated.sh.j2 b/roles/x509/uacme/cert/prepare/templates/updated.sh.j2
new file mode 100644
index 00000000..275ca189
--- /dev/null
+++ b/roles/x509/uacme/cert/prepare/templates/updated.sh.j2
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+BASE_D="/var/lib/uacme.d/{{ uacme_cert_name }}"
+
+# split fullchain and fix permissions
+awk '{if(length($0) > 0) print} /-----END CERTIFICATE-----/ { exit }' "$BASE_D/{{ uacme_cert_name }}-cert.pem" > "$BASE_D/crt.pem"
+awk '(show==1) {if(length($0) > 0) print} /-----END CERTIFICATE-----/ { show=1 }' "$BASE_D/{{ uacme_cert_name }}-cert.pem" > "$BASE_D/chain.pem"
+chmod "{{ uacme_cert_config.cert.mode | default('0644') }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem
+{% if uacme_cert_config.cert.owner is defined %}
+chown "{{ uacme_cert_config.cert.owner }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem
+{% endif %}
+{% if uacme_cert_config.cert.group is defined %}
+chgrp "{{ uacme_cert_config.cert.group }}" $BASE_D/{{ uacme_cert_name }}-cert.pem $BASE_D/crt.pem $BASE_D/chain.pem
+{% endif %}
+{% if x509_certificate_renewal is defined and 'install' in x509_certificate_renewal %}
+{% for file in x509_certificate_renewal.install %}
+
+install{% if 'mode' in file %} -m {{ file.mode }}{% endif %}{% if 'owner' in file %} -o {{ file.owner }}{% endif %}{% if 'owner' in file %} -g {{ file.group }}{% endif %} /dev/null "{{ file.dest }}.new"
+{% for src in file.src %}
+cat "{{ hostvars[inventory_hostname]['x509_certificate_path_' + src] }}" >> "{{ file.dest }}.new"
+mv "{{ file.dest }}.new" "{{ file.dest }}"
+{% endfor %}
+{% endfor %}
+{% endif %}
+
+## reload services
+{% for service in (x509_certificate_reload_services | default([])) %}
+systemctl reload "{{ service }}.service"
+{% endfor %}
+{% if x509_certificate_renewal is defined and 'reload' in x509_certificate_renewal %}
+
+{{ x509_certificate_renewal.reload | trim }}
+{% endif %}