Forwarding Client Certificates with NGINX Ingress
Ray Chuan Tay
Introduction
If you are building an application service, there are several ways to authenticate an incoming request from another application. Using API keys is one way. Another is to have the application make the request (which we’ll refer to in the rest of this guide as the client) and send a certificate to authenticate itself. While we might commonly associate the TLS protocol with certificates being sent by the server-side of the request and authenticating them, such as when we browse to a https://
website in our browser, TLS also supports clients sending certificates 1.
What advantage do client certificates offer compared to API keys? As Cloudflare2 puts it,
Note: [C]lient certificates offer a layer of security that API keys cannot provide. If an API key gets compromised mid-connection, it can be reused to fire its own valid, trusted requests to the backend infrastructure. However, the private key of the client certificate is used to create a digital signature in every TLS connection, and so even if the certificate is sniffed mid-connection, new requests can’t be instantiated with it.
In this post, we’ll look at how to configure NGINX Ingress to forward or pass-through client certificates to your application service without NGINX Ingress performing client certificate validation of its own. This is useful if you already have your client certificate validation logic in your application, and you’d like to deploy that application on a Kubernetes cluster using NGINX Ingress.
Kubernetes Setup
-
Place your certificate authority (CA) certificate (if you have just one) or concatenate the CA certificates (if you have multiple) into a file named
ca.crt
.# if single CA certificate cat certificate.crt > ca.crt # if multiple CA certificates cat certificate1.crt certificate2.crt > ca.crt
-
Create a secret containing the CA certificate(s). Take note that the file in the secret containing the CA certificate(s) must be named
ca.crt
. On the other hand, you may name the secret however you wish.kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt
For the rest of the guide, we’ll assume this secret is named
ca-secret
in the namespacedefault
. -
Add the following annotations on your NGINX Ingress resource:
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: # Specify the secret containing CA certificates in the form <namespace>/<secret name> nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret" # Specify that certificates are to be passed on nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" # Do not fail request if no or an otherwise invalid certificate is provided nginx.ingress.kubernetes.io/auth-tls-verify-client: "optional" name: my-ingress namespace: default
With this configuration, if the client does not send certificates in their request, NGINX Ingress still allows the request to your application. Whereas with the following configuration, the client must send certificates in their requests to your application. When the client does not send certificates in their requests, NGINX Ingress responds with a 400 Bad Request error.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
# Specify the secret containing CA certificates in the form <namespace>/<secret name>
nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
# Specify that certificates are to be passed on
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
name: my-ingress
namespace: default
Application Setup
By default, NGINX Ingress places the URL-encoded PEM client certificate in the ssl-client-cert
header, for example -----BEGIN%20CERTIFICATE-----%0A...---END%20CERTIFICATE-----%0A
.
This corresponds to the NGINX variable $ssl_client_escaped_cert
3.
Closing
For other values to auth-tls-verify-client
, as well as additional verification configurations such as validation depth, refer to the NGINX Ingress documentation on client certificate authentication.