diff --git a/meta/requirements.yml b/meta/requirements.yml index dfad1cf..a2e9da0 100644 --- a/meta/requirements.yml +++ b/meta/requirements.yml @@ -1,2 +1,3 @@ --- +- src: bviktor.file_changed - src: bviktor.setfcontext diff --git a/readme.md b/readme.md index 017bda2..cb8078d 100644 --- a/readme.md +++ b/readme.md @@ -47,7 +47,13 @@ This role obtains HTTPS certificates using the ACME protocol from Let's Encrypt, ## Return Values -N/A +| Key | Type | Example | Description | +|---|---|---|---| +| `acme.changed` | boolean | `true` if `acme.cert_file` has been updated, `false` if not. | +| `acme.cert_file` | string | `/etc/foo.com/foo.com.cer` | Path to deployed certificate. | +| `acme.key_file` | string | `/etc/foo.com/foo.com.key` | Path to deployed private key. | +| `acme.ca_file` | string | `/etc/foo.com/ca.cer` | Path to deployed CA certificate. | +| `acme.fullchain_file` | string | `/etc/foo.com/fullchain.cer` | Path to deployed full certificate chain (CA + own). | ## Support diff --git a/requirements.yml b/requirements.yml index dfad1cf..a2e9da0 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,2 +1,3 @@ --- +- src: bviktor.file_changed - src: bviktor.setfcontext diff --git a/tasks/acme.yml b/tasks/acme.yml index 4da74ff..98ec0e2 100644 --- a/tasks/acme.yml +++ b/tasks/acme.yml @@ -1,13 +1,21 @@ --- +- name: Create cert deployment directory + file: + path: "{{ acme_domain_dir }}" + state: directory + owner: root + group: root + mode: '0700' + - name: Check for existing cert stat: - path: "/root/.acme.sh/{{ domain }}/{{ domain }}.cer" + path: "{{ deploy_cert_file }}" register: current_file # rc = 1 both when expires, and when doesn't exist / not a valid cert - name: Check current cert expiry command: - cmd: "openssl x509 -checkend {{ eff_min_days | int * 86400 }} -noout -in /root/.acme.sh/{{ domain }}/{{ domain }}.cer" + cmd: "openssl x509 -checkend {{ eff_min_days | int * 86400 }} -noout -in {{ deploy_cert_file }}" changed_when: false failed_when: current_expiry.rc != 0 and current_expiry.stderr | length register: current_expiry @@ -15,29 +23,61 @@ - name: Check if current cert is wildcard shell: - cmd: "openssl x509 -ext subjectAltName -noout -in /root/.acme.sh/{{ domain }}/{{ domain }}.cer | grep '*.{{ domain }}'" + cmd: "openssl x509 -ext subjectAltName -noout -in {{ deploy_cert_file }} | grep '*.{{ domain }}'" changed_when: false failed_when: false register: current_wildcard when: current_file.stat.exists -- name: Obtain initial cert # noqa no-changed-when +- include_role: + name: bviktor.file_changed + vars: + path: "{{ deploy_cert_file }}" + mode: before + +- name: Obtain initial cert command: - cmd: "{{ acme_sh_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --keylength 4096 --issue --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force -d {{ domain }}{% if eff_wildcard %} -d *.{{ domain }}{% endif %}" + cmd: "{{ acme_install_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --ecc --issue --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force --domain {{ domain }}{% if eff_wildcard %} --domain *.{{ domain }}{% endif %}" environment: "{{ credential }}" + changed_when: false when: not current_file.stat.exists or (eff_wildcard and current_wildcard.rc != 0) -- name: Renew existing cert # noqa no-changed-when +- name: Renew existing cert command: - cmd: "{{ acme_sh_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --keylength 4096 --renew --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force -d {{ domain }}{% if eff_wildcard %} -d *.{{ domain }}{% endif %}" + cmd: "{{ acme_install_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --ecc --renew --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force --domain {{ domain }}{% if eff_wildcard %} --domain *.{{ domain }}{% endif %}" environment: "{{ credential }}" + changed_when: false when: current_file.stat.exists and current_expiry.rc != 0 +# Unlike --issue, --renew updates the files in the target automatically, but it won't hurt to copy twice +- name: Deploy cert + command: + cmd: "{{ acme_install_dir }}/acme.sh --install-cert --ecc --domain {{ domain }}{% if eff_wildcard %} --domain *.{{ domain }}{% endif %} --cert-file {{ deploy_cert_file }} --key-file {{ deploy_key_file }} --ca-file {{ deploy_ca_file }} --fullchain-file {{ deploy_fullchain_file }}" + changed_when: false + +- include_role: + name: bviktor.file_changed + vars: + path: "{{ deploy_cert_file }}" + mode: after + +- debug: + msg: "{% if file_changed.changed %}The certificate has been updated.{% else %}No change has been made to the certificate.{% endif %}" + changed_when: file_changed.changed + - name: Check obtained cert command: - cmd: "openssl x509 -issuer -subject -dates -ext subjectAltName -noout -in /root/.acme.sh/{{ domain }}/{{ domain }}.cer" + cmd: "openssl x509 -issuer -subject -dates -ext subjectAltName -noout -in {{ deploy_cert_file }}" changed_when: false register: openssl_info - debug: msg: "{{ openssl_info.stdout_lines }}" + +- set_fact: + acme: + cert_file: "{{ deploy_cert_file }}" + key_file: "{{ deploy_key_file }}" + ca_file: "{{ deploy_ca_file }}" + fullchain_file: "{{ deploy_fullchain_file }}" + changed: "{{ file_changed.changed | bool }}" diff --git a/tasks/cron.yml b/tasks/cron.yml index e3c58ac..48af660 100644 --- a/tasks/cron.yml +++ b/tasks/cron.yml @@ -10,7 +10,7 @@ systemd: name: "cron{% if ansible_os_family == 'RedHat' %}d{% endif %}.service" state: started - enabled: yes + enabled: true - name: Deploy acme.sh cronjob template: diff --git a/tasks/deps.yml b/tasks/deps.yml index 14d4b69..730d42a 100644 --- a/tasks/deps.yml +++ b/tasks/deps.yml @@ -2,7 +2,7 @@ # Fix dumb apt - name: Update apt cache apt: - update_cache: yes + update_cache: true when: ansible_os_family == 'Debian' # TODO On EL, cURL is installed by default. On top of that, on AlmaLinux 9, curl @@ -27,5 +27,5 @@ - name: Obtain acme.sh sources git: repo: https://github.com/acmesh-official/acme.sh.git - dest: "{{ acme_sh_dir }}" + dest: "{{ acme_install_dir }}" register: installed_acme_deps diff --git a/tasks/main.yml b/tasks/main.yml index b54c3a2..cbcf665 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -7,9 +7,9 @@ - include_role: name: bviktor.setfcontext vars: - path: '/root/.acme.sh' + path: "{{ acme_deploy_dir }}" type: 'cert_t' - pattern: "/root/.acme.sh(/.*)?" + pattern: "{{ acme_deploy_dir }}(/.*)?" when: ansible_os_family == 'RedHat' - include_tasks: cron.yml diff --git a/templates/acme.j2 b/templates/acme.j2 index ae140b9..6dc2d20 100644 --- a/templates/acme.j2 +++ b/templates/acme.j2 @@ -1,4 +1,4 @@ -{{ 60 | random }} 07 1 * * root {{ acme_sh_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --keylength 4096 --renew --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force -d {{ domain }}{% if eff_wildcard %} -d *.{{ domain }}{% endif %} >> /var/log/letsencrypt 2>&1 +{{ 60 | random }} 07 1 * * root {{ acme_install_dir }}/acme.sh {% if eff_staging %}--staging{% else %}--server letsencrypt{% endif %} --ecc --renew --dns dns_{{ provider }} --dnssleep {{ eff_sleep }} --force --domain {{ domain }}{% if eff_wildcard %} --domain *.{{ domain }}{% endif %} >> /var/log/letsencrypt 2>&1 # TODO make params for this #1 08 1 * * root /sbin/nginx -s reload diff --git a/tests/main.yml b/tests/main.yml index 6d4ba77..03b4055 100644 --- a/tests/main.yml +++ b/tests/main.yml @@ -2,7 +2,16 @@ - hosts: 127.0.0.1 tasks: - include_tasks: issuance.yml + - include_tasks: print.yml + - include_tasks: idempotency1.yml + - include_tasks: print.yml + - include_tasks: renewal.yml + - include_tasks: print.yml + - include_tasks: wildcard.yml + - include_tasks: print.yml + - include_tasks: idempotency2.yml + - include_tasks: print.yml diff --git a/tests/print.yml b/tests/print.yml new file mode 100644 index 0000000..7f831d6 --- /dev/null +++ b/tests/print.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "{{ acme }}" diff --git a/vars/main.yml b/vars/main.yml index 82b20dd..7e49c45 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,5 +1,13 @@ --- -acme_sh_dir: '/opt/acme.sh' +acme_install_dir: '/opt/acme.sh' +acme_deploy_dir: '/etc/acme' +acme_domain_dir: "{{ acme_deploy_dir }}/{{ domain }}" + +deploy_cert_file: "{{ acme_domain_dir }}/{{ domain }}.cer" +deploy_key_file: "{{ acme_domain_dir }}/{{ domain }}.key" +deploy_ca_file: "{{ acme_domain_dir }}/ca.cer" +deploy_fullchain_file: "{{ acme_domain_dir }}/fullchain.cer" + eff_staging: "{{ staging | default(false) | bool }}" eff_wildcard: "{{ wildcard | default(false) | bool }}" eff_cronjob: "{{ cronjob | default(false) | bool }}"