In this episode of Lightboard Lessons, I describe the Let's Encrypt automated certificate generation process and how to customize a hook script to automate the challenges and and certificate deployment.

What is Let's Encrypt?

Let’s Encrypt is a certificate authority available in beta since Dec 2015, but launched publicly in April 2016. To steal the opening paragraph from their "Getting Started" page:

Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate can be. Let’s Encrypt automates away the pain and lets site operators turn on and manage HTTPS with simple commands. Using Let’s Encrypt is free, so there is no need to arrange payment.

Free? Say what? Yep...free. And automated, which is right up our alley here at DevCentral!

How Does Let's Encrypt Work?

I’ll defer to their how it works page for details, but basically the steps are:

  1. Define a list of domains you want to secure
  2. Your client reaches out to the Let’s Encrypt servers to initiate a challenge for those domains.
  3. The servers will issue an http or dns challenge based on your request
  4. You need to place a file on your web server or a txt record in the dns zone file with that challenge information.
  5. The servers will validate your challenge information and notify you
  6. You will clean up your challenge files or txt records
  7. The servers will issue the certificate and certificate chain to you
  8. You now have the key, cert, and chain, and can deploy to your web servers or in our case, to the BIG-IP

 An overview of that process for the dns challenge is shown in the image below.

Let's Encrypt Overview

Components

As discussed in the video, there are a few components to this solution. Let’s Encrypt has a staging server you can use until you have everything working properly, which I’d recommend so you don’t get rate limited on production requesting the same domains repeatedly. This is set in the config.sh file.

  • letsencrypt.sh - this is the Let’s Encrypt client application, which is unaltered from lukas2511’s github repository. This is the central nervous system of the operation
  • config files - these are helper files for the shell script
    • config.sh - this is the config file for the letsencrypt.sh script. Staging server, local directories, key sizes, challenge type, etc are defined here.
    • creds.json - I used this file to store my BIG-IP credentials and DNS provider API keys so when I do demoes I am not sharing this information with the world.
    • domains.txt - this is where you put the domains you want to sign with Let’s Encrypt. If you want your root domain and a www alias, you’d put these on the same line.
  • le_hook.py - this is the python hook script I wrote for letsencrypt.sh to hand off to manage the dns challenges and the certificate deployment.

Running the script, I added (hook) to the logging output so you can see who has control, the shell script or the hook script at any particular time.

jrahm@ubuntu:/var/tmp/le$ ./letsencrypt.sh -c -f /var/tmp/le/config/config.sh
./letsencrypt.sh: line 1: h: command not found
# INFO: Using main config file /var/tmp/le/config/config.sh
# INFO: Using additional config file /var/tmp/le/config/config.sh
Processing rahmen-empire.net with alternative names: www.rahmen-empire.net
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for rahmen-empire.net...
 + Requesting challenge for www.rahmen-empire.net...
 + (hook) executing: deploy_challenge
 + (hook) TXT record created: _acme-challenge.rahmen-empire.net => tv10Ta53-7yKiy8jLTQCBtx7-ixoLD15JQYK1TH3Ick
 + (hook) Result: {u'message': u'Command Successful', u'code': 100}
 + (hook) Settling down for 10s...
 + (hook) All challenge records found!
 + Responding to challenge for rahmen-empire.net...
 + (hook) executing: clean_challenge
 + (hook) TXT record deleted: 254547326
 + (hook) Result: {u'message': u'Command Successful', u'code': 100}
 + Challenge is valid!
 + (hook) executing: deploy_challenge
 + (hook) TXT record created: _acme-challenge.www.rahmen-empire.net => LFuj9hnWQiSpZc2-jyS0E8l9p_bt7nvUtK39EUu3Xlw
 + (hook) Result: {u'message': u'Command Successful', u'code': 100}
 + (hook) Settling down for 10s...
 + (hook) All challenge records found!
 + Responding to challenge for www.rahmen-empire.net...
 + (hook) executing: clean_challenge
 + (hook) TXT record deleted: 254547327
 + (hook) Result: {u'message': u'Command Successful', u'code': 100}
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + (hook) executing: deploy_cert
 + (hook) New Certificate/Key created.
INFO:__main__: + (hook) New Certificate/Key created.
 + Done!

And if we take a look at the directory where files are dumped, we can see the key, the cert, and the chain are ready for us! Note that each time certs are generated the symlinks will update so the old files will still be available.

Cert Directory Lights

If you run it again, you’ll see that it will not even reach out to the Let’s Encrypt servers because not enough time has expired.

jrahm@ubuntu:/var/tmp/le$ ./letsencrypt.sh -c -f /var/tmp/le/config/config.sh
./letsencrypt.sh: line 1: h: command not found
# INFO: Using main config file /var/tmp/le/config/config.sh
# INFO: Using additional config file /var/tmp/le/config/config.sh
Processing rahmen-empire.net with alternative names: www.rahmen-empire.net
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Nov  7 13:29:00 2016 GMT (Longer than 30 days). Skipping renew!
 + (hook) executing: unchanged_cert
 + (hook) No changes necessary.

Now we can look at the BIG-IP and see the key/cert/chain in place. Note that the Fake LE Intermediate is due to the staging server I used, it would not show that if I used production.

Let's Encrypt Certs on BIG-IP

And finally, the clientssl profile created and ready for use.

Let's Encrypt Certs - clientssl profile

You can take this the next step forward and apply the profile to the virtual server, but I’ll leave that to you!

Resources

This project is hosted here in our github repositories. I will be clean up the hook script once my pull requests hit production release for f5-common-python.

The links below are the resources I used to reference or build upon for my solution.