Creating Self Signed Certificates on Kubernetes
February 22, 2020
Welcome to 2020. Creating self signed TLS certificates is still hard. Five (5) years ago I created a project on github called omgwtfssl which is a fairly simple bash script wrapping a bunch of openssl
commands to create certificates.
I’ve been using it ever since and kind of forgot about the pain of creating certificates.
Skip the words and jump to the examples [Creating self signed certificates with cert-manager] (#creating-self-signed-certificates-with-cert-manager), Creating multiple certificates from the same self signed CA with cert-manager.
With the advent of letsencrypt and later the Kubernetes cert-manager controller we can make real signed certificates with a quick flourish of some YAML.
I’ve been happily chugging along with this combination of cert-manager
cert-manager for real certificates, and omgwtfssl for self signed (despite the fact that the name is inappropriate and unprofessional..
“We should try to find a replacement for omgwtfssl, which is currently used to generate self-signed certificates. The name is inappropriate and unprofessional.” - gitlab
As amusing as docker run paulczar/omgwtfssl
is to type (I giggle every time), its a bit weird to tell people to create certificates locally then add them to their Kubernetes manifests or Helm charts. So I finally decided to sit down and figure out how to create them sensibly with cert-manager.
Create a Kubernetes in Docker Cluster
You’ll need a Kubernetes cluster, we’re not doing anything too resource intensive so a kind cluster should be fine.
Create kind cluster:
kind create cluster
export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"
Test the cluster:
kubectl cluster-info
Creating self signed certificates with cert-manager
Install cert-manager:
kubectl create namespace cert-manager
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.13.1/cert-manager.yaml
If you receive a validation error relating to the x-kubernetes-preserve-unknown-fields add
--validate
to the above command and run again.
Create a namespace to work in:
kubectl create namespace sandbox
Create an Issuer:
Note: you can create a ClusterIssuer instead if you want to be able to request certificates from any namespace.
kubectl apply -n sandbox -f <(echo "
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
")
Create a self signed certificate:
This creates a wildcard certificate that could be used for any services in the sandbox namespace.
kubectl apply -n sandbox -f <(echo '
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: first-tls
spec:
secretName: first-tls
dnsNames:
- "*.sandbox.svc.cluster.local"
- "*.sandbox"
issuerRef:
name: selfsigned-issuer
')
Validate the secret is created
Check the certificate resource:
$ kubectl -n sandbox get certificate
NAME READY SECRET AGE
first-tls True first-tls 9s
Check the subsequent secret:
$ kubectl -n sandbox get secret first-tls
NAME TYPE DATA AGE
first-tls kubernetes.io/tls 3 73s
This secret contains three keys
ca.crt
,tls.crt
,tls.key
. You can runkubectl -n sandbox get secret first-tls -o yaml
to see the whole thing.
Test that the certificate is valid:
openssl x509 -in <(kubectl -n sandbox get secret \
first-tls -o jsonpath='{.data.tls\.crt}' | base64 -d) \
-text -noout
If you scan through the output you should find
X509v3 Subject Alternative Name: DNS:*.first.svc.cluster.local, DNS:*.first
.
Congratulations. You’ve just created your first self signed certificate with Kubernetes. While it involves more typing than docker run paulczar/omgwtfssl
it is much more useful for Kubernetes enthusiasts to have the cluster generate them for you.
However, what if you want to use TLS certificates signed by the same CA for performing client/server authentication? Never fear we can do that too.
Creating multiple certificates from the same self signed CA with cert-manager
Install cert-manager:
Skip this step if you already installed cert-manager from the first example.
kubectl create namespace cert-manager
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.13.1/cert-manager.yaml
If you receive a validation error relating to the x-kubernetes-preserve-unknown-fields add
--validate
to the above command and run again.
Create a namespace to work in:
kubectl create namespace sandbox2
Create an Issuer:
Note: you can create a ClusterIssuer instead if you want to be able to request certificates from any namespace.
kubectl apply -n sandbox2 -f <(echo "
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
")
Create a CA Certificate:
note
isCA
is set to true in the body of thespec
.
kubectl apply -n sandbox2 -f <(echo '
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: sandbox2-ca
spec:
secretName: sandbox2-ca-tls
commonName: sandbox2.svc.cluster.local
usages:
- server auth
- client auth
isCA: true
issuerRef:
name: selfsigned-issuer
')
Check the certificate and secret were created:
$ kubectl -n sandbox2 get certificate sandbox2-ca
NAME READY SECRET AGE
sandbox2-ca True sandbox2-ca-tls 15s
$ kubectl -n sandbox2 get secret sandbox2-ca-tls
NAME TYPE DATA AGE
sandbox2-ca-tls kubernetes.io/tls 3 22s
Create a second Issuer using the secret name from the sandbox2-ca
secret:
In order to sign multiple certificates from the same CA we need to create an Issuer resource from secret created by the CA.
kubectl apply -n sandbox2 -f <(echo '
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: sandbox2-ca-issuer
spec:
ca:
secretName: sandbox2-ca-tls')
Create a TLS Certificate from the new CA Issuer:
We can add
usages
to the certificatespec
to ensure that the certificates can be used for client/server authentication.
kubectl apply -n sandbox2 -f <(echo '
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: sandbox2-server
spec:
secretName: sandbox2-server-tls
isCA: false
usages:
- server auth
- client auth
dnsNames:
- "server.sandbox2.svc.cluster.local"
- "server"
issuerRef:
name: sandbox2-ca-issuer
')
Create a second TLS Certificate from the new CA Issuer:
kubectl apply -n sandbox2 -f <(echo '
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: sandbox2-client
spec:
secretName: sandbox2-client-tls
isCA: false
usages:
- server auth
- client auth
dnsNames:
- "client.sandbox2.svc.cluster.local"
- "client"
issuerRef:
name: sandbox2-ca-issuer
')
Check that all three certificates are created:
$ kubectl -n sandbox2 get certificate
NAME READY SECRET AGE
sandbox2-ca True sandbox2-ca-tls 7m34s
sandbox2-client True sandbox2-client-tls 7s
sandbox2-server True sandbox2-server-tls 16s
$ kubectl -n sandbox2 get secret
NAME TYPE DATA AGE
sandbox2-ca-tls kubernetes.io/tls 3 8m14s
sandbox2-client-tls kubernetes.io/tls 3 48s
sandbox2-server-tls kubernetes.io/tls 3 57s
Validate the certificates against the CA:
$ openssl verify -CAfile \
<(kubectl -n sandbox2 get secret sandbox2-ca-tls \
-o jsonpath='{.data.ca\.crt}' | base64 -d) \
<(kubectl -n sandbox2 get secret sandbox2-server-tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d)
/proc/self/fd/18: OK
$ openssl verify -CAfile \
<(kubectl -n sandbox2 get secret sandbox2-ca-tls \
-o jsonpath='{.data.ca\.crt}' | base64 -d) \
<(kubectl -n sandbox2 get secret sandbox2-client-tls \
-o jsonpath='{.data.tls\.crt}' | base64 -d)
/proc/self/fd/18: OK
Validate the Client / Server authentication
Run an openssl
server as a background process:
touch test.txt
openssl s_server \
-cert <(kubectl -n sandbox2 get secret sandbox2-server-tls -o jsonpath='{.data.tls\.crt}' | base64 -d) \
-key <(kubectl -n sandbox2 get secret sandbox2-server-tls -o jsonpath='{.data.tls\.key}' | base64 -d) \
-CAfile <(kubectl -n sandbox2 get secret sandbox2-ca-tls -o jsonpath='{.data.ca\.crt}' | base64 -d) \
-WWW -port 12345 \
-verify_return_error -Verify 1 &
Run an openssl
client test:
look for HTTP/1.0 200 ok
in the client output.
echo -e 'GET /test.txt HTTP/1.1\r\n\r\n' | \
openssl s_client \
-cert <(kubectl -n sandbox2 get secret sandbox2-client-tls -o jsonpath='{.data.tls\.crt}' | base64 -d) \
-key <(kubectl -n sandbox2 get secret sandbox2-client-tls -o jsonpath='{.data.tls\.key}' | base64 -d) \
-CAfile <(kubectl -n sandbox2 get secret sandbox2-client-tls -o jsonpath='{.data.ca\.crt}' | base64 -d) \
-connect localhost:12345 -quiet
stop the background process:
kill %1
Congratulations, you’ve now created a pair of certificates signed by the same CA that can be used for client/server authentication.
Conclusion
Creating self signed certificates is now officially easy. You can use omgwtfssl locally, or cert-manager in your Kubernetes cluster. Either way you get cheap and easy self signed certificates for testing. Obviously you should use real certificates in production, in which case you would still be able to use cert-manager.