In this article we’ll explore how to use Traefik in Kubernetes combined with Cert-manager as an ACME (Automatic Certificate Management Environment) client to issue certificates through Let’s Encrypt.
If instead of Kubernetes you’re running docker-compose, Major Hayden has an excellent tutorial on how to configure Wildcard LetsEncrypt certificates with Traefik and Cloudflare.
In order to issue wildcard certificates we need to prove to a Certificate Authority (CA) that we own the domain. One way to prove ownership is with a DNS-01 challenge.
We’ll be using Cloudflare as a DNS provider, but the examples should be easily adapted for other providers.
Overview#
There’s a lot of moving parts when dealing with TLS/SSL certificates and public key infrastructure which we’ll not cover here.
Below is a simplified overview of the certificate issuance flow we’ll implement. If you feel the explanation is lacking or just plain wrong I’d be happy to hear from you!
- The
Issuer
asks Let’s Encrypt for a DNS-01 challenge. - Using the Cloudflare API Token the
Issuer
creates a TXT DNS record as an answer to the DNS-01 challenge. - Let’s Encrypt verifies the TXT record to establish trust.
- After verification Let’s Encrypt will now sign certificate requests from the
Issuer
. - The
Certificate
resource asks theIssuer
for a valid TLS certificate and stores it as aSecret
. - Traefik attaches the TLS
Secret
to eligibleIngress
andIngressRoute
resources.
Traefik Proxy#
In their own words,
Traefik is an open-source Edge Router that makes publishing your services a fun and easy experience.
On Kubernetes Traefik can be installed using their Helm Chart
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik
I’ve described a different approach suitable for Argo CD and Kustomize in the Summary section.
Cert-manager#
While Traefik has native Let’s Encrypt integration, running multiple instances can raise some issues. To solve those issues we can either pay for Traefik Enterprise, or delegate the certificate lifecycle management to a centralised service, e.g. Cert-manager.
Cert-manager keeps a list of supported DNS-01 providers on their webpage.1 Luckily for us Cloudflare is one of those providers, though any of the providers listed by Let’s Encrypt should be possible to integrate with.
We can install Cert-manager by using their Helm Chart
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.13.3 --set installCRDs=true
For other options check the Summary section or Cert-manager’s docs.
DNS Provider (Cloudflare)#
I went with Cloudflare as a DNS provider since they’re well known and offer a free tier. Creating an account and setting up your site to use Cloudflare is outside the scope of this article, but it should be pretty straightforward by going to their site.
Cert-manager has great documentation on how to configure DNS-01 providers. For Cloudflare we only need an API-token with the appropriate permissions.
After logging in to Cloudflare navigate to My Profile in the upper right corner, find the API Tokens pane in the left sidebar, and locate the Create Token button under User API Tokens.
Create a custom token and give it the following permissions
- Zone – Zone – Read
- Zone – DNS – Edit
For the Zones Resources it’s recommended to include all zones.
Take note of the token as we need it in the next section.
Configuration#
With a Cloudflare API token in hand, and having set up both Traefik and Cert-manager, we’re finally ready to configure wildcard certificates for use by Traefik!
First we create a secret2 with the API token we got from Cloudflare.
Using stringData
instead of data
allows us to use clear text content instead of having to base64
encode the
token.3
|
|
Then we create an Issuer
resource providing the domain owner e-mail on line 9 and reference the API token
secret for apiTokenSecretRef
(line 16).
For testing purposes it might be a good idea to
use Let’s Encrypt staging server instead of the production server
to avoid being rate limited.
To do so change the server
on line 8 to https://acme-staging-v02.api.letsencrypt.org/directory
.
|
|
Next we create a Certificate
which references the above Issuer
by name on line 12,
inserting your own domain name on line 4, 7, 9, and 10 where appropriate.
|
|
Verify that everything is ready by running
$ kubectl get certificate -n traefik
NAME READY SECRET AGE
wildcard-<...> True wildcard-<...>-tls 5m
If the certificate is not ready, the logs of the Cert-manager pod can give you more information.
Default certificate#
Assuming everything is running fine you can start using the certificate by adding
|
|
to the Traefik Helm Chart values.yaml
.
This instructs Traefik to present the TLS Secret
generated by the Certificate
resource by default for all Ingress
and IngressRoute
resources.
Update Traefik to apply the new settings
helm upgrade traefik traefik/traefik --values values.yaml
and you should be all set!
Per Ingress#
It’s also possible to issue certificates per Ingress
or IngressRoute
resource.
For instructions on how to do this you can
read this blog post by Traefik
where they use Cert-manager annotations to request certificates.
Summary#
I’m running Argo CD with Kustomize + Helm in an attempt to follow GitOps best-practices.
I’m employing
an App of Apps Pattern
where I’m using Argo CD’s ApplicationSet
to generate Application
resources for each folder.
You can find my current homelab configuration
on GitHub as a demonstration.
Cert-manager#
#cert-manager/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ns.yaml
helmCharts:
- name: cert-manager
repo: https://charts.jetstack.io
version: 1.13.3
releaseName: cert-manager
namespace: cert-manager
valuesInline:
crds.enabled: true
#cert-manager/ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
Traefik#
#traefik/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ns.yaml
- cloudflare-token-cert-manager.yaml
- cloudflare-issuer.yaml
- cloudflare-cert.yaml
helmCharts:
- name: traefik
repo: https://traefik.github.io/charts
version: 26.0.0
releaseName: traefik
namespace: traefik
includeCRDs: true
valuesFile: values.yaml
#traefik/values.yaml
tlsStore:
default:
defaultCertificate:
secretName: wildcard-<--YOUR DOMAIN-->-tls
#traefik/ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: traefik
This should preferably be a SealedSecret
2 instead of an
unencrypted Secret
#traefik/cloudflare-token-cert-manager.yaml
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: traefik
type: Opaque
stringData:
api-token: "<--CLOUDFLARE API TOKEN-->"
#traefik/cloudflare-issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: cloudflare-issuer
namespace: traefik
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: "<--YOUR EMAIL-->"
privateKeySecretRef:
name: cloudflare-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
#traefik/cloudflare-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-<--YOUR DOMAIN-->
namespace: traefik
spec:
secretName: wildcard-<--YOUR DOMAIN-->-tls
dnsNames:
- "<--YOUR DOMAIN-->"
- "*.<--YOUR DOMAIN-->"
issuerRef:
name: cloudflare-issuer
kind: Issuer
Curiously Traefik’s list of supported DNS-01 providers that can automate DNS verification is longer than Cert-manager’s. ↩︎
Check out Sealed Secrets if you plan on doing it the GitOps way. ↩︎ ↩︎