How to integrate cert-manager with Let’s Encrypt and Cloudflare

If you’re looking to automatically issue and renew certificates using cert-manager and Let’s Encrypt for a domain record managed and proxied by Cloudflare using Full (strict) TLS, you’re in the right place. The process is straightforward, and you’ll have everything set up in under 5 minutes.

Install cert-manager

Ensure that cert-manager is correctly installed on your cluster. I recommend using the official Helm chart for installation. Alternatively, you can apply the manifests for the latest version (v1.15.2 at the time of writing).

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.2/cert-manager.yaml

Generate a Cloudflare API token

To prove domain ownership using the DNS-01 challenge, we’ll need to provide cert-manager with a Cloudflare API token. This token allows cert-manager to create a TXT record under the domain for which we’re requesting a certificate.

Make sure the token has the following permissions on all target zones as shown in the picture below:

  • Zone – DNS – Edit
  • Zone – Zone – Read
Creating a Cloudflare API Token for cert-manager

Don’t forget to copy the value before closing the browser tab. 😅

Successful API token creation

Create a Kubernetes Secret

Let’s wrap the token generated in the previous step in a Kubernetes Secret.

kubectl -n cert-manager create secret generic cloudflare --from-literal=token=<Cloudflare API Token>

Configure the Cluster Issuer

We’re now ready to configure our cert-manager ClusterIssuer. Use the following file as a template and replace the email where necessary. Something I wish to highlight is that we are using a dns01 solver rather than http01. I’ll later explain why.

apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata:  name: letsencrypt-prodspec:  acme:    server: https://acme-v02.api.letsencrypt.org/directory    email: <your email>    privateKeySecretRef:      name: letsencrypt-prod    solvers:    - dns01:        cloudflare:          email: <your email>          apiTokenSecretRef:            name: cloudflare            key: token

Save it and apply it.

kubectl apply -f cluster-issuer.yaml

Generating a TLS certificate

For this example, I am going to create a pod running nginx which I intend to expose on https://test-proxy.marcolenzo.eu.

Configuring the DNS record

I first make sure the DNS record is properly configured on Cloudflare. I am using a CNAME but you can use an A record if you wish.

Proxied DNS Record

Creating Namespace, Pod and Service

Now I create quickly namespace, pod and the necessary service.

kubectl create ns testkubectl -n test run nginx --image nginxkubectl -n test expose pod nginx --port 80

Defining an Ingress with TLS managed by our Cluster Issuer

Then, I define the Ingress. Use my configuration as a template for yours.

apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  annotations:    cert-manager.io/cluster-issuer: letsencrypt-prod # ClusterIssuer name  name: test  namespace: testspec:  ingressClassName: public # kubectl get ingressclasses (if you don't know it)  rules:  - host: test-proxy.marcolenzo.eu # replace with your domain    http:      paths:      - backend:          service:            name: nginx            port:              number: 80        path: /        pathType: Prefix  tls:  - hosts:    - test-proxy.marcolenzo.eu # replace with your domain    secretName: test-proxy-tls

Make sure to replace the ingressClassName, host, and hosts with the proper values. Save to file and apply.

kubectl apply -f ingress.yaml

Success!

That was it! Now we can simply verify that the certificate was issued correctly.

kubectl -n test get certificate test-proxy-tls -o wideNAME             READY   SECRET           ISSUER             STATUS                                          AGEtest-proxy-tls   True    test-proxy-tls   letsencrypt-prod   Certificate is up to date and has not expired   11m

If you want to read the content of the certificate use the following command.

kubectl -n test get secrets test-proxy-tls -o jsonpath='{.data.tls\.crt}' | base64 -d

Notice that you will not be able to see the same certificate on your browser since the request is proxied through Cloudflare which offers a different one.

This is not the certificate we have generated

The reason we need a valid certificate on our backend is to enforce Full (strict) encryption for our website on Cloudflare. Without this setting, all our efforts would be in vain, as Cloudflare would accept a self-signed certificate on our backend.

Why is the HTTP-01 Ingress solver incompatible?

Before issuing a certificate, Let’s Encrypt verifies ownership by performing challenges as defined in the ACME standard.

When using the HTTP-01 challenge, Let’s Encrypt gives a token to our ACME client (cert-manager in this case) which must be served as a file on our webserver at http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>

Unfortunately, this approach cannot be used if you intend to proxy requests on Cloudflare, because the request above will be served by the Cloudflare proxy and not the backend running cert-manager!

Conclusion

Hope this helped. Feel free to subscribe to the newsletter and comment below if you need more help!

Share the Post:

Related Posts