Les ops règnent-ils en divinité sur leur système d'information ?
Doivent-ils et elles pouvoir tout faire et tout connaitre ?
Avec la généralisation du chiffrement de bout en bout,
l'omniscience en a déjà pris un coup. Mais est-il nécessaire que
les ops aient accès à un seul secret sur l’ensemble de leur
système d’information ?
Peut-on imaginer un système d'information sans que le
moindre secret soit connu des ops ? Est-ce souhaitable ou même
possible ?
Je vais vous démontrer que cette approche est effectivement
possible, automatisable, pragmatique et désirable.
Bienvenue dans ce troisième épisode du podcast Yakafokon !
[Musique d'introduction]
Nombreux sont les outils du DevOps qui permettent la
manipulation des secrets.
Il y a bien sûr les gestionnaires de secrets, comme
Hashicorp Vault, OpenBao, Conjur,
ou Bitwarden Secret Manager.
Les secrets peuvent également être stockés dans la
configuration, avec Ansible Vault
ou autres databags chiffrés.
Les plus téméraires peuvent même les stocker dans l'état
d'outils, tels que Terraform ou OpenTofu.
Pourtant, aucune de ces solutions n'est satisfaisante.
Toutes impliquent directement ou indirectement que des ops aient
accès à des secrets.
Or, l'accès aux secrets devrait être accordé aux seuls acteurs
en ayant le besoin fonctionnel.
C'est ce qu'on appelle le besoin d'en connaitre.
Dans un système d'information, ce sont les logiciels qui ont le
besoin fonctionnel de ces secrets ; pour s'authentifier,
pour signer des documents,
pour déchiffrer des communications, etc.
La problématique est donc de réussir à fournir aux logiciels
les secrets dont ils ont besoin sans pour autant que les ops
les connaissent.
Ce n'est pas seulement le respect d'un principe de
sécurité que de chercher à ce que les ops n'aient pas accès
aux secrets. C'est aussi un problème très pragmatique lié à
la gestion du cycle de vie des secrets.
Lorsqu’un ou une ops perd son rôle, que ce soit par démission,
changement d’équipe ou tout autre événement, il est
nécessaire de révoquer et de remplacer tous les secrets en
cours d’usage auxquels cette personne a pu avoir accès.
Cela veut dire qu'il faut être en mesure de lister les secrets
concernés, et de les changer, sans perturber la production !
En théorie, c’est une chose facile à faire, ou en tout cas
automatisable, grâce aux outils d’infrastructure codifiée
ou aux gestionnaires de secrets.
Ces solutions présentent cependant toutes des problèmes,
que nous allons étudier un à un.
Pour commencer, parlons d’outils de configuration codifiée, comme
Ansible Vault et Chef. Si vous ne les utilisez pas, c'est le
moment de rappeler que ce podcast est chapitré ; vous
pouvez sauter à la section suivante !
Ansible Vault et Chef utilisent tous deux des clés
cryptographiques symétriques pour chiffrer les données
sensibles. Pour Ansible Vault, le secret symétrique est calculé
à partir d'un mot de passe. Pour simplifier, à partir de
maintenant, je parlerai juste de clé symétrique, même pour les
mots de passe.
L'ops qui connait cette clé symétrique a donc accès à
l'ensemble des données sensibles chiffrées par cette clé. Lors de
son départ, il est donc nécessaire de renouveler non
seulement cette clé symétrique, mais aussi toutes les données
sensibles chiffrées avec cette clé.
Le remplacement de toutes ces données sensibles peut être
fastidieux, mais surtout, cela peut perturber la production !
Que se passe-t-il si je change le mot de passe permettant
à mon application web de se connecter à la base de données ?
Que se passe-t-il si je change la clé privée associée au
certificat TLS protégeant un serveur web ?
Dans tous ces cas, il va falloir préparer une fenêtre de
maintenance, et la mise en production du nouveau secret.
Avec de la chance, ça se passera bien... ou pas.
Mais bon, l'auditoire fidèle se souviendra que, dans le premier
épisode de ce podcast, on avait déjà écarté Ansible pour des
raisons de sécurité.
Si les outils de configuration codifiée ne sont pas terribles,
peut-être que les outils d'infrastructure codifiée sont
meilleurs ?
En infrastructure codifiée, je vais essentiellement parler de
Terraform et d'OpenTofu.
Je ne parle pas d'OpenTofu uniquement pour des histoires de
licence libre. Certes, les deux ont des bases très communes,
mais OpenTofu a réellement une approche différente de celle de
Terraform.
Terraform et OpenTofu sont tous deux des outils fonctionnant sur
le principe d'une machine à états. Chaque donnée, chaque
ressource qu'ils manipulent sont stockées dans une base de
données qu'on appelle l'état.
Lorsque des changements sont codés dans la configuration de
ces outils, ces changements sont comparés avec les informations
de l'état, et des opérations sont entreprises afin de
réconcilier la configuration et l'état.
Terraform et OpenTofu peuvent manipuler des secrets. Par
exemple, ils peuvent être utilisés pour créer des clés
TLS, des clés SSH, des mots de passe temporaire, etc.
Ces secrets finissent dans l'état, et l'état est stocké
clair sur le disque dur ou dans une base de données distante.
Parfois, cette base de données est chiffrée par le serveur ;
parfois pas. Comme c'est fait côté serveur, on n'en sait
réellement rien.
Donc, pour le dire plus directement, quand on manipule
des ressources ou des sources de données sensibles avec
Terraform, on stocke des secrets en clair, à poil, sans
protection. Aie !
Mais, j'ai dit "avec Terraform". C'est différent avec OpenTofu ?
Eh bien, oui ! Et c'est probablement l'une des premières
fonctionnalités vraiment importantes qui diffère entre
Terraform et OpenTofu !
OpenTofu a introduit la possibilité de chiffrer l'état
côté client avant le stockage sur disque dur ou dans une base
de données distante.
Pour chiffrer, on utilise une clé dérivée d'un mot de passe.
Ce mot de passe est demandé à chaque exécution d'OpenTofu pour
déchiffrer l'état, le modifier et le rechiffrer.
À première vue, cela semble formidable de pouvoir enfin
chiffrer l'état. Mais, en réalité, c'est le même problème
qu'avec les outils de configuration codifiés qu'on
vient juste de discuter.
Une personne connaissant le mot de passe a accès à tous les
secrets contenus dans l'état. Lorsque cette personne perd le
besoin de connaitre ces secrets, il faut changer tous les
secrets, et rechiffrer l'état avec un nouveau mot de passe.
Hashicorp a bien identifié ce problème et a toujours refusé
d'implémenter ce type de chiffrement. Néanmoins, une
révolution a eu lieu avec la sortie de Terraform v1.10.
Terraform v1.10 introduit la notion de ressources et de
variables éphémères.
Les ressources éphémères sont des ressources qui ne sont pas
stockées dans l'état. Elles ont un cycle de vie différent des
autres ressources, et les opérations qui peuvent être
effectuées sur ces dernières sont plus limitées.
Les valeurs des ressources éphémères ne peuvent notamment
être utilisées que dans d'autres ressources éphémères
ou dans des provisionneurs.
Avec ces ressources, il est possible de générer et manipuler
des secrets sans que l'état les stocke et que les ops ne
puissent les apprendre.
AWS, GCP et Azure ont tous déjà implémenté des ressources
éphémères pour leurs KMS ou Key Management System.
Il ne reste plus que la problématique de la diffusion de
ces secrets aux logiciels ayant le besoin d'en connaitre !
Les diffuser par un provisionneur SSH est possible,
mais... Dans une infrastructure immuable, se connecter en SSH
aux machines est un contresens, comme discuté dans l'épisode 2
de ce podcast.
De surcroit, la connexion d'un provisionneur à une machine via
SSH n'est pas sécurisée, car l'empreinte de la clé SSH du
serveur n'est généralement pas vérifiée ou obtenue de façon
intègre. Sans cela, difficile de dire si la clé n'a pas été
compromise lors de son transfert !
Bon, en vrai, on peut imaginer quelques moyens de faire cette
connexion SSH en toute sécurité, à l'aide de l'hyperviseur et de
sockets de type AF_VSOCK, mais c'est un sujet assez avancé ; on
pourra en reparler dans un futur épisode !
Diffuser les secrets par Cloud-init ou Ignition n'est pas
non plus une bonne idée.
Ignition, déjà présenté dans l'épisode sur l'infrastructure
immuable, n'est pas censé contenir de secrets, comme le
dit sa spécification, et rien n'est donc fait pour préserver
la confidentialité des données qu'on met dans sa configuration.
Mais au moins, avec Ignition, les choses sont claires.
On ne peut pas en dire autant de Cloud-Init, qui ne dit rien à ce
sujet et pousse donc clairement au crime.
En réalité, et pour apporter un peu de nuances, le niveau de
sécurité de Cloud-Init varie grandement en fonction des
sources de données de Cloud-Init.
Par exemple, la source de données « NoCloud », qui
consiste à fournir les données par le biais de fichiers stockés
sur un disque nommé CIDATA, expose toutes les données de
configuration Cloud-Init à tous les processus du système !
Cloud-Init, en effet, impose que le disque CIDATA soit formaté
soit en vfat, soit en ISO9660, et il le monte sans options de
montage particulières. Le disque se retrouve donc monté avec les
droits 755 et 644, donc en lecture par tous. OK, on peut
améliorer ça avec AppArmor, mais de vous à moi, qui fait ça ?
Ce n'est cependant pas la seule source de données peu sécurisée.
Autre exemple : la source de données Amazon EC2. Celle-ci
expose les données de configuration sur un service web
accessible par une requête HTTP sur une adresse IP APIPA :
l'adresse IP 169.254.169.254.
N'importe quel processus de la machine peut donc effectuer une
requête HTTP sur cette adresse pour obtenir la configuration !
Même en établissant une règle de pare-feu Netfilter pour
n’autoriser l’accès à cette adresse IP que depuis un
processus fonctionnant sous l’UID 0, on permet toujours à
tous les processus root d’obtenir cette information.
Or, cela va à l'encontre des travaux effectués ces quinze
dernières années à désarmer root, avec les capabilities, les
namespaces, seccomp-bpf, et les Linux Security Modules. Tous les
processus tournant en root n'ont pas forcément le besoin de
connaitre tous les secrets de chaque serveur ! Ici aussi, on
peut améliorer ça avec SELinux, mais qui fait ça ?
Après, je n'ai pas étudié l'ensemble des sources de
données de Cloud-Init. Peut-être que certaines sont plus fiables
que d'autres, mais cela élimine déjà pas mal de clouds pour
lesquels il faudra une méthode alternative !
En parlant d'alternative, une a déjà été évoquée : les
gestionnaires de secrets ou KMS, comme ceux proposés par certains
fournisseurs de Cloud, ou auto-hébergeables, comme
HashiCorp Vault ou OpenBao.
Les gestionnaires de secrets permettent aux ops de stocker
des secrets dans un logiciel. Celui-ci les distribuera ensuite
aux autres logiciels ayant le besoin d'en connaitre.
Immédiatement, deux problèmes sautent aux yeux. D'une part,
les ops manipulent les secrets, et d'autre part, les logiciels
ayant le besoin d'en connaitre doivent pouvoir s'authentifier
auprès du gestionnaire de secrets pour avoir accès aux
seuls secrets dont ils ont le besoin.
Or, pour s'authentifier auprès du gestionnaire de secrets, il
faut connaitre un secret : celui qui permet au logiciel de
prouver qui il est ! On a donc un problème de poule et d'œuf :
pour distribuer des secrets, il faut avoir déjà distribué un
secret !
Ces deux problèmes sont contournables, mais ce n'est pas
forcément trivial à faire.
Pour le problème des ops manipulant les secrets, cela
dépend de la nature du secret manipulé.
Si c'est un secret symétrique utilisé par deux logiciels
connectés au même gestionnaire de secrets, comme c'est le cas
avec une application web qui se connecte à sa base de données,
alors le gestionnaire de secrets peut aider. Il suffit au
gestionnaire de générer lui-même le secret, et de le distribuer
aux deux applications.
Si c'est un secret symétrique, mais qu'un seul des logiciels
est connecté au gestionnaire de secrets, alors il n'y a pas de
bonne solution. Le secret passe forcément par les mains des ops.
C'est un scénario qu'on veut absolument éviter, et la vraie
bonne solution est de ne pas avoir recours à ce type de
secrets.
À la place, on peut utiliser des secrets asymétriques.
Avec ce type de secrets, seules les clés publiques ont besoin
d'être manipulées par les ops. Les clés privées sont générées
directement par les logiciels ayant le besoin d'en connaitre,
ou par les gestionnaires de secrets auxquels ils ont accès.
Ce qui est intéressant, c'est que c'est une méthode de
fonctionnement déjà très déployée.
Tous les serveurs SSH fonctionnent ainsi. Quand on
installe le package, les clés d'hôte sont générées directement
par la machine qui fait tourner le serveur SSH. Les ops
manipulent seulement les clés publiques d'hôtes afin de les
mettre dans un fichier known_hosts ou dans un
enregistrement DNS de type SSHFP.
Un autre exemple très répandu est Let's Encrypt. Ou plus
précisément le protocole ACME utilisé par Let's Encrypt, et
toutes les autres autorités de certification qui font de la
vérification en ligne automatisée en vue d'émettre des
certificats.
Quand on demande un certificat avec le protocole ACME, la clé
privée est directement générée sur la machine qui demande le
certificat. Le serveur ACME fait ensuite des vérifications pour
s'assurer qu'il ne délivre pas un certificat à un attaquant.
Pour cela, il s'assure que la machine qui demande le
certificat contrôle bien le nom de domaine, ou l'adresse IP
pointée par le nom de domaine, demandé dans le certificat.
Après cette vérification, le serveur ACME délivre le
certificat à la machine.
À aucun moment dans ce processus la clé privée n'a été exposée
aux ops ni à qui que ce soit qui n'a pas le besoin
d'en connaitre !
Le protocole ACME permet donc d'obtenir un certificat qui
associe cryptographiquement une clé publique à une identité.
Avec ça, on peut s'authentifier auprès du gestionnaire de
secrets. On peut même l'utiliser pour s'authentifier auprès
de n'importe quel serveur, pour peu qu'il fasse confiance à
l'autorité de certification qui a émis le certificat !
Grâce au protocole ACME, tout semble ainsi réuni pour
permettre la distribution de secrets. Les secrets sont dans
le gestionnaire de secrets. Le gestionnaire de secrets
s'authentifie auprès des applications avec un certificat
obtenu par ACME. Les applications s'authentifient
elles aussi auprès du gestionnaire de secrets avec des
certificats obtenus par ACME.
Mais...
Oui, il y a un mais. Sinon, ça ne serait pas drôle. Dans le cas
de Hashicorp Vault et d'Openbao, son équivalent vraiment libre,
il y a besoin d'un secret pour déverrouiller le gestionnaire de
secrets, notamment lors de son démarrage ! En effet, à moins de
pouvoir profiter d'un KMS préexistant, généralement fourni
par la cloud faisant tourner nos applications, il va falloir
desceller, c'est-à-dire déchiffrer, la base de données
du gestionnaire de secrets.
Et pour faire ce déchiffrement, il faut que les ops manipulent
un secret symétrique ou des parts de secrets symétriques !
Retour à la case départ.
Certes, il n'y a maintenant plus qu'un seul secret qui est
manipulé sur toute l'infrastructure... et certes,
c'est uniquement dans le cas où le fournisseur de cloud
n'offrirait pas de KMS utilisable par le gestionnaire
de secrets... mais... une personne qui a accès à ce secret
a en réalité accès à tous les secrets de l'infrastructure !
C'est donc toujours tous les secrets de l'infrastructure
qu'il faudra remplacer en cas de changement dans l'équipe ops !
Alors, est-ce qu'on peut faire mieux ? Est-ce qu'on peut faire
une infrastructure complètement sécurisée, sans que le moindre
ops manipule de secrets ?
En réalité, on a déjà évoqué plus tôt dans cet épisode la
voie que je propose de suivre : celle des serveurs SSH et des
serveurs ACME.
L'idée est de générer directement sur les serveurs
l'ensemble des secrets dont ils ont besoin, et d'effectuer
l'ensemble des authentifications à l'aide de certificats
émis par ACME.
Pour un certain nombre de services, cette approche demande
quelques modifications de la configuration. Par exemple,
l'authentification à la base de données se fera avec une
authentification mTLS, c'est-à-dire avec un certificat
client et un certificat serveur.
Le déploiement de l'ensemble des certificats nécessaires à ces
authentifications peut être entièrement automatisé avec un
serveur ACME local. Personnellement, j'utilise Caddy
à cette fin dans de nombreux systèmes d'information.
Pour les services ne prenant pas en charge mTLS, et devant
absolument utiliser des secrets symétriques,
cela se complexifie un petit peu.
Pour la génération des secrets symétriques, il y a plusieurs
possibilités. J’en vois au moins une qui n’a besoin d’aucune
infrastructure et une autre qui nécessite le déploiement de
serveurs pour distribuer ces secrets.
Pour permettre le partage d'un secret symétrique, par exemple
entre un serveur d'application et un serveur de base de données
ne prenant pas en charge mTLS ou entre deux pairs WireGuard
utilisant une clé prépartagée, on peut utiliser TLS.
L'idée est ici d'utiliser TLS non pas pour sécuriser une
communication, mais pour établir un secret partagé ! C'est
d'ailleurs ce qui est fait à chaque ouverture d'une nouvelle
session TLS.
Pour faire cela, il faut déployer un petit programme sur
une des deux machines devant connaitre le secret partagé
qui jouera le rôle de serveur TLS, et un autre petit programme
sur l'autre machine qui jouera le rôle de client TLS. Lorsque
le client TLS se connectera au serveur TLS, ils feront une
poignée de main (ou handshake), et génèreront un secret qu'il
est possible d'exporter selon la procédure de la RFC5705 ou de la
section 7.5 de la RFC8446. Et boom ! On a un secret partagé
entre les deux machines !
Ces programmes jouant le rôle de clients et serveurs TLS peuvent
être développés en moins de 300 lignes de Go. Vous pourrez
retrouver un lien vers un exemple d'implémentation dans
les notes de ce podcast.
L'autre solution pour le partage de secrets symétriques entre
deux machines ou plus nécessite une infrastructure de
distribution de clés et de secrets.
Cette infrastructure consiste en une grappe de serveurs etcd.
Etcd est un serveur de base de données de type clé/valeur avec
un dispositif de contrôle d'accès par clé (ou chemin de
clé). L'authentification entre les membres de la grappe etcd,
et entre les clients etcd et les serveurs etcd se font tous en
mTLS. En conséquence, toutes les interactions peuvent être
sécurisées et authentifiées grâce à un déploiement d'un
serveur ACME local.
Grâce à cette infrastructure, un des serveurs ayant le besoin de
connaitre le secret symétrique le génère localement. Il le
chiffre ensuite à l'aide de la clé publique contenue dans les
certificats de tous les autres serveurs ayant le besoin de
connaitre ce secret. Les versions chiffrées sont ensuite
publiées sur etcd. Chaque serveur ayant le besoin d'en
connaitre pourra se connecter à etcd, récupérer la version
chiffrée du secret symétrique, le déchiffrer grâce à leur clé
privée associée à leur certificat émis par ACME. Et
boom ! Tous les serveurs connaitront le même secret.
Avec l’une ou l’autre de ces deux solutions de distribution
de clés symétriques, les ops n’ont jamais eu connaissance ni
manipulé des secrets. Lorsqu'ils ont été en transit, ils étaient
chiffrés. Seules les machines ayant le besoin de connaitre ces
secrets les ont manipulés.
Voilà ! Dans cet épisode, je vous ai présenté un certain
nombre de solutions et de non-solutions pour permettre de
déployer des infrastructures codifiées sans que les ops ne
manipulent le moindre secret ! Grâce à cette approche, les
changements dans la composition des équipes ops n'ont aucune
incidence sur la sécurité des secrets du système
d'information.
Pour récapituler, utiliser un outil de Configuration as Code
comme Ansible, OpenTofu ou un gestionnaire de secrets comme
Hashicorp Vault sans descellement par un KMS expose à
ce qu'on appelle en anglais le secret sprawling. Cette
expression indique une divulgation mal contrôlée des
secrets, menant à leur divulgation à des acteurs
n'ayant pas le besoin fonctionnel d'en connaitre, à
commencer par les ops.
À l'inverse, en utilisant des ressources éphémères de
Terraform couplées à des sockets de type AF_VSOCK, il est
possible d'utiliser l'hyperviseur comme rebond de
confiance pour déposer des secrets générés par Terraform et
déployés par un provisionneur SSH.
Une autre solution évoquée est d'utiliser un gestionnaire de
secrets comme Hashicorp Vault, si ce dernier est descellé de
manière automatisée par un autre gestionnaire de secrets, comme
un KMS du fournisseur cloud ou une autre instance d'un
gestionnaire de secrets.
Finalement, la solution universelle, celle qui
fonctionne sans prérequis particulier, a été détaillée :
générer les secrets en local, directement sur les machines qui
en ont besoin. Idéalement, utiliser exclusivement des clés
asymétriques est préférable ; pas besoin d'infrastructure
particulière, en dehors d'un serveur ACME local. Lorsque ce
n'est pas possible, nous avons vu des solutions de partage de
secrets avec et sans infrastructure, qui sont plus
complexes à mettre en œuvre, mais qui satisfont parfaitement
au problème de confidentialité des clés.
J'espère que cet épisode vous a plu. Je ne doute pas qu'il vous
fera réagir ; n'hésitez pas à poster vos commentaires sur le
lien disponible en description.
Dans le prochain épisode, j’aborderai la sauvegarde
sécurisée des serveurs, traitant notamment des mérites respectifs
des approches « pull » et « push », et de la manière de
bénéficier des avantages des deux simultanément !
À bientôt !