Cert-Manager
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”.
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 !
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: [email protected]
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
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: [email protected]
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: [email protected]
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