Skip to content
This repository has been archived by the owner on Jun 4, 2021. It is now read-only.

Commit

Permalink
Switch to certbot for letsencrypt certificates (#1668)
Browse files Browse the repository at this point in the history
Modifies the lets-encrypt role to use certbot for certificate issuance and auto-renewal. Also upgrades to using Let's Encrypt's ACMEv2 server.
  • Loading branch information
nickgnazzo authored and nopdotcom committed Dec 5, 2019
1 parent be8e7a1 commit 0130140
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 86 deletions.
28 changes: 0 additions & 28 deletions playbooks/roles/lets-encrypt/files/acmetool_ppa.pem

This file was deleted.

24 changes: 13 additions & 11 deletions playbooks/roles/lets-encrypt/tasks/install.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
- name: "Add the APT key for acmetool; hiding 25 lines of log..."
apt_key:
id: 9862409EF124EC763B84972FF5AC9651EDB58DFA
data: "{{ item }}"
with_file: acmetool_ppa.pem
no_log: True

- name: Add the official acmetool repository
- name: Enable the Universe repository
apt_repository:
repo: "deb http://ppa.launchpad.net/hlandau/rhea/{{ ansible_distribution|lower }} {{ ansible_lsb.codename }} main"
repo: "deb http://archive.ubuntu.com/ubuntu {{ ansible_distribution_release }} universe"
state: present
register: le_add_apt_repository
until: not le_add_apt_repository.failed
retries: "{{ apt_repository_retries }}"
delay: "{{ apt_repository_delay }}"

- name: Install acmetool
- name: Add the certbot PPA
apt_repository:
repo: "ppa:certbot/certbot"
register: le_add_certbot_ppa
until: not le_add_certbot_ppa.failed
retries: "{{ apt_repository_retries }}"
delay: "{{ apt_repository_delay }}"

- name: Install certbot
apt:
package: acmetool
package: certbot
68 changes: 36 additions & 32 deletions playbooks/roles/lets-encrypt/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
- import_tasks: install.yml

- name: Prepare response file directory
file:
path: "{{ le_base }}/conf"
state: directory
recurse: yes

- name: Query Let's Encrypt to find the current subscriber agreement URL
uri:
url: "{{ le_api_endpoint }}"
body_format: json
register: http_body

- name: Extract Let's Encrypt subscriber agreement URL
set_fact:
le_agreements: "{{ http_body['json']['meta']['terms-of-service'] }}"

- name: Put reponse file in place
template:
src: response.yaml.j2
dest: "{{ le_base }}/conf/responses"

- name: Perform initial configuration of acmetool
command: acmetool quickstart
args:
creates: "{{ le_base }}/conf/target"

# acmetool should now run in redirector mode listening on 80/tcp for HTTP
# requests (including ACME chanllenges). All HTTP requests are simply
# redirected to their HTTPS counterparts.

- import_tasks: firewall.yml

# Now we try to request a certificate. If it fails. We print a warning and
# fall back to self-signed certificates.
- block:
- set_fact:
le_email_flag: "--email {{ streisand_admin_email }}"
when: streisand_admin_email != ""
- set_fact:
le_email_flag: "--register-unsafely-without-email"
when: streisand_admin_email == ""

- name: Request initial certificate from Let's Encrypt
command: "acmetool want {{ streisand_domain }}"
# Use certbot's "standalone" plugin to listen on port 80 for the initial
# challenge request/response. After this role runs, the nginx config
# handles port 80 requests, and whitelists the ACME challenge response dir.
command: |-
certbot certonly --standalone -d {{ streisand_domain }}
{{ le_email_flag }} --agree-tos --non-interactive
--rsa-key-size {{ le_rsa_key_size }}
--server {{ le_api_endpoint }}
args:
creates: "{{ le_certificate }}"

# We modify certbot's renewal config file to use the "webroot" plugin.
# By default, certbot uses the same options during the initial cert request
# when doing renewals. This would cause port conflicts since nginx will
# be listening on 80 (for 80->443 redirects, and allowing ACME challenges).
# We could technically let certbot restart/modify nginx temporarily for
# port 80 during renewal, but using "webroot" helps avoid downtime, since
# it just needs to write to the webroot directory to complete the challenge.
- name: Modify certbot renewal options to use webroot authenticator
ini_file:
path: "/etc/letsencrypt/renewal/{{ streisand_domain }}.conf"
section: renewalparams
option: authenticator
value: webroot

- name: Point webroot to the right directory where nginx will serve challenge responses.
ini_file:
path: "/etc/letsencrypt/renewal/{{ streisand_domain }}.conf"
section: renewalparams
option: webroot_path
value: "{{ nginx_default_html_path }}"

- set_fact:
le_ok: True

Expand Down
10 changes: 0 additions & 10 deletions playbooks/roles/lets-encrypt/templates/response.yaml.j2

This file was deleted.

12 changes: 7 additions & 5 deletions playbooks/roles/lets-encrypt/vars/main.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
le_base: /var/lib/acme
le_base: /etc/letsencrypt
le_port: 80
le_api_endpoint: "https://acme-v01.api.letsencrypt.org/directory"
le_certificate: "{{ le_base }}/live/{{ streisand_domain }}/fullchain"
le_private_key: "{{ le_base }}/live/{{ streisand_domain }}/privkey"
le_chain: "{{ le_base }}/live/{{ streisand_domain }}/chain"
# RSA key size to request for SSL certificate
le_rsa_key_size: 4096
le_api_endpoint: "https://acme-v02.api.letsencrypt.org/directory"
le_certificate: "{{ le_base }}/live/{{ streisand_domain }}/fullchain.pem"
le_private_key: "{{ le_base }}/live/{{ streisand_domain }}/privkey.pem"
le_chain: "{{ le_base }}/live/{{ streisand_domain }}/chain.pem"
18 changes: 18 additions & 0 deletions playbooks/roles/nginx/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@
dest: "{{ nginx_systemd_service_path }}/10-restart-failure.conf"
mode: 0644

# This directory is used to create ACME challenge responses when using letsencrypt.
# N.B.: this should be different from streisand's gateway path to avoid any
# chance of leaking gateway files (VPN credentials, configs, etc.) over port 80.
# The nginx rules do redirect http to https, with the only exception
# being requests to /.well-known/acme-challenge for letsencrypt.
# So there is probably a low risk of that ever happening,
# but still, keeping them separate can't hurt.
- name: Ensure the default nginx HTML directory is empty
file:
state: absent
path: "{{ nginx_default_html_path }}/"
when: streisand_le_enabled

- file:
state: directory
path: "{{ nginx_default_html_path }}"
when: streisand_le_enabled

- name: Enable the nginx service
systemd:
daemon_reload: yes
Expand Down
1 change: 1 addition & 0 deletions playbooks/roles/nginx/vars/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ apache_packages_to_remove:
- apache2-mpm-worker

nginx_systemd_service_path: /etc/systemd/system/nginx.service.d
nginx_default_html_path: /usr/share/nginx/html
37 changes: 37 additions & 0 deletions playbooks/roles/streisand-gateway/templates/vhost.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
# Setup an http -> https redirect
server {
listen 80;

{% if streisand_le_enabled %}
server_name {{ streisand_domain }};
# Allow ACME challenge requests by the ACME server (letsencrypt)
location /.well-known/acme-challenge/ {
# This directory is intentionally set to be different
# than the Streisand gateway directory. We want to avoid any
# chance of leaking gateway credentials/pages over port 80.
root {{ nginx_default_html_path }};
allow all;
}
{% else %}
server_name {{ streisand_ipv4_address }};
{% endif %}

location / {
return 301 https://$server_name$request_uri;
}

# Disable all logging
# Comment these lines out if you want to verify the letsencrypt
# ACME challenge/response process is working for debugging purposes.
access_log /dev/null;
error_log /dev/null crit;
}

# Catch-all server to prevent requests that either change the HOST header
# or request an invalid domain/subdomain.
server {
listen 80 default_server;
server_name _;
return 444; # "Connection closed without response"
}

# SSL Gateway
server {
listen 127.0.0.1:{{ nginx_port }} default_server ssl http2;
Expand Down

0 comments on commit 0130140

Please sign in to comment.