Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certbot support using certbot nginx plugin #136

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
11 changes: 11 additions & 0 deletions HISTORY.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
1.3.9 unreleased

- Get certbot role working for provisioning on focal.
Certificate issuance is not yet working there due to a hopefully soon-to-be-fixed bug.
[smcmahon]

- Add certbot test file; this tests provisioning of certbot-nginx, but not certificate issuance.
[smcmahon]

- Add certbot playbook, role and documentation.
Provides LetsEncrypt support using certbot-nginx plugin.
[smcmahon, stevepiercy]

- Add "tcp-check connect" to haproxy tcp-check sequence.
Fixes #123.
Thanks, jid.
Expand Down
52 changes: 52 additions & 0 deletions docs/certbot.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Certbot options
```````````````

The certbot playbook
~~~~~~~~~~~~~~~~~~~~

As a convenience, the Plone Ansible Playbook kit includes a separate
playbook that will install certbot-nginx and create certificates as necessary for specified hostnames.

The certbot playbook currently only supports Debian-family target servers.

To use the certbot playbook, edit your ``local-configure.yml`` file to add a ``certbot_hosts`` list variable containing an entry for each hostname for which you wish to get a certbot certificate:

.. code-block:: yaml

certbot_hosts:
- one.mcsmith.org
- two.mcsmith.org

Run the playbook as you would the main playbook, adding whatever command-line switches you need (like ``-k`` or ``-K``):

.. code-block:: console

ansible-playbook -k certbot.yml

This will first install ``python3-certbot-nginx`` from the certbot/certbot ppa.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's "ppa"? Is there a link to it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal Package Archive; standard debian mechanism for maintaining an additional package source. In this case, it's certbot's.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will first install ``python3-certbot-nginx`` from the certbot/certbot ppa.
This will first install ``python3-certbot-nginx`` from the certbot/certbot Personal Package Archive.

Then it will create certificates as necessary for each hostname in the ``certbot_hosts`` list.
If a certificate already exists, it will not attempt addition.

Note that ``python3-certbot-nginx`` includes an auto-renewal cronjob.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see this on my server in /var/spool/cron/crontabs/ after running the playbook. Where is it located?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/etc/cron.d/certbot

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

I note that the cron job does not include --nginx flag for certbot -q renew. I guess we will find out if it works for me on May 12 when my current cert is due to expire.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auto-renew cronjob failed. See #136 (comment)



Webserver support
~~~~~~~~~~~~~~~~~

When the nginx role creates a configuration file for a virtual host, it will check TLS hostnames against the ``certbot_hosts`` list.
If the hostname matches, the certbot certificate/key will be used automatically (unless you override this by specifying certificate/key files).

Certificate/key files for certbot are expected to be in ``/etc/letsencrypt/live/HOST_NAME`` or this mechanism will fail when nginx is reloaded after configuration.


Why is this a separate playbook?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As with the ``firewall.yml`` playbook, we want to encourage users to think and research before using the certbot playbook.
*Let's Encrypt* is security software and is not for everyone.
It should be used only with knowledge and deliberation and not as an autopilot choice.

Note in particular that the certbot-nginx support uses root priveleges for both certificate creation and renewal.
Some sysadmins choosing certbot may wish to set up their own creation/renewal systems to avoid this exposure.

Note that even if you never run the certbot playbook, you may still find the webserver setup support useful.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ Plone's Ansible Playbook
multiserver
restart_script
audit
certbot
21 changes: 21 additions & 0 deletions docs/webserver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,27 @@ To use files that already exist on the controlled server, use:
key: /etc/ssl/private/ssl-cert-snakeoil.key
crt: /etc/ssl/certs/ssl-cert-snakeoil.pem

Alternatively, you can use certbot to create and renew certificates.
Certificates are in the usual ``/etc/letsencrypt/live/HOST_NAME`` folders.
If you specify a global ``certbot_hosts`` list variable, then certificates managed by certbot from Let's Encrypt will be used for all matching hosts.

.. code-block:: yaml

certbot_hosts:
- one.mcsmith.org
- two.mcsmith.org

Or if you have the ``inventory_hostname`` variable defined:

.. code-block:: yaml

certbot_hosts:
- "{{ inventory_hostname }}"

Remember, the ``certbot_hosts`` variable must be global, not part of ``webserver_virtualhosts`` list.
Also the ``certificate`` key and items under ``webserver_virtualhosts`` takes precedence over all other certificate management methods.
If you want to use certbot, then remove the ``certificate`` block.


Redirections, etc.
~~~~~~~~~~~~~~~~~~
Expand Down
21 changes: 16 additions & 5 deletions roles/certbot/tasks/Debian.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
---

- name: Add certbot repository
apt_repository:
repo: 'ppa:certbot/certbot'
state: present
- name: Install software-properties-common
package:
name: "software-properties-common"
state: latest

- name: Add universe repository
command: "add-apt-repository universe"

- name: Check for python3-certbot-nginx
command: "apt-cache search python3-certbot-nginx"
register: pcn_present

- name: Add certbot repository if not present
command: "add-apt-repository ppa:certbot/certbot"
when: pcn_present.failed

- name: Update packages via apt
when: ansible_os_family == 'Debian'
Expand All @@ -12,4 +23,4 @@
- name: Install python3-certbot-nginx
package:
name: "python3-certbot-nginx"
state: present
state: latest
5 changes: 5 additions & 0 deletions roles/certbot/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@
- name: "Generate new certificates as necessary"
shell: >
certbot certonly --nginx --email '{{ admin_email }}'
--non-interactive
--agree-tos -d '{{ item }}'
{% if certbot_staging %} --staging {% endif %}
args:
creates: "/etc/letsencrypt/live/{{ item }}/cert.pem"
loop: "{{ certbot_hosts }}"

- name: "Test renewal with a dry run."
shell: >
certbot renew --dry-run --nginx
3 changes: 3 additions & 0 deletions roles/nginx/templates/host.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ server {
{% if item.get('certificate') %}
ssl_certificate {{ item.certificate.crt }};
ssl_certificate_key {{ item.certificate.key }};
{% elif certbot_hosts is defined and item.hostname in certbot_hosts %}
ssl_certificate /etc/letsencrypt/live/{{ item.hostname }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ item.hostname }}/privkey.pem;
{% else %}
ssl_certificate /etc/nginx/ssl/{{ item.hostname }}.crt;
ssl_certificate_key /etc/nginx/ssl/{{ item.hostname }}.key;
Expand Down
56 changes: 56 additions & 0 deletions tests/certbot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
------------------------------------------
Test the certbot playbook for provisioning
------------------------------------------
>>> sample = 'sample-medium.yml'

>>> extras = r"""
... admin_email: [email protected]
... plone_initial_password: admin
... additional_packages:
... - curl
... - lsof
... muninnode_query_ips:
... - 127.0.0.1
... certbot_hosts: []
... """

>>> import subprocess
>>> import sys
>>> import time

Set up local-configure.yml by copying our sample.
Append extras.

>>> with open(sample, 'r') as f:
... with open('local-configure.yml', 'w') as g:
... g.write(f.read() + extras)

Vagrant up

>>> print >> sys.stderr, "Bringing up %s" % box
>>> run("vagrant up %s --provision-with write_vbox_cfg" % box)

Vagrant provision -- unless contraindicated.

>>> if skip_provisioning:
... print >> sys.stderr, "Skipping provisioning"
... else:
... print >> sys.stderr, "Provisioning"
... run("ansible-playbook -i vbox_host.cfg certbot.yml")


And, now run tests against the box.

>>> print >> sys.stderr, "Running tests against box"

Check hostname:

>>> ssh_run('which certbot').strip()
'/usr/bin/certbot'

>>> ssh_run('ls -d /etc/letsencrypt').strip()
'/etc/letsencrypt'

>>> ssh_run('certbot --help | grep "\--nginx"')
' --nginx Use the Nginx plugin for authentication & installation\r\n'