Bonjour à toutes et à tous, et bienvenue dans ce deuxième
épisode de Yakafokon.
Dans le précédent épisode, j'ai exposé les problèmes que je
voyais à l'utilisation d'Ansible dans le but d'administrer une
infrastructure codifiée par mutation, c'est-à-dire en
modifiant les machines en production.
Outre les problèmes de licence et d'attaques par chaines
d'approvisionnement, l'absence de garantie d'idempotence est
centrale pour comprendre l'approche alternative :
une infrastructure immuable.
Cet épisode est consacré à présenter différentes approches
à l'immuabilité, afin de mieux en cerner l'intérêt.
Mais tout d'abord : qu'est-ce qu'une infrastructure immuable ?
Il s'agit d'une infrastructure composée de serveurs immuables.
Bon, heu... en disant cela, on n'a pas fait beaucoup avancer le
schmilblick. Donc, qu'est-ce qu'un serveur immuable ?
Il s'agit d'un serveur qui applique le principe de sécurité
W xor X, c'est-à-dire qu'une donnée ne peut être
qu'inscriptible ou exécutable, mais jamais les deux
en même temps.
La démarche W xor X implique d'identifier et de séparer
le code, d'une part, et les données manipulées par les applications,
d'autre part.
Ainsi, les serveurs immuables font tourner du code qui ne peut
ou n'est pas censé être modifié.
Pas d'ajout de logiciel, pas de mise à jour, pas de corruption.
Rien.
Si un serveur immuable a besoin de faire l'une de ces
opérations, il faut passer par un mode opératoire qui est
spécifique à chaque manière d'implémenter l'immuabilité.
Généralement, cela implique un redémarrage de la machine,
voire la machine est détruite et remplacée par une nouvelle,
contenant les modifications désirées.
Ce mode opératoire peut vous sembler familier si vous
travaillez régulièrement avec des conteneurs.
Lorsqu'on veut modifier un conteneur, on fabrique une
nouvelle image.
Ensuite, on arrête le conteneur précédent et on démarre un
nouveau conteneur faisant tourner la nouvelle image.
Un serveur immuable peut donc ressembler, en certains égards,
à des conteneurs, le noyau en plus.
D'ailleurs, certaines approches à l'immuabilité
sont précisément cela.
Ce mode opératoire a déjà fait ses preuves avec les conteneurs,
apportant une meilleure reproductibilité des
déploiements, une meilleure auditabilité et facilitant les
retours en arrière.
Les serveurs immuables sont le pendant des conteneurs, mais à
l'échelle du système d'exploitation complet.
Mais voyons justement différentes approches à
l'immuabilité, afin de rendre les choses un peu concrètes et
pragmatiques.
L'immuabilité est devenue un terme à la mode, notamment grâce
au tao de Hashicorp qui l'érige au rang de principe fondamental.
De nombreuses entreprises ont donc franchi le pas vers des
formes d'immuabilité.
Certaines ont des garanties de sécurité ou de stabilité plus
fortes que d'autres.
L'approche immuable la plus faible est conventionnelle.
C'est-à-dire qu'il n'existe aucun moyen technique pour
forcer l'immuabilité ; c'est une convention.
Avec cette approche, nous ne sommes pas censé.es effectuer de
modification en se connectant aux serveurs, que ce soit
manuellement ou avec des outils de configuration.
À la place, nous concevons une nouvelle image système, et
réinstallons la machine à partir de cette image.
Néanmoins, rien ne nous empêche réellement de nous connecter à
une machine et d'effectuer une modification.
Cette modification serait éphémère et perdue la prochaine
fois que la procédure conventionnelle de modification
sera utilisée.
Cette approche présente l'avantage d'être extrêmement
simple à mettre en œuvre.
Nul besoin d'adopter une distribution Linux atypique
ou de nouveaux outils.
Les images peuvent être créées à l'aide d'outils DevOps assez
classiques, comme Packer ou mkosi.
Pour rappel, Packer vise à dériver une image système
afin d'en former une nouvelle.
mkosi permet de créer des images de toutes pièces
pour différentes distributions Linux.
Nous reviendrons plus tard sur cet outil, puisqu'il est aussi
utilisé pour les formes les plus fortes de l'immuabilité.
Il est ainsi possible de créer une image très générique, et
d'y installer tous les outils, scripts et fichiers de
configuration dont le serveur aura besoin.
Le résultat forme alors notre "golden image", c'est-à-dire
l'image système prête à l'emploi et à laquelle il ne sera plus
nécessaire de faire des modifications.
Un des défauts de cette approche est que le système n'est pas
vraiment immuable.
Il ne l'est qu'aux yeux de ses administrateurs
et administratrices.
Le système lui-même est en mesure de se modifier, y compris
si des logiciels sont compromis.
L'immuabilité conventionnelle n'a donc aucun apport de
sécurité particulier, en dehors de réduire la durée de la
persistance d'un attaquant ou d'une attaquante
dans le système.
Néanmoins, d'un point de vue DevOps, cette approche de
l'immuabilité offre déjà d'excellents avantages.
Les "golden images" sont des artefacts finaux.
Une fois construits, ils peuvent être déployés à l'identique en
développement, en préproduction et en production.
Il est donc possible de créer des circuits de tests et de
promotion de l'image.
Cette approche facilite aussi le déploiement de N instances
identiques, puisque c'est toujours la même image
qui est utilisée.
En outre, si une nouvelle image déployée présente un problème,
le retour en arrière à une version fonctionnelle se résume
à faire démarrer le serveur sur une version
précédente de l'image.
On peut donc conclure que cette approche est déjà intéressante
pour le DevOps, mais pas spécialement pour le SecDevOps.
Pour améliorer les choses, notamment la sécurité, on peut
tourner le regard vers des distributions Linux
en lecture seule.
Ces distributions visent à permettre le déploiement et
l'administration de systèmes dont les partitions contenant du
code sont montées en lecture seule dès le démarrage.
Avec ces distributions, les modifications s'effectuent
avant le démarrage.
Avec NixOS, le nouveau système est installé et configuré
avant le redémarrage.
Cela présuppose donc d'avoir un système déjà démarré, que ce
soit sur LiveCD ou depuis un système déjà installé.
Avec des distributions comme Fedora CoreOS ou openSUSE
MicroOS, le nouveau système est initialisé et configuré lors
de son premier démarrage, dans l'initramfs.
Finalement, certains systèmes, comme Kairos OS ou openSUSE
MicroOS sont configurés à chaque démarrage par Cloud Init.
Quelle que soit la distribution utilisée, les modifications
sont exprimées soit de manière déclarative,
soit de manière impérative.
La manière déclarative consiste à décrire l'état désiré du
système, et à laisser les outils interpréter ces descriptions et
effectuer les modifications.
La manière impérative consiste à faire tourner des scripts
arbitraires pour altérer le système.
Un exemple de manière déclarative est l'outil
Ignition. Ce dernier est intégré aux distributions Fedora
CoreOS, openSUSE MicroOS et Flatcar.
Avec Ignition, on écrit des fichiers JSON décrivant les
disques, les partitions, les systèmes de fichiers, les
répertoires à créer et les fichiers à copier, les
utilisateurs à initialiser, etc.
Pendant le premier démarrage de la machine, ce fichier est
consommé par Ignition qui s'exécute dans l'initramfs.
Le fichier peut être récupéré sur le réseau, sur un point de
montage ou intégré dans un fichier ISO personnalisé
de la distribution Linux.
En cas de modification de la configuration, on réinstalle la
machine, et la nouvelle configuration sera appliquée
lors du premier démarrage suivant la réinstallation.
Pour la manière impérative, on peut citer l'outil Combustion,
qui est utilisé par openSUSE MicroOS.
Ce dernier exécute, lors de l'initramfs du premier
démarrage, un script arbitraire.
À titre personnel, je considère que la manière déclarative est
préférable à la méthode impérative. J'admets volontiers
que la méthode impérative est plus flexible
et permet de tout faire.
Néanmoins, nous nous retrouvons alors individuellement
responsables des bugs et problèmes de sécurité éventuels
des scripts que nous avons développés.
En comparaison, l'approche déclarative repose sur un outil
commun dont nous sommes toutes et tous
collectivement responsables.
Cela permet de mutualiser les efforts, de bénéficier
collectivement des correctifs et améliorations proposées par
la communauté, tout en rendant abstraits les détails
d'implémentation qui peuvent varier dans le temps.
Dans le cas d'openSUSE MicroOS qui propose les approches
déclaratives avec ignition et cloud-init, je pense que
l'approche d'ignition est à la fois plus
simple et plus puissante.
Cloud-init s'exécute pendant le démarrage du système
d'exploitation, en plusieurs fois, à l'aide de différents
services systèmes.
Cela lui permet d'effectuer des actions une fois le réseau
obtenu ou une fois tous les services système démarrés.
Néanmoins, cela occasionne des complexités liés à
l'ordonnancement des actions. Par exemple, les fichiers sont
écrits sur le système de fichiers très tôt ; tellement
tôt que l'initialisation et le montage de nouvelles partitions
se fait ultérieurement.
Créer un fichier dans une nouvelle partition, mais avant
qu'un service spécifique ne se lance est un casse-tête.
Ignition s'exécute dans l'initramfs, avant que le
système d'exploitation ne démarre. Il est donc libre de
faire toutes les modifications qu'il souhaite, sans avoir à
réfléchir à l'état d'avancement du démarrage.
Et s'il a besoin d'intercaler des opérations pendant le
démarrage du système, il lui suffit d'ajouter des
services systemd.
Outre cette philosophie de configuration précédant ou
intervenant lors du démarrage, la plupart de ces distributions
intègrent des mécanismes d'installation de mises à jour
et de retour en arrière sophistiqués.
Comme les systèmes sont en lecture seule, les mises à jour
ne s'effectuent pas par altération du système. Elles
nécessitent un redémarrage afin de terminer tous les processus
anciens, et redémarrer tous les processus depuis le système
de fichiers mis à jour.
La préparation de cette mise à jour varie suivant la
distribution Linux.
MicroOS de openSUSE utilise le mécanisme de snapshots
du système de fichiers BTRFS.
Les mises à jour sont faites dans un nouveau snapshot, dérivé
du système actuellement en cours d'exécution, et qui est monté
avec l'autorisation en écriture.
Une fois l'ensemble des mises à jour et modifications faites,
ce snapshot est marqué en lecture seule, et le système est
redémarré pour utiliser ces napshot
comme son système de fichiers.
Pour revenir en arrière sur une modification cassante du
système, il suffit de faire démarrer la machine sur
le précédent snapshot.
Fedora CoreOS utilise une autre méthode reposant sur rpm-ostree.
Ostree est un peu l'équivalent de l'outil de versionnement git,
mais à l'échelle d'un système de fichiers.
Lorsqu'on veut faire une modification, comme
l'installation ou la mise à jour d'un programme, on ajoute un
commit à Ostree contenant ces modifications.
Lors du prochain démarrage, c'est ce nouveau commit qui est
utilisé et démarré. Pour cela, Ostree créé des hardlinks entre
les fichiers contenus dans le dépôt Ostree
et l'arborescence /usr.
Un retour en arrière consiste simplement à démarrer sur un
commit précédent.
D'un point de vue sécurité, le fait que le système
d'exploitation démarre avec ses partitions de code en lecture
seule semble à première vue améliorer nettement la sécurité.
Cela prévient certainement les modifications accidentelles.
Cela ne prévient cependant pas les modifications malveillantes.
Linux ne dispose de mécanismes de verrouillage du système, à
l'instar des niveaux de sécurité d'OpenBSD, qui empêchent de
remonter en écriture des partitions montées en
lecture seule.
Il est certes possible d'inhiber l'appel système mount avec
des Linux Security Modules ou seccomp-bpf, mais un processus
root non limité pourra tout de même effectuer ces
modifications.
Les développeurs d'Ostree travaillent sur un outil appelé
composefs qui ajoute du contrôle d'intégrité, grâce à
FS-Verity et des signatures cryptographiques sur les
fichiers gérés par Ostree.
Les développeurs d'Ostree travaillent notamment sur BootC,
qui est une nouvelle manière de déployer des systèmes
immuables, distribués sous la forme d'images OCI.
BootC utilise notamment composefs.
Il existe également des travaux pour combiner Linux Integrity
Measurement Architecture à Ostree, pour arriver à des fins
similaires.
Néanmoins, ces preuves cryptographiques sont en cours
d'intégration et en leur absence, l'immuabilité des
systèmes de fichiers de ces distributions n'est que
marginalement meilleure que celle des systèmes utilisant
une immuabilité conventionnelle.
Ce que ces distributions ont pour elles, en revanche, est une
excellente reproductibilité des déploiements, notamment si
l'on s'en tient à l'approche déclarative de la configuration,
ainsi que des processus de mises à jour et de retour en
arrière efficace et faciles à mettre en œuvre.
Pour ces raisons, Fedora CoreOS est parmi mes systèmes
d'exploitation favoris. Il est notamment utilisé pour héberger
ce podcast.
Mais est-il possible de faire mieux ? Peut-on concevoir des
systèmes apportant une réelle immuabilité avec des assurances
cryptographiques ?
La réponse est oui, et il est assez probable que les auditeurs
et auditrices soient en train d'utiliser un tel système pour
écouter ce podcast.
Android est un système d'exploitation dont
l'immuabilité est assurée par des vérifications de preuves
cryptographiques. C'est également le cas de Flatcar.
Pour cela, ces systèmes d'exploitation utilisent des
images de systèmes de fichiers, dont l'intégrité est vérifiée
avec dm-verity.
dm-verity est une fonctionnalité du noyau Linux qui vérifie en
direct et à chaque accès que les blocs d'un périphérique de
blocs, typiquement un disque dur, sont intègres.
S'ils ne le sont pas, la lecture de ce bloc est bloquée.
Grâce à cette approche, il n'est pas possible, même pour un
attaquant, de modifier le système de fichiers.
La contrepartie est que les modifications légitimes comme
les mises à jour ou l'installation de nouveaux logiciels
sont rendues relativement complexes.
En effet, puisque l'intégrité cryptographique couvre
l'ensemble du périphérique de bloc, il n'est pas aisé de
remplacer uniquement un seul ou quelques fichiers.
La solution la plus simple est de régénérer l'image en
intégralité.
Il existe d'autres méthodes, comme des diffs binaires ou
l'utilisation de couches à l'instar de ce qui est fait avec
OverlayFS pour les conteneurs.
Systemd est très actif dans l'écosystème des images Linux et
il propose de nombreux outils qui vont dans ce sens.
Ainsi, Systemd utilise des options de la ligne de commande
de démarrage du noyau pour spécifier les options
nécessaires pour dm-verity.
De même, Systemd gère des sysext ou system extensions, qui
sont une implémentation de couches permettant de faire de
l'ajout ou du remplacement de fichiers par superposition
d'images.
Il peut notamment être utilisé par les consoles SteamDeck pour
étendre le système quand l'installation d'applications de
manière non privilégiée, sous la forme de Flatpaks,
ne suffit pas.
Les system extensions peuvent être générées de plein de
manières différentes, mais un outil, lui aussi fourni par
l'écosystème Systemd mérite d'être mentionné : mkosi.
Nous en avons déjà parlé il y a quelques minutes pour la
conception d'images.
mkosi peut fabriquer autant des images système pour le système
entier, que pour des system extensions.
Il permet de faire des images reproductibles (sous certaines
conditions), et gère nativement dm-verity et même les
signatures pour Secure Boot.
Il trouve donc sa place autant dans l'escarcelle de personnes
voulant faire de l'immuabilité conventionnelle que dans celle
de personnes souhaitant mettre en oeuvre la forme
d'immuabilité la plus forte.
Voilà ! Je ne pense pas avoir fait le tour des systèmes
immuables. Tant s'en faut.
Néanmoins, j'imagine que cet épisode pourra servir de
première introduction.
Pour les auditeurs et les auditrices souhaitant en savoir
plus, je ne peux que recommander d'écouter les nombreuses
conférences sur ce sujet.
Je pense notamment à la salle Image-based Linux de FOSDEM
2023 ou à la conférence All Systems Go!.
Je n'ai aucun doute que l'immuabilité sera un élément
central de la sécurité système de la prochaine décennie.
C'est d'autant plus vrai que Secure Boot commence
à montrer ses limites.
J'ai d'ailleurs écrit un article à ce sujet dans MISC où je
fustigeais la recommandation de l'ANSSI relative à Secure Boot
dans son guide Linux.
Or, une roue de secours pour le démarrage sécurisé, après que
Secure Boot se soit révélé un tuyau percé, est l'emploi des
TPM, qui se trouvent être très friands des systèmes immuables,
puisqu'ayant toujours la même empreinte cryptographique !
Pour ma part, les systèmes d'exploitation immuable sont
dans mes pratiques SecDevOps depuis maintenant
de nombreuses années.
Chez la plupart de mes clients, je rencontre une légère
résistance à passer à des distributions Linux spécialisées
comme Fedora CoreOS ou Flatcar par appréhension d'avoir des
difficultés de recrutement.
J'arrive néanmoins assez facilement à les convaincre
d'utiliser au moins l'approche de l'immuabilité
conventionnelle, qui offre déjà des avantages assez nets de
reproductibilité des déploiements et d'auditabilité.
L'utilisation d'outils de configuration déclarative comme
cloud-init reste cependant une souffrance du quotidien,
comparé à Ignition.
De même, les mises à jour et les retours en arrière dans le
cas de l'immuabilité conventionnelle sont moins aisés qu'avec
des distributions conçues pour.
J'aspire à ce que ces distributions deviennent plus
présentes, et que les habitudes DevOps évoluent vers elles,
afin qu'elles puissent se départir des quelques verrues
violant le principe W xor X qui persistent encore.
J'espère donc vous avoir peut-être fait découvrir, ou
mieux, vous avoir convaincu d'adopter l'approche immuable !
Si c'est le cas, je vous conseille d'essayer Fedora Core
OS, plus facile, ou Flatcar, plus immuable, qui sont deux
superbes distributions.
Dans le prochain épisode, nous verrons que cette approche
comporte cependant quelques défis inattendus.
Un que je n'avais pas anticipé lorsque j'ai emprunté cette voie
est la gestion des secrets !
En effet, les gestionnaires de configuration par mutation sont
souvent utilisés pour déployer des secrets qu'ils stockent sous
une forme chiffrée dans le gestionnaire de version de code.
Or, avec l'approche immuable, on ne se connecte pas ou peu sur
les machines, puisqu'il n'est pas nécessaire de les modifier !
Aller, je ne vous en dis pas plus ; cet épisode est déjà bien
assez long !
Comme à chaque fois, je vous rappelle que vous pouvez
commenter, liker et même booster ce podcast sur le fédiverse.
Il vous suffit de copier/coller l'adresse de cet épisode dans
le champ de recherche de votre logiciel, puis d'interagir avec
le post.
N'hésitez pas à me dire si cet épisode vous a plu, ou à
apporter une critique constructive !
À bientôt !