Kubernetes: TLS, autorités de certification et authentification
Kubernetes est un animal complexe. Configurer le TLS correctement au sein du cluster l’est aussi. Je parle dans cet article de TLS dans Kubernetes, d’autorités de certification, d’authentification, de TLS bootstrapping, et de bonnes pratiques à respecter.
Que vous soyez dev ou ops, que vous utilisez Kubernetes on premise ou chez un fournisseur cloud, ces sujets sont très importants.
Kubernetes et TLS
Kubernetes est composé de différentes briques. Ces briques communiquent entre elles. Des humains (admin sys) communiquent aussi avec l’API pour réaliser des actions dans le cluster. Tous ces flux utilisent TLS, voir pour la plupart mTLS (donc avec authentification client: le client doit présenter un certificat signé par une autorité de certification reconnue par le serveur).
Configurer TLS correctement dans un cluster est compliqué pour plusieurs raisons:
-
Des tonnes d’options (voir fin de l’article) prennent un certificat en paramètre, il est difficile de dire quel certificat sera utilisé par quoi et dans quel contexte quand on débute avec Kubernetes.
-
Cette partie de Kubernetes est très mal documentée, et globalement personne n’en parle donc il y a peu de retours d’expérience.
Je vais essayer de détailler dans cet article un ensemble de bonnes pratiques concernant le TLS dans le cluster, et j’en profiterai pour expliquer comment fonctionne l’authentification par certificat dans Kubernetes.
N’hésitez pas à m’adresser des retours (en commentaire, par email, sur twitter…) si vous n’êtes pas d’accord avec certaines parties de cet article, je suis toujours preneur de retours sur ces sujets.
Cet article sera prochainement disponible au format vidéo pour ceux qui préfèrent ce format.
Les flux réseau d’un cluster.
On peut identifier selon moi plusieurs flux réseau dans un cluster, chaque flux ayant une utilité spécifique. Ce n’est pas très clair dit comme ça donc je vais tout de suite détailler cela.
Flux control plane
Dans Kubernetes, on a ce qu’on appelle un control plane. Ce sont des composants obligatoires pour que le cluster fonctionne, ceux standards. On peut citer par exemple:
-
L’API server, qui est le point d’entrée du cluster. C’est l’API server qui se charge de gérer l’état du cluster.
-
Le controller manager et le scheduler, qui vont faire converger votre cluster vers l’état voulu. C’est eux qui vont réagir quand vous créez une ressource sur un cluster.
-
Le cloud controller manager: c’est un composant optionnel mais qui existe lorsque vous déployez Kubernetes chez un fournisseur cloud. C’est lui qui fait le pont entre Kubernetes et le cloud, pour par exemple automatiquement configurer des load balancers chez votre cloud provider pour exposer des ressources du cluster. On y reviendra.
-
Konnectivity, qui est un composant aussi optionnel. On le voit assez peu car il est utilisé dans un cas assez spécifique: quand le control plane Kubernetes et les noeuds workers ne sont pas dans le même réseau. En effet, le control plane doit avoir une "patte" dans le réseau du cluster. Konnectivity peut servir de proxy dans ce cas. Mais ce n’est pas le sujet de cet article (peut être un autre ?).
Tous ces composants parlent entre eux dans un sens ou dans l’autre. Voici mon premier flux: des flux réseaux concernant l’administration du cluster.
Flux Kubelet
Kubelet est le composant de Kubernetes tournant sur les workers. Il est responsable entre autre de la gestion des conteneurs. Kubelet parle à l’API server, et d’ailleurs l’API server parle aussi à Kubelet. Voici notre second flux: des noeuds kubernetes qui parlent à l’API server.
Flux opérateurs
Des opérateurs ou opératrices doivent pouvoir communiquer avec Kubernetes (avec kubectl
par exemple), pour administrer le cluster. On a donc des humains parlant à l’API server. Voici notre troisième flux.
Flux ETCD
L’état de Kubernetes est stocké dans une base de données clé/valeur appelée ETCD. C’est l’API server qui se charge de gérer ces données dans ETCD. On a donc une communication entre l’API server et ETCD. Ce sera notre quatrième flux.
Flux aggregation layer
Kubernetes peut être étendu de plusieurs façons, l’aggregation layer en est une. Vous pouvez déployer un programme exposant une API et indiquer ensuite à Kubernetes de rediriger le trafic vers cette API pour certaines ressources.
C’est comme cela que fonctionne metrics-server de Kubernetes par exemple.
C’est donc là aussi un flux particulier: nous avons ici l’API server qui est utilisée comme proxy vers une autre API. C’est notre cinquième flux.
Résumé
Si on récapitule, ça nous donne ça:
Autorités de certifications.
Nous avons donc cinq flux réseaux, chaque flux étant composé de plusieurs composants qui parlent entre eux. Toutes ces communications doivent, comme dit précédemment, utiliser TLS (et il faut donc des certificats dans tous les sens).
Pour générer ces certificats, il faut des autorités de certification. Mais combien en faut-il ?
La réponse est simple: 5 flux, 5 autorités.
Pourquoi autant d’autorités ? Au final, pourquoi ne utiliser la même autorité de certification partout, et générer tous les certificats avec cette unique autorité ?
Cela marcherait, mais ne serait pas très sécurisé.
Reprenons le schéma précédent. On voit que l’API server communique avec ETCD. Elle seulement doit avoir accès à ETCD qui contient des informations sensibles. Est ce que ça a du sens qu’un potentiel certificat généré pour Kubelet (donc sur un noeud) puisse parler à ETCD ? Non.
De la même manière, est ce que ça a du sens qu’un humain puisse parler à Kubelet ? Non plus, seulement l’API server devrait communiquer avec ce composant (bon, on a aussi quelques outils de monitoring ou autre qui parlent à Kubelet mais vous comprenez l’idée).
Avoir plusieurs autorités permet de cloisonner les flux, et apporte énormément de garanties. Cela évite un grand nombre d’attaques et d’erreurs potentiels via les certificats. Un grand nombre de composants ne sont censés jamais se parler, empêchons le par design.
Pour l’aggregation layer, c’est carrément préconisé par Kubernetes. Voici un extrait de la documentation officielle: Warning: Do not reuse a CA that is used in a different context unless you understand the risks and the mechanisms to protect the CA’s usage.
Vous aurez des soucis si vous ne suivez pas ces recommandations.
Authentification
On a donc nos flux, nos autorités, nos certificats. Il est temps de répondre maintenant à la question "mais comment marche l’authentification par certificat dans Kubernetes" ?
L’authentification se base sur le sujet du certificat. Voici par exemple le sujet de mon certificat que j’utilise pour administrer mon cluster personnel créé sur SKS, l’offre Kubernetes d’Exoscale (qui a été conçue notamment par mon équipe):
La première ligne Issuer contient le sujet de l’autorité de certification ayant généré le certificat. On est ici sur un certificat utilisé par un humain (seulement ceux là peuvent être générés via l’API), c’est donc bien l’autorité opérateur (operators-ca) qui a été utilisée.
En effet, sur SKS nous générons comme expliqué dans cet article 4 autorités par cluster (aggregation, kubelet, control plane, operateurs. L’autorité ETCD étant elle un peu spéciale mais c’est aussi une autorité dédiée). Ce que je raconte ici est donc un design utilisé en production.
On a ensuite à la fin de l’image le Subject. On a les champs habituels (country…) mais ce qui nous intéresse ici sont surtout deux champs:
-
O (pour Organization). Il est possible d’avoir une ou plusieurs organisations sur un certificat. Kubernetes utilise cette information pour l’authentification. Si une organisation d’un certificat correspond à un groupe Kubernetes, le certificat aura les permissions allouées à ce groupe.
Ici, mon certificat aura les pouvoirs "super admin" via le groupe systems:master (d’ailleurs, évitez d’utiliser ce groupe en vrai, créez un autre jeu de permissions admin et ne réutilisez plus jamais systems:master ensuite). -
CN (pour Common Name): Si le CN d’un certificat correspond à un utilisateur dans Kubernetes, le certificat aura les permissions de cet utilisateur. Ici j’ai mathieu en CN mais je n’ai en réalité aucun utilisateur mathieu dans Kubernetes, j’utilise seulement le groupe pour m’authentifier.
Un autre exemple
Je parle d’utilisateurs, de permissions… mais comment définir ça dans le cluster ? Via des Roles ou RolesBindings (ou ClusterRoles et ClusterRolesBindings).
La documentation de Kubernetes contient beaucoup d’informations mais voici un exemple avec les yaml de la documentation.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
J’ai ici un rôle Kubernetes appelé pod-reader, qui donnera une fois assigné à une entité des permissions (les verbs
) sur la ressource pod
. Je peux ensuite assigner ce rôle à une utilisatrice (encore une fois, exemple tiré de la doc):
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# You can specify more than one "subject"
- kind: User
name: jane # "name" is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" specifies the binding to a Role / ClusterRole
kind: Role #this must be Role or ClusterRole
name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io
On assigne ici à l’utilisatrice jane (on voit le kind: User
) le role pod-reader créé précédemment.
Si je génère un certificat ayant dans son sujet Common Name: jane, ce certificat aura les droits donnés par le rôle pod-reader.
Et voilà ! On a donc des rôles, et on peut assigner ces rôles à des utilisateurs ou des groupes que l’on référence ensuite dans les certificats.
Il faut savoir qu’il existe des utilisateurs ou groupes par défaut dans Kubernetes. C’est le cas du groupe super admin systems:master vu précédemment, mais il existe aussi des utilisateurs comme system:kube-controller-manager pour le controller manager. Vous pouvez donc utiliser ces utilisateurs existants, pour faire communiquer certains composants du control plane avec l’API server notamment
La doc Kubernetes contient une section sur les CN à utiliser pour les briques de base.
L’authentification par certificat, ça vaut quoi ?
L’authentification par certificat est un moyen simple et globalement sécurisé pour accéder à l’API server. C’est super pour les programmes, cela reste correct pour les opérateurs. Il existe d’autres méthodes intéressantes (OpenID) pour les opérateurs mais c’est hors du périmètre de cet article (mais on aura l’occasion d’en reparler).
Il y a quand même quelques points importants à retenir:
-
Les certificats clients doivent avoir l’extension TLS Web Client Authentication, les certificats serveur l’extension TLS Web Server Authentication. De cette manière, il n’est pas possible d’utiliser un certificat client côté serveur ou vice versa.
-
Evitez les communications en TLS
insecure
dans le cluster. C’est possible sur certains composants, voir parfois c’est dans le README des outils. Ne faites pas ça. -
Générez toujours des certificats ayant les bonnes permissions pour l’action à réaliser (que ce soit pour les humains ou des programmes), évitez les certificats trop puissants. Le système RBAC de Kubernetes est puissant, tirez-en partie.
-
Configurer le Time To Live (TTL) du certificat est également intéressant. Une fois le certificat expiré, il devient inutilisable. Si un système doit parler à l’API server de Kubernetes que pour quelques secondes, pourquoi ne pas générer un certificat valide que durant une minute par exemple ?
Nous utilisons cette technique en interne chez Exoscale. Nous avons un orchestrateur gérant comme un grand les control planes Kubernetes. Cet orchestrateur doit parfois faire des requêtes à l’API server. Dans ce cas, nous générons un certificat avec une durée de vie très courte, nous réalisons l’action, et nous le supprimons. En cas de soucis avec le certificat (ce qui n’est pas censé arriver, mais vaut mieux prévoir les pires cas dès le début), il sera de toute façon inutilisable car il expire immédiatement.
Sur l’offre Kubernetes d’Exoscale, vous pouvez spécifier tous ces paramètres lors de la récupération du kubeconfig, par exemple avec notre CLI:
exo sks kubeconfig <cluster> <utilisateur/Common Name> --ttl <TTL seconde> --group <groupe k8s/organisation>
Cette commande générera un kubeconfig contenant un certificat configuré avec le Common Name, le TTL, et les organisations spécifiées.
Vous pouvez même avec la commande exo sks authority-cert
récupérer les certificats publics des autorités de certification kubelet
et aggregation
de votre cluster. Cela vous permet de les utiliser si besoin pour des extensions de l’API server et éviter comme cela le TLS insecure
.
Révocation
Il y a un problème avec les certificats: il ne sont pas révocables dans Kubernetes. Il n’y a pas de CRL. C’est selon moi un problème mais l’écosystème Golang est contre toute forme de CRL. OK.
Comment faire ? Plusieurs solutions.
La première est de ne jamais générer des certificats utilisant des groupes (organisations dans le sujet), mais toujours utiliser des utilisateurs (Common Name).
Imaginons que vous ayez un certificat pour l’utilisateur mathieu
. Vous avez un leak (genre vous le poussez sur un dépôt public sur Github). Vous pouvez donc:
-
Supprimer le RoleBinding donnant les permissions à l’utilisateur
mathieu
dans Kubernetes. -
En recréer un pour un nouveau utilisateur, comme
mathieu1
. -
Régénérer un certificat pour l’utilisateur
mathieu1
.
Le certificat orignal mathieu
sera toujours valide d’un point de vue TLS, mais l’utilisateur mathieu
n’existe plus dans Kubernetes. Vous êtes sauvé.
La seconde solution est de sortir la bombe nucléaire: faire une rotation de l’autorité de certification.
En recréant l’autorité opérateur
, et en reconfigurant votre cluster, vous invaliderez tous les certificats opérateur
existants.
Encore une bonne raison d’avoir plusieurs autorités ! Vous pouvez faire des rotations de ces autorités sans impact sur les flux non concernés.
Les offres Kubernetes dans le cloud
Beaucoup de fournisseurs cloud ont un produit du type "managed Kubernetes". J’ai remarqué sur certaines offres les problèmes suivants:
-
Pas d’authentification du tout par certificat mais génération de kubeconfig utilisant des tokens statiques. Ces tokens sont très puissants et c’est la pire technique d’authentification dans Kubernetes, c’est une méthode selon moi à éviter. Pas de permissions, pas de TTL, seulement admin.
Comment faire communiquer de manière sécurisée des composants externes au cluster avec l’API server dans ce cas ? Mystère. -
Possibilité de générer des kubeconfig utilisant des certificats, mais que pour l’utilisateur admin. Tout le monde admin ici aussi.
-
Kubeconfig contenant un certificat avec l’extension TLS Web Server Authentication, pourtant on parle ici d’un client.
-
Une seule autorité par cluster, aggregation layer inclus (pourtant c’est un warning en gras dans la documentation Kubernetes).
-
Composants tournant avec des flags TLS type
--insecure
.
Bref, faites un peu gaffe quand même.
TLS bootstrapping
Je voulais aborder un autre sujet dans cet article: le TLS bootstrapping.
Vous voulez, lorsque vous utilisez Kubernetes, pouvoir ajouter rapidement des machines au cluster et que ces dernières rejoignent automatiquement le cluster. Le TLS bootstrapping peut être utilisé pour ça.
La procédure est bien expliquée dans la documentation mais je vais la résumer rapidement.
Vous devez d’abord créer un token dans Kubernetes. Un token est un Secret
spécial, contenant des champs comme token-id
, token-secret
, expiration
(le token ne sera plus valide après cette date et sera supprimé).
Vous devez ensuite fournir d’une façon ou d’une autre à votre nouvelle machine, et plus spécifiquement à Kubelet, le token.
Kubelet utilisera ce token pour s’authentifier à l’API server et lancer une procédure qui, au final, permettra à Kubelet d’obtenir un certificat client pour s’authentifier de manière permanente et avec les bonnes permissions à l’API server et donc de fonctionner correctement.
Kubelet gère aussi le renouvellement du certificat automatiquement.
En conclusion, le TLS bootstrapping est une bonne technique pour démarrer des noeuds mais souffre d’un défaut majeur: il ne gère que le certificat client de Kubelet. Pourtant, pour fonctionner correctement, Kubelet a aussi besoin d’un certificat serveur.
Il est possible de configurer Kubelet pour générer une CSR pour ce certificat serveur. Mais il est impossible d’approuver automatiquement cette CSR par défaut.
Une solution est de lancer la commande manuellement, c’est à dire qu’à chaque nouveau noeud (et chaque renouvellement du certificat) vous aurez besoin de lancer kubectl certificate approve <csr>
pour approuver la CSR. Pénible.
Si vous aimez bien développer, vous pouvez également développer un controller Kubernetes qui va:
-
Ecouter l’API de Kubernetes pour voir les nouvelles CSR.
-
Vérifier si la demande est valide.
-
Approuver le CSR.
C’est ce que fait le Cloud Controller Manager d’Exoscale. Il va vérifier pour chaque CSR serveur que la machine appartient bien à votre compte, que les informations dans le CSR (IP, hostname…) correspondent bien aux informations de la machine récupérées dans l’API d’Exoscale. Si tout est OK, le CSR est approuvé.
Le Cloud Controller Manager apporte donc aussi certaines garanties sur l’authenticité de la machine, car des vérifications de ce type sont aussi réalisées quand la machine rejoint le cluster pour de bon.
Glossaire des options TLS
Pour conclure, voici une liste non exhaustive des options concernant le TLS et les certificats dans un cluster:
Composant | Option | Autorité | Description |
---|---|---|---|
API server |
--client-ca-file |
Multiples |
Contient les certs publics des autorités de certifications pouvant s’authentifier au cluster. |
API server |
--egress-selector-config-file |
Control Plane |
Référence les certificats utilisés pour parler à Konnectivity server. |
API server |
--etcd-cafile |
ETCD |
Le cert public de l’autorité de certification ETCD |
API server |
--etcd-certfile |
ETCD |
Le certificat utilisé par le client ETCD |
API server |
--etcd-keyfile |
ETCD |
La clé privée utilisé par le client ETCD |
API server |
--kubelet-certificate-authority |
Kubelet |
L’autorité de certification de Kubelet |
API server |
--kubelet-client-certificate |
Kubelet |
Le certificat utilisé par le client Kubelet |
API server |
--kubelet-client-key |
Kubelet |
La clé privée utilisée par le client Kubelet |
API server |
--proxy-client-cert-file |
Aggregation |
Le certificat utilisé par le client de l’aggregation layer |
API server |
--proxy-client-key |
Aggregation |
La clé privée utilisée par le client de l’aggregation layer |
API server |
--requestheader-client-ca-file |
Aggregation |
L’autorité de certification Aggregation |
API server |
--service-account-key-file |
Control plane |
La clé utilisée pour vérifier les tokens ServiceAccount. L’autorité n’est en fait pas importante ici. |
API server |
--service-account-signing-key-file |
Control plane |
La clé utilisée pour signer les tokens ServiceAccount. L’autorité n’est en fait pas importante ici. |
API server |
--tls-cert-file |
Control plane |
Le certificat pour le serveur de l’API server |
API server |
--tls-private-key-file |
Control plane |
La clé privée pour le serveur de l’API server |
Controller Manager |
--authentication-kubeconfig |
Control Plane |
Un Kubeconfig contenant des certificats client pour s’authentifier à l’API server |
Controller Manager |
--authorization-kubeconfig |
Control Plane |
Un Kubeconfig contenant des certificats client pour s’authentifier à l’API server |
Controller Manager |
--cluster-signing-cert-file |
Kubelet |
Le certificat de l’autorité de certification Kubelet, pour le TLS bootstrapping |
Controller Manager |
--cluster-signing-key-file |
Kubelet |
La clé privée de l’autorité de certification Kubelet, pour le TLS bootstrapping |
Controller Manager |
--requestheader-client-ca-file |
Aggregation |
Le certificat de l’autorité de certification de l’aggregation layer |
Controller Manager |
--root-ca-file |
Control Plane |
Le certificat public de l’autorité de certification control plane |
Controller Manager |
--root-ca-file |
Control Plane |
Le certificat public de l’autorité de certification control plane |
Scheduler |
--authentication-kubeconfig |
Control Plane |
Un Kubeconfig contenant des certificats client pour s’authentifier à l’API server |
Scheduler |
--authorization-kubeconfig |
Control Plane |
Un Kubeconfig contenant des certificats client pour s’authentifier à l’API server |
Konnectivity server |
--cluster-cert |
Control Plane |
Le certificat public pour le serveur recevant les connexions de l’agent Konnectivity |
Konnectivity server |
--cluster-key |
Control Plane |
La clé privée pour le serveur recevant les connexions de l’agent Konnectivity |
Konnectivity server |
--kubeconfig |
Control Plane |
Le kubeconfig pour communiquer avec l’API server |
Konnectivity server |
--server-ca-cert |
Control Plane |
Le certificat public de l’autorité de certification Control Plane |
Konnectivity server |
--server-cert |
Control Plane |
Le certificat pour le serveur recevant les connexions de l’API server |
Konnectivity server |
--server-key |
Control Plane |
La clé privée pour le serveur recevant les connexions de l’API server |
Conclusion
La façon de configurer le TLS et de générer les certificats est très important dans Kubernetes.
J’espère que ces informations vous ont été utiles.
Add a comment
If you have a bug/issue with the commenting system, please send me an email (my email is in the "About" section).