From a34cb1e666cb21dbfa4be7941b4cf632adb03bcd Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 19 Mar 2021 23:39:30 +0200 Subject: [PATCH] roles/nginx: Switch to acme.sh for Let's Encrypt The certbot-auto client that I've been using for a long time is now only supported if you install it using snap. I don't use snap on my systems so I decided to switch to the acme.sh client, which is imp- lemented in POSIX shell with no dependencies. One bonus of this is that I can start using ECC certificates. This also configures the .well-known directory so we can use webroot when installing and renewing certificates. I have yet to understand how the renewal works with regards to webroot, though. I may have to update the systemd timers to point to /var/lib/letsencrypt/.well-known. --- roles/nginx/defaults/main.yml | 9 +- roles/nginx/tasks/letsencrypt.yml | 166 +++++------------- roles/nginx/tasks/main.yml | 2 +- roles/nginx/templates/https.j2 | 4 +- .../templates/renew-letsencrypt.service.j2 | 6 +- roles/nginx/templates/vhost.conf.j2 | 2 + roles/nginx/templates/well-known.j2 | 6 + 7 files changed, 63 insertions(+), 132 deletions(-) create mode 100644 roles/nginx/templates/well-known.j2 diff --git a/roles/nginx/defaults/main.yml b/roles/nginx/defaults/main.yml index 88e922d..08d7954 100644 --- a/roles/nginx/defaults/main.yml +++ b/roles/nginx/defaults/main.yml @@ -25,10 +25,13 @@ nginx_ssl_stapling_resolver: '1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700: use_letsencrypt: True # Directory root for Let's Encrypt certs -letsencrypt_root: /etc/letsencrypt/live +letsencrypt_root: /etc/ssl -# Location of Let's Encrypt's certbot script -letsencrypt_certbot_dest: /opt/certbot-auto +# Location where to save initial acme.sh script. After installation the script +# will automatically create its home in the /root/.acme.sh directory (including +# a copy of the script itself). +letsencrypt_acme_script: /root/acme.sh +letsencrypt_acme_home: /root/.acme.sh # stable is 1.18.x # mainline is 1.19.x diff --git a/roles/nginx/tasks/letsencrypt.yml b/roles/nginx/tasks/letsencrypt.yml index c40fc37..e33b82f 100644 --- a/roles/nginx/tasks/letsencrypt.yml +++ b/roles/nginx/tasks/letsencrypt.yml @@ -1,135 +1,53 @@ --- +# Use acme.sh instead of certbot because they only support installation via +# snap now. - block: + - name: Remove certbot + apt: name=certbot state=absent + + - name: Remove old certbot post and pre hooks for nginx + file: dest={{ item }} state=absent + with_items: + - /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh + - /etc/letsencrypt/renewal-hooks/post/start-nginx.sh + + - name: Download acme.sh + get_url: + url: https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh + dest: "{{ letsencrypt_acme_script }}" + + - name: Prepare Let's Encrypt well-known directory + file: + state: directory + path: /var/lib/letsencrypt/.well-known + owner: root + group: nginx + mode: g+s + - name: Copy systemd service to renew Let's Encrypt certs - template: src=renew-letsencrypt.service.j2 dest=/etc/systemd/system/renew-letsencrypt.service mode=0644 owner=root group=root + template: + src: renew-letsencrypt.service.j2 + dest: /etc/systemd/system/renew-letsencrypt.service + mode: 0644 + owner: root + group: root - name: Copy systemd timer to renew Let's Encrypt certs - copy: src=renew-letsencrypt.timer dest=/etc/systemd/system/renew-letsencrypt.timer mode=0644 owner=root group=root + copy: + src: renew-letsencrypt.timer + dest: /etc/systemd/system/renew-letsencrypt.timer + mode: 0644 + owner: root + group: root - # always issues daemon-reload just in case the server/timer changed + # always issues daemon-reload just in case the service/timer changed - name: Start and enable systemd timer to renew Let's Encrypt certs - systemd: name=renew-letsencrypt.timer state=started enabled=yes daemon_reload=yes - - - name: Download certbot - get_url: dest={{ letsencrypt_certbot_dest }} url=https://dl.eff.org/certbot-auto mode=700 - - # Dependencies certbot checks for on its first run. I set them in a fact so that - # I can pass the list directly to the apt module to install in one transaction. - - name: Set certbot dependencies (Debian 10) - when: ansible_distribution == 'Debian' and ansible_distribution_major_version is version('10', '==') - set_fact: - certbot_dependencies: - - augeas-lenses - - binutils - - binutils-common - - binutils-x86-64-linux-gnu - - cpp - - cpp-8 - - gcc - - gcc-8 - - libasan5 - - libatomic1 - - libaugeas0 - - libbinutils - - libc-dev-bin - - libc6-dev - - libcc1-0 - - libexpat1-dev - - libffi-dev - - libgcc-8-dev - - libgomp1 - - libisl19 - - libitm1 - - liblsan0 - - libmpc3 - - libmpfr6 - - libmpx2 - - libpython-dev - - libpython2-dev - - libpython2.7 - - libpython2.7-dev - - libquadmath0 - - libssl-dev - - libtsan0 - - libubsan1 - - linux-libc-dev - - python-dev - - python-pip-whl - - python-pkg-resources - - python-virtualenv - - python2-dev - - python2.7-dev - - python3-distutils - - python3-lib2to3 - - python3-virtualenv - - virtualenv - - # Dependencies certbot checks for on its first run. I set them in a fact so that - # I can pass the list directly to the apt module to install in one transaction. - - name: Set certbot dependencies (Ubuntu 18.04) - when: ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('18.04', '==') - set_fact: - certbot_dependencies: - - augeas-lenses - - binutils - - binutils-common - - binutils-x86-64-linux-gnu - - cpp - - cpp-7 - - gcc - - gcc-7 - - gcc-7-base - - libasan4 - - libatomic1 - - libaugeas0 - - libbinutils - - libc-dev-bin - - libc6-dev - - libcc1-0 - - libcilkrts5 - - libexpat1-dev - - libffi-dev - - libgcc-7-dev - - libgomp1 - - libisl19 - - libitm1 - - liblsan0 - - libmpc3 - - libmpx2 - - libpython-dev - - libpython2.7 - - libpython2.7-dev - - libquadmath0 - - libssl-dev - - libtsan0 - - libubsan0 - - linux-libc-dev - - python-dev - - python-pip-whl - - python-pkg-resources - - python-virtualenv - - python2.7-dev - - python3-virtualenv - - virtualenv - - - name: Install certbot dependencies - apt: name={{ certbot_dependencies }} state=present update_cache=yes - - when: ansible_distribution != 'Ubuntu' and ansible_distribution_major_version is version('20.04', '!=') - tags: letsencrypt - -# On Ubuntu 20.04 it is no longer recommended/supported to use the standalone -# certbot-auto so I guess we need to use the one from the repositories. -- block: - - name: Install certbot (Ubuntu 20.04) - apt: name=certbot state=present update_cache=yes - - - name: Copy certbot post and pre hooks for nginx - copy: src={{ item.src }} dest={{ item.dest }} owner=root group=root mode=0755 - with_items: - - { src: 'stop-nginx.sh', dest: '/etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh' } - - { src: 'start-nginx.sh', dest: '/etc/letsencrypt/renewal-hooks/post/start-nginx.sh' } + systemd: + name: renew-letsencrypt.timer + state: started + enabled: yes + daemon_reload: yes when: ansible_distribution == 'Ubuntu' and ansible_distribution_version is version('20.04', '==') tags: letsencrypt diff --git a/roles/nginx/tasks/main.yml b/roles/nginx/tasks/main.yml index db65c8f..5b51525 100644 --- a/roles/nginx/tasks/main.yml +++ b/roles/nginx/tasks/main.yml @@ -71,7 +71,7 @@ - name: Configure Let's Encrypt include_tasks: letsencrypt.yml - when: use_letsencrypt is defined and use_letsencrypt + #when: use_letsencrypt is defined and use_letsencrypt tags: letsencrypt # vim: set ts=2 sw=2: diff --git a/roles/nginx/templates/https.j2 b/roles/nginx/templates/https.j2 index 91de324..00b9cc8 100644 --- a/roles/nginx/templates/https.j2 +++ b/roles/nginx/templates/https.j2 @@ -16,8 +16,8 @@ # concatenated key + cert # See: http://nginx.org/en/docs/http/configuring_https_servers.html - ssl_certificate {{ letsencrypt_root }}/{{ domain_name }}/fullchain.pem; - ssl_certificate_key {{ letsencrypt_root }}/{{ domain_name }}/privkey.pem; + ssl_certificate {{ letsencrypt_root }}/certs/{{ domain_name }}.fullchain.pem; + ssl_certificate_key {{ letsencrypt_root }}/private/{{ domain_name }}.key.pem; {% endif %} diff --git a/roles/nginx/templates/renew-letsencrypt.service.j2 b/roles/nginx/templates/renew-letsencrypt.service.j2 index c5212d1..1bb5d05 100644 --- a/roles/nginx/templates/renew-letsencrypt.service.j2 +++ b/roles/nginx/templates/renew-letsencrypt.service.j2 @@ -1,7 +1,9 @@ [Unit] Description=Renew Let's Encrypt certificates -ConditionFileIsExecutable={{ letsencrypt_certbot_dest }} +ConditionFileIsExecutable={{ letsencrypt_acme_home }}/acme.sh [Service] Type=oneshot -ExecStart={{ letsencrypt_certbot_dest }} renew --standalone --pre-hook "/bin/systemctl stop nginx" --post-hook "/bin/systemctl start nginx" +ExecStart={{ letsencrypt_acme_home }}/acme.sh --cron --home {{ letsencrypt_acme_home }} --reloadcmd "/bin/systemctl reload nginx" + +SuccessExitStatus=0 2 diff --git a/roles/nginx/templates/vhost.conf.j2 b/roles/nginx/templates/vhost.conf.j2 index f8b0ee2..1c2a4b1 100644 --- a/roles/nginx/templates/vhost.conf.j2 +++ b/roles/nginx/templates/vhost.conf.j2 @@ -14,6 +14,8 @@ server { listen [::]:80; server_name {{ domain_name }} {{ domain_aliases }}; + {% include 'well-known.j2' %} + # redirect http -> https location / { # ? in rewrite makes sure nginx doesn't append query string again diff --git a/roles/nginx/templates/well-known.j2 b/roles/nginx/templates/well-known.j2 new file mode 100644 index 0000000..2d8b9e0 --- /dev/null +++ b/roles/nginx/templates/well-known.j2 @@ -0,0 +1,6 @@ +location ^~ /.well-known/acme-challenge/ { + allow all; + root /var/lib/letsencrypt/; + default_type "text/plain"; + try_files $uri =404; +}