DNS-01 challenge: Certificats wildcard TLS sur Kubernetes

When deploying applications on a Kubernetes cluster, we often expose them outside using ingress controllers, and using cert-manager to manage TLS certificates with Let’s Encrypt. When requesting certificates, the most common way is to use the HTTP-01 challenge, that checks if you can host content on a specific URL.

Sometimes you can use another challenge type, called DNS-01: Let’s Encrypt checks a specific DNS entry, to be sure you own the domain you requested the certificate for. This can be useful for some use cases:

Applications that are not exposed to Internet (Let’s Encrypt cannot connect to the URL content) Wildcard certificates, as there is no default URL to store content You can read more about it here: https://letsencrypt.org/docs/challenge-types/

To request such a certificate in a Kubernetes cluster, you need to automatically update the DNS records from your provider. Only major cloud providers are natively supported by default, but you can extend it using webhooks.

In this article, we will see how to quickly set up a k8s cluster, configuring cert-manager to handle DNS-01 challenge and get wildcard certificates.

Setup & Prerequisites

For this setup, I will use the following platform and tools:

  • Scaleway Kubernetes Kapsule to bootstrap a k8s cluster
  • Scaleway DNS, as we can manage it with cert-manager webhooks
  • Terraform to quickly setup everything
  • k8s Lens to easily view the k8s resources

I will not cover Terraform installation or other kubectl & helm tools, you can find good resources somewhere else.

Cluster provisioning

Using Terraform, you can provision a Kapsule cluster. We just use a scaleway_k8s_cluster and a scaleway_k8s_pool resource:

 1terraform {
 2  required_providers {
 3    scaleway = {
 4      source = "scaleway/scaleway"
 5      version = "2.1.0"
 6    }
 7  }
 8  required_version = ">= 0.13"
 9}
10
11provider "scaleway" {
12  zone            = "fr-par-1"
13  region          = "fr-par"
14}
15
16resource "scaleway_k8s_cluster" "k8s_cluster" {
17  name = var.k8s_cluster_name
18  version = "1.22.2"
19  cni = "cilium"
20}
21
22resource "scaleway_k8s_pool" "k8s_pool" {
23  cluster_id = scaleway_k8s_cluster.k8s_cluster.id
24  name = var.k8s_pool_name
25  node_type = "DEV1-M"
26  size = 2
27  autohealing = true
28}
29
30resource "local_file" "kubeconfig" {
31  content = scaleway_k8s_cluster.k8s_cluster.kubeconfig[0].config_file
32  filename = "${path.module}/${scaleway_k8s_cluster.k8s_cluster.name}-kubeconfig"
33  file_permission = "0600"
34}

Now we can provision the cluster:

1$ terraform init
2$ terraform plan -out k8s_scaleway.out
3$ terraform apply k8s_scaleway.out

Wait few minutes and we will have a functional cluster with Nginx as an ingress controller, with a load-balancer, and a cert-manager configured. We will also find a kubeconfig file ready for playing with the cluster!

DNS Setup

We can configure DNS entry to link the load balancer IP address to our cluster domain. I could do it automatically, but there is no Terraform resource for Scaleway right now (planned for the next release, see here).

For this setup, my domain is scw.vrchr.fr, so any URL ending with it will be redirect to the k8s cluster through the load balancer.

scw.vrch.fr

Edit: the cluster just created does not exists anymore as you are reading this, as it was only online for this article. So don’t try to resolve the following FQDN ;)

Wildcard Certificate with DNS-01

Scaleway webhook

Remember, for some providers we need to use cert manager webhooks to manage the DNS entries. So we install it:

1$ git clone https://github.com/scaleway/cert-manager-webhook-scaleway.git
2$ cd cert-manager-webhook-scaleway
3$ helm install scaleway-webhook deploy/scaleway-webhook --set secret.accessKey=changeme --set secret.secretKey=changeme --set certManager.serviceAccountName=jetstack-cert-manager --namespace=cert-manager

Be careful of the serviceAccountName which has to be the same as the one created with the cert-manager Helm chart.

So now we can see, using our preferred tool, the deployments and all the cert-manager configs and custom resources:

cert-manager deployments:center
this is a test

cert-manager CRDs

Issuer and Certificates

Next, we have to create a certificate issuer, which will be responsible to request the TLS certificates. This is a Custom Resource, with the property “dns01”. We create a ClusterIssuer instead of Issuer to be able to manage certificates cluster-wide:

 1---
 2apiVersion: cert-manager.io/v1
 3kind: ClusterIssuer
 4metadata:
 5  name: scaleway-issuer-prod
 6spec:
 7  acme:
 8    email: name@company.com
 9    server: https://acme-v02.api.letsencrypt.org/directory
