From bb1f7f53408f1119042b45530157db585a076615 Mon Sep 17 00:00:00 2001 From: Andrea Dell'Amico Date: Sat, 24 Jun 2023 15:55:07 +0200 Subject: [PATCH] The link to the fake certificate is not removed. --- defaults/main.yml | 30 ++--- meta/main.yml | 19 +-- tasks/acmetool_deb.yml | 22 +-- tasks/acmetool_rh.yml | 56 +++++--- tasks/main.yml | 294 +++++++++++++++++++++++++---------------- 5 files changed, 253 insertions(+), 168 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index 64a57a3..76abd62 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,8 +1,8 @@ --- -letsencrypt_acme_install: False +letsencrypt_acme_install: false letsencrypt_acme_sh_install: '{{ letsencrypt_acme_install }}' -letsencrypt_acme_sh_git_install: True -letsencrypt_update_acme_distribution: True +letsencrypt_acme_sh_git_install: true +letsencrypt_update_acme_distribution: true letsencrypt_acme_sh_git_url: https://github.com/acmesh-official/acme.sh.git letsencrypt_acme_sh_default_ca: 'letsencrypt' letsencrypt_acme_user: acme @@ -17,23 +17,21 @@ letsencrypt_acme_sh_certificates_install_dir: '{{ ansible_fqdn }}' letsencrypt_acme_sh_certificates_install_base_path: '{{ letsencrypt_acme_sh_user_home }}/live' letsencrypt_acme_sh_certificates_install_path: '{{ letsencrypt_acme_sh_certificates_install_base_path }}/{{ letsencrypt_acme_sh_certificates_install_dir }}' letsencrypt_acme_sh_log_dir: /var/log/acme -letsencrypt_acme_sh_install_cron: False -letsencrypt_acme_sh_log_enabled: True -letsencrypt_acme_sh_auto_upgrade: False +letsencrypt_acme_sh_install_cron: false +letsencrypt_acme_sh_log_enabled: true +letsencrypt_acme_sh_auto_upgrade: false letsencrypt_acme_sh_install_options: '--install' -letsencrypt_acme_sh_test_request: False -letsencrypt_acme_sh_use_syslog: True +letsencrypt_acme_sh_test_request: false +letsencrypt_acme_sh_use_syslog: true letsencrypt_acme_sh_syslog_level: 6 # We only support the PowerDNS API. Adding other ones should be straightforward -letsencrypt_acme_sh_use_dns_provider: False +letsencrypt_acme_sh_use_dns_provider: false letsencrypt_acme_sh_dns_provider_type: dns_pdns letsencrypt_acme_sh_dns_api_url: 'http://localhost:8081' letsencrypt_acme_sh_dns_api_provider_id: localhost # Use a vault variable for this one letsencrypt_acme_sh_dns_api_token: XXXXXXX - - letsencrypt_acme_sh_command: acme.sh # The data directory is created by the acme.sh install letsencrypt_acme_sh_dirs: @@ -47,14 +45,13 @@ letsencrypt_acme_certs_dir: '{{ letsencrypt_acme_sh_certificates_install_path }} # The various services maintainers need to put the reconfigure/restart scripts there letsencrypt_acme_services_scripts_dir: /usr/lib/acme/hooks letsencrypt_acme_sh_services_scripts_dir: '{{ letsencrypt_acme_services_scripts_dir }}' - -letsencrypt_acme_sh_explicitly_install_certs: True - +letsencrypt_acme_sh_explicitly_install_certs: true +letsencrypt_force_cert_request: false # ECC is better, but most old distributions fail on them -letsencrypt_acme_sh_use_ecc: False +letsencrypt_acme_sh_use_ecc: false letsencrypt_acme_sh_ecc_key_lenght: ec-384 letsencrypt_acme_sh_rsa_key_lenght: 4096 -letsencrypt_acme_sh_ocsp_must_staple: False +letsencrypt_acme_sh_ocsp_must_staple: false # Default: ISRG Root X1 letsencrypt_acme_sh_specific_root_ca: '--preferred-chain "ISRG Root X1"' letsencrypt_acme_email: sysadmin@example.com @@ -82,4 +79,3 @@ letsencrypt_acme_sh_domains_install: cert_file: '{{ letsencrypt_acme_sh_certificates_install_path }}/cert' key_file: '{{ letsencrypt_acme_sh_certificates_install_path }}/privkey' fullchain_file: '{{ letsencrypt_acme_sh_certificates_install_path }}/fullchain' - diff --git a/meta/main.yml b/meta/main.yml index 4a9c39f..9a4262c 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,25 +1,26 @@ galaxy_info: author: Andrea Dell'Amico - description: Systems Architect + namespace: adellam + role_name: letsencrypt_acme_sh_client + description: Role to installs and configure the acme.sh Letsencrypt client company: ISTI-CNR - - issue_tracker_url: https://redmine-s2i2s.isti.cnr.it/projects/provisioning - license: EUPL 1.2+ - - min_ansible_version: 2.8 - + min_ansible_version: "2.9" # To view available platforms and versions (or releases), visit: # https://galaxy.ansible.com/api/v1/platforms/ # platforms: - name: Ubuntu versions: + - xenial - bionic + - focal + - jammy - name: EL versions: - - 7 - - 8 + - "7" + - "8" + - "9" galaxy_tags: - letsencrypt diff --git a/tasks/acmetool_deb.yml b/tasks/acmetool_deb.yml index 967d0b0..a521ec5 100644 --- a/tasks/acmetool_deb.yml +++ b/tasks/acmetool_deb.yml @@ -1,10 +1,16 @@ --- -- block: - - name: Install the socat utility, needed when using the http protocols to request the certificates - apt: pkg=socat state=present cache_valid_time=1800 +- name: Actions on Debian-like distributions + tags: ['letsencrypt', 'letsencrypt_acme_sh'] + block: + - name: Install the socat utility, needed when using the http protocols to request the certificates + ansible.builtin.apt: + pkg: socat + state: present + cache_valid_time: 1800 - - name: Install the git client if we are installing using git - apt: pkg=git state=present cache_valid_time=1800 - when: letsencrypt_acme_sh_git_install - - tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + - name: Install the git client if we are installing using git + ansible.builtin.apt: + pkg: git + state: present + cache_valid_time: 1800 + when: letsencrypt_acme_sh_git_install diff --git a/tasks/acmetool_rh.yml b/tasks/acmetool_rh.yml index 5abb9f3..0fa219c 100644 --- a/tasks/acmetool_rh.yml +++ b/tasks/acmetool_rh.yml @@ -1,24 +1,38 @@ -- block: - - name: Install the socat utility, needed when using the http protocols to request the certificates - yum: pkg=socat state=present +- name: Packages in EL derivatives + tags: ['letsencrypt', 'letsencrypt_acme_sh'] + block: + - name: Install the socat utility, needed when using the http protocols to request the certificates + ansible.builtin.yum: + pkg: socat + state: present - - name: Install the git client if we are installing using git - yum: pkg=git state=present - when: letsencrypt_acme_sh_git_install + - name: Install the git client if we are installing using git + ansible.builtin.yum: + pkg: git + state: present + when: letsencrypt_acme_sh_git_install - - name: Activate the firewalld rule for the http, if we require certificates using the http protocol - firewalld: service=http zone={{ firewalld_default_zone }} permanent=True state=enabled immediate=True - with_items: '{{ letsencrypt_acme_sh_domains }}' - when: - - item.standalone is defined - - firewalld_enabled is defined and firewalld_enabled - - letsencrypt_firewalld_http_enabled_on_default_zone + - name: Activate the firewalld rule for the http, if we require certificates using the http protocol + ansible.posix.firewalld: + service: http + zone: "{{ firewalld_default_zone }}" + permanent: true + state: enabled + immediate: true + with_items: '{{ letsencrypt_acme_sh_domains }}' + when: + - item.standalone is defined + - firewalld_enabled is defined and firewalld_enabled + - letsencrypt_firewalld_http_enabled_on_default_zone - - name: Custom firewalld rule for http - firewalld: service={{ item.service}} zone={{ item.zone }} permanent={{ item.permanent | default(True) }} state={{ item.state }} immediate=True - with_items: '{{ letsencrypt_firewalld_services }}' - when: - - firewalld_enabled is defined and firewalld_enabled - - not letsencrypt_firewalld_http_enabled_on_default_zone - - tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + - name: Custom firewalld rule for http + ansible.posix.firewalld: + service: "{{ item.service}}" + zone: "{{ item.zone }}" + permanent: "{{ item.permanent | default(true) }}" + state: "{{ item.state }}" + immediate: true + loop: '{{ letsencrypt_firewalld_services }}' + when: + - firewalld_enabled is defined and firewalld_enabled + - not letsencrypt_firewalld_http_enabled_on_default_zone diff --git a/tasks/main.yml b/tasks/main.yml index 7599960..80cbef1 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,135 +1,203 @@ --- -- import_tasks: acmetool_deb.yml +- name: Import the deb tasks + ansible.builtin.import_tasks: acmetool_deb.yml when: ansible_distribution_file_variety == "Debian" -- import_tasks: acmetool_rh.yml +- name: Import the RH and derivatives + ansible.builtin.import_tasks: acmetool_rh.yml when: ansible_distribution_file_variety == "RedHat" -- block: - - name: Create the letsencrypt acme user - user: name={{ letsencrypt_acme_sh_user }} home={{ letsencrypt_acme_sh_user_home }} createhome=no shell=/usr/sbin/nologin system=yes - tags: [ 'letsencrypt', 'letsencrypt_user' ] - - - name: Create the letsencrypt acme home, if it does not exist already. In a separate step because it could be already there. - file: dest={{ letsencrypt_acme_sh_user_home }} owner={{ letsencrypt_acme_sh_user }} group={{ letsencrypt_acme_sh_user }} state=directory recurse=yes - - - name: Create a directory where to put the cron job and hooks logs - file: dest={{ letsencrypt_acme_sh_log_dir }} state=directory owner={{ letsencrypt_acme_sh_user }} group={{ letsencrypt_acme_sh_user }} mode=0750 - - - name: Install the acme.sh environment variables file - template: src=acme_sh_request_env.j2 dest=/etc/default/acme_sh_request_env owner=root group=root mode=0444 - register: acme_sh_issue - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_env' ] - - - name: Install the script that initializes the acme.sh environment - copy: src=acme-sh-install dest=/usr/local/bin/acme-sh-install owner=root group=acme mode=0750 - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - - - name: Install a script that issues the certificates - template: - src: acme-sh-request-cert.sh.j2 - dest: /usr/local/bin/acme-sh-request-cert - owner: root - group: acme - mode: 0750 - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - - - name: Install a script that installs the issued certificates - copy: src=acme-sh-install-certs dest=/usr/local/bin/acme-sh-install-certs owner=root group=acme mode=0750 - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - - - name: Install the script that will run the services hooks when a certificate is installed - template: src=acme-services-hook.j2 dest=/usr/local/bin/acme-services-hook owner=root group=acme mode=0750 - - - name: Install the scripts that will be run as a cron job - template: - src: '{{ item }}.sh.j2' - dest: '/usr/local/bin/{{ item }}' - owner: root - group: acme - mode: 0750 - loop: - - acme-sh-cron-script - - acme-sh-cron-command - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - - - name: Remove the cron job under spool if it exists - cron: - name: "Letsencrypt certificate renewal" - day: '{{ letsencrypt_acme_cron_day_of_month }}' - hour: '{{ letsencrypt_acme_cron_hour }}' - minute: '{{ letsencrypt_acme_cron_minute }}' - job: "/usr/local/bin/acme-sh-cron-script > {{ letsencrypt_acme_sh_log_dir }}/acme-cron.log 2>&1" - state: absent - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - - - name: Install a daily cron job to renew the certificates when needed. It runs as root - cron: - name: "Letsencrypt certificate renewal" - cron_file: letsencrypt_renew_certificates - user: root - day: '{{ letsencrypt_acme_cron_day_of_month }}' - hour: '{{ letsencrypt_acme_cron_hour }}' - minute: '{{ letsencrypt_acme_cron_minute }}' - job: "/usr/local/bin/acme-sh-cron-script > {{ letsencrypt_acme_sh_log_dir }}/acme-cron.log 2>&1" - tags: [ 'letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts' ] - +- name: Prepare the acme.sh environment when: letsencrypt_acme_sh_install | bool - tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + tags: ['letsencrypt', 'letsencrypt_acme_sh'] + block: + - name: Create the letsencrypt acme user + ansible.builtin.user: + name: "{{ letsencrypt_acme_sh_user }}" + home: "{{ letsencrypt_acme_sh_user_home }}" + createhome: false + shell: /usr/sbin/nologin + system: true + tags: ['letsencrypt', 'letsencrypt_user'] -- block: - - name: Download the acme.sh distribution - git: repo={{ letsencrypt_acme_sh_git_url }} dest={{ letsencrypt_acme_git_dest_dir }} recursive=yes update=yes - when: letsencrypt_update_acme_distribution + - name: Create the letsencrypt acme home, if it does not exist already. In a separate step because it could be already there. + ansible.builtin.file: + dest: "{{ letsencrypt_acme_sh_user_home }}" + owner: "{{ letsencrypt_acme_sh_user }}" + group: "{{ letsencrypt_acme_sh_user }}" + state: directory + mode: 0755 + recurse: true - - name: Create the letsencrypt acme.sh directory tree - file: dest={{ item }} state=directory mode=0755 - with_items: '{{ letsencrypt_acme_sh_dirs }}' + - name: Create a directory where to put the cron job and hooks logs + ansible.builtin.file: + dest: "{{ letsencrypt_acme_sh_log_dir }}" + state: directory + owner: "{{ letsencrypt_acme_sh_user }}" + group: "{{ letsencrypt_acme_sh_user }}" + mode: 0750 - - name: Run the installation command for acme.sh - shell: /usr/local/bin/acme-sh-install - args: - creates: '{{ letsencrypt_acme_sh_user_home }}/bin/acme.sh' + - name: Install the acme.sh environment variables file + ansible.builtin.template: + src: acme_sh_request_env.j2 + dest: /etc/default/acme_sh_request_env + owner: root + group: root + mode: 0444 + register: acme_sh_issue + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_env', 'letsencrypt_req_cert'] - - name: Create the letsencrypt acme.sh account configuration - template: src=account.conf.j2 dest={{ letsencrypt_acme_sh_base_data_dir }}/data/account.conf mode=0640 - tags: [ 'letsencrypt', 'letsencrypt_account_conf', 'letsencrypt_acme_sh' ] + - name: Install the script that initializes the acme.sh environment + ansible.builtin.copy: + src: acme-sh-install + dest: /usr/local/bin/acme-sh-install + owner: root + group: "{{ letsencrypt_acme_user }}" + mode: 0750 + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] - become: True + - name: Install a script that issues the certificates + ansible.builtin.template: + src: acme-sh-request-cert.sh.j2 + dest: /usr/local/bin/acme-sh-request-cert + owner: root + group: "{{ letsencrypt_acme_user }}" + mode: 0750 + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] + + - name: Install a script that installs the issued certificates + ansible.builtin.copy: + src: acme-sh-install-certs + dest: /usr/local/bin/acme-sh-install-certs + owner: root + group: "{{ letsencrypt_acme_user }}" + mode: 0750 + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] + + - name: Install the script that will run the services hooks when a certificate is installed + ansible.builtin.template: + src: acme-services-hook.j2 + dest: /usr/local/bin/acme-services-hook + owner: root + group: "{{ letsencrypt_acme_user }}" + mode: 0750 + + - name: Install the scripts that will be run as a cron job + ansible.builtin.template: + src: '{{ item }}.sh.j2' + dest: '/usr/local/bin/{{ item }}' + owner: root + group: acme + mode: 0750 + loop: + - acme-sh-cron-script + - acme-sh-cron-command + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] + + - name: Remove the cron job under spool if it exists + ansible.builtin.cron: + name: "Letsencrypt certificate renewal" + day: '{{ letsencrypt_acme_cron_day_of_month }}' + hour: '{{ letsencrypt_acme_cron_hour }}' + minute: '{{ letsencrypt_acme_cron_minute }}' + job: "/usr/local/bin/acme-sh-cron-script > {{ letsencrypt_acme_sh_log_dir }}/acme-cron.log 2>&1" + state: absent + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] + + - name: Install a daily cron job to renew the certificates when needed. It runs as root + ansible.builtin.cron: + name: "Letsencrypt certificate renewal" + cron_file: letsencrypt_renew_certificates + user: root + day: '{{ letsencrypt_acme_cron_day_of_month }}' + hour: '{{ letsencrypt_acme_cron_hour }}' + minute: '{{ letsencrypt_acme_cron_minute }}' + job: "/usr/local/bin/acme-sh-cron-script > {{ letsencrypt_acme_sh_log_dir }}/acme-cron.log 2>&1" + tags: ['letsencrypt', 'letsencrypt_cron', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_scripts'] + +- name: Acme.sh distribution + become: true become_user: '{{ letsencrypt_acme_sh_user }}' when: letsencrypt_acme_sh_install | bool - tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + tags: ['letsencrypt', 'letsencrypt_acme_sh'] + block: + - name: Download the acme.sh distribution + ansible.builtin.git: + repo: "{{ letsencrypt_acme_sh_git_url }}" + dest: "{{ letsencrypt_acme_git_dest_dir }}" + recursive: true + update: true + when: letsencrypt_update_acme_distribution -- block: - - name: Remove the ok_certificate_issued file when the env file has been changed so that we can force a new request - file: dest={{ letsencrypt_acme_sh_base_data_dir }}/data/ok_certificate_issued state=absent - when: acme_sh_issue is changed + - name: Create the letsencrypt acme.sh directory tree + ansible.builtin.file: + dest: "{{ item }}" + state: directory + mode: 0755 + with_items: '{{ letsencrypt_acme_sh_dirs }}' - - name: Request the certificates. - shell: /usr/local/bin/acme-sh-request-cert - args: - creates: '{{ letsencrypt_acme_sh_base_data_dir }}/data/ok_certificate_issued' - register: acme_sh_certificate_issued + - name: Run the installation command for acme.sh + ansible.builtin.command: /usr/local/bin/acme-sh-install + args: + creates: '{{ letsencrypt_acme_sh_user_home }}/bin/acme.sh' - - name: Create the certificates installation directory - file: dest={{ letsencrypt_acme_sh_certificates_install_path }} state=directory owner=root group=root mode=0755 + - name: Create the letsencrypt acme.sh account configuration + ansible.builtin.template: + src: account.conf.j2 + dest: "{{ letsencrypt_acme_sh_base_data_dir }}/data/account.conf" + owner: root + group: "{{ letsencrypt_acme_user }}" + mode: 0640 + tags: ['letsencrypt', 'letsencrypt_account_conf', 'letsencrypt_acme_sh'] - - name: Install the certificates - shell: /usr/local/bin/acme-sh-install-certs - when: - - letsencrypt_acme_sh_explicitly_install_certs | bool - - acme_sh_certificate_issued is defined - - acme_sh_certificate_issued is changed +- name: Certificates management + when: letsencrypt_acme_sh_install | bool + tags: ['letsencrypt', 'letsencrypt_acme_sh', 'letsencrypt_req_cert'] + block: + - name: Remove the ok_certificate_issued file when the env file has been changed so that we can force a new request + ansible.builtin.file: + dest: "{{ letsencrypt_acme_sh_base_data_dir }}/data/ok_certificate_issued" + state: absent + when: (acme_sh_issue is changed) or letsencrypt_force_cert_request - - name: Fix the http port in the configuration. Needed when we renew using the http protocol and we are behind a web server - lineinfile: + - name: Request the certificates. + ansible.builtin.command: /usr/local/bin/acme-sh-request-cert + args: + creates: '{{ letsencrypt_acme_sh_base_data_dir }}/data/ok_certificate_issued' + register: acme_sh_certificate_issued + + - name: Check if the certificates install path is a link + ansible.builtin.stat: + path: "{{ letsencrypt_acme_sh_certificates_install_path }}" + register: cert_install_path + + - name: Remove the certificates install path if it is a link + ansible.builtin.file: + dest: "{{ letsencrypt_acme_sh_certificates_install_path }}" + state: absent + when: cert_install_path.stat.islink is defined and cert_install_path.stat.islink + + - name: Create the certificates installation directory + ansible.builtin.file: + dest: "{{ letsencrypt_acme_sh_certificates_install_path }}" + state: directory + owner: root + group: root + mode: 0755 + + - name: Install the certificates + ansible.builtin.command: /usr/local/bin/acme-sh-install-certs + when: + - letsencrypt_acme_sh_explicitly_install_certs | bool + - acme_sh_certificate_issued is defined + - acme_sh_certificate_issued is changed + + - name: Fix the http port in the configuration. Needed when we renew using the http protocol and we are behind a web server + ansible.builtin.lineinfile: path: '{{ letsencrypt_acme_sh_certs_data_path }}/{{ letsencrypt_acme_sh_certs_data_prefix }}.conf' - create: no + create: false state: present regexp: "^Le_HTTPPort=" line: "Le_HTTPPort='{{ letsencrypt_acme_standalone_port }}'" - when: not letsencrypt_acme_sh_use_dns_provider | bool - tags: [ 'letsencrypt', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_http_port' ] - - when: letsencrypt_acme_sh_install | bool - tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + when: not letsencrypt_acme_sh_use_dns_provider | bool + tags: ['letsencrypt', 'letsencrypt_acme_sh', 'letsencrypt_acme_sh_http_port', 'letsencrypt_req_cert']