Automatic SSL Certification with LetsEncrypt (and Bind9 zones)
Update: Letsencrypt is live!!! Since Fri. Dec 4th, 2015, I have valid SSL certificates :-) (Until 2016-03-03 apparently, then I'll just have to rerun the script at the bottom of this article.)
Since I launched this VPS hosting this blog, I've only used self-signed certificates. Partly because of the price of certificated certificates, partly because of the setup difficulty. But today, LetsEncrypt is about to go beta public (December 3, 2015), so it's time to get a valid HTTPS connection !
For quite a while, I thought that my setup required a "wildcard" certificate (*.0x972.info), to cover the different subdomains I manage, as well as the ones I'll add tomorrow. So I was very disappointed when I read that LetsEncrypt would not allow them! But then I checked how LetsEncrypt works: a simple shell command :-) That means that I can easily create a script that list my subdomains and pass it to letsencrypt. And tomorrow with new subdomains, I'll just relaunch the script.
letsencrypt -d example.com auth
Listing VPS subdomains
So, where can I get the list of the subdomains currently setup? (I use AlternC to manage my VPS) I should be able to query the database to that ... but I could find where AlternC stores that function. I found another quick and easy solution: with the DNS configuration files! It makes sense :-)
$ ls /var/lib/alternc/bind/zones/ pouget.me 0x972.info $ sudo cat /var/lib/alternc/bind/zones/0x972.info .... @ IN A 62.4.19.144 blog IN A 62.4.19.144 www IN A 62.4.19.144
With a bit of bash-script around it, we get:
get_subdomains() { domain=$1 cat $BIND_ZONES/$domain \ | grep "IN A" \ | cut -d" " -f1 \ | while read subdomain do if [[ $subdomain == '@' ]]; then echo $domain else echo $subdomain.$domain fi done }
Configuring LetsEncrypt
LetsEncrypt generates SSL certificates that authenticates the website you're communicating with, and encrypts the communication channel between the webserver and your computer. So the first step of the generation process is to make sure that the certificate is delivered to the website owner. To that purpose, LetsEncrypt uses a challenge/response protocol: the certification server tries to access an URL on the domain to certified: https://blog.0x972.info/.well-known/acme-challenge/$CHALLENGE, and the webserver should answer that request with the right answer, that it's the only one to know.
LetsEncrypt fully automates this process, but it needs help. The default method asks to stop any webserver listening on port :80, and starts its own server. This method means that you need to shutdown your webserver during the certificate generation. For a one-shot that may not be impossible, but LetsEncrypt certificates are only valid 90 days, so you have to renew it ~every two months. So we need to find a better way...
... and the solution already exists, it's called the webroot authenticator. Instead of letting LetsEncrypt starts is own webserver, you give it a path where it will store its challenge-response tokens, and you're in charge of putting it online. I did it this way:
$ cat /etc/apache2/conf.d/letsencrypt.conf Alias /.well-known /var/www/path/to/letsencrypt/.well-known <Directory "/var/www/path/to/letsencrypt/.well-known"> AllowOverride All </Directory>
I tell Apache to create on every virtual host an alias directory, named /.well-known that points to /var/www/path/to/letsencrypt/.well-known. This directory has to be reachable and readable by Apache, and LetsEncrypt needs a read-write access (this part is easy, you run it as root !).
Then, just run LetsEncrypt with the following command (not that --webroot-path is not exactly the alias path):
sudo letsencrypt $DOMAINS auth --email $EMAIL -a webroot --webroot-path /var/www/path/to/letsencrypt --renew-by-default
Configuring Apache
LetEncrypt put everything you need into /etc/letsencrypt/live/$DOMAIN:
$ sudo ls /etc/letsencrypt/live/0x972.info cert.pem chain.pem fullchain.pem privkey.pem
In Apache configuration, you'll need to add the following lines, either in the global configuration or in the virtual host parts:
SSLEngine on SSLCaCertificatePath /etc/ssl/certs # not part of LetsEncrypt SSLCertificateFile /etc/letsencrypt/live/0x972.info/cert.pem SSLCertificateKeyFile etc/letsencrypt/live/0x972.info/privkey.pem SSLCertificateChainFile etc/letsencrypt/live/0x972.info/chain.pem
Finally reload Apache and your certificate should be live! (These certificates are not valid, don't forget that, you'll have to regenerate them after Dec, 3rd.)
Automating Everything
Finally, we need to script all of that for the automatic renewal. Nothing to to in Apache, the alias can stay here. I just have to skip some of the subdomains of the DNS that I don't use anymore:
#! /bin/bash # Make sure only root can run our script if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" 1>&2 exit 1 fi TO_SKIP=("to_skip.0x972.info" "to_skip_2.0x972.info") BIND_ZONES=/var/lib/alternc/bind/zones/ WEBROOT_PATH=/var/www/path/to/letsencrypt letsencrypt() { letsencrypt $* auth --agree-dev-preview --renew-by-default -a webroot --webroot-path $WEBROOT_PATH } get_subdomains() { domain=$1 cat $BIND_ZONES/$domain \ | grep "IN A" \ | cut -d" " -f1 \ | while read subdomain do if [[ $subdomain == '@' ]]; then echo $domain else echo $subdomain.$domain fi done } get_all_subdomains() { for domain in $(ls $BIND_ZONES) do get_subdomains $domain | while read subdomain do echo $subdomain done done } subdomains_to_letsencrypt_opt() { while read subdom do if [[ " ${TO_SKIP[@]} " =~ " ${subdom} " ]] then continue fi echo "-d $subdom" done } letsencrypt $(get_all_subdomains | subdomains_to_letsencrypt_opt)
Tested in Debian GNU/Linux 7 (wheezy).
ahmed :
hi
do i have to make any adjustment to my bind9 zone config