Le cluster est désormais opérationnel, prêt à accueillir, exécuter et exposer des applications.
Il reste cependant un point à traiter, à savoir la gestion des certificats.
De plus en plus d’environnements applicatifs sont construits sur des modèles web, ou les échanges transitent par des flux SSL. L’usage de certificats devient vite obligatoire, surtout pour l’exposition des assets à l’extérieur du cluster.
Plus les déploiements se font, plus la gestion des certificats peut devenir un vrai casse-tête. Entre leur génération et leur renouvellement, on peut vite enchainer les incidents.
C’est pourquoi il existe des compléments à kubernetes comme Cert-Manager. C’est un gestionnaire cloud natif de certificats. Il permet d’automatiser et de gérer tout le cycle de vie des certificats.
De plus, il s’intègre très bien avec Traefik que l’on a déployé dans l’article précédent.
La logique est la suivante:
Cert-Manager étend l’API kubernetes avec de nouveaux objets. Ces objets permettent de construire et de déclarer des chaines de certification, à commencer par des Issuers.
Un Issuer est un objet auquel on peut soumettre une demande de certificat, celui-ci va traiter l’information et fonction de son paramétrage va s’occuper de la génération du certificat pour ensuite le stocker dans un secret.
Ce secret va être déclaré au niveau de l’Ingress chargé d’exposer l’application à l’extérieur du cluster sous la gestion de l’Ingress controler.
Si dans l’article précédent, le CSR (Certificate Signing Request SSL) était généré manuellement, soumis à une PKI (public key infrastructure) externe, et obligé à créer le secret, soit même, à partir du certificat reçu, dans le cas de CertManager, toutes ces opérations vont être automatisées.
Un Issuer peut être transverse au cluster pour être sollicité depuis n’importe quel namespace (on parlera alors de ClusterIssuer) ou déclaré dans un namespace spécifique.
Il est bien sûr possible de multiplier les Issuers. Chacun pouvant solliciter une PKI différente. Il est ainsi aisé d’établir des Issuers par besoin, périmètre ou exigence de sécurité.
Dans l’exemple qui va suivre c’est Let's Encrypt qui va être retenu comme autorité de certification (soit la PKI). Elle est gratuite, ouverte à tous et peut s’utiliser à travers le protocole ACME.
En effet, si on veut automatiser l’obtention d’un certificat, il faut pouvoir justifier de la propriété du domaine pour lequel ce certificat va être déployé.
Pour cela le protocole ACME va proposer différents challenges possibles, que le demandeur devra résoudre pour prouver sa légitimité à générer un certificat pour le domaine souhaité.
ACME est parfaitement intégré à Cert-Manager qui va pouvoir l’utiliser pour communiquer avec Let's Encrypt.
Le type de challenge que l’on va utiliser est http.
Lorsque Let's Encrypt va recevoir une demande de certificat, il va chercher à accéder à un contenu web spécifique exposé sur une URL appartenant au domaine pour lequel le certificat est demandé. Le contenu est une clef unique, générée par Letsencrypt à chaque demande.
L’URL appelée est toujours de la forme https://adressedusite/.well-known/acme-challenge/la_clef.
Si le contenu est trouvé, alors cela prouve que le demandeur est propriétaire du domaine, puisqu’il a pu mettre à disposition la clef unique exigée par Let's Encrypt.
Cert-Manager va donc s’occuper de faire la demande et de provisionner sous forme d’un pod un mini serveur web répondant à l’URL appelé par Let's Encrypt dans laquelle ce dernier s’attend à trouver la clef.
C’est là qu’intervient Traefik, puis qu’il va détecter la mise en place de ce pod temporaire et va permettre l’exposition du service associé en créant dynamiquement la règle de routage.
L’appel à la sous-URL https://adressedusite/.well-known va ainsi pouvoir être redirigé non pas vers le pod de l’application de base, mais vers le pod temporaire généré par Cert-Manager.
Le contenu délivré par le pod devient ainsi accessible à l’extérieur, donc à Let's Encrypt.
Une fois le challenge passé, le certificat est délivré par Let's Encrypt puis le pod est détruit par Cert-Manager qui va stocker le certificat dans un secret.
Ce même secret va être utilisé par l’Ingress propre à l’application exposée…et voilà.
Cliquez sur l'image pour l'agrandir.
Chaque certificat fourni par lets’encrypt est valable 6 mois…et le plus beau c’est que Cert-Manager va automatiquement s’occuper de faire les demandes de renouvellement à l’approche de l’expiration.
La première étape consiste à dédier un namespace à l’hébergement des ressources propres à Cert-Manager.
Ce n’est pas obligatoire, mais c’est toujours plus propre.
Pour cela je reste sur ma convention de nommage défini en début de projet et je lance la commande suivante pour créer un namespace inf-cert-lan.
kubectl create ns inf-cert-lan
Cliquez sur l'image pour l'agrandir.
Ensuite, on bascule avec l’usage de Helm (déployé dans cet article) puisque Cert-Manager dispose d’un package.
Il suffit d’appeler les instructions suivantes:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace inf-cert-lan --version v1.15.1 --set installCRDs=true --set nodeSelector.network=lan
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
La seule customisation nécessaire à ce niveau est la déclaration de la version de Cert-Manager à utiliser. Au moment de cet article c’est la 1.15.1
Cert-Manager est désormais disponible sur le cluster. On peut le vérifier en tapant la commande.
kubectl get pod -n inf-cert-lan -o wide
Cliquez sur l'image pour l'agrandir.
On observe bien les pods propres à CertManager.
Pour illustrer le fonctionnement de l’ensemble. On va repartir de l’exemple issu de l’article précédent, mais cette fois-ci on va publier l’application sur le web, à travers une URL de mon domaine myprivatelab.tech.
On déclare donc un nouveau namespace dev-democertmanager-dmz:
kubectl create ns dev-democertmanager-dmz
Cliquez sur l'image pour l'agrandir.
Le premier fichier utilisé est 01-deploy-democertmanager-front.yaml dont le contenu est le suivant:
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-democertmanager-front
namespace: dev-democertmanager-dmz
labels:
environment: dev
network: dmz
application: democertmanager
spec:
strategy:
type: Recreate
selector:
matchLabels:
environment: dev
network: dmz
application: democertmanager
template:
metadata:
labels:
environment: dev
network: dmz
application: democertmanager
spec:
nodeSelector:
network: dmz
tolerations:
- key: "node-role.kubernetes.io/worker-dmz"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: demo-nginx-traefik-lan
image: nginx:1.27
resources:
requests:
memory: "8Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: 1
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 5
exec:
command:
- ls /usr/share/nginx/html
ports:
- containerPort: 80
Il reprend la création d’un objet Deployment utilisé précédemment, mais cette fois-ci avec une exécution du pod rattaché sur un nœud en DMZ (voir présentation de l’infrastructure).
Cette sélection se fait via les options:
nodeSelector:
network: dmz
tolerations:
- key: "node-role.kubernetes.io/worker-dmz"
operator: "Exists"
effect: "NoSchedule"
On s’assure de choisir un nœud avec le label network : dmz et on autorise le pod à supporter la contrainte worker-dmz (voir ici pour plus d’explication).
On utilise toujours une simple image NGINX en version 1.27.
On applique le fichierkubectl apply -f 01-deploy-democertmanager-front.yaml
Cliquez sur l'image pour l'agrandir.
On s’occupe ensuite de la création du service via le fichier 02-svc-democertmanager-front.yaml.
Celui-ci va se raccrocher au pod grâce aux labels positionnés sur l’option selector (encore une fois, on verra la logique globale de déploiement d’une application dans un article dédié).
Le contenu du fichier est le suivant:
---
kind: Service
apiVersion: v1
metadata:
name: svc-democertmanager-front
namespace: dev-democertmanager-dmz
labels:
environment: dev
network: dmz
application: democertmanager
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
environment: dev
network: dmz
application: democertmanager
L'application se fait par la commande:
kubectl apply -f 02-svc-democertmanager-front.yaml
Cliquez sur l'image pour l'agrandir.
Le fichier suivant 03-issuer-democertmanager-letsencrypt.yaml concerne l’issuer.
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: issuer-democertmanager-letsencrypt
namespace: dev-democertmanager-dmz
labels:
environment: dev
network: dmz
application: democertmanager
spec:
acme:
email: votremail@domaine.fr
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: sec-issuer-democertmanager
solvers:
- http01:
ingress:
class: traefik-dmz
podTemplate:
metadata:
labels:
network: dmz
spec:
nodeSelector:
network: dmz
tolerations:
- key: "node-role.kubernetes.io/worker-dmz"
operator: "Exists"
effect: "NoSchedule"
ingressTemplate:
metadata:
labels:
network: dmz
On appelle une branche de l’API K8S différente et propre à Cert-Manager.
En dehors des classiques labels et de la précision du namespace à utiliser, on arrive à la section spec.
C’est à partir de ce point qu’on va indiquer que cet issuer appelé issuer-democertmanager-letsencrypt va se baser sur le protocole ACME.
Puis on rentre dans la sous-section du protocole pour y définir les propriétés rattachées. Le plus important étant l’URL de la PKI supportant ACME à solliciter pour établir la demande et établir le challenge.
Let's Encrypt dispose de plusieurs URL dont certaines sont dédiées à des environnements de tests, d’autres à des environnements de productions. À savoir qu’il ne vous est pas possible de faire trop de demande dans un temps très cours.
Vous devez respecter les conditions d’usage et ne pas vous amuser à solliciter le service sans arrêt, sous peine de vous faire bannir pendant un laps de temps défini.
Je vous invite à lire la documentation associée pour en apprendre davantage et obtenir plus d’informations sur les conditions d’usages de Let's Encrypt.
J’en profite aussi pour vous signaler que pour qu’un certificat généré par Let's Encrypt soit reconnu sur vos devices, il faut que ces derniers considèrent Let's Encrypt comme une PKI de confiance, et donc disposer du CA public de Let's Encrypt dans son référentiel.
C’est le cas de la majorité des OS et navigateur récents, mais il est possible que certains périphériques spécifiques nécessitent au préalable d’être configurés pour reconnaitre Let's Encrypt.
L’autre composante importante de la configuration de notre issuer concerne la partie solver. C’est ici qu’on va indiquer l’usage d’un challenge de type http. C’est aussi à ce niveau et plus particulièrement dans les sous sections podTemplate et IngressTemplate qu’on va spécifier les caractéristiques que devront avoir les pod et Ingress temporaires utilisés.
Sans ces informations, Traefik ne pourra détecter la création de ces composants, et donc ne pas créer les règles de routage nécessaires.
Le challenge ne pourra ainsi jamais être réalisé et le certificat jamais obtenu.
On applique la commande suivante pour appliquer notre fichier:
kubectl apply -f 03-issuer-democertmanager-letsencrypt.yaml
Cliquez sur l'image pour l'agrandir.
On peut vérifier la statue de notre issuer à l’aide de l’instruction suivante:
kubectl describe issuer -n dev-democertmanager-dmz
Cliquez sur l'image pour l'agrandir.
(Veuillez noter que je ne précise pas le nom de l'issuer, car ce dernier étant seul dans le namespace, il est sélectionné par défault...cela marche pour d'autres objets).
Le plus intéressant est la partie Status.
On n’a bien confirmation que notre issuer c’est correctement enregistré auprès de Let's Encrypt. Il est donc prêt à relayer les demandes de certificats.
C’est maintenant le tour du dernier fichier04-ing-democertmanager-front.yaml.
Pour une fois, je ne vais pas déclarer qu’un objet par fichier, mais bien deux.
Le premier est un objet Certificate issue de l’API Cert-Manager.
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cert-myprivatelab-default
namespace: dev-democertmanager-dmz
labels:
environment: prd
network: dmz
application: myprivatelab
tier: default
spec:
commonName: democertmanager.myprivatelab.tech
secretName: sec-certificate-democertmanager
dnsNames:
- democertmanager.myprivatelab.tech
issuerRef:
kind: Issuer
name: issuer-democertmanager-letsencrypt
Dans la partie spec de cet objet, on trouve des caractéristiques propres à des certificats : le commonName et le ou les dnsNames.
C’est classiquement ce qu’on retrouve dans un CSR (Certificate Signing Request SSL) comme on l’a vu dans l’article l’article précédent. On pourrait d’ailleurs ajouter des caractéristiques et des informations complémentaires qu’on voudrait retrouver dans le certificat.
On indique aussi le secretName, qui n’est autre que le nom du secret dans lequel le certificat va être stocké.
Enfin dans la sous-section issueRef on précise l’issuer à appeler, en l’occurrence celui qu’on vient de créer.
On passe à la deuxième partie du fichier qui concerne l’objet Ingress. Ici on reprend exactement ce qu’on n’a pu voir précédemment.
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ing-democertmanager-default
namespace: dev-democertmanager-dmz
labels:
environment: dev
network: dmz
application: democertmanager
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
ingressClassName: traefik-dmz
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik-dmz
tls:
- hosts:
- democertmanager.myprivatelab.tech
secretName: sec-certificate-democertmanager
rules:
- host: democertmanager.myprivatelab.tech
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc-democertmanager-front
port:
number: 80
Seul l’environnement « dmz » remplace l’environnement « lan » puisqu’il s’agit cette fois d’exposer notre application sur le web.
On y retrouve les règles de routages, ainsi que la référence au secret contenant le certificat qui doit bien entendu être le même que celui qu’on a spécifié juste au-dessus.
On applique le tout.
kubectl apply -f 04-ing-democertmanager-front.yaml
Cliquez sur l'image pour l'agrandir.
Plusieurs choses sont intéressantes.
Si on liste les pods du namespace:
kubectl get pod -n dev-democertmanager-dmz -o wide
Cliquez sur l'image pour l'agrandir.
On y retrouve désormais deux pods. Le premier propre à notre application et un second commençant par cm-acme-http-solver.
C’est ce second pod qui va délivrer le contenu web attendu par Let's Encrypt.
Si on observe le dashboard Traefik en dmz, on n’y retrouve les nouvelles règles de routages propres à la redirection des requetes spécifiques au challenge.
Cliquez sur l'image pour l'agrandir.
Ces composants n’apparaissent que très peu de temps, puisqu’une fois le challenge réussi, le pod déployé par Cert-Manager et les objets associés sont automatiquement supprimés.
Si on liste à nouveau les pods, il ne reste que notre pod applicatif et côté Traefik seul la règle de routage propre à notre application de test est visible.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
D'ailleur on peut vérifier le statue du certificat directement avec la commande:
kubectl get certificate -n dev-democertmanager-dmz o wide
Cliquez sur l'image pour l'agrandir.
La section READY est à true
Il ne reste plus qu’à vérifier du succès de l’opération. Pour cela rien de plus simple puisqu’il s’agit de se rendre sur l’URL de notre site.
Celui-ci répond, délivre son contenu et on peut observer que le certificat employé est valide et délivré par Let's Encrypt.
Cliquez sur l'image pour l'agrandir.
Cliquez sur l'image pour l'agrandir.
La gestion des certificats est un élément très important au sein des applications actuelles. Ils sont largement utilisés et leur usage devient de plus en plus restrictif concernant leur niveau de sécurité (chiffrement utilisé, durée de vie...).
L’usage de composant comme Cert-Manager est un avantage clef dans un environnement micro services et apporte une tranquillité d’esprit salutaire pour les administrateurs.
Une fois de plus, on voit que l’écosystème kubernetes bénéficie d’un large support d’outillage tiers avec des interactions complémentaires à l'image de Cert-Manager et Traefik.
Comme j’ai pu le souligné dans ma définition de kubernetes, on n’est vraiment dans une logique modulaire ou chacun peut exploiter les briques qu’il l’intéresse pour construire une plateforme répondant à ses exigences.
Dans l’exemple vu ici, c’est une PKI public à savoir Let's Encrypt qui est utilisé…mais rien n’empeche la logique présentée de s’appliquer à d’autre PKI, comme des PKI internes. Même une PKI Microsoft comme celle utilisée dans l’article précédent peut être appelée….grâce à des proxys ACME comme acme2certifier.
On va enfin pouvoir déployer une réelle application. Il ne reste plus qu’à capitaliser sur l’ensemble des briques déployées précédemment…mais pour ça il faut basculer sur cet article.