Ingress with TLS
Dans l’article précédent, nous avons vu comment les objets Ingress dans Kubernetes permettaient d’accéder à nos workloads sur le cluster via un reverse proxy HTTP. Le trafic HTTP est par défaut lisible sur tout le chemin entre le client et le serveur, c’est-à-dire qu’on peut lire le contenu de ce qui est transporté sur HTTP simplement en récupérant le trafic sur le chemin. C’est pour cela que la plupart des échanges HTTP sur internet aujourd’hui se font en HTTPS (S pour Secure). HTTPS est en vérité le protocole HTTP sur le protocole TLS (Transport Layer Security) qui permet de chiffrer le trafic entre le client et le serveur. Ainsi, il n’y a que le client et le serveur qui peuvent connaître le contenu de l’échange.
Les Ingress Kubernetes nous permettent d’utiliser le protocole HTTPS en mettant en place une connexion TLS entre le client et le reverse proxy qui traite la demande.
Voyons un exemple. Nous allons repartir de notre déploiement de serveurs apache de l’article précédent, et de notre configuration d’ingress, mais en y ajoutant des lignes pour signifier qu’on veut utiliser HTTPS et pas juste HTTP :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-server-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
tls:
- hosts:
- customwebsite.com
secretName: cert-customwebsite-com
rules:
- host: customwebsite.com
http:
paths:
- pathType: Prefix
path: /apache
backend:
service:
name: apache-ingress
port:
number: 8080
Nous avons ajouté ici la section ‘tls’ où il y a deux informations; hosts : quels noms de domaines nous voulons utiliser avec la couche TLS (donc HTTPS) secretName : le nom du secret qui renferme les informations sur le certificat et la clé de chiffrement de l’échange Expliquons un peu plus cette dernière partie. Lorsqu’on utilise TLS, il se passe entre le client et le serveur un échange qui permet de se mettre d’accord sur comment la communication va être chiffrée. Avant de chiffrer la communication, ici, le serveur (donc notre ingress) doit présenter un certificat qui prouve son identité (il est bien customwebsite.com). Ce certificat porte une signature avec une clé privée qui doit accompagner le certificat et qui n’est connue que du serveur. Je fais très bref en une phrase, mais pour plus d’informations, vous pouvez consulter le fonctionnement des PKI ici par exemple
C’est pour cela qu’on met dans l’objet ingress un secret qui va contenir à la fois le certificat qui prouve qu’on est customwebsite.com, et la clé privée qui accompagne le certificat.
Pour notre besoin, nous allons générer nous-mêmes un certificat auto-signé pour customwebsite.com :
openssl req -x509 -newkey rsa:2048 -keyout tls.key -out tls.crt -sha256 -days 365 -nodes -subj "/CN=customwebsite.com" -addext "subjectAltName = DNS:customwebsite.com"
Cette commande nous donne en sortie le certificat dans le fichier tls.crt et la clé dans le fichier tls.key. Nous utilisons le champ SAN (SubjectAlternativeName) car le champ CN (CommonName) n’est plus utilisé par les dernières versions du reverse proxy nginx qu’on utilise en tant qu’ingress. Le champ CN est là uniquement pour des raisons historiques.
Nous allons pouvoir créer le secret attendu par l’ingress :
kubectl create secret tls cert-customwebsite-com --cert=tls.crt --key=tls.key
secret/cert-customwebsite-com created
Ce Secret Kubernetes est de type TLS. On lui donne bien sûr le même nom que celui sur l’ingress.
Maintenant que nous avons le secret, nous allons ajouter lancer la nouvelle configuration de l’ingress que nous avons plus haut :
kubectl apply -f web-server-ingress.yaml
ingress.networking.k8s.io/web-server-ingress created
Si on s’intéresse aux logs de notre reverse proxy, nous pouvons voir qu’il a bien pris en compte le nouvel objet ingress, ainsi que le secret qui l’accompagne :
kubectl logs ingress-nginx-test-controller-7b689896b4-v25d6
…
I0811 14:40:48.952767 8 main.go:110] "successfully validated configuration, accepting" ingress="test/web-server-ingress"
I0811 14:40:48.969692 8 store.go:432] "Found valid IngressClass" ingress="test/web-server-ingress" ingressclass="nginx"
I0811 14:40:48.970610 8 backend_ssl.go:67] "Adding secret to local store" name="test/cert-customwebsite-com"
…
Nous pouvons donc faire un appel vers le serveur apache via la même commande que dans l’article précédent mais en précisant qu’on veut communiquer en HTTPS :
curl -k --header 'Host: customwebsite.com' https://20.101.169.20/apache
<html><body><h1>It works!</h1></body></html>
L’option -k dit à curl de ne pas vérifier le certificat auprès des autorités de certifications qu’il reconnaît. Comme le certificat est auto-signé, curl va sortir une erreur s’il le vérifie. La communication est tout de même chiffrée avec TLS, même si le certificat n’est pas vérifié. Bien évidemment, dans un environnement réel, nous allons faire en sorte d’avoir un certificat signé par une autorité de certification reconnue par le client.
Ici, ce n’est que la communication entre le client et le reverse proxy qui est chiffrée avec TLS. La communication entre le reverse proxy et les pods apaches reste en HTTP. C’est intentionnel, car cette communication n’est pas au travers d’internet, elle se fait uniquement au sein du cluster Kubernetes. Comme nous l’avons dit dans l’article précédent, cela nous permet de faire ce qu’on appelle du TLS offloading. Il n’y a que le reverse proxy qui se charge de TLS, pour que les pods apache en destination des requêtes n’aient pas à faire tout le chiffrement et déchiffrement, ce qui représente un traitement en plus qui n’est pas justifié dans notre cas.
Si pour des raisons de sécurité, nous voudrions chiffrer toutes les communications entre pods, même si elles se passent sur le même cluster, nous pouvons préciser à l’ingress qu’il faudrait envoyer les requêtes vers les pods apache en HTTPS (en utilisant des certificats sur les pods). Avec notre ingress basé sur nginx, cela se ferait avec l’annotation suivante : nginx.ingress.kubernetes.io/backend-protocol: “HTTPS”
Ou alors, il est aussi possible de chiffrer automatiquement toutes les communications dans un cluster au travers d’un outil fort puissant et fort complexe qu’est un Service Mesh. Par exemple, Istio (qui s’appuie sur le proxy Envoy), permet d’avoir du mTLS (mutual TLS, c-à-d à la fois le client et le serveur qui présentent des certificats) dans toutes les communications entre pods. Mais ça, c’est une histoire pour un autre jour (fort lointain).