03 - March - 2016

A first look at Let's Encrypt

Post by Andrew A Andrew A

Movements such as HTTPS Everywhere and Google’s initiative to ensure that all search queries and all Gmail actions are secured using HTTPS have challenged the concept that HTTPS is only needed for small sections of sites, such as payment gateways.

More recently, Let’s Encrypt, a new certificate authority has been created to make the generation, installation and renewal of certificates easier than ever. Let’s Encrypt aims to increase HTTPS adoption by providing a service that’s free, automatic, secure and transparent. It was started in 2012 by two Mozilla employees, Josh Aas and Eric Rescorla together with Peter Eckersley at the Electronic Frontier Foundation and J. Alex Halderman at the University of Michigan.

The benefits of using Let’s Encrypt (LE) over traditional certificate providers include:

  • Ease of generation    
    No email responses or DNS changes needed
  • Ease of installation    
    Several ways are provided using LE’s own software
  • Ease of renewal   
    No payments and is performed by LE’s software
  • Already Trusted   
    LE’s intermediate certificates have been cross-signed by IdenTrust
  • Cost           
    The certificates are free to generate and renew

For the above reasons, we decided that Let’s Encrypt would be an excellent fit for our configuration management suite, that uses Ansible.

The process of integrating LE support with our Ansible playbooks would consist of 3 parts:

  1. Investigation into and testing of LE software and dependencies on supported OSs.
  2. Integration of LE within our current ‘web’ Ansible role.
  3. Testing the generation, installation & renewal or certificates using Ansible.

Installing Let’s Encrypt

For maximum portability, we opted to install LE using pip (pip is a package management system used to install and manage software packages written in Python) inside a virtual Python environment which will isolate the LE installation from the rest of the system. This was accomplished using Ansible’s pip module:

- name: Install virtualenv
apt: name=virtualenv state=present
when: letsencrypt is defined letsencrypt == True

- name: Install LE dependencies
pip: virtualenv="/usr/local/share/LE/" virtualenv_site_packages=no name={{ item }} state=latest
with_items:
- setuptools
- pip
when: letsencrypt is defined and letsencrypt == True

- name: Install LE
pip: virtualenv="/usr/local/share/LE/" virtualenv_site_packages=no name=letsencrypt state=latest

Integrating LE into our current web role was slightly more convoluted as we had to account for not only the individual setups of our clients, but also the individual server setups within each client’s estate. For example, in the group_vars inventory file that defines Ixis’ servers, we could have multiple servers with multiple environments for each, such as live and for stage. If we only wanted LE on one vhost, for example, we’d need to take that into account.

Live & stage vhosts for Ixis.co.uk could look like:

vhosts:
- server_name: ixis.co.uk
ssl: False
default_vhost: True
# Aliases for this vhost. Serve this site if HTTP Host: header matches any of these.
alias:
- www.ixis.co.uk

- server_name: stage.ixis.co.uk
ssl: False
default_vhost: True
# Aliases for this vhost. Serve this site if HTTP Host: header matches any of these.
alias:
- www.stage.ixis.co.uk

There are several options for SSL in the above vhost list. SSL could be wanted for the main ixis.co.uk domain but not the alias of www.ixis.co.uk. Similarly, stage could have SSL for the www alias but not for the stage.ixis.co.uk domain.

Authenticators

Let’s Encrypt uses plugins called “authenticators” that are used to obtain the certificates. There are currently 5 authenticators:

  • Apache
  • Nginx
  • Webroot
  • Manual
  • Standalone

The Apache & Nginx authenticators automate the obtaining and installation of the certificates. This would be an ideal solution but the Nginx authenticator is still in an early stage of development and is listed as being experimental.

The webroot authenticator obtains a cert by using the webroot of an already running webserver. This would be ideal as our web Ansible role will already have installed a webserver and the webroots would already be defined.

The manual authenticator provides instructions on how to obtain the certificate manually - not a good option as our goal is automation.

The standalone authenticator uses a standalone webserver to obtain a certificate. This isn’t needed as we’re guaranteed to already have a webserver running in the web role.

We decided to use the webroot authenticator, which automatically creates a file in a directory on your site and tells Let’s Encrypt to check for the existence of that file. Once ownership is confirmed, the certificates are generated. The check is performed using the ACME Protocol and needs to be able to access the path at which the resource is provisioned and is comprised of:

webroot + ".well-known/acme-challenge/" + token value in the challenge

If the letsencrypt variable is set to True, Ansible will allow access to the challenge file (we block access to non-standard paths and filetypes, such as txt, by default) by including a block of code for the webserver (in this example, Nginx):

location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/{{ item.server_name }};
}

The webroot authenticator step looks like:

- name: Attempt to get the certificate using the webroot authenticator
command: "/usr/local/share/LE/bin/letsencrypt --agree-tos --email hosting@ixis.co.uk --keep --expand --text {% for domain in item.letsencrypt_domains %}-d {{ domain }} {% endfor %} -a webroot --webroot-path {{ item.letsencrypt_webroot }} certonly"
with_items: vhosts
when: item.letsencrypt is defined and item.letsencrypt == True
notify: reload nginx

The above command will get the list of vhosts and run the webroot authenticator only on vhosts where the letsencrypt variable is set to True. There are several switches that are passed into the command:

--agree-tos Agree to LE’s terms & conditions
--email Not required but useful for reminders & recovery
--keep Renew the certificate it there are fewer than X days left until expiration date. 10 is default.
--expand If an existing cert covers some subset of the requested names, expand and replace it with the additional names
--text Use the text output instead of the UI
-a webroot Use the webroot authenticator (and path of webroot)
certonly Only generate the certs, don’t install automatically

All of the above switches help with the automatic regular run of Ansible that keeps server configs in parity with the version kept in source control. The --keep switch means that when there are less than 10 days left before expiry, the certificates will automatically renew on the next Ansible run.

The lifetime of a Let’s Encrypt certificate is 90 days. This number was chosen as it limits damage from key compromise and mis-issuance as certificates are valid for a shorter period of time and it encourage automation - manually updating certificates every 90 days would be a laborious task.

Using the example with the --keep switch in the table above, the certificate would be automatically renewed 80 days after being issued.

Integration with existing vhosts

In order to integrate our vhosts with the webroot authenticator command, some variables need adding to the vhosts. In the example above, the authenticator command needs:

  • item.letsencrypt_domains
  • item.letsencrypt_webroot
  • item.letsencrypt

item represents each item within a list - in this case, each item is an item in the vhost list
letsencrypt is a boolean variable and tells Ansible if a vhost will be using LE
letsencrypt_domains is a list of domains that will be looped through and sent
letsencrypt_webroot is the root directory of the website

If SSL was needed for www.stage.ixis.co.uk and stage.ixis.co.uk, the vhost might look like:

vhosts:
- server_name: stage.ixis.co.uk
ssl: True
letsencrypt: True
default_vhost: True
letsencrypt_webroot: /var/www/ixis.co.uk
# Aliases for this vhost. Serve this site if HTTP Host: header matches any of these.
alias:
- www.stage.ixis.co.uk
letsencrypt_domains:
- stage.ixis.co.uk
- www.stage.ixis.co.uk

Test

Running the web Ansible role on the server provides the following response:

TASK: [webserver | Attempt to get the certificate using the webroot authenticator] ***
changed: [stage.ixis.co.uk] => (item={'server_name': 'stage.ixis.co.uk', 'letsencrypt_domains': ['stage.ixis.co.uk','www.stage.ixis.co.uk'], 'ssl': True, 'letsencrypt': True, 'alias': ['www.stage.ixis.co.uk'], 'letsencrypt_webroot': '/var/www/ixis.co.uk', 'default_vhost': True})

Success!

Let’s Encrypt’s authenticators make generating and installing certificates very simple. In order to prove identity with other providers, you would be asked to make DNS changes or to manually upload files onto a webserver. Let’s Encrypt manages everything for you.

Even renewing a certificate is easy. If you don’t have an automated system running against your servers, a simple cron job can be setup to run that will automatically renew all certificates that are within the renewal window.

We’ll be rolling out Let’s Encrypt for our clients shortly, so please get in touch if you’d like to discuss having SSL on your site.

(All code examples, domain names & server names do not reflect in-use code or server configuration and are provided only as examples to show how Let’s Encrypt could be integrated)

Andrew A

Andrew A

Senior Systems Administrator

Comments

Great tutorial Andrew, but all those codes make it look complicated to install a simple certificate. A much easier way is to use Cloudways platform. Their platform allows you to enable or disable https on your website with just a click of a button. There is really no need to go through CLI, unless you have a sub-domain.

Azaz Qadir

Add new comment

Share this article

Our thoughts

Let's work together

Get in touch and find out how we can empower your organisation.
Back to top