From dd2b1e16489fd7678bc4651555d7385f39316b85 Mon Sep 17 00:00:00 2001 From: Andrea Dell'Amico Date: Mon, 20 Aug 2018 19:19:56 +0200 Subject: [PATCH] letsencrypt acme.sh role. --- letsencrypt-acme-sh-client/defaults/main.yml | 65 ++++++++++++++ .../files/acme-sh-cron-command | 17 ++++ .../files/acme-sh-cron-script | 21 +++++ .../files/acme-sh-install | 12 +++ .../files/acme-sh-install-certs | 22 +++++ .../files/acme-sh-request-cert | 23 +++++ letsencrypt-acme-sh-client/handlers/main.yml | 7 ++ letsencrypt-acme-sh-client/tasks/main.yml | 90 +++++++++++++++++++ .../templates/account.conf.j2 | 21 +++++ .../templates/acme_sh_request_env.j2 | 56 ++++++++++++ 10 files changed, 334 insertions(+) create mode 100644 letsencrypt-acme-sh-client/defaults/main.yml create mode 100644 letsencrypt-acme-sh-client/files/acme-sh-cron-command create mode 100644 letsencrypt-acme-sh-client/files/acme-sh-cron-script create mode 100644 letsencrypt-acme-sh-client/files/acme-sh-install create mode 100644 letsencrypt-acme-sh-client/files/acme-sh-install-certs create mode 100644 letsencrypt-acme-sh-client/files/acme-sh-request-cert create mode 100644 letsencrypt-acme-sh-client/handlers/main.yml create mode 100644 letsencrypt-acme-sh-client/tasks/main.yml create mode 100644 letsencrypt-acme-sh-client/templates/account.conf.j2 create mode 100644 letsencrypt-acme-sh-client/templates/acme_sh_request_env.j2 diff --git a/letsencrypt-acme-sh-client/defaults/main.yml b/letsencrypt-acme-sh-client/defaults/main.yml new file mode 100644 index 00000000..38f04bd2 --- /dev/null +++ b/letsencrypt-acme-sh-client/defaults/main.yml @@ -0,0 +1,65 @@ +--- +letsencrypt_acme_sh_install: False +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_sh_user_home: '{{ letsencrypt_acme_user_home }}' +letsencrypt_acme_sh_base_data_dir: '{{ letsencrypt_acme_sh_user_home }}/acme_data' +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 }}' +# The various services maintainers need to put the reconfigure/restart scripts there +letsencrypt_acme_sh_services_scripts_dir: /usr/lib/acme/hooks + +letsencrypt_acme_sh_explicitly_install_certs: False + +letsencrypt_acme_sh_use_ecc: True +letsencrypt_acme_sh_key_lenght: ec-384 +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 }}' + +# 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 }}', dns_provider: '{{ letsencrypt_acme_sh_dns_provider_type }}', dns_alias_challenge: 'dns-challenge.example.org', standalone: True } + +letsencrypt_acme_sh_domains_install: + - '' +# - { domain: '{{ ansible_fqdn }}', 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', reloadcmd: 'letsencrypt_acme_sh_services_scripts_dir/hook_script' } + diff --git a/letsencrypt-acme-sh-client/files/acme-sh-cron-command b/letsencrypt-acme-sh-client/files/acme-sh-cron-command new file mode 100644 index 00000000..9c4bc821 --- /dev/null +++ b/letsencrypt-acme-sh-client/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/letsencrypt-acme-sh-client/files/acme-sh-cron-script b/letsencrypt-acme-sh-client/files/acme-sh-cron-script new file mode 100644 index 00000000..dafbe4dc --- /dev/null +++ b/letsencrypt-acme-sh-client/files/acme-sh-cron-script @@ -0,0 +1,21 @@ +#!/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 + +su - acme -s /bin/bash -c '/usr/local/bin/acme-sh-cron-command' + +if [ "$ACME_SH_INSTALL_CERTS" == "True" ] ; then + $ACME_SH_BIN $ACME_SH_INSTALL_CERT_REQUEST > "$ACME_SH_INSTALL_LOG_FILE" 2>&1 +fi + +exit $? diff --git a/letsencrypt-acme-sh-client/files/acme-sh-install b/letsencrypt-acme-sh-client/files/acme-sh-install new file mode 100644 index 00000000..e006f15e --- /dev/null +++ b/letsencrypt-acme-sh-client/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_HOME" +./acme.sh $ACME_SH_INSTALL_OPTS + +exit $? diff --git a/letsencrypt-acme-sh-client/files/acme-sh-install-certs b/letsencrypt-acme-sh-client/files/acme-sh-install-certs new file mode 100644 index 00000000..283887b9 --- /dev/null +++ b/letsencrypt-acme-sh-client/files/acme-sh-install-certs @@ -0,0 +1,22 @@ +#!/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" -a -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 + +exit $? diff --git a/letsencrypt-acme-sh-client/files/acme-sh-request-cert b/letsencrypt-acme-sh-client/files/acme-sh-request-cert new file mode 100644 index 00000000..b50458fb --- /dev/null +++ b/letsencrypt-acme-sh-client/files/acme-sh-request-cert @@ -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 +RETVAL= + +$ACME_SH_BIN $ACME_SH_ISSUE_CERT_REQUEST > "$ACME_SH_ISSUE_LOG_FILE" 2>&1 +RETVAL=$? + +if [ $RETVAL -ne 0 ] ; then + touch "$ACME_SH_CONFIG_HOME/ok_certificate_issued" +fi + +exit $RETVAL diff --git a/letsencrypt-acme-sh-client/handlers/main.yml b/letsencrypt-acme-sh-client/handlers/main.yml new file mode 100644 index 00000000..2414d0ab --- /dev/null +++ b/letsencrypt-acme-sh-client/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- 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/letsencrypt-acme-sh-client/tasks/main.yml b/letsencrypt-acme-sh-client/tasks/main.yml new file mode 100644 index 00000000..20a59905 --- /dev/null +++ b/letsencrypt-acme-sh-client/tasks/main.yml @@ -0,0 +1,90 @@ +--- +- 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 + + - 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 + + - 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 + + - 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 + + - 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' ] + + when: letsencrypt_acme_sh_install + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + +- block: + - name: Download the acme.sh distribution + git: repo={{ letsencrypt_acme_sh_git_url }} dest={{ letsencrypt_acme_sh_user_home }} 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 configuration + template: src=account.conf.j2 dest={{ letsencrypt_acme_sh_base_data_dir }}/data/account.conf mode=0640 + + - 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 + when: acme_sh_issue is changed + notify: Install the requested certificates + + - name: Install a daily cron job to renew the certificates when needed + 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' ] + + become: True + become_user: '{{ letsencrypt_acme_sh_user }}' + when: letsencrypt_acme_sh_install + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] + +- block: + - 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 + - acme_sh_certificate_issued is defined + - acme_sh_certificate_issued is changed + ignore_errors: True + + when: letsencrypt_acme_sh_install + tags: [ 'letsencrypt', 'letsencrypt_acme_sh' ] diff --git a/letsencrypt-acme-sh-client/templates/account.conf.j2 b/letsencrypt-acme-sh-client/templates/account.conf.j2 new file mode 100644 index 00000000..3573d973 --- /dev/null +++ b/letsencrypt-acme-sh-client/templates/account.conf.j2 @@ -0,0 +1,21 @@ +{% if letsencrypt_acme_sh_log_enabled %} +LOG_FILE="{{ letsencrypt_acme_sh_base_data_dir }}/logs" +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/letsencrypt-acme-sh-client/templates/acme_sh_request_env.j2 b/letsencrypt-acme-sh-client/templates/acme_sh_request_env.j2 new file mode 100644 index 00000000..160cd401 --- /dev/null +++ b/letsencrypt-acme-sh-client/templates/acme_sh_request_env.j2 @@ -0,0 +1,56 @@ +# +# 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_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="--issue -k {{ letsencrypt_acme_sh_key_lenght }} --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 --ocsp" +{% endif %} +{% if letsencrypt_acme_sh_use_syslog %} +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST --syslog {{ letsencrypt_acme_sh_syslog_level }}" +{% endif %} +{% if letsencrypt_acme_sh_test_request %} +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST --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 %}" + +# The complete command line to issue a certificate +ACME_SH_ISSUE_CERT_REQUEST="$ACME_SH_ISSUE_CERT_REQUEST $ACME_SH_ISSUE_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_DOMAINS="$ACME_SH_INSTALL_CERT_DOMAINS --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 }} {% 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"