Cert Manager and Letsencrypt

Overview

This article will quickly go through how to create wildcard certificates and automatically renew them with Lets Encrypt and Cert-Manager

Cert-Manager

cert-manager adds certificates and certificate issuers as resource types in Kubernetes clusters, and simplifies the process of obtaining, renewing and using those certificates.

It can issue certificates from a variety of supported sources, including Let's Encrypt, HashiCorp Vault, and Venafi as well as private PKI.

It will ensure certificates are valid and up to date, and attempt to renew certificates at a configured time before expiry. link

Install Cert-Manager

I prefer the Helm way so lets add the cert-manager helm chart:

1helm repo add jetstack https://charts.jetstack.io
2helm repo update

Then we need to deploy cert-manager. This can be done out-of-the-box with the commands given from the official docs (this also installed the necessary CRDs):

1helm install \
2  cert-manager jetstack/cert-manager \
3  --namespace cert-manager \
4  --create-namespace \
5  --version v1.9.1 \
6  --set installCRDs=true

Or if you need to customize some settings, as I needed to do, I used this command:

1helm install -f /path/to/cert-manager.values.yaml cert-manager jetstack/cert-manager   --namespace cert-manager   --version v1.9.1   --set installCRDs=true --set 'extraArgs={--dns01-recursive-nameservers-only,--dns01-recursive-nameservers=xx.xx.xx.xx:53\,xx.xx.xx:53}'

The above command takes care of the cert-manager installation including the necessary CRDs, but it will also adjust the DNS servers Cert-Manager will use to verify the ownership of my domain.

DNS01 - Wildcard certificate

In this post I will go with wildcard certificate creation. I find it easier to use instead of having a separate cert for everthing I do, as long as they are within the same subdomain. So if I have my services in *.example.com they can use the same certificate. But if I have services in *.int.example.com I can not use the same certificate as LetsEncrypt certificates dont support that. Then you need to create a separate wildcard cert for each subdomain. But Cert-manager will handle that for you very easy.

The offiicial Cert-Manager supported DNS01 providers are:

There is also an option to use Webhooks. I did try that as my previous DNS registrar were not on the DNS01 supported list. I did not succeed with using the webhook approach. It could be an issue with the specific webhooks I used or even with my registrar so I decided to migrate over to CloudFlare which is on the supported list, "out of the box".

Issuer - CloudFlare and LetsEncrypt

The first we need to do is to create a secret for Cert-Manager to use when "interacting" with CloudFlare. I went with API Token. So head over to your CloudFlare control panel and create a token for Cert Manager like this:

Here is the permissions:

Now use the tokens to create your secret:

1apiVersion: v1
2kind: Secret
3metadata:
4  name: cloudflare-api-token-secret
5  namespace: cert-manager
6type: Opaque
7stringData:
8  api-token:

Apply it kubect apply -f name.of.yaml

Now create your issuer. LetsEncrypt have two repos, one called staging and one production. Start out with staging until everything works so you dont hit the LetsEncrypt limit. In regards to this I created two issuers, one for staging and one for production. When everything was working and I have verified the certificates etc I deployed the certs using the prod-issuer.

Issuer-staging:

 1apiVersion: cert-manager.io/v1
 2kind: ClusterIssuer
 3metadata:
 4  name: letsencrypt-staging
 5spec:
 6  acme:
 7    # ACME Server
 8    # prod : https://acme-v02.api.letsencrypt.org/directory
 9    # staging : https://acme-staging-v02.api.letsencrypt.org/directory
10    server: https://acme-staging-v02.api.letsencrypt.org/directory
11    # ACME Email address
12    email: xxx.xxx@xxx.xxx
13    privateKeySecretRef:
14      name: letsencrypt-key-staging # staging or production
15    solvers:
16    - dns01:
17        cloudflare:
18          apiTokenSecretRef:
19            name: cloudflare-api-token-secret ## created and applied above
20            key: api-token

Issuer-production:

 1apiVersion: cert-manager.io/v1
 2kind: ClusterIssuer
 3metadata:
 4  name: letsencrypt-prod
 5  namespace: cert-manager
 6spec:
 7  acme:
 8    # ACME Server
 9    # prod : https://acme-v02.api.letsencrypt.org/directory
10    # staging : https://acme-staging-v02.api.letsencrypt.org/directory
11    server: https://acme-v02.api.letsencrypt.org/directory
12    # ACME Email address
13    email: xxx.xxx@xxx.xxx
14    privateKeySecretRef:
15      name: letsencrypt-key-prod # staging or production
16    solvers:
17    - dns01:
18        cloudflare:
19          apiTokenSecretRef:
20            name: cloudflare-api-token-secret # created and applied above
21            key: api-token

Request certificate

Now that the groundwork for cert-manager has been setup, its time to "print" some certificates. Prepare your yamls for both the staging key and production key.

Wildcard-staging:

 1apiVersion: cert-manager.io/v1
 2kind: Certificate
 3metadata:
 4  name: name-tls-test
 5  namespace: namespace-you-want-the-cert-in
 6spec:
 7  secretName: name-tls-staging
 8  issuerRef:
 9    name: letsencrypt-staging
10    kind: ClusterIssuer
11  duration: 2160h # 90d
12  renewBefore: 720h # 30d before SSL will expire, renew it
13  dnsNames:
14    - "*.example.com"

Wildcard-production:

 1apiVersion: cert-manager.io/v1
 2kind: Certificate
 3metadata:
 4  name: name-tls-production
 5  namespace: namespace-you-want-the-cert-in
 6spec:
 7  secretName: name-tls-prod
 8  issuerRef:
 9    name: letsencrypt-prod
10    kind: ClusterIssuer
11  duration: 2160h # 90d
12  renewBefore: 720h # 30d before SSL will expire, renew it
13  dnsNames:
14    - "*.example.com"

Apply the staging request first. Check your certificate status with this command:

1$ kubectl get certificate -n namespace-you-wrote 
2NAME               READY   SECRET                AGE
3name-tls-staging   True    name-tls-staging      8d

Please note that it can take a couple of minutes before the certificate is ready. This applies for production also.

If everything went well, delete your staging certificate and apply your production certificate with the production yaml. Thats it. Now Cert-Manager will take care of updating your certificate for your, sit back and enjoy your applications with your always up to date certificates.

Troubleshooting tips, commands

If something should fail there is a couple of commands you can use to figure out whats going on.

1$ kubectl get issuer
2$ kubectl get clusterissuer
3$ kubectl describe issuer 
4$ kubectl describe clusterissuer
5$ kubectl describe certificaterequest
6$ kubectl describe order
7$ kubectl get challenges
8$ kubectl describe challenges

For more detailed explanation go here