Previously we added ssl to an Nginx server. On this example we shall enhance our security by adding mTLS to Nginx.

Apart from encrypting the traffic between client and server, SSL is also a way for the client to make sure that the server connecting to, is a trusted source.

On the other hand mTLS is a way for the server to ensure that the client is a trusted one. The client does accept the SSL connection to the server however it has to present to the server a certificate signed from an authority that the Server accepts. This way the Server, by validating the certificate the client presents can allow the connection.

More or less we shall build upon the previous example. The ssl certificates shall be the same, however we shall add the configuration for mtls.

The server ssl creation.

  mkdir certs    cd certs    openssl genrsa -des3 -out ca.key 4096  #Remove passphrase for example purposes  openssl rsa -in ca.key -out ca.key  openssl req -new -x509 -days 3650 -key ca.key -subj "/CN=*.your.hostname" -out ca.crt    printf test > passphrase.txt  openssl genrsa -des3 -passout file:passphrase.txt -out server.key 2048  openssl req -new -passin file:passphrase.txt -key server.key -subj "/CN=*.your.hostname" -out server.csr    openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt  

The above is sufficient to secure out Nginx with SSL. So let's create the mTLS certificates for the clients.
In order to create a certificate for mTLS we need a certificate authority. For convenience the certificate authority will be the same as the one we generated on the previous example.

  printf test > client_passphrase.txt  openssl genrsa -des3 -passout file:client_passphrase.txt -out client.key 2048  openssl rsa -passin file:client_passphrase.txt -in client.key -out client.key  openssl req -new -key client.key -subj "/CN=*.client.hostname" -out client.csr    ##Sign the certificate with the certificate authority  openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt    

Take note that the client common name needs to be different from the server's certs common name, or else your request will be reject.

So we have our client certificate generated.
The next step is to configure Nginx to force mTLS connections from a specific authority

  server {  error_log /var/log/nginx/error.log debug;      listen 443 ssl;      server_name  test.your.hostname;      ssl_password_file /etc/nginx/certs/password;      ssl_certificate /etc/nginx/certs/tls.crt;      ssl_certificate_key /etc/nginx/certs/tls.key;        ssl_client_certificate /etc/nginx/mtls/ca.crt;      ssl_verify_client on;      ssl_verify_depth  3;        ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;        location / {      }    }  

By using the ssl_client_certificate we point to the certificate authority that the client certificates should be signed from.
By using the ssl_verify_client as on, we enforce mTLS connections.

Since we have all certificates generated let's spin up the Nginx server using docker.

  docker run --rm --name mtls-nginx -p 443:443 -v $(pwd)/certs/ca.crt:/etc/nginx/mtls/ca.crt -v $(pwd)/certs/server.key:/etc/nginx/certs/tls.key -v $(pwd)/certs/server.crt:/etc/nginx/certs/tls.crt -v $(pwd)/nginx.mtls.conf:/etc/nginx/conf.d/nginx.conf -v $(pwd)/certs/passphrase.txt:/etc/nginx/certs/password nginx  

Our server is up and running. Let's try to do a request using curl without using any client certificates.

  curl https://localhost/ --insecure  

The result shall be

  <html>  <head><title>400 No required SSL certificate was sent</title></head>  <body>  <center><h1>400 Bad Request</h1></center>  <center>No required SSL certificate was sent</center>  <hr><center>nginx/1.21.3</center>  </body>  </html>  

As expected our request is rejected.
Let's use the client certificates we generated from the expected certificate authority.

  curl --key certs/client.key --cert certs/client.crt https://127.0.0.1 --insecure  
  <html>  <head><title>404 Not Found</title></head>  <body>  <center><h1>404 Not Found</h1></center>  <hr><center>nginx/1.21.3</center>  </body>  </html>  

The connection was established and the client could connect to the Nginx instance.

Let's put them all together

  mkdir certs    cd certs    openssl genrsa -des3 -out ca.key 4096  #Remove passphrase for example purposes  openssl rsa -in ca.key -out ca.key  openssl req -new -x509 -days 3650 -key ca.key -subj "/CN=*.your.hostname" -out ca.crt    printf test > passphrase.txt  openssl genrsa -des3 -passout file:passphrase.txt -out server.key 2048  openssl req -new -passin file:passphrase.txt -key server.key -subj "/CN=*.your.hostname" -out server.csr    openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt    printf test > client_passphrase.txt  openssl genrsa -des3 -passout file:client_passphrase.txt -out client.key 2048  openssl rsa -passin file:client_passphrase.txt -in client.key -out client.key  openssl req -new -key client.key -subj "/CN=*.client.hostname" -out client.csr    ##Sign the certificate with the certificate authority  openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt    cd ../    docker run --rm --name mtls-nginx -p 443:443 -v $(pwd)/certs/ca.crt:/etc/nginx/mtls/ca.crt -v $(pwd)/certs/server.key:/etc/nginx/certs/tls.key -v $(pwd)/certs/server.crt:/etc/nginx/certs/tls.crt -v $(pwd)/nginx.mtls.conf:/etc/nginx/conf.d/nginx.conf -v $(pwd)/certs/passphrase.txt:/etc/nginx/certs/password nginx  

You can find the code on github


This post is ad-supported