mTLS is a widely used for securing sensitive data the world over. It is dependent on that both sides of a communication trust each other before a transaction can happen. The trust is based on certificates and can be devided into two parts, one part where the clients needs to trust the servers presented certificate and another part where the server needs to trust the clients presented certificate. We are now going to see an example of how to set this up in Kubernetes
All certificates are going to be self-signed in this example, regular certificates from trusted sources like Thwate, GlobalSign and many others will also work.
For Kubernetes I will use Minikube with the Ingress addon:
minikube addons enable ingress
1. First we need a server certificate to present to any client wanting to connect
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt -subj "/CN=test.localdev.me/O=test.localdev.me"
This should give you two files, a server.key and a server.crt file with the private key and the certificate to present to clients trying to connect.
NOTE: the “domain” test.localdev.me will normally return 127.0.0.1 automatically which makes it perfect to use in cases like this
2. Lets add the certificate to the cluster via a Secret and the special type tls
kubectl create secret tls server-certificate --key server.key --cert server.crt
3. Now we need the client key and certificate. We start by creating our own “CA Authority”
openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 356 -nodes -subj "/CN=My CA"
4. Add the CA to the cluster as a Secret with the type ca-secret
kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt
5. A CSR for our client cert
openssl req -new -newkey rsa:4096 -keyout client.key -out client.csr -nodes -subj "/CN=My Client"
6. Sign the CSR with our CA (same we put into the cluster)
openssl x509 -req -sha256 -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client.crt
We should now have a client.key and a client.crt ready to use
7. Now we need an application to call. We create one with the Deployment below:
apiVersion: apps/v1 kind: Deployment metadata: labels: app: mywebserver name: mywebserver spec: replicas: 1 selector: matchLabels: app: mywebserver template: metadata: labels: app: mywebserver spec: containers: - image: httpd name: httpd ports: - containerPort: 80
8. A Service to expose the application to the cluster
apiVersion: v1 kind: Service metadata: labels: app: my-service name: my-service spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: mywebserver
9. A Ingress to handle the authentication
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" nginx.ingress.kubernetes.io/auth-tls-secret: default/ca-secret nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" name: mtls-ingress namespace: default spec: ingressClassName: nginx rules: - host: test.localdev.me http: paths: - backend: service: name: my-service port: number: 80 path: / pathType: Prefix tls: - hosts: - test.localdev.me secretName: server-certificate
10. Time to test our mTLS setup
First we need to setup a port binding to port 443 on our local machine
sudo kubectl port-forward -n ingress-nginx service/ingress-nginx-controller 443:443
and now we can test with a call with our client certificate and key
curl -k -v https://test.localdev.me/ --key client.key --cert client.crt
If everything is working we should get “It works!” from the Web Server
Tested in Minikube 1.26.0 and with OpenSSL 1.1.1f on Ubuntu 20.08