Securing Wikilogic with HTTPS

By IainJMcCallum, July 15, 2017

A quick summary of our set up

Note this is the set up at the time of writing

  • All connections are run through an nginx reverse proxy server running on Ubuntu, which also serves the static files for our react app.
  • That app gets data from our API, an expressjs server which sits behind the nginx server.
  • Finally there’s a third server which runs our database but that’s not really of interest for this post.

These three services run in Docker containers, each has it’s own Dockerfile, all are linked up using docker-compose, and the deployment to our VPS is managed by docker machine.

Where to start

The first thing I did was to plug in our site to the Mozilla Observatory. We received an “F”, worse actually, we got 0/100. Zero! At this stage, I’m not really surprised, but 0!? Yikes. They also give a lot of recommendations but going to HTTPS is clearly the number 1 priority. They have some nginx configuration recommendations so I added those in and began replacing the placeholders with Wikilogic specific info… Except it’s looking for SSL certificates.

Let’s Encrypt

At this stage we could not go to HTTPS without the fantastic service these people provide: free certificates! They even make it easy (relatively). Docker did make this mini project a little more interesting. Have a quick look at their site. If you’re new to setting up HTTPS, you’re now in the same spot I was. Below I’ll go through my attempts to get it working, try to explain the failures, and hopefully manage to share something worth your while.

Installing Certbot

Clearly we have shell access. This brings you to Certbot. A thing you install on your server which will enable HTTPS with magic. My first attempt was to add the installation commands to the nginx server’s Dockerfile, turns out it has to run on your actual server*.

*OK “has” is a bit strong, more like “recommended” to install it on your server and not in a Docker container. There are docker images that seem to have certbot and other Let’s Encrypt utilities on them, but we’re deep enough into our current Docker set up that this option seemed like a rabbit hole I didn’t fancy descending into.

So I installed certbot on our server, and ran certbot --nginx --dry-run. The --dry-run bit is needed as they ration you’re attempts to get real certificates. So avoid hitting that limit! Could be awkward for a while.

Running Certbot

So in running it, certbot --nginx --dry-run, it asked a few easy questions – and one hard one. A web root. Certbot needs to add stuff to the server to prove that this is really the server in control of your domain. But we’re running from a Docker container that is almost completely isolated from the host server so there’s no webroot to point at. To solve this, I tweaked the nginx dockerfile to set the web root as a volume. In the docker-compose.yml file I hooked that volume up to the static files on the host server. And now we can point certbot to that directory.

Bonus feature! Previously the static files were added to the Docker image through a COPY in the nginx Dockerfile. So to deploy any static file updates we had to rebuild the image. Now that it’s set up with a VOLUME, we can just push! Brilliant.

While working all this out, for some reason I can’t recall, I latched onto certbot-auto. So that’s now installed! You’ll see the commands change from certbot ... to ./certbot-auto ....

Lets’ give it a spin!

./certbot-auto --nginx --dry-run failed – could not restart nginx… A little reading, it’s going to try and update our nginx configuration. That worries me as I’ve already run through the nginx configuration recommendations by the Mozilla Observatory. Fortunatly it turns out we can tell certbot to leave our configuration alone! Replace --nginx with certonly.

Also, I found out we can pass in arguments for the webroot / domain.

./certbot-auto certonly --dry-run --webroot -w /root/react-app/dist -d failed – interestingly the error message spat back some HTML for a 401 not authorized page… ah yes, we had simple authentication set up in the nginx configuration to keep out the spam. Turn it off, try again… It worked! Now, with a little trepidation, lets take out the --dry-run.

./certbot-auto certonly --webroot -w /root/react-app/dist -d WE HAVE CERTIFICATES!! They have been added to the host at /etc/letsencrypt. Lets immediately pull down a copy of those docker-machine scp -r wikilogic:/etc/letsencrypt ./react-app. (react-app is where I’m currently keeping the nginx configuration, that will change, just noting it so you know what’s going on there). And now have a cup of tea. Feels like half time.

Getting the Let’s Encrypt certificates into the Docker container

Now that we have the certs, they need to be added to the container for nginx to reference. Again this should be done through volumes so when we come to renewing them in the future (every 90 days) we can do so with the minimum of disruption. Somewhat arbitrarily I chose /root/SSL as the volume in the container. In the compose file pointed I pointed that volume to /etc/letsencrypt/live/ which is where they landed after certbot did it’s thing. Finally, the nginx config can now be pointed to /root/SSL. At last we’re solving the first issue from the “Where to start” section above!

Note I’m back on my local here to test it out

Run a build aaaaand fail – nginx can find no such files. Weird – I can see them and I know the volume is good. It’s just that the files aren’t appearing… Lets try a COPY from the docker file to rule out VOLUME doing something odd. Run a build aaaand fail. hmm. Next up, copy the whole letsencrypt directory. run a build aaaand PASS! It worked! Lets move that up to live: build aaaaand fail – docker can’t COPY /etc/letsencrypt/. Alright, back to the VOLUME set up but this time sharing the entire letsencrypt… IT WORKED!!!

I have a hunch that the files in /etc/letsencrypt/live/ are shortcuts / aliases / symlinks (I don’t know enough to know what the correct term is for that but hopefully you get the idea) to /etc/letsencrypt/archive/

We’re up and running on HTTPS! That was one satisfying Saturday morning. Lets run through Mozilla Observatory again and see if we get a better grade… “F”. Really!? At least we did get a few more points though. If you recall at the start we got 0/100. This time – 15/100! Only 85 more to go – if that’s even practical. But there you go – I’m calling today a win!