DNS-01 challenge: Certificats wildcard TLS sur Kubernetes

Sommaire

Lorsqu'on déploie des applications sur un cluster kubernetes, celles-ci sont souvent exposées à l'extérieur en HTTPS via un ingress controller, généralement en utilisant cert-manager pour gérer les certificats TLS avec Let's Encrypt.

La demande de certificat TLS est généralement faite avec le challenge HTTP-01, qui vérifie que l'on peut accéder à du contenu sur une URL spécifique.

Cependant, d'autres types de challenges existent, notamment le challenge DNS-01 : Let's Encrypt vérifier une entrée DNS spécifiques, pour s'assurer que le domaine sur lequel nous demandons un certificat nous appartien bien. Cela peut être utile dans plusieurs cas :

  • Les applications ne sont pas exposées sur Internet (Let's Encrypt ne peut alors pas se connecter sur l'URL)
  • Le certificats wildcard, car il n'y a pas d'URL par défaut ou héberger la preuve de certificat

On peut retrouver plus d'informations sur les différents types de challenge ici : https://letsencrypt.org/docs/challenge-types/

Pour demander un certificat de cette manière dans un cluster kubernetes, on aura besoin de mettre à jour automatiquement les entrées DNS associées à l'application chez le cloud provider. Seulement les providers "majeurs" sont nativement supportés, mais on peut étendre ce fonctionnement avec des webhooks.

Dans cet article, nous verrons comment paramétrer un cluster kubernetes, configurer cert-manager pour gérer des challenges DNS-01 avec des certificats Wildcards, et déployer une application utilisatn ces certificats.

Paramétrage et Pré-requis

Pour la démo, on utilise les outils et plateformes suivants :

  • Cluster Kubernetes Kapsule de chez Scaleway
  • Serveur DNS de chez Scaleway toujours, car on peut le piloter avec les cert-manager webhooks
  • Terraform pour facilement tout mettre en palce
  • k8s Lens pour facilement voir les ressourcs déployées

L'article ne couvre pas l'installation de Terraform & autres outils kubectl, helm and co.

Provisionnement du cluster

Avec Terraform, on provisionne un cluster Kapsule simple. Il suffit juste d'utilser les ressources scaleway_k8s_cluster et scaleway_k8s_pool:

 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}

Maintenant on peut provisionner le cluster :

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

Après quelques minutes d'attente, le cluster est prêt et dispo, avec Nginx comme ingress controller, un load-balancer, et cert-manager de configuré. On a également un fichier kubeconfig à disposition pour contacter le cluster !

Paramétrage DNS

On peut maintenant configurer l'entrée DNS pour faire le lien avec l'IP du load-balancer. On pourrait faire cela automatiquement, mais les ressources Terraform n'existent pas encore, c'est prévu pour une prochaine release, voir ici.

Pour ce setup, le domaine utilisé est scw.vrchr.fr, et donc chaque URL se terminant ainsi sera automatiquement dirigée vers l'IP du load-balancer du cluster kubernetes préalablement installé.

scw.vrch.fr

Note : le cluster de démo n'existe plus à l'heure actuelle, pas la peine d'essayer de le joindre, cela n'a été fait que le temps de l'article ;)

Certificat wildcard avec DNS-01

Webhook Scaleway

Pour rapppel, pour certains cloud providers, on doit utiliser les webhooks cert-manager afin de piloter les entrées DNS. C'est le cas pour Scaleway, alors installons ce qu'il faut :

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

Attention au serviceAccountName qui doit être le même que celui créé avec le chart helm cert-manager.

On peut maintenant voir les ressources déployées :

cert-manager deployments

cert-manager CRDs

Issuer and Certificates

Maintenant, on va créer un "Certificate Issuer", qui sera responsable des demandes de certificats TLS. Il s'agit d'une Custom Resource avec la propriété "dns01". Dans l'exemple on créé un ClusterIssuer plutôt qu'un Issuer, afin de gérer les demandes de certificats sur l'ensemble du cluster :

 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

On peut rajouter un issuer pour les certificats de type staging si besoin.

Maintenant, créons une demande de certificat ! La demande sera faite pour "*.scw.vrchr.fr", qui sera enregistrée dans un secret nommé "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

Que se passe-t'il maintenant ?

  • Cert-manager détecte une demande de certificat, et génère une demande vers les serveurs Let’s Encrypt
  • Cert-manager utilise le webhook pour créer une entrée DNS TXT DNS : _acme-challenge.scw.vrchr.fr
  • Les serveurs Let's Encrypt vont vérifier cette entrée DNS, et valident la requpete. Il faut être patient, le temps de la propagation DNS qui peut être longue
  • Une fois la demande validée, le certificat est créé dans un secret disponible dans le cluster !

TLS Certificate stored in secrets

Deploiement d'application

OK ! Maintenant qu'on a un certificat wildcard généré automatiquement, utilisons-le sur des applications.

On créé un déploiement simple, on l'expose via un service, et on créé une règle d'ingress. La partie importante de la configuration est dans l'ingress, qui spécifie quel certificat TLS utilisé. Pas besoin d'annotations particulières pour géré le certificat automatique, car il est déjà créé et géré ailleurs.

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

Comme on a créé un certificat wildcard, chaque FQDN qui matchera le wildcard sera valide.

Voici l'exemple d'un déploiement complet de l'application jpetazzo/webcolor qui expose différentes couleurs.

Exemple de déploiement & service:

 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

Maintenant, accédons à https://green.scw.vrchr.fr, qui devrait présenter le certificat wildcard !

Green Page

Yellow Page

Wildcard Certificate

Conclusion et remarques

Dans cet article on a pu :

  • Déployer un cluster kubernetes sur Scaleway avec Terraform
  • Déployer Cert Manager & Nginx via Helm & Terraform
  • Déployer les DNS Webhook Scaleway
  • Générer des certificats wildcard avec le protocole DNS-01 via les webhooks
  • Déployer une application dans un sous-domaine, présentant le certificats wildcard

Maintenant on peut déployer autant d'application dans le sous-domaine que l'on veut, en utilisant toujours le même certificat. Pour faciliter l'exercice, ici tout a été fait dans un même namespace, ne pas hésiter à lire la doc de cert-manager pour partager les certificats entre plusieurs namespaces.

Enfin on retrouvera le code d'exemple de l'articile ici : https://gitlab.com/rverchere/vrchr-k8s-dns-demo

Et n'oublions pas : "It's always DNS" !

DNS Haiku

Références

Quelques ressources très utiles pour comprendre les concepts utilisés dans l'articule :