Free SSL Certificates with LetsEncrypt and Docker

Everyone loves security, yeah?!

Up until recently, it's cost a fair whack of money to use SSL certificates on your website. This has been a barrier for a lot of smaller website, which subsequently leaves them vulnerable to common attacks such as man in the middle.

Then comes letsencrypt, a free, crowdfunded way to get valid https certificates, like the one used on this website! In order to keep costs down (you know, as its free) they rely a lot on automated scripts and domain validation, which can be a little fiddly. I also have to do this for lots, and lots of microservices and websites that I host - so I wanted to encapsulate all of these concerns into a simple container which was generic, and did it all for me.

docker-nginx-letsencrypt

I created this docker container which is an NGINX reverse proxy, which will front your microservice with a valid HTTPS certificate without you having to do much at all!

Lets take this website, karlstoney.com as an example. The blog is actually a ghost nodejs application, which serves up http on port karlstoney.www.svc.cluster.local:2368. However, I want to expose that to the world on karlstoney.com. I could use the following docker-compose file to do just that:

version: '2'

services:
  nginx:
    image: stono/docker-nginx-letsencrypt 
    restart: always
    volumes:
      - ./certs:/etc/letsencrypt/live
    environment:
      - HOST_WEBSITE1=karlstoney.com,karlstoney.www.svc.cluster.local:2368,default_server
    ports:
      - 443
      - 80

That's it, watch what happens when the container boots on my server:

Generating self signed certificate for karlstoney.com
Generating a 4096 bit RSA private key
................................................++
.............++
writing new private key to 'key.pem'
-----
Writing nginx config for karlstoney.com with an upstream of karlstoney.www.svc.cluster.local:2368
server {
    listen 80;
    server_name karlstoney.com;

    location ~ /.well-known {
        allow all;
        break;
    }
    location / {
        return  301 https://$host$request_uri;
    }
}

server {
    listen 443 default_server;
    server_name karlstoney.com;

    ssl on;
    ssl_certificate           /etc/letsencrypt/live/karlstoney.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/karlstoney.com/key.pem;

    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    access_log            /var/log/nginx/karlstoney.com.access.log;

    set $upstream_endpoint_host_karlstoney http://karlstoney.www.svc.cluster.local:2368;
    resolver 10.37.80.10;
    resolver_timeout 5s;

    location / {
        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        proxy_http_version  1.1;
        proxy_set_header    Upgrade $http_upgrade;
        proxy_set_header    Connection "upgrade";
        proxy_set_header    Host $host;

        proxy_pass          $upstream_endpoint_host_karlstoney;
        proxy_redirect      http://karlstoney.www.svc.cluster.local:2368 https://karlstoney.com;
        proxy_read_timeout  90;
    }
}
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Starting Nginx
2016/12/30 21:06:31 [notice] 26#0: using the "epoll" event method
2016/12/30 21:06:31 [notice] 26#0: nginx/1.11.5
2016/12/30 21:06:31 [notice] 26#0: built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC)
2016/12/30 21:06:31 [notice] 26#0: OS: Linux 4.4.21+
2016/12/30 21:06:31 [notice] 26#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/12/30 21:06:31 [notice] 26#0: start worker processes
2016/12/30 21:06:31 [notice] 26#0: start worker process 28
Nginx started with pid 26
Running simp_le letsencrypt for karlstoney.com
2016-12-30 21:06:34,455:INFO:simp_le:1211: Generating new account key
10.37.64.1 - - [30/Dec/2016:21:06:36 +0000] "GET /.well-known/acme-challenge/h_rKjS1mbLjzs9d0bw6yDfk7M8RBr7_FVkPprVD_wkE HTTP/1.1" 200 87 "-" "python-requests/2.12.4"
2016/12/30 21:06:36 [info] 28#0: *1 client 10.37.64.1 closed keepalive connection
2016-12-30 21:06:36,648:INFO:simp_le:1305: karlstoney.com was successfully self-verified
2016-12-30 21:06:36,861:INFO:simp_le:1313: Generating new certificate private key
10.37.64.1 - - [30/Dec/2016:21:06:37 +0000] "GET /.well-known/acme-challenge/h_rKjS1mbLjzs9d0bw6yDfk7M8RBr7_FVkPprVD_wkE HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
2016-12-30 21:06:39,370:INFO:simp_le:391: Saving account_key.json
2016-12-30 21:06:39,371:INFO:simp_le:391: Saving fullchain.pem
2016-12-30 21:06:39,383:INFO:simp_le:391: Saving key.pem
2016/12/30 21:06:39 [notice] 26#0: signal 1 (SIGHUP) received, reconfiguring
2016/12/30 21:06:39 [notice] 26#0: reconfiguring
2016/12/30 21:06:39 [notice] 26#0: using the "epoll" event method
2016/12/30 21:06:39 [notice] 26#0: start worker processes
2016/12/30 21:06:39 [notice] 26#0: start worker process 40
2016/12/30 21:06:39 [notice] 28#0: gracefully shutting down
2016/12/30 21:06:39 [notice] 28#0: exiting
2016/12/30 21:06:39 [notice] 28#0: exit
2016/12/30 21:06:39 [notice] 26#0: signal 17 (SIGCHLD) received
2016/12/30 21:06:39 [notice] 26#0: worker process 28 exited with code 0
2016/12/30 21:06:39 [notice] 26#0: signal 29 (SIGIO) received

You can view the full repo here