From ba21e09bd6ce9a76218401ba929466edb4ce7943 Mon Sep 17 00:00:00 2001 From: Andrea Dell'Amico Date: Sun, 17 May 2020 16:39:11 +0200 Subject: [PATCH] Letsencrypt acme-sh-client has its own repository. --- README.md | 38 +++++---- defaults/main.yml | 75 ++++++++++++++++- files/acme-sh-cron-command | 17 ++++ files/acme-sh-cron-script | 28 +++++++ files/acme-sh-install | 12 +++ files/acme-sh-install-certs | 23 ++++++ files/acme-sh-request-cert | 55 +++++++++++++ handlers/main.yml | 7 +- meta/main.yml | 60 +++----------- tasks/acmetool_deb.yml | 19 +++++ tasks/acmetool_rh.yml | 15 ++++ tasks/main.yml | 133 ++++++++++++++++++++++++++++++- templates/account.conf.j2 | 21 +++++ templates/acme-services-hook.j2 | 15 ++++ templates/acme_sh_request_env.j2 | 65 +++++++++++++++ 15 files changed, 517 insertions(+), 66 deletions(-) create mode 100644 files/acme-sh-cron-command create mode 100644 files/acme-sh-cron-script create mode 100644 files/acme-sh-install create mode 100644 files/acme-sh-install-certs create mode 100644 files/acme-sh-request-cert create mode 100644 tasks/acmetool_deb.yml create mode 100644 tasks/acmetool_rh.yml create mode 100644 templates/account.conf.j2 create mode 100644 templates/acme-services-hook.j2 create mode 100644 templates/acme_sh_request_env.j2 diff --git a/README.md b/README.md index 225dd44..ed4e1a3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,46 @@ Role Name ========= -A brief description of the role goes here. +A role that installs the acme.sh Letsencrypt.org client Requirements ------------ -Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. +Git must be available as a package Role Variables -------------- -A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. +Here are listed the most important defaults. See defaults/main.yml for the complete set of variables. + +``` yaml +letsencrypt_acme_install: False +letsencrypt_acme_sh_git_install: True +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_email: sysadmin@example.com +``` + +* Set the following one to `80` if there is no web server that acts as reverse proxy. + +``` yaml +letsencrypt_acme_standalone_port: 4402 +letsencrypt_acme_sh_domains: + - { domain: '{{ ansible_fqdn }}', standalone: True } +``` Dependencies ------------ -A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. - -Example Playbook ----------------- - -Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: - - - hosts: servers - roles: - - { role: username.rolename, x: 42 } +None License ------- -BSD +EUPL-1.2 Author Information ------------------ -An optional section for the role authors to include contact information, or a website (HTML is not allowed). +Andrea Dell'Amico, diff --git a/defaults/main.yml b/defaults/main.yml index 95d3c70..6b631e3 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,2 +1,75 @@ --- -# defaults file for ansible-role-template \ No newline at end of file +letsencrypt_acme_install: False +letsencrypt_acme_sh_install: '{{ letsencrypt_acme_install }}' +letsencrypt_acme_sh_git_install: True +letsencrypt_acme_sh_git_url: https://github.com/Neilpang/acme.sh.git +letsencrypt_acme_user: acme +letsencrypt_acme_sh_user: '{{ letsencrypt_acme_user }}' +letsencrypt_acme_user_home: /var/lib/acme +letsencrypt_acme_git_dest_dir: '{{ letsencrypt_acme_user_home }}/acme_sh_dist' +letsencrypt_acme_sh_user_home: '{{ letsencrypt_acme_user_home }}' +letsencrypt_acme_sh_base_data_dir: '{{ letsencrypt_acme_sh_user_home }}/acme_data' +letsencrypt_acme_sh_certs_data_prefix: '{{ ansible_fqdn }}' +letsencrypt_acme_sh_certs_data_path: '{{ letsencrypt_acme_sh_base_data_dir }}/certs/{{ letsencrypt_acme_sh_certs_data_prefix }}' +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_options: '--install' +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_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: + - '{{ letsencrypt_acme_sh_user_home }}/bin' + - '{{ letsencrypt_acme_sh_base_data_dir }}/certs' + - '{{ letsencrypt_acme_sh_base_data_dir }}/logs' +# - '{{ letsencrypt_acme_sh_base_data_dir }}/data' +letsencrypt_acme_sh_dest_dir: '{{ ansible_fqdn }}' +letsencrypt_acme_sh_certs_dir: '{{ letsencrypt_acme_sh_base_data_dir }}/certs/{{ letsencrypt_acme_sh_dest_dir }}' +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 + +# ECC is better, but most old distributions fail on them +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_email: sysadmin@example.com +letsencrypt_acme_sh_email: '{{ letsencrypt_acme_email }}' +letsencrypt_acme_standalone_port: 4402 +letsencrypt_acme_sh_standalone_port: '{{ letsencrypt_acme_standalone_port }}' +letsencrypt_acme_cron_day_of_month: '*' +letsencrypt_acme_cron_hour: '{{ range(1, 4) | random }}' +letsencrypt_acme_cron_minute: '{{ range(0, 59) | random }}' +letsencrypt_acme_services_hook_script: /usr/local/bin/acme-services-hook + +# Use this when you want a single certificate. Even when multiple provider methods are needed +# The dns_provider and standalone options are mutually exclusive +letsencrypt_acme_sh_domains: + - { domain: '{{ ansible_fqdn }}', standalone: True } + +letsencrypt_acme_sh_domains_install: + - { domain: '{{ letsencrypt_acme_sh_certificates_install_dir }}', ecc: '{{ letsencrypt_acme_sh_use_ecc }}', 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' } + +### Stuff related to the obsolete acmetool package. Needed to cleanup systems where it was installed in the past +letsencrypt_acme_ppa_repo: 'ppa:hlandau/rhea' +letsencrypt_acme_debian_repo: 'deb http://ppa.launchpad.net/hlandau/rhea/ubuntu xenial main' diff --git a/files/acme-sh-cron-command b/files/acme-sh-cron-command new file mode 100644 index 0000000..9c4bc82 --- /dev/null +++ b/files/acme-sh-cron-command @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + . "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +if [ -f "$ACME_SH_ENV_FILE" ] ; then + . "$ACME_SH_ENV_FILE" +else + exit 1 +fi + +$ACME_SH_BIN --cron --home "$ACME_SH_BINDIR" --config-home "$ACME_SH_CONFIG_HOME" > "$ACME_SH_CRON_LOG_FILE" 2>&1 + +exit $? diff --git a/files/acme-sh-cron-script b/files/acme-sh-cron-script new file mode 100644 index 0000000..d96d2be --- /dev/null +++ b/files/acme-sh-cron-script @@ -0,0 +1,28 @@ +#!/bin/bash + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + . "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +if [ -f "$ACME_SH_ENV_FILE" ] ; then + . "$ACME_SH_ENV_FILE" +else + exit 1 +fi + +chown -R acme:acme "$ACME_SH_HOME" +if [ "$ACME_SH_HTTP_BIND_PORT" -eq 80 ] && [ "$ACME_SH_USE_DNS_PROVIDER" == "False" ] ; then + /usr/local/bin/acme-sh-cron-command + chown -R acme:acme "$ACME_SH_HOME" +else + sudo -u acme -s /bin/bash /usr/local/bin/acme-sh-cron-command +fi + +if [ "$ACME_SH_INSTALL_CERTS" == "True" ] ; then + $ACME_SH_BIN $ACME_SH_INSTALL_CERT_REQUEST > "$ACME_SH_INSTALL_LOG_FILE" 2>&1 + chown -R acme:acme "$ACME_SH_HOME" +fi + +exit $? diff --git a/files/acme-sh-install b/files/acme-sh-install new file mode 100644 index 0000000..e212f7f --- /dev/null +++ b/files/acme-sh-install @@ -0,0 +1,12 @@ +#!/bin/bash + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + . "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +cd "$ACME_SH_GIT_DIST_DIR" +./acme.sh $ACME_SH_INSTALL_OPTS + +exit $? diff --git a/files/acme-sh-install-certs b/files/acme-sh-install-certs new file mode 100644 index 0000000..5b1925c --- /dev/null +++ b/files/acme-sh-install-certs @@ -0,0 +1,23 @@ +#!/bin/bash + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + . "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +if [ -f "$ACME_SH_ENV_FILE" ] ; then + . "$ACME_SH_ENV_FILE" +else + exit 1 +fi + +if [ -d "$ACME_SH_HOME/keys/fakeselfsignedcert" ] && [ -d "$ACME_SH_HOME/certs/fakeselfsignedcert" ] ; then + rm -fr "$ACME_SH_HOME/keys" + rm -fr "$ACME_SH_HOME/certs" +fi + +$ACME_SH_BIN $ACME_SH_INSTALL_CERT_REQUEST > "$ACME_SH_INSTALL_LOG_FILE" 2>&1 +chown -R acme:acme "$ACME_SH_HOME" + +exit $? diff --git a/files/acme-sh-request-cert b/files/acme-sh-request-cert new file mode 100644 index 0000000..327789b --- /dev/null +++ b/files/acme-sh-request-cert @@ -0,0 +1,55 @@ +#!/bin/bash + +if [ $# -ne 1 ] ; then + ACME_SH_ENV_FILE="$1" +fi + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + . "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +if [ -n "$ACME_SH_ENV_FILE" ] && [ -f "$ACME_SH_ENV_FILE" ] ; then + . "$ACME_SH_ENV_FILE" +else + exit 1 +fi +RETVAL= + +if [ ! -f "$ACME_SH_CONFIG_HOME/ok_certificate_issued" ] && [ "$ACME_SH_USE_DNS_PROVIDER" == "False" ] ; then + # First request. Try to shut down all the services running on port 80 + if [ -x /bin/systemctl ] ; then + /bin/systemctl stop nginx >/dev/null 2>&1 + /bin/systemctl stop apache2 >/dev/null 2>&1 + /bin/systemctl stop httpd >/dev/null 2>&1 + else + service nginx stop + service apache2 stop + service httpd stop + fi + $ACME_SH_BIN $ACME_SH_FIRST_CERT_REQUEST > "$ACME_SH_ISSUE_LOG_FILE" 2>&1 + RETVAL=$? + if [ -x /bin/systemctl ] ; then + /bin/systemctl start nginx >/dev/null 2>&1 + /bin/systemctl start apache2 >/dev/null 2>&1 + /bin/systemctl start httpd >/dev/null 2>&1 + else + service nginx start + service apache2 start + service httpd start + fi +else + $ACME_SH_BIN $ACME_SH_ISSUE_CERT_REQUEST > "$ACME_SH_ISSUE_LOG_FILE" 2>&1 + RETVAL=$? +fi + +if [ $RETVAL -eq 0 ] ; then + touch "$ACME_SH_CONFIG_HOME/ok_certificate_issued" +fi + +if [ $RETVAL -eq 2 ] ; then + # There is a valid certificate already + exit 0 +fi +exit $RETVAL diff --git a/handlers/main.yml b/handlers/main.yml index 27474e0..2414d0a 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -1,2 +1,7 @@ --- -# handlers file for ansible-role-template \ No newline at end of file +- name: Install the requested certificates + shell: /usr/local/bin/acme-sh-install-certs + when: + - letsencrypt_acme_sh_explicitly_install_certs + - acme_sh_certificate_issued is changed + diff --git a/meta/main.yml b/meta/main.yml index 5cc055c..4a9c39f 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,61 +1,25 @@ galaxy_info: - author: your name - description: your description + author: Andrea Dell'Amico + description: Systems Architect company: ISTI-CNR - # If the issue tracker for your role is not on github, uncomment the - # next line and provide a value issue_tracker_url: https://redmine-s2i2s.isti.cnr.it/projects/provisioning - # Some suggested licenses: - # - BSD (default) - # - MIT - # - GPLv2 - # - GPLv3 - # - Apache - # - CC-BY license: EUPL 1.2+ min_ansible_version: 2.8 - # If this a Container Enabled role, provide the minimum Ansible Container version. - # min_ansible_container_version: - - # Optionally specify the branch Galaxy will use when accessing the GitHub - # repo for this role. During role install, if no tags are available, - # Galaxy will use this branch. During import Galaxy will access files on - # this branch. If Travis integration is configured, only notifications for this - # branch will be accepted. Otherwise, in all cases, the repo's default branch - # (usually master) will be used. - #github_branch: - - # - # Provide a list of supported platforms, and for each platform a list of versions. - # If you don't wish to enumerate all versions for a particular platform, use 'all'. # To view available platforms and versions (or releases), visit: # https://galaxy.ansible.com/api/v1/platforms/ # - # platforms: - # - name: Fedora - # versions: - # - all - # - 25 - # - name: SomePlatform - # versions: - # - all - # - 1.0 - # - 7 - # - 99.99 - - galaxy_tags: [] - # List tags for your role here, one per line. A tag is a keyword that describes - # and categorizes the role. Users find roles by searching for tags. Be sure to - # remove the '[]' above, if you add tags to this list. - # - # NOTE: A tag is limited to a single word comprised of alphanumeric characters. - # Maximum 20 tags per role. - -dependencies: [] - # List your role dependencies here, one per line. Be sure to remove the '[]' above, - # if you add dependencies to this list. + platforms: + - name: Ubuntu + versions: + - bionic + - name: EL + versions: + - 7 + - 8 + galaxy_tags: + - letsencrypt diff --git a/tasks/acmetool_deb.yml b/tasks/acmetool_deb.yml new file mode 100644 index 0000000..a064d57 --- /dev/null +++ b/tasks/acmetool_deb.yml @@ -0,0 +1,19 @@ +--- +- block: + - name: Remove the old acme package because the tool is not supported anymore. + apt: pkg=acmetool state=absent purge=yes + + - name: Remove the old letsencrypt acmetool repo on ubuntu + apt_repository: repo={{ letsencrypt_acme_ppa_repo }} state=absent update_cache=yes + + - name: Remove the old letsencrypt acmetool repo on debian + apt_repository: repo={{ letsencrypt_acme_debian_repo }} state=absent update_cache=yes + + - 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: 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' ] diff --git a/tasks/acmetool_rh.yml b/tasks/acmetool_rh.yml new file mode 100644 index 0000000..a51b060 --- /dev/null +++ b/tasks/acmetool_rh.yml @@ -0,0 +1,15 @@ +- block: + - name: Remove the old acme package because the tool is not supported anymore. + yum: pkg=acmetool state=absent + + - name: Remove the acmetool repo file + file: dest=/etc/yum.repos.d/hlandau-acmetool-epel-7.repo state=absent + + - name: Install the socat utility, needed when using the http protocols to request the certificates + 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 + + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] diff --git a/tasks/main.yml b/tasks/main.yml index 53c6cae..44ecb31 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,2 +1,133 @@ --- -# tasks file for ansible-role-template \ No newline at end of file +- import_tasks: acmetool_deb.yml + when: ansible_distribution_file_variety == "Debian" + +- import_tasks: acmetool_rh.yml + when: ansible_distribution_file_variety == "RedHat" + +- block: + - name: Remove the sudoers config needed to run the old acmetool package hooks + file: dest=/etc/sudoers.d/letsencrypt-acme state=absent + + - name: Remove the old acmetool script that requested certificates + file: dest=/usr/local/bin/acme-cert-request state=absent + + - name: Remove the old letsencrypt cron job + cron: name="Letsencrypt certificate renewal" user={{ letsencrypt_acme_user }} state=absent + + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + +- 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 + copy: src=acme-sh-request-cert 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 + copy: src={{ item }} dest=/usr/local/bin/{{ item }} owner=root group=acme mode=0750 + with_items: + - acme-sh-cron-script + - acme-sh-cron-command + 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" 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' ] + + when: letsencrypt_acme_sh_install | bool + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + +- block: + - name: Download the acme.sh distribution + git: repo={{ letsencrypt_acme_sh_git_url }} dest={{ letsencrypt_acme_git_dest_dir }} recursive=yes update=yes + + - name: Create the letsencrypt acme.sh directory tree + file: dest={{ item }} state=directory mode=0755 + with_items: '{{ letsencrypt_acme_sh_dirs }}' + + - 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: 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: Remove the daily cron job that run as acme user. + 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' ] + + become: True + become_user: '{{ letsencrypt_acme_sh_user }}' + when: letsencrypt_acme_sh_install | bool + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + +- 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: Request the certificates. As root because we must bind on port 80 + 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 + ignore_errors: True + + - name: Check if the 'live' path is a symling. It is, if acmetool was installed + stat: path={{ letsencrypt_acme_sh_certificates_install_path }} + register: is_symlink + + - name: Remove the 'live' path if it was a symlink + file: dest={{ letsencrypt_acme_sh_certificates_install_path }} state=absent + when: is_symlink.stat.islnk is defined and is_symlink.stat.islnk + + - name: Create the certificates installation directory + file: dest={{ letsencrypt_acme_sh_certificates_install_path }} state=directory owner=root group=root mode=0755 + + - 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 + ignore_errors: True + + - name: Fix the http port in the configuration. Needed when we renew using the http protocol and we are behind a web server + lineinfile: + path: '{{ letsencrypt_acme_sh_certs_data_path }}/{{ letsencrypt_acme_sh_certs_data_prefix }}.conf' + create: no + 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' ] diff --git a/templates/account.conf.j2 b/templates/account.conf.j2 new file mode 100644 index 0000000..7e455e8 --- /dev/null +++ b/templates/account.conf.j2 @@ -0,0 +1,21 @@ +{% if letsencrypt_acme_sh_log_enabled %} +LOG_FILE="{{ letsencrypt_acme_sh_base_data_dir }}/logs/cert_request.log" +LOG_LEVEL=1 +{% endif %} + +{% if letsencrypt_acme_sh_auto_upgrade %} +AUTO_UPGRADE="1" +{% endif %} +#NO_TIMESTAMP=1 + +CERT_HOME='{{ letsencrypt_acme_sh_base_data_dir }}/certs' +ACCOUNT_EMAIL='{{ letsencrypt_acme_sh_email }}' + +{% if letsencrypt_acme_sh_use_dns_provider %} +{% if letsencrypt_acme_sh_dns_provider_type == 'dns_pdns' %} +PDNS_Url="{{ letsencrypt_acme_sh_dns_api_url }}" +PDNS_ServerId="{{ letsencrypt_acme_sh_dns_api_provider_id }}" +PDNS_Token="{{ letsencrypt_acme_sh_dns_api_token }}" +PDNS_Ttl=180 +{% endif %} +{% endif %} diff --git a/templates/acme-services-hook.j2 b/templates/acme-services-hook.j2 new file mode 100644 index 0000000..d3e2435 --- /dev/null +++ b/templates/acme-services-hook.j2 @@ -0,0 +1,15 @@ +#!/bin/bash + +ACME_LETSENCRYPT_HOOKS_DIR=/usr/lib/acme/hooks + +if [ -f "/etc/default/acme_sh_request_env" ] ; then + source "/etc/default/acme_sh_request_env" +else + exit 1 +fi + +for script in "${ACME_LETSENCRYPT_HOOKS_DIR}"/* ; do + if [ -x "$script" ] ; then + $script + fi +done diff --git a/templates/acme_sh_request_env.j2 b/templates/acme_sh_request_env.j2 new file mode 100644 index 0000000..8986618 --- /dev/null +++ b/templates/acme_sh_request_env.j2 @@ -0,0 +1,65 @@ +# +# Globals +# +ACME_SH_HOME={{ letsencrypt_acme_sh_user_home }} +ACME_SH_BINDIR={{ letsencrypt_acme_sh_user_home }}/bin +ACME_SH_BIN="{{ letsencrypt_acme_sh_user_home }}/bin/acme.sh --config-home {{ letsencrypt_acme_sh_base_data_dir }}/data" +ACME_SH_CONFIG_HOME={{ letsencrypt_acme_sh_base_data_dir }}/data +ACME_SH_ENV_FILE=${ACME_SH_BINDIR}/acme.sh.env +ACME_SH_ISSUE_LOG_FILE={{ letsencrypt_acme_sh_base_data_dir }}/logs/cert_issue.log +ACME_SH_CRON_LOG_FILE={{ letsencrypt_acme_sh_base_data_dir }}/logs/cron.log +ACME_SH_INSTALL_LOG_FILE={{ letsencrypt_acme_sh_log_dir }}/cert_install.log +ACME_SH_GIT_DIST_DIR={{ letsencrypt_acme_git_dest_dir }} +ACME_LETSENCRYPT_HOOKS_DIR={{ letsencrypt_acme_services_scripts_dir }} +ACME_SH_HTTP_BIND_PORT={{ letsencrypt_acme_standalone_port }} +ACME_SH_USE_DNS_PROVIDER="{{ letsencrypt_acme_sh_use_dns_provider }}" + +ACME_SH_INSTALL_CERTS={{ letsencrypt_acme_sh_explicitly_install_certs }} + +# +# Install options +# +ACME_SH_INSTALL_OPTS="{{ letsencrypt_acme_sh_install_options }}" +{% if not letsencrypt_acme_sh_install_cron %} +ACME_SH_INSTALL_OPTS="$ACME_SH_INSTALL_OPTS --nocron" +{% endif %} +ACME_SH_INSTALL_OPTS="$ACME_SH_INSTALL_OPTS --home {{ letsencrypt_acme_sh_user_home }}/bin --config-home {{ letsencrypt_acme_sh_base_data_dir }}/data --certhome {{ letsencrypt_acme_sh_base_data_dir }}/certs --log {{ letsencrypt_acme_sh_base_data_dir }}/logs/acme.sh.log" + +# +# Certificate issue options +# +ACME_SH_ISSUE_CERT_REQUEST_OPTIONS="--issue -k {% if letsencrypt_acme_sh_use_ecc %}{{ letsencrypt_acme_sh_ecc_key_lenght }}{% else %}{{ letsencrypt_acme_sh_rsa_key_lenght }}{% endif %} --log {{ letsencrypt_acme_sh_base_data_dir }}/logs/acme.sh.log" +{% if letsencrypt_acme_sh_ocsp_must_staple %} +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST_OPTIONS --ocsp" +{% endif %} +{% if letsencrypt_acme_sh_use_syslog %} +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST_OPTIONS --syslog {{ letsencrypt_acme_sh_syslog_level }}" +{% endif %} +{% if letsencrypt_acme_sh_test_request %} +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST_OPTIONS --test" +{% endif %} + +ACME_SH_ISSUE_CERT_DOMAINS="{% for dom in letsencrypt_acme_sh_domains %} -d {{ dom.domain }} {% if dom.dns_provider is defined %} --dns {{ dom.dns_provider }} {% if dom.dns_alias_challenge is defined %} --challenge-alias {{ dom.dns_alias_challenge }} {% endif %} {% endif %} {% if dom.standalone is defined %} --standalone --httpport {{ letsencrypt_acme_standalone_port }} {% endif %} {% endfor %}" + +ACME_SH_FIRST_REQUEST_CERT_DOMAINS="{% for dom in letsencrypt_acme_sh_domains %} -d {{ dom.domain }} {% if dom.dns_provider is defined %} --dns {{ dom.dns_provider }} {% if dom.dns_alias_challenge is defined %} --challenge-alias {{ dom.dns_alias_challenge }} {% endif %} {% endif %} {% if dom.standalone is defined %} --standalone --httpport 80 {% endif %} {% endfor %}" + +# The complete command line to issue a certificate +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST_OPTIONS $ACME_SH_ISSUE_CERT_DOMAINS" + +# The complete command line to issue a certificate. The first time we have to use port 80 when not using the dns protocol +ACME_SH_FIRST_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST_OPTIONS --force $ACME_SH_FIRST_REQUEST_CERT_DOMAINS" + +# +# Certificate install options +# +ACME_SH_INSTALL_CERT_REQUEST="--install-cert" +{% if letsencrypt_acme_sh_use_ecc %} +ACME_SH_INSTALL_CERT_REQUEST="$ACME_SH_INSTALL_CERT_REQUEST --ecc" +{% endif %} +{% if letsencrypt_acme_sh_use_syslog %} +ACME_SH_INSTALL_CERT_REQUEST="$ACME_SH_INSTALL_CERT_REQUEST --syslog {{ letsencrypt_acme_sh_syslog_level }}" +{% endif %} +ACME_SH_INSTALL_CERT_DOMAINS="{% for dom in letsencrypt_acme_sh_domains_install %} -d {{ dom.domain }} --cert-file {{ dom.cert_file }} --key-file {{ dom.key_file }} --fullchain-file {{ dom.fullchain_file }} --reloadcmd {{ dom.reloadcmd | default('/usr/local/bin/acme-services-hook') }} {% endfor %}" + +# The complete command line to install a certificate. Run as root +ACME_SH_INSTALL_CERT_REQUEST="$ACME_SH_INSTALL_CERT_REQUEST $ACME_SH_INSTALL_CERT_DOMAINS"