Trusted local TLS certificates with mkcert and Kubernetes
18 May, 2024Something you might often do when deploying and testing your application on a local or a test development Kubernetes cluster is to hit the Ingress and test access. You’re almost certainly configuring TLS, and with that comes the question of what to do with x509 certificates. Typically you’d let the venerable cert-manager handle this for you, but what if you don’t want to battle with self-signed certificates and your browser (thisisunsafe
) or local client moaning everytime you connect? Turns out there’s an easy solution to this by making use of a criminally overlooked project called mkcert
and leveraging that in combination with cert-manager to provide a very simple solution.
I’m going to go with the assumption that you have a local Kubernetes cluster installed along with cert-manager plus the NGINX Ingress controller, but that you’ve not yet defined any Issuers or ClusterIssuers. The first step then is to install mkcert on your local machine, and then use that to generate and install the CA certificate and keys:
mkcert -install
This is a one-time thing, and now your browser plus other clients on your local machine will trust any certificates generated via this CA. Now let’s create a Secret in Kubernetes with this CA’s certificate and Key:
kubectl create secret tls mkcert-ca-key-pair \
--key "$(mkcert -CAROOT)"/rootCA-key.pem \
--cert "$(mkcert -CAROOT)"/rootCA.pem -n cert-manager
And now for the bit that tells cert-manager to make use of this:
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: mkcert-issuer
namespace: cert-manager
spec:
ca:
secretName: mkcert-ca-key-pair
EOF
And that’s it. Now when you define your Ingress, you just need to make sure the cert-manager related annotation refers to this ClusterIssuer we defined (mkcert-issuer
) and it’ll generate the appropriate certificate and key for you. Let’s do a quick test:
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: demo
spec:
replicas: 3
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- args:
- netexec
image: registry.k8s.io/e2e-test-images/agnhost:2.40
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: demo-service
labels:
app: demo
spec:
selector:
app: demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: mkcert-issuer
spec:
ingressClassName: nginx
tls:
- hosts:
- hello.192.168.106.4.nip.io
secretName: hello-ingress-cert
rules:
- host: hello.192.168.106.4.nip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-service
port:
number: 80
EOF
Season to taste (i.e change the IP address) and all being well your browser should trust the HTTPS site you’re connecting to: