Aller au contenu

Runbook : TLSCertExpiresSoon

Alerte : TLSCertExpiresSoon (PrometheusRule, severity warning à J-30, critical à J-7)

Symptôme :

Un certificat TLS (ingress, Keycloak, Harbor) expire dans moins de 30 jours et n'a pas été renouvelé automatiquement.

Severity : 🟡 warning à J-30, 🔴 critical à J-7


Diagnostic

1. Identifier le certificat qui expire

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

# Via cert-manager
kubectl get certificate -A \
  -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,READY:.status.conditions[0].status,NOT_AFTER:.status.notAfter

# Via les secrets TLS directement
for s in $(kubectl get secret -A -o json | jq -r '.items[] | select(.type=="kubernetes.io/tls") | "\(.metadata.namespace)/\(.metadata.name)"'); do
  NS=$(echo $s | cut -d/ -f1)
  NAME=$(echo $s | cut -d/ -f2)
  NOT_AFTER=$(kubectl get secret -n $NS $NAME -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  echo "$NS/$NAME -> $NOT_AFTER"
done | sort

2. Vérifier que cert-manager est sain

kubectl get pod -n cert-manager
kubectl logs -n cert-manager -l app=cert-manager --tail=50

3. Vérifier le Issuer/ClusterIssuer

kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod | tail -20

Causes fréquentes + correctif

Cause Symptôme Correctif
Rate limit ACME Let's Encrypt too many certificates already issued Utiliser le staging pendant les tests + attendre 1 semaine.
Challenge HTTP-01 bloqué self-check failed Vérifier que l'Ingress acme-http-solver est joignable depuis internet (80/TCP).
Challenge DNS-01 non configuré Pour les certs wildcard Configurer le fournisseur DNS (Cloudflare, Route53) dans le ClusterIssuer.
Secret TLS corrompu failed to parse certificate kubectl delete secret <name> → cert-manager le recrée.
CertificateRequest bloqué Pending Voir kubectl describe certificaterequest <name>.
Clé compte Let's Encrypt perdue 401 Unauthorized Recréer le ClusterIssuer avec une nouvelle privateKey.

Correctif d'urgence (< 10 min)

Forcer un renouvellement manuel (cert-manager)

# Supprime le cert, cert-manager le recrée
kubectl delete certificate -n <namespace> <cert-name>
# Ou annotation pour forcer le renew
kubectl annotate certificate -n <namespace> <cert-name> \
  cert-manager.io/issue-temporary-certificate=true --overwrite

Si Let's Encrypt prod est rate-limited — basculer temporairement vers staging

Jamais à long terme en prod — uniquement pendant l'investigation.


Correctif pérenne (R02)

  1. Auto-renewal cert-manager doit être sain :
  2. Tous les certs doivent avoir renewBefore: 720h (30 jours avant expiration)
  3. Alertes précoces : PrometheusRule à J-30 et non à J-7
  4. Cert wildcard via DNS-01 pour éviter les problèmes HTTP-01 par sous-domaine

Jamais : copier un cert en live avec kubectl create secret tls. Toujours via cert-manager + Git.


Prévention

  • PrometheusRule précoce : alerte à J-30 via probe_ssl_earliest_cert_expiry (blackbox-exporter)
    - alert: TLSCertExpiresSoon
      expr: probe_ssl_earliest_cert_expiry{} - time() < 30 * 86400
      severity: warning
    - alert: TLSCertExpiresCritical
      expr: probe_ssl_earliest_cert_expiry{} - time() < 7 * 86400
      severity: critical
    
  • Test Playwright hebdomadaire : vérifier HTTPS root + expiry > 7 j
  • Documentation : liste des certs critiques + qui les renouvelle

Lessons learned

  • Let's Encrypt a des rate limits stricts (50 certs/semaine/domaine, 5 duplicates/semaine). Utiliser le staging pour tous les tests.

Liens utiles