Featured image of post cert-manager et le challenge DNS01 Cloudflare

cert-manager et le challenge DNS01 Cloudflare

Pour une URL interne, seul le challenge ACME DNS01 permet à cert-manager d'obtenir un certificat Let's Encrypt : le challenge HTTP01 exige que le serveur soit joignable depuis Internet.

Contexte

Je voulais un certificat Let’s Encrypt “vrai” (pas auto-signé) pour une URL interne, jamais exposée sur Internet, servie par un ingress Kubernetes piloté par cert-manager. Avec l’Issuer/ClusterIssuer ACME par défaut (challenge HTTP01), la validation échoue systématiquement : Let’s Encrypt essaie de joindre http://<mon-url-interne>/.well-known/acme-challenge/... et ne peut évidemment pas l’atteindre.

Ce que j’ai appris

Le challenge HTTP01 nécessite que l’URL soit accessible publiquement ; le challenge DNS01 non : il vérifie uniquement qu’un enregistrement TXT _acme-challenge.<domaine> existe dans la zone DNS publique du domaine. Comme mon domaine est géré sur Cloudflare, cert-manager peut créer/supprimer ce TXT automatiquement via l’API Cloudflare, sans que l’hôte lui-même ne soit joignable depuis l’extérieur.

Détails

Un token API Cloudflare scopé (pas la Global API Key), avec la permission Zone:DNS:Edit sur la zone concernée, stocké en Secret :

kubectl create secret generic cloudflare-api-token \
  --namespace cert-manager \
  --from-literal=api-token=<TOKEN_SCOPE_ZONE_DNS_EDIT>

Un ClusterIssuer qui utilise ce secret pour le challenge DNS01 :

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns01-cloudflare
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: moi@example.com
    privateKeySecretRef:
      name: letsencrypt-dns01-cloudflare
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
        selector:
          dnsZones:
            - "example.com"

Puis un Certificate (ou une annotation cert-manager.io/cluster-issuer sur l’Ingress) pour le sous-domaine interne :

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: interne-example-com
  namespace: mon-app
spec:
  secretName: interne-example-com-tls
  issuerRef:
    name: letsencrypt-dns01-cloudflare
    kind: ClusterIssuer
  dnsNames:
    - interne.example.com

Point important : interne.example.com n’a besoin d’aucun enregistrement A/CNAME public : seule la zone example.com doit être déléguée à Cloudflare, pour que le TXT temporaire _acme-challenge.interne.example.com y soit visible le temps de la validation. La résolution réelle du sous-domaine reste purement interne (DNS privé, /etc/hosts, split-horizon…).

Pourquoi c’est utile

Ça permet d’avoir un certificat reconnu par tous les navigateurs pour des services internes (dashboards, VPN-only, ingress privés) sans certificat auto-signé à distribuer manuellement, et sans jamais exposer le service sur Internet : seule la zone DNS publique du domaine est sollicitée, pas le service lui-même.

Sources

Généré avec Hugo
Thème Stack conçu par Jimmy