In large organisations you often need additional ways to validate a client certificate since most or all certificates use the same CA and you might want to have a more fine grained validation. To use this type of extra validation we also need to setup mTLS. This is a example of how to accomplish both mTLS and an extra layer of validation
All certificates are going to be self-signed in this example, regular certificates from trusted sources like Thwate, GlobalSign and many others will naturally also work.
For Kubernetes I will use Minikube with the Ingress addon:
minikube addons enable ingress
1. First we need a server certificate
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.
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=MyClient"
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. Another client certificate for testing the “match” function
CSR:
openssl req -new -newkey rsa:4096 -keyout client_2.key -out client_2.csr -nodes -subj "/CN=MyOtherClient"
Sign:
openssl x509 -req -sha256 -days 365 -in client_2.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out client_2.crt
8. Now we need an application to call. We create one with the Deployment and Service 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 --- apiVersion: v1 kind: Service metadata: labels: app: my-service name: my-service spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: mywebserver
8. Now we need to configure the Ingress for mTLS and our extra layer of 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" nginx.ingress.kubernetes.io/auth-tls-match-cn: "CN=MyClient" 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
Here we added the nginx.ingress.kubernetes.io/auth-tls-match-cn for our extra validation. In this case we are looking for a “CN=MyClient” property in the Subject part of the client certificate. If the string is found we continue the communication between client and server, if not then the connection will be terminated with a HTTP 403 error
9. Time to test our mTLS setup with extra validation
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
10. Now we are going to test the “match” function. Remember that both client.crt and client_2.crt uses the same CA so without the “auth-tls-match-cn” function they would both be accepted
curl -k -v https://test.localdev.me/ --key client_2.key --cert client_2.crt
This should fail and you should now get a HTTP 403 (Forbidden)
NOTE: A match with “CN=My Client” does not work! Spaces does not work when matching like this
Tested in Minikube 1.26.0 and with OpenSSL 1.1.1f on Ubuntu 20.08