From 394e28b8150327e8da06ff54055b2acf34742726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lecour?= Date: Fri, 27 Sep 2019 00:13:30 +0200 Subject: [PATCH] WIP: new certbot role --- CHANGELOG.md | 1 + certbot/defaults/main.yml | 3 ++ certbot/files/hooks/apache.sh | 30 +++++++++++ certbot/files/hooks/commit-etc.sh | 32 ++++++++++++ certbot/files/hooks/dovecot.sh | 30 +++++++++++ certbot/files/hooks/haproxy.sh | 41 +++++++++++++++ certbot/files/hooks/nginx.sh | 30 +++++++++++ certbot/files/hooks/postfix.sh | 30 +++++++++++ certbot/handlers/main.yml | 16 ++++++ certbot/tasks/acme-challenge.yml | 50 +++++++++++++++++++ certbot/tasks/main.yml | 23 +++++++++ .../templates/acme-challenge/apache.conf.j2 | 6 +++ .../templates/acme-challenge/nginx.conf.j2 | 5 ++ 13 files changed, 297 insertions(+) create mode 100644 certbot/defaults/main.yml create mode 100644 certbot/files/hooks/apache.sh create mode 100644 certbot/files/hooks/commit-etc.sh create mode 100644 certbot/files/hooks/dovecot.sh create mode 100644 certbot/files/hooks/haproxy.sh create mode 100644 certbot/files/hooks/nginx.sh create mode 100644 certbot/files/hooks/postfix.sh create mode 100644 certbot/handlers/main.yml create mode 100644 certbot/tasks/acme-challenge.yml create mode 100644 certbot/tasks/main.yml create mode 100644 certbot/templates/acme-challenge/apache.conf.j2 create mode 100644 certbot/templates/acme-challenge/nginx.conf.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index dbec4b78..3b3dbe9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The **patch** part changes incrementally at each release. ## [Unreleased] ### Added +* certbot : new role to install and configure certbot * evocheck: explicit PATH * evolinux-base: On debian 10 and later, add noexec on /dev/shm * evolinux-base: default value for "evolinux_ssh_group" diff --git a/certbot/defaults/main.yml b/certbot/defaults/main.yml new file mode 100644 index 00000000..876be14e --- /dev/null +++ b/certbot/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +certbot_work_dir: /var/lib/letsencrypt diff --git a/certbot/files/hooks/apache.sh b/certbot/files/hooks/apache.sh new file mode 100644 index 00000000..765943e0 --- /dev/null +++ b/certbot/files/hooks/apache.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +if [ -n "$(pidof apache2)" ]; then + apache2ctl_bin=$(command -v apache2ctl) + if ${apache2ctl_bin} configtest > /dev/null; then + if grep --dereference-recursive -E "^\s*SSLCertificate" /etc/apache2/sites-enabled | grep -q "letsencrypt"; then + debug "Apache detected... reloading" + systemctl reload apache2 + else + debug "Apache doesn't use Let's Encrypt certificate. Skip." + fi + else + error "Apache config is broken, you must fix it !" + fi +else + debug "Apache is not running. Skip." +fi diff --git a/certbot/files/hooks/commit-etc.sh b/certbot/files/hooks/commit-etc.sh new file mode 100644 index 00000000..a37ec85d --- /dev/null +++ b/certbot/files/hooks/commit-etc.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +git_bin=$(command -v git) +letsencrypt_dir=/etc/letsencrypt +export GIT_DIR="/etc/.git" +export GIT_WORK_TREE="/etc" + +if test -x "${git_bin}" && test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then + changed_lines=$(${git_bin} status --porcelain -- ${letsencrypt_dir} | wc -l | tr -d ' ') + + if [ "${changed_lines}" != "0" ]; then + debug "Committing for ${RENEWED_DOMAINS}" + ${git_bin} add --all ${letsencrypt_dir} + message="[letsencrypt] certificates renewal (${RENEWED_DOMAINS})" + ${git_bin} commit --message "${message}" --quiet + else + error "Weird, nothing has changed but the hook has been executed for '${RENEWED_DOMAINS}'" + fi +fi diff --git a/certbot/files/hooks/dovecot.sh b/certbot/files/hooks/dovecot.sh new file mode 100644 index 00000000..ffed2994 --- /dev/null +++ b/certbot/files/hooks/dovecot.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +if [ -n "$(pidof dovecot)" ]; then + doveconf_bin=$(command -v doveconf) + if ${doveconf_bin} > /dev/null; then + if ${doveconf_bin} | grep -E "^ssl_cert[^_]" | grep -q "letsencrypt"; then + debug "Dovecot detected... reloading" + systemctl reload dovecot + else + debug "Dovecot doesn't use Let's Encrypt certificate. Skip." + fi + else + error "Dovecot config is broken, you must fix it !" + fi +else + debug "Dovecot is not running. Skip." +fi diff --git a/certbot/files/hooks/haproxy.sh b/certbot/files/hooks/haproxy.sh new file mode 100644 index 00000000..b6023bfc --- /dev/null +++ b/certbot/files/hooks/haproxy.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +if [ -z "${RENEWED_LINEAGE}" ]; then + error "This script must be called only by certbot!" +fi + +if [ -n "$(pidof haproxy)" ]; then + haproxy_bin=$(command -v haproxy) + if ${haproxy_bin} -c -f /etc/haproxy/haproxy.cfg > /dev/null; then + if [ -f "${RENEWED_LINEAGE}/fullchain.pem" ] && [ -f "${RENEWED_LINEAGE}/privkey.pem" ]; then + haproxy_cert_file="/etc/ssl/haproxy/$(basename "${RENEWED_LINEAGE}").pem" + + debug "Concatenating certificate files to ${haproxy_cert_file}" + cat "${RENEWED_LINEAGE}/fullchain.pem" "${RENEWED_LINEAGE}/privkey.pem" > "${haproxy_cert_file}" + chmod 600 "${haproxy_cert_file}" + chown root: "${haproxy_cert_file}" + + debug "HAProxy detected... reloading" + systemctl reload apache2 + else + error "Couldn't find ${RENEWED_LINEAGE}/fullchain.pem or ${RENEWED_LINEAGE}/privkey.pem" + fi + else + error "HAProxy config is broken, you must fix it !" + fi +else + debug "HAProxy is not running. Skip." +fi diff --git a/certbot/files/hooks/nginx.sh b/certbot/files/hooks/nginx.sh new file mode 100644 index 00000000..ff78166a --- /dev/null +++ b/certbot/files/hooks/nginx.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +if [ -n "$(pidof nginx)" ]; then + nginx_bin=$(command -v nginx) + if ${nginx_bin} -t > /dev/null; then + if grep --dereference-recursive -E "^\s*ssl_certificate" /etc/nginx/sites-enabled | grep -q "letsencrypt"; then + debug "Nginx detected... reloading" + systemctl reload nginx + else + debug "Nginx doesn't use Let's Encrypt certificate. Skip." + fi + else + error "Nginx config is broken, you must fix it !" + fi +else + debug "Nginx is not running. Skip." +fi diff --git a/certbot/files/hooks/postfix.sh b/certbot/files/hooks/postfix.sh new file mode 100644 index 00000000..68948d0a --- /dev/null +++ b/certbot/files/hooks/postfix.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +readonly VERBOSE=${VERBOSE:-"0"} +readonly QUIET=${QUIET:-"0"} + +error() { + >&2 echo "${PROGNAME}: $1" + exit 1 +} +debug() { + if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then + >&2 echo "${PROGNAME}: $1" + fi +} + +if [ -n "$(pidof master)" ]; then + postconf_bin=$(command -v postconf) + if ${postconf_bin} > /dev/null; then + if ${postconf_bin} | grep -E "^smtpd_tls_cert_file" | grep -q "letsencrypt"; then + debug "Postfix detected... reloading" + systemctl reload postfix + else + debug "Postfix doesn't use Let's Encrypt certificate. Skip." + fi + else + error "Postfix config is broken, you must fix it !" + fi +else + debug "Postfix is not running. Skip." +fi diff --git a/certbot/handlers/main.yml b/certbot/handlers/main.yml new file mode 100644 index 00000000..903fe696 --- /dev/null +++ b/certbot/handlers/main.yml @@ -0,0 +1,16 @@ +--- + +- name: reload nginx + service: + name: nginx + state: reloaded + +- name: reload apache + service: + name: apache2 + state: reloaded + +- name: reload haproxy + service: + name: haproxy + state: reloaded diff --git a/certbot/tasks/acme-challenge.yml b/certbot/tasks/acme-challenge.yml new file mode 100644 index 00000000..467b4606 --- /dev/null +++ b/certbot/tasks/acme-challenge.yml @@ -0,0 +1,50 @@ +--- + +- name: Certbot work directory is present + file: + dest: "{{ certbot_work_dir }}" + state: directory + mode: "0755" + +- name: Check if Nginx is installed + stat: + path: /etc/nginx + register: is_nginx + +- name: ACME challenge for Nginx is installed + template: + src: acme-challenge/nginx.conf.j2 + dest: /etc/nginx/snippets/letsencrypt.conf + force: yes + notify: reload nginx + when: is_nginx.stat.exists + +- name: Check if Apache is installed + stat: + path: /etc/apache2 + register: is_apache + +- block: + - name: ACME challenge for Apache is installed + template: + src: acme-challenge/apache.conf.j2 + dest: /etc/apache2/conf-available/letsencrypt.conf + force: yes + notify: reload apache + + - name: ACME challenge for Apache is enabled + command: "a2enconf letsencrypt" + register: command_result + changed_when: "'Enabling' in command_result.stderr" + notify: reload apache + when: is_apache.stat.exists + +- name: Check if HAProxy is installed + stat: + path: /etc/haproxy + register: is_haproxy + +- name: ACME challenge for HAProxy is installed + debug: + msg: "ACME challenge configuration for HAProxy should be configured manually" + when: is_haproxy.stat.exists diff --git a/certbot/tasks/main.yml b/certbot/tasks/main.yml new file mode 100644 index 00000000..b0218092 --- /dev/null +++ b/certbot/tasks/main.yml @@ -0,0 +1,23 @@ +--- + +- name: "System compatibility checks" + assert: + that: + - ansible_distribution == "Debian" + - ansible_distribution_major_version | version_compare('9', '>=') + msg: only compatible with Debian 9+ + +- name: certbot package is installed + apt: + name: certbot + state: latest + +- include: acme-challenge.yml + +- name: Deploy hooks are present + copy: + src: hooks/ + dest: /etc/letsencrypt/renewal-hooks/deploy/ + mode: "0700" + owner: root + group: root diff --git a/certbot/templates/acme-challenge/apache.conf.j2 b/certbot/templates/acme-challenge/apache.conf.j2 new file mode 100644 index 00000000..bae29e10 --- /dev/null +++ b/certbot/templates/acme-challenge/apache.conf.j2 @@ -0,0 +1,6 @@ +Alias /.well-known/acme-challenge {{ certbot_work_dir }}/.well-known/acme-challenge + + Options -Indexes + Allow from all + Require all granted + diff --git a/certbot/templates/acme-challenge/nginx.conf.j2 b/certbot/templates/acme-challenge/nginx.conf.j2 new file mode 100644 index 00000000..a0730f3c --- /dev/null +++ b/certbot/templates/acme-challenge/nginx.conf.j2 @@ -0,0 +1,5 @@ +location ~ /.well-known/acme-challenge { + alias {{ certbot_work_dir }}/; + try_files $uri =404; + allow all; +}