Tech tip: deploy NGINX in container with client certificate verification
In this post I’m going to show how prepare and deploy certificate and CA for web server NGINX and deploy client certificate to authorize web clients to access in a more safety way, restful API, SOAP or wathever is running on HTTPs. All is realized using docker and docker-compose to bring together all pieces of this chain. This could be useful in mobile development to authorize (or revoke) mobile application accessing to web services and eliminate the problem of unauthorized connection (like bot and robot).
The following instructions are intend for a simple deployment; for large deployment an internal CA, an automatic software distribution and docker-swarm or kube are quite mandatory to don’t make sysadmin crazy (…and trust me! this is not a big deal )
You could find all files included in this example here: https://github.com/linoproject/blog/tree/master/nginx_certificate
Preparing self certificate chain
Note: all the following commands must issue in privileged mode (use sudo in Ubuntu and Mac)
Preparing the CA:
1 2 |
openssl genrsa -aes256 -out ca.key 4096 openssl req -new -x509 -key ca.key -days 365 -sha256 -subj "/C=IT/ST=Italy/L=Cremona/O=RD/OU=RD/CN=test.local" -out ca.crt |
Preparing Server certificate (private key included):
1 2 3 4 |
openssl genrsa -aes256 -out server.key 4096 openssl req -subj "/CN=test.local" -sha256 -new -key server.key -out server.csr echo "subjectAltName = DNS:test.local" > extfile.cnf openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extfile extfile.cnf |
finally it’s time to create client cert (similar to server cert):
1 2 3 4 |
openssl genrsa -out client.key 4096 openssl req -subj "/CN=client" -new -key client.key -out client.csr echo "extendedKeyUsage = clientAuth" > extfile2.cnf openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -extfile extfile2.cnf |
NGNIX in a Dockerfile
The best way to deploy NGINX is in container with Docker, so let’s prepare a directory with this files:
- Dockerfile
- server.key
- server.crt
- ca.crt
- nginx.conf
Here the content of my Dockerfile for NGINX:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
FROM ubuntu:16.04 MAINTAINER Lino Telera RUN apt-get update #Tools RUN apt-get install -y nano wget dialog net-tools # Install Nginx RUN apt-get install -y nginx # Remove the default Nginx configuration file and add cert directory RUN rm -v /etc/nginx/nginx.conf # Add ngnix config file ADD nginx.conf /etc/nginx/ # Add certifcate (crt and key) ADD ca.crt /etc/nginx/certs/ ADD server.crt /etc/nginx/certs/ ADD server.key /etc/nginx/certs/ RUN echo "daemon off;" >> /etc/nginx/nginx.conf RUN chown -R www-data:www-data /var/lib/nginx VOLUME ["/etc/nginx/sites-enabled", "/etc/nginx/certs", "/etc/nginx/conf.d", "/var/log/nginx", "/var/www/html"] # Expose ports 80 to redirect EXPOSE 80 443 # Finally execute ngnix CMD ["nginx"] |
The complete Dockerfile could be downloaded here: https://github.com/linoproject/blog/blob/master/nginx_certificate/Dockerfile
NGINX config file explained
NGINX in the config file requires an instruction to “force” pairing certificate: ssl_verify_client. Check the official documentation for availabe instructions/options.
Here is the instructions for server section:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
server { listen 443; server_name test.local; location / { proxy_pass <a href="http://backend/">http://backend/</a>; proxy_set_header X-Real-IP $remote_addr; } ssl on; ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; ssl_client_certificate /etc/nginx/certs/ca.crt; ssl_verify_client on; 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; } |
Note: the FQDN of the service must be specified in in the server_name instruction; in this case you must substitute “test.local” with your site.
You could find the full ngnix.conf here: https://github.com/linoproject/blog/blob/master/nginx_certificate/nginx.conf
An example with a backend
To complete the proxy chain a backend service must be instantiated linking with the nginx container. In my example I’m using a simple webserver container ( from linoproject/web_apachephp_mysql image ) which has a simple html file that expose my test service. So the compose file looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
version: "2" services: ngnix_1: build: . ports: - "443:443" - "80:80" links: - "backend" backend: image: linoproject/web_apachephp_mysql volumes: - /<localpath>/test:/var/www/html/ |
Here the complete file: https://github.com/linoproject/blog/blob/master/nginx_certificate/docker-compose.yml
A test with curl
To test the correct functionality you could simply use curl after the creation of pkcs12 certificate chain including private key and certificate. In the curl option, after certificate file indication, you must specify the certificate passphrase you’ve used during client certificate preparation.
1 2 3 |
openssl pkcs12 -export -inkey client.key -in client.crt -name test-curl-client-side -out client.p12 curl -k --cert client.p12:password https://test.local/test.html |
Now it’s time to build your mobile application.
That’s all folks! Enjoy
Sources
http://nategood.com/client-side-certificate-authentication-in-ngi
http://nginx.org/en/docs/http/ngx_http_ssl_module.html