Passer au contenu principal

Kubernetes Management

Traefik : Intégration ACME

Traefik dispose nativement d'une intégration à ACME et Let's Encrypt, sans passer par l'habituel cert-manager.

La documentation officielle décrit comment l'implémenter, avec un DNS CloudFlare comme exemple :

traefik-helm-chart/EXAMPLES.md at master · traefik/traefik-helm-chart
Traefik Proxy Helm Chart. Contribute to traefik/traefik-helm-chart development by creating an account on GitHub.

Le contexte est simple ici :

  • Domaine déjà déclaré chez GoDaddy
  • LoadBalancer nginx frontal en passthru, donc SSL termination sur le contrôleur Ingress
  • Cluster RKE2 (mais pareil pour K8S)
  • Une application déjà exposée via Ingress : ArgoCD, à qui l'on doit délivrer un certificat Let's Encrypt valide.

Etape 1 : Activer l'intégration Let's Encrypt x Traefik

Activer de Let's Encrypt

Nous ne documenterons pas le déploiement de Traefik ici, mais on partira du principe qu'il est déployé depuis un Helm Chart.

Par défaut Traefik n'a pas de CertResolver définit, le Helm Chart a les values suivantes :

# -- Certificates resolvers configuration
certResolvers: {}
#   letsencrypt:
#     # for challenge options cf. https://doc.traefik.io/traefik/https/acme/
#     email: email@example.com
#     dnsChallenge:
#       # also add the provider's required configuration under env
#       # or expand then from secrets/configmaps with envfrom
#       # cf. https://doc.traefik.io/traefik/https/acme/#providers
#       provider: digitalocean
#       # add futher options for the dns challenge as needed
#       # cf. https://doc.traefik.io/traefik/https/acme/#dnschallenge
#       delayBeforeCheck: 30
#       resolvers:
#         - 1.1.1.1
#         - 8.8.8.8
#     tlsChallenge: true
#     httpChallenge:
#       entryPoint: "web"
#     # It has to match the path with a persistent volume
#     storage: /data/acme.json

Il y a trois types de challenges :

  • La partie dnsChallenge est utilisée lorsque les services ne sont pas publiquement disponible, il est ainsi d'interroger un provider avec une clé d'API pour vérifier l'appartenance du domaine et récupérer un certificat.
  • La partie tlsChallenge permet d'utiliser HTTPS plutôt que HTTP pour la validation du certificat. Le service doit être donc accessible sur le port 443 depuis internet.
  • La partie httpChallenge, standard, utilise de l'HTTP. Le service doit être accessible sur le port 80 depuis internet.

Pour activer l'intégration avec Let's Encrypt, on ajuste les valeurs suivantes :

certResolvers:
  letsencrypt:
    email: votremail@example.com
    storage: /data/acme.json

Stockage des certificats

La section storage permet de définir où les certificats seront stockés, en le pointant de préférence vers un volume persistent pour éviter de perdre les certificats (et devoir les redemander) en cas de crash du pod.

Pensez donc à activer la persistence des données dans la section suivante, enabled doit être fixé à true.

persistence:
  accessMode: ReadWriteOnce
  annotations: {}
  enabled: true
  name: data
  path: /data
  size: 128Mi

Il semble y avoir un bug lié au podSecurityContext. Si l'on déploie avec la persistence d'activer, on obtient une erreur sur le resolver letsencrypt.

💡
L'article a été rédigé pour Traefik v2. L'opération est similaire en v3, certains bugs ont pu être corrigé.

Sur GitHub, on trouve un paquet de solution qui ne fonctionnent pas. Les dernières mentionnent deux choses qui fonctionnent :

  • Modifier le paramètre fsGroup
  • Exécuter l'initContainer avec l'ID de l'utilisateur fixé dans podSecurityContext, qui est le 65532.
acme resolver not working with persistence enabled · Issue #396 · traefik/traefik-helm-chart
The acme resolver isn’t working with persistence enabled due to file permissions. See below the log. time=“2021-03-22T11:03:24Z” level=error msg=“The ACME resolver \“le\” is skipped from the resolv…

Aucune des solutions n'a fonctionné chez moi, en revanche, ce qui a fonctionné c'est de définir le fsGroup de podSecurityContext à 65532 et de modifier l'initContainer pour l'exécuter et donner les droits au compte du conteneur, 65532.

deployment:
  initContainers:
  # The "volume-permissions" init container is required if you run into permission issues.
  # Related issue: https://github.com/traefik/traefik-helm-chart/issues/396
  - name: volume-permissions
    image: busybox:latest
    command: ["sh", "-c", "touch /data/acme.json; chmod -v 600 /data/acme.json; chown -R 65532:65532 /data"]
    securityContext:
      runAsNonRoot: false
      runAsGroup: 0
      runAsUser: 0
    volumeMounts:
      - name: data
        mountPath: /data

podSecurityContext:
  fsGroupChangePolicy: OnRootMismatch
  runAsGroup: 65532
  runAsNonRoot: true
  runAsUser: 65532
  fsGroup: 65532

Mettez à jour votre Helm Chart, en cas de soucis avec l'initContainer, le Pod restera en pending mais vous pourrez vérifiez ses logs.

Si l'initContainer tourne, les logs de traefik indiqueront si le certresolver de Let's Encrypt est ignoré, uniquement en cas d'erreur.

helm -n traefik upgrade traefik traefik/traefik -f values.traefik.yaml

Il est possible de vérifier le contenu du fichier /data/acme.json directement avec un shell.

💡
Quelle est l'intérêt de stocker le certificat si on peut en redemander un nouveau ?

Let's Encrypt limite le nombre de requête pour un domaine. Si traefik plante une dizaine de fois par exemple, suite à un bug ou une erreur humaine, vous pourriez bien vous retrouver sans certificats.

Etape 2: Création de la route Ingress

Traefik a ses propres CRDs, différentes d'un Ingress standard, on définira donc une IngressRoute dans l'API de traefik

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: argocd-server
  namespace: argocd
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`argocd.prod.zenops.fr`)
      priority: 10
      services:
        - name: argocd-server
          port: 80
    - kind: Rule
      match: Host(`argocd.prod.zenops.fr`) && Headers(`Content-Type`, `application/grpc`)
      priority: 11
      services:
        - name: argocd-server
          port: 80
          scheme: h2c
  tls:
    certResolver: letsencrypt
    secretName: argocd-server-tls

Déployer l'Ingress, le certificat devrait être automatiquement être demandé par Traefik, qui l'ajoutera dans son fichier /data/acme.json .

💡
Bien entendu, l'utilisation de l'intégration ACME fournie par Traefik n'est en aucun cas obligatoire. Vous pouvez utilisez cert-manager pour gérer vos certificats.