Sunday, December 24, 2017

Nginx + Docker - Client Side Certificate Auth

Every so often, a website will find itself needing to limit the website/server access to specific users in a way that is more secure than a simple username and password. A typical way of doing this is to create a ACL (Access contorl list) that helps to identify who has access. But many ACL schemes work based on the IP address of the device accessing the server, this method is great if the client's IP address doen't change often, however, the minute that IP addresses start changing, an IP-based ACL becomes a nightmare for both administration and maintenance.

To address this issue, a "client-side SSL certificate" authentication mechanism is introduced to authenticate user access to the server website. The client certificate is managed on a per-user basis by a CA (Central Certification Authority) and can be revoked at any time. This blog will walk you though the Docker implementation of a Nginx "client-side SSL certificate" authentication.


  • A Linux server with Docker engine installed. (preferably Ubuntu 17.04)
  • git
  • openssl

Creating and Signing Your Certs

If you already have your CA keys and CA certs, that's awesome, but in case you don't, I will quickly show you how to create a private one.
1. Create the CA Key and Certificate for signing Client Certs
# openssl genrsa  -out ca.key 2048
# openssl req -new -x509 -days 365 -key ca.key -out ca.crt

2. Create the Server Key, CSR, and Certificate
# openssl genrsa -out server.key 2048
# openssl req -new -key server.key -out server.csr

3. Self signing our own server cert here, DO NOT self signing your cert for prod!
# openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

4. Create the Client Key and CSR
# openssl genrsa -out client.key 2048
# openssl req -new -key client.key -out client.csr

5. Sign the client certificate with our CA cert.
# openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

Checkout my modified version of Nginx-Proxy Docker container:

Nginx-Proxy Docker Container

Create a "nginx-proxy" Docker network

# docker network create nginx-proxy-public

Import all the certificates:

Go into the nginx-proxy folder:
# cp ca.crt volumes/certs/
# cp server.key volumes/certs/
# cp server.key volumes/certs/

The "nginx.tmpl" file should auto detect these certificates and generate properate server configurations.

Start nginx-proxy container:

# docker-compose up -d

Check the Nginx is up and running

# docker-compose ps
pixelcloud-nginx_proxy-docker_gen     /usr/local/bin/docker-gen  ...   Up
pixelcloud-nginx_proxy-lets_encrypt   /bin/bash /app/entrypoint. ...   Up
pixelcloud-nginx_proxy-nginx          nginx                            Up>443/tcp,>80/tcp

Now your Nginx Proxy network is ready for "client side" SSL certificate authentication.

The line "195 ssl_verify_client on;" in "nginx.tmpl" is the main technique behind this. This tells nginx to verify to SSL certificate.

Now after this step, any other Docker containers you spin up and join the "nginx-proxy-public" will verify the client connections using "client side SSL certificate".

If the client doesn't have the client SSL certs imported into their brower when browsing the website, it will receive a "400 Bad Request" images like the following:

No comments: