From 64cd268f1cf113911bf4472fbb63efe53f6eb760 Mon Sep 17 00:00:00 2001 From: Aaron LI Date: Sun, 22 Sep 2019 10:18:57 +0800 Subject: web: Use 'acme.sh' to issue and renew certificates The 'acme-client' seems obsolete and is missing from DPorts. --- group_vars/all/vars.yml | 2 + roles/web/tasks/main.yml | 150 +++++++++++++++++----------------- roles/web/tasks/nginx-gensites.yml | 2 +- roles/web/templates/acme/deploy.sh.j2 | 27 ++++++ roles/web/templates/acme/issue.sh.j2 | 19 +++++ 5 files changed, 125 insertions(+), 75 deletions(-) create mode 100644 roles/web/templates/acme/deploy.sh.j2 create mode 100644 roles/web/templates/acme/issue.sh.j2 diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml index 8d09fc4..107aa41 100644 --- a/group_vars/all/vars.yml +++ b/group_vars/all/vars.yml @@ -15,7 +15,9 @@ pf: max_conn_rate: 15/5 # 15 of connections per 5 seconds web: + acme_home: /var/db/acme acme_webroot: /home/www/acme + ssl_root: /usr/local/etc/ssl/acme domains: - name: liwt.net diff --git a/roles/web/tasks/main.yml b/roles/web/tasks/main.yml index 3841cb8..0bc6466 100644 --- a/roles/web/tasks/main.yml +++ b/roles/web/tasks/main.yml @@ -3,7 +3,7 @@ pkgng: name: - nginx - - acme-client + - acme.sh state: present - name: (local) ssl/tls - check dhparam existence @@ -75,103 +75,105 @@ /var/log/nginx/error.log 644 7 * @T00 Z /var/run/nginx.pid tags: nginx -- name: acme - copy scripts - copy: - src: "{{ item }}" - dest: /usr/local/etc/acme/{{ item | basename }} +- name: acme - create webroot directory + file: + path: "{{ web.acme_webroot }}/.well-known/acme-challenge" + state: directory + owner: acme + group: www mode: 0755 - with_fileglob: - - "acme/*.sh" - tags: acme - -- name: acme - copy deployment scripts - copy: - src: acme/deploy.d/ # note the trailing '/' - dest: /usr/local/etc/acme/deploy.d/ - tags: acme - -- name: (local) acme - check account private key existence - become: false - stat: - path: "{{ playbook_dir }}/private/acme/privkey.pem" - delegate_to: localhost - register: stat_result - tags: acme - -- name: (local) acme - generate account private key (4096 bit) - become: false - command: > - openssl genrsa - -out "{{ playbook_dir }}/private/acme/privkey.pem" 4096 - delegate_to: localhost - when: not stat_result.stat.exists + recurse: true tags: acme -- name: acme - copy account private key - copy: - src: "{{ playbook_dir }}/private/acme/privkey.pem" - dest: /usr/local/etc/acme/privkey.pem - mode: 0400 +- name: acme.sh - touch log file + file: + path: /var/log/acme.sh.log + owner: acme + group: acme + mode: 0640 + state: touch tags: acme -- name: acme - create domain private directory - file: - path: /usr/local/etc/ssl/acme/private/ - state: directory - mode: 0700 +- name: acme.sh - set newsyslog to rotate log file + lineinfile: + path: /etc/newsyslog.conf + regexp: '^#?/var/log/acme.sh.log' + line: "/var/log/acme.sh.log acme:acme 640 90 * @T00 Z" tags: acme -# Credit: https://shasawas.wordpress.com/2016/05/23/how-to-loop-over-a-set-of-tasks-in-ansible/ -- block: - - name: acme - generate and copy domain private keys - include_tasks: acme-domainkey.yml - vars: - domain: "{{ item.name }}" - with_items: "{{ domains }}" +- name: acme.sh - generate issue script + template: + src: acme/issue.sh.j2 + dest: "{{ web.acme_home }}/issue.sh" + mode: 0755 tags: - acme - acme-renew -- name: acme - generate domains.txt - template: - src: domains.txt.j2 - dest: /usr/local/etc/acme/domains.txt +- name: acme.sh - issue certificates + become: true + become_user: acme + command: sh "{{ web.acme_home }}/issue.sh" tags: - acme - acme-renew -- name: acme - create challenge directory - file: - path: /usr/local/www/acme/.well-known/acme-challenge - state: directory - group: www - recurse: true - tags: acme - -- name: nginx - force reload - command: rcreload nginx +- name: acme.sh - generate deploy script + template: + src: acme/deploy.sh.j2 + dest: "{{ web.acme_home }}/deploy.sh" + mode: 0755 tags: - acme - acme-renew -- name: acme - request domain certificates - command: sh /usr/local/etc/acme/acme-client.sh -e - notify: deploy-acme +- name: acme.sh - deploy certificates + command: sh "{{ web.acme_home }}/deploy.sh" tags: - acme - acme-renew -- name: acme - setup periodic tasks for cert renewal - blockinfile: - path: /etc/periodic.conf - marker: "# {mark} ANSIBLE MANAGED - acme" - block: | - # Auto renew certificates with acme-client - weekly_acme_client_enable="YES" - weekly_acme_client_renewscript="/usr/local/etc/acme/acme-client.sh" - weekly_acme_client_deployscript="/usr/local/etc/acme/deploy.sh" +- name: acme.sh - touch local deploy script + file: + path: "{{ web.acme_home }}/deploy.local.sh" + mode: 0755 + state: touch + tags: acme + +- name: acme.sh - add nginx reload to deploy + lineinfile: + path: "{{ web.acme_home }}/deploy.local.sh" + line: "service nginx reload" + tags: acme + +- name: acme.sh - generate renew script + copy: + dest: "{{ web.acme_home }}/renew.sh" + mode: 0755 + content: | + acme.sh --cron + sh {{ web.acme_home }}/deploy.sh + tags: acme + +- name: acme.sh - install cron job to renew (1) + cron: + user: acme + name: MAILTO + env: true + job: root + tags: acme + +- name: acme.sh - install cron job to renew (2) + cron: + user: acme + name: "acme.sh-renew" + special_time: monthly + job: "sh {{ web.acme_home }}/renew.sh" + tags: acme - block: - name: nginx - re-generate sites include_tasks: nginx-gensites.yml - tags: sites + tags: + - acme + - sites diff --git a/roles/web/tasks/nginx-gensites.yml b/roles/web/tasks/nginx-gensites.yml index 162a8f3..daea875 100644 --- a/roles/web/tasks/nginx-gensites.yml +++ b/roles/web/tasks/nginx-gensites.yml @@ -1,7 +1,7 @@ --- - name: domains - check certificate existence stat: - path: /usr/local/etc/ssl/acme/{{ item.name }}/fullchain.pem + path: "{{ web.ssl_root }}/{{ item.name }}/fullchain" register: stat with_items: "{{ domains }}" diff --git a/roles/web/templates/acme/deploy.sh.j2 b/roles/web/templates/acme/deploy.sh.j2 new file mode 100644 index 0000000..141b112 --- /dev/null +++ b/roles/web/templates/acme/deploy.sh.j2 @@ -0,0 +1,27 @@ +#!/bin/sh +# +# Deploy the issued certificates. +# +# Aaron LI +# 2019-09-21 +# + +SSL_ROOT="{{ web.ssl_root }}" +[ -d "${SSL_ROOT}" ] || mkdir -p -m 0700 ${SSL_ROOT} + +{% for domain in domains %} +CERT_DIR="${SSL_ROOT}/{{ domain.name }}" +[ -d "${CERT_DIR}" ] || mkdir -m 0700 ${CERT_DIR} +acme.sh --install-cert --log /var/log/acme.sh.log \ + --config-home {{ web.acme_home }}/.acme.sh \ + --cert-home {{ web.acme_home }}/certs \ + --domain {{ domain.name }} \ + --key-file ${CERT_DIR}/key \ + --cert-file ${CERT_DIR}/cert \ + --fullchain-file ${CERT_DIR}/fullchain + +{% endfor %} + +echo "Reload relevant services ..." +SCRIPT="{{ web.acme_home }}/deploy.local.sh" +[ -f "${SCRIPT}" ] && sh ${SCRIPT} || exit 0 diff --git a/roles/web/templates/acme/issue.sh.j2 b/roles/web/templates/acme/issue.sh.j2 new file mode 100644 index 0000000..6e63fb4 --- /dev/null +++ b/roles/web/templates/acme/issue.sh.j2 @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Use 'acme.sh' to issue certificates. +# +# Aaron LI +# 2019-09-21 +# + +{% for domain in domains %} +acme.sh --issue --log /var/log/acme.sh.log \ + --config-home {{ web.acme_home }}/.acme.sh \ + --domain {{ domain.name }} \ + {% for sub in domain.sub %}--domain {{ sub }}.{{ domain.name }} {% endfor %} \ + --webroot {{ web.acme_webroot }} || + echo "WARNING: exit with non-zero code: $?" + +{% endfor %} + +acme.sh --list -- cgit v1.2.2