10    privateKeySecretRef:
11      name: scaleway-private-key-secret
12    solvers:
13    - dns01:
14        webhook:
15          groupName: acme.scaleway.com
16          solverName: scaleway
1$ kubectl apply -f cert_issuer.yaml

We can add another issuer for staging certificates if needed.

Now, Let’s create a certificate request! We’ll make request one for “*.scw.vrchr.fr”, which will be stored in a secret called “wildcard-scw-vrchr-fr-tls”:

 1apiVersion: cert-manager.io/v1
 2kind: Certificate
 3metadata:
 4  name: wildcard-scw-vrchr-fr-tls
 5  namespace: default
 6spec:
 7  dnsNames:
 8  - "*.scw.vrchr.fr"
 9  issuerRef:
10    name: scaleway-issuer-prod
11    kind: ClusterIssuer
12  secretName: wildcard-scw-vrchr-fr-tls

What happens now?

Let’s Encrypt detects a Certificate Request, and makes the request to the servers It uses the webhook to create a TXT DNS entry, here it’s : _acme-challenge.scw.vrchr.fr The server will check the DNS entry, and validate the request. Be patient, DNS propagation can be long… The certificate is now created, and a secret is now in you cluster!

TLS Certificate stored in secrets

Application deployment

OK! We have now a wildcard certificate automatically managed, so let’s use it!

We create a simple deployment, expose the application, and create an ingress rule. The important configuration is in the ingress, which specify which TLS certificate to use. We don’t need to add specific annotation for the ingress controller, as the wildcard certificate is already created and managed by the previous CRD. As we have created a wildcard, every FQDN matching the wildcard will be valid:

1[...]
2spec:
3  tls:
4  - hosts:
5    - "*.scw.vrchr.fr" 
6    secretName: wildcard-scw-vrchr-fr-tls
7[...]

Here is the complete deployment, using the jpetazzo/webcolor application to expose different colors.

Deployment & Service example:

 1---
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: green
 6spec:
 7  selector:
 8    matchLabels:
 9      app: green
10  replicas: 1
11  template:
12    metadata:
13      labels:
14        app: green
15    spec:
16      containers:
17      - image: jpetazzo/webcolor
18        name: webcolor
19
20---
21apiVersion: v1
22kind: Service
23metadata:
24  name: green
25spec:
26  ports:
27  - port: 8000
28    targetPort: 8000
29  selector:
30    app: green

Ingress:

 1---
 2apiVersion: networking.k8s.io/v1
 3kind: Ingress
 4metadata:
 5  name: color-ingress
 6  annotations:
 7    kubernetes.io/ingress.class: nginx
 8spec:
 9  tls:
10  - hosts:
11    - "*.scw.vrchr.fr" 
12    secretName: wildcard-scw-vrchr-fr-tls
13  rules:
14  - host: green.scw.vrchr.fr
15    http:
16      paths:
17      - path: /
18        pathType: Prefix
19        backend:
20          service:
21            name: green
22            port:
23              number: 8000
24  - host: purple.scw.vrchr.fr
25    http:
26      paths:
27      - path: /
28        pathType: Prefix
29        backend:
30          service:
31            name: purple
32            port:
33              number: 8000
34  - host: yellow.scw.vrchr.fr
35    http:
36      paths:
37      - path: /
38        pathType: Prefix
39        backend:
40          service:
41            name: yellow
42            port:
43              number: 8000
1$ kubectl apply -f dep_green.yaml
2$ kubectl apply -f wildcard_tls_ingress.yaml

Now, accessing to https://green.scw.vrchr.fr would present a wildcard certificate!

Green Page

Yellow Page

Wildcard Certificate

Conclusion and Remarks

We have seen here how to:

  • Deploy a Kubernetes Cluster on Scaleway using Terraform
  • Deploy Cert Manager and Nginx using Helm chart in Terraform
  • Deploy Scaleway DNS Webhook
  • Request a Wildcard Certificate using the DNS-01 protocol
  • Deploy an application in a subdomain, using a ingress rule presenting the wildcard DNS

Now you can deploy many sub-application as we want, all using the same wildcard TLS certificates. For the comprehensive read, I deployed applications in the same namespace, please feel free to read cert-manager docs if you want to share certificates across multiples NS.

Note that I could use another cloud provider, but I like experiment french ones ;)

Finally, You can find code example in my GitLab repository, here : https://gitlab.com/rverchere/vrchr-k8s-dns-demo

And remember, it’s always DNS!

DNS Haiku

References

These resources were very useful to understand these concepts and write my article: