Add mTLS to Nginx
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
Published on System Code Geeks with permission by Emmanouil Gkatziouras, partner at our SCG program. See the original article here: Add mTLS to Nginx Opinions expressed by System Code Geeks contributors are their own. |