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.
