Introduction

D’habitude, lorsque je commence à écrire une documentation, je commence toujours par une définition (provenant souvent de Wikipédia). Comme la page Wikipédia de cert-manager n’est pas très complète (et aussi pour être à la mode et en parler au moins une fois), je vais demander à ChatGPT pour m’écrire cette définition.

Cert-manager est un outil de gestion de certificats pour Kubernetes. Il permet d’automatiser la génération, la renouvellement et la gestion des certificats SSL/TLS pour vos applications sur le cluster. Cert-manager prend en charge plusieurs fournisseurs de certification tels que Let’s Encrypt, Vault, etc. En utilisant Cert-manager, vous pouvez déclarer la nécessité d’un certificat pour une application spécifique via un objet Kubernetes, appelé “Certificate” ou “Issuer”. Cert-manager s’occupe alors de la génération ou du renouvellement du certificat selon les spécifications définies dans l’objet. Cert-manager est un composant clé pour assurer la sécurité et la confidentialité des données dans les applications sur Kubernetes. Il facilite la gestion des certificats pour les administrateurs de cluster, ce qui permet d’éviter les erreurs humaines et de garantir la disponibilité constante de certificats valides pour les applications. En résumé, Cert-manager est un outil puissant pour gérer les certificats SSL/TLS dans un environnement Kubernetes, permettant une gestion plus efficace et sécurisée des certificats pour vos applications.

ps: Oui, oui. ChatGPT m’a écrit “la renouvellement”.

Schema cert-manager

Jusqu’à maintenant, j’utilisais Traefik en tant que Ingress. Celui-ci générait les certificats et les stockait dans un fichier texte situé dans un volume longhorn. (en sachant pertinemment que ce n’était pas très propre)

Aujourd’hui, c’est l’heure de la propreté.. on passe sur Cert-Manager !

Même incroyable pour parler de propreté

Installer Cert-Manager

Au jour où j’écris cette page, nous en sommes à la version v1.11.0.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml

Avertissement

Pensez à récupérer la dernière version de cert-manager. Vous trouverez les versions disponibles : ici

Fonctionnement de Cert-Manager

L’installation de Cert-Manager va différents objets. Ceux qui nous intéressent sont :

  • Les Issuers
  • Les certificats

Les Issuers sont les fournisseurs de certificats. Cert-Manager est compatible avec les fournisseurs suivants :

  • ACME HTTP/DNS (compatible letsencrypt)
  • Auto-signé (Je conseille plutôt de générer son propre CA)
  • CA custom
  • Vault
  • Venafi

Pour le moment, seul le fournisseur LetsEncrypt nous intéresse. (Nous verrons peut-être le cas du CA un jour)

Ajouter un fournisseur (Issuer)

ACME via challenge HTTP

Le cas le plus courant lorsqu’on génère un certificat est d’utiliser LetsEncrypt avec un challenge HTTP. (ex, CertBot) Sa configuration est assez rapide, voici le manifest permettant d’ajouter le ACME de LetsEncrypt. (Pensez à remplacer ‘istio’ par votre Ingress)

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: votre_email@ici.tld
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - selector: {}
      http01:
        ingress:
          class: istio

à retenir :

  • Il faut bien donner l’ingress utilisé, le challenge a besoin d’être fait sur le port 80 en http.
  • l’email fourni servira à LetsEncrypt de vous notifier lorsque le certificat doit être renouvelé.

Astuce

Si vous échouez trop de challenges (ou que vous générez trop de fois le même certificat). Il se peut que vous soyez bloqué par LetsEncrypt. Lorsque vous voulez juste tester les procédures, il est possible d’utiliser l’API staging (donc sans rate-limits).

Les certificats ne seront pas acceptés par votre navigateur, mais à des fins de tests : c’est l’idéal.

Il vous suffit de remplacer l’url par https://acme-staging-v02.api.letsencrypt.org/directory

Il vous est possible de vérifier que l’Issuer est bien présent via la commande :

➜  kubectl describe issuers.cert-manager.io letsencrypt

Status:
  Acme:
    Last Registered Email:  redacted
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/941914187
  Conditions:
    Last Transition Time:  2023-01-31T10:05:12Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

ACME via challenge DNS

Avant tout : votre fournisseur n’est pas toujours compatible avec cette méthode. J’utilise CloudFlare qui (grâce à son API) permet de créer des entrées dans votre domaine pour résoudre le challenge. Cette méthode possède certains avantages comme le fait que nous n’avons pas à ouvrir un port pour résoudre le challenge.

Pour utiliser l’API, il faut créer un token pour authentifier notre requête. Rendez-vous sur cette page pour créer votre jeton. Les permissions nécéssaires sont :

  • Zone.Zone READ
  • Zone.DNS WRITE

Génération Token

Avec le token, créez ce secret:

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
type: Opaque
stringData:
  api-token: aaaaaabbbbbbbcccccccdddddd

Et d’ajouter notre fournisseur Cloudflare. (Celui-ci utilisera notre secret)

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: cloudflare
spec:
  acme:
    email: votre_email@ici.tld
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: cloudflare
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token

Créer un Issuer pour tous les namespaces

(Update du 14/12/2023)

Nous avons précédemment créé un Issuer pour le namespace courant (ce qui implique ne pouvoir créer des certificats avec cet Issuer que dans ce namespace).

Mais nous avons rarement un seul namespace, et dès lorsque nous devons mettre à jour la configuration de notre Issuer, il faut le faire dans tous les namespaces (ce qui est assez lourd et source d’erreur).

C’est pourquoi il est possible de créer un Issuer valide sur l’ensemble des namespaces : le ClusterIssuer.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: cloudflare
spec:
  acme:
    email: votre_email@ici.tld
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: cloudflare
    solvers:
    - dns01:
        cloudflare:
          apiTokenSecretRef:
            name: cloudflare-api-token-secret
            key: api-token

Le ClusterIssuer possède exactement les mêmes paramètres que l’Issuer. Le seul changement à prévoir est que le secret cloudflare-api-token-secret doit être dans le namespace cert-manager plutôt que dans le namespace courant.

Créer un certificat

Fournisseur configuré, il est maintenant possible de créer notre certificat. Je vais générer le mien pour mon domaine test.une-tasse-de.cafe.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-coffee
spec:
  secretName: test-coffee-tls
  issuerRef:
    name: letsencrypt
  commonName: test.une-tasse-de.cafe
  dnsNames:
  - test.une-tasse-de.cafe

Information

Si vous utilisez l’objet ClusterIssuer, il faut bien préciser le kind dans le issuerRef :

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-coffee
spec:
  secretName: test-coffee-tls
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
  commonName: test.une-tasse-de.cafe
  dnsNames:
  - test.une-tasse-de.cafe

Vérifiez bien que le certificat est généré et disponible.

➜  kubectl describe certificate test-coffee
Events:
  Type    Reason     Age    From                                       Message
  ----    ------     ----   ----                                       -------
  Normal  Issuing    7m9s   cert-manager-certificates-trigger          Issuing certificate as Secret was previously issued by Issuer.cert-manager.io/letsencrypt
  Normal  Reused     7m9s   cert-manager-certificates-key-manager      Reusing private key stored in existing Secret resource "test-coffee-tls"
  Normal  Requested  7m8s   cert-manager-certificates-request-manager  Created new CertificateRequest resource "test-coffee-j8x9j"
  Normal  Issuing    5m46s  cert-manager-certificates-issuing          The certificate has been successfully issued

Et que le secret soit bien créé :

➜  kubectl get secret test-coffee-tls
NAME                      TYPE                DATA   AGE
test-coffee-tls   kubernetes.io/tls   2      169m

Créer un certificat wildcard

Un certificat wildcard est un certificat qui permet de sécuriser un domaine et tous ses sous-domaines. (ex: *.une-tasse-de.cafe). Il vous faudra utiliser la vérification DNS pour générer ce certificat.

Celui-ci se génère de la même manière qu’un certificat classique, il suffit de rajouter le * devant le nom de domaine.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-coffee
  namespace: default
spec:
  secretName: wildcard-coffee
  issuerRef:
    name: cloudflare
    kind: ClusterIssuer
  commonName: "*.une-tasse-de.cafe"
  dnsNames:
  - "une-tasse-de.cafe"
  - "*.une-tasse-de.cafe"

Utiliser un certificat

Voici un exemple de yaml permettant de générer un Ingress en utilisant le secret.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-coffee
  annotations:
    kubernetes.io/ingress.class: "istio"
spec:
  tls:
  - hosts:
    - test.une-tasse-de.cafe
    secretName: test-coffee-tls
  rules:
  - host: "test.une-tasse-de.cafe"
    http:
      paths:
        - pathType: Prefix
          path: "/"
          backend:
            service:
              name: srvc-coffee
              port:
                number: 80

Ou avec un objet IngressRoute si (comme moi) vous utilisez Traefik.

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: test-coffee
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`test.une-tasse-de.cafe`)
      kind: Rule
      services:
        - name: srvc-coffee
          port: 80
  tls:
    secretName: test-coffee-tls