Les pratiques modernes de développement rendent la sécurisation de la chaîne logicielle plus importante que jamais. Le code a des dépendances sur des bibliothèques open source qui elles-mêmes ont leurs dépendances sur d'autres bibliothèques et ainsi de suite. Une chaîne de code développée et compilée par ses propres développeurs et dont on n'a pas - ou peu d'idée - sur sa provenance, peut être problématique. Or une partie de ce code est presque omniprésente, comme le montre l'exploit Log4Shell ayant causé des ravages dans l'industrie numérique résultant d'une faille liée dans un ancien bogue d'un composant de journalisation Java commun, Log4j.

L'industrie logicielle ne repose pas sur les épaules de géants, mais sur celles d'une poignée de mainteneurs d'applications et de composants qui font fonctionner une infrastructure mondiale pendant leur temps libre. Il ne faut cependant pas minimiser leur travail : ils sont une partie essentielle de la chaîne logicielle moderne, fournissant tout, des petits modules aux plateformes entières basées sur des conteneurs. Ils sont sous-évalués et sous-payés pour l'importance de leur code. Malheureusement, il y a eu plusieurs cas où des acteurs malveillants ont proposé de prendre en charge la maintenance du code uniquement pour y ajouter des malwares et en attendant d'être installé automatiquement car historiquement catalogué comme digne de confiance. On doit donc s'attendre à voir plus d'attaques comme celles-ci alors qu'une plus grande partie du code conçu aujourd'hui devient un effort de groupe. Comment se protéger et sécuriser ses applications ? Tout d'abord, il faut comprendre que la chaîne logicielle existe et que nous ne construisons pas de code de manière isolée. Les bibliothèques open source et tierces sont ainsi une partie essentielle de la façon dont les logiciels sont créés et elles ne feront que gagner en importance.

Le développement distribué multiplie les risques

Quelles mesures prendre pour sécuriser la chaîne des logiciels ? Beaucoup d'effort a été consacré pour fournir des outils de gestion de la nomenclature logicielle : numérisation de code pour les bibliothèques, utilisation d'analyses statiques et dynamiques, ajout de signatures numériques et de hachages au code et intégration de tout cela dans des référentiels gérés. Mais une partie de l'image manque : comment valider ce travail et valider le code que nous utilisons ? Après tout, l'un des adages séculaires en matière de sécurité n'est-il pas de « faire confiance mais vérifier » ?

Nous devons donc faire confiance au code que nous utilisons, mais également vérifier qu'il est digne de confiance. Cela doit également être fait sous la pression du temps, dans un contexte cloud natif qui envoie de nouvelles versions au fur et à mesure que le code change dans les référentiels. La nature automatisée du développement moderne est clé. Et ce avec des plateformes comme GitHub au cœur de son cycle de vie logiciel, apportant une intégration continue et une livraison continue (CI/CD). Les situations deviennent plus complexes lorsque l'on travaille avec des technologies telles que Kubernetes, qui sont conçues pour s'appuyer sur la philosophie de « mix-and-match » des architectures de microservices et des conteneurs. Bien que notre code puisse s'exécuter dans des conteneurs isolés, il s'exécute dans des zones utilisateur abstraites imbriquées, chaque fichier docker collectant une sélection de dépendances, dont beaucoup ne sont pas entièrement documentées. Comment faire confiance à la nomenclature des conteneurs que nous utilisons ?

Ratify, réponse de Microsoft pour sécuriser la chaîne logicielle de Kubernetes

L'équipe open source cloud native de Microsoft a travaillé sur une  spécification qui devrait aider à cela. Ratify est un framework de vérification qui prend en charge les différents composants qui se réunissent dans les applications Kubernetes. Il utilise un ensemble de métadonnées de sécurité fiables et une nomenclature signée pour garantir que tout ce qui est déployé correspond bien à ce qui a été validé.

Les conteneurs et autres briques tirent parti de l'outil de signature et de vérification Notary V2 et de la spécification de composants ORAS (OCI Registry As Storage). ORAS fait partie de la définition du registre Open Container Initiative, l'étendant au stockage de n'importe quoi, pas seulement des conteneurs. Cela fonctionne bien comme moyen de constituer une nomenclature de logiciels. Il est intéressant de noter qu'il existe des points communs entre une définition de programme d'installation d'application distribuée par Bindle et un manifeste ORAS, ce qui facilite le passage d'un SBOM à un programme d'installation d'application distribué vérifié. Ensemble, les deux fournissent un graphique de la chaîne logicielle qui peut être analysé et utilisé dans le cadre d'un schéma de vérification au sein d'un cluster Kubernetes.

Ratify regroupe ces concepts, en ajoutant un moteur de workflow pour appliquer des politiques à la nomenclature logicielle, en vérifiant des chaînes différentes dans le code et ses dépendances. En son cœur se trouve un coordinateur qui gère les règles sur l'ensemble des conteneurs. Il est extensible et peut donc fonctionner dans les registres publics et privés, en utilisant un modèle de plug-in familier, similaire à ceux utilisés dans Kubernetes.

Une connexion aux systèmes d'intégration et de développement continu

Le modèle de politique utilisé par Ratify est basé sur des outils familiers, offrant un moyen de déployer rapidement des politiques de base à l'aide de ses propres configurations, ou plus complexes, élaborées à l'aide d'Open Policy Agent. Il fonctionnera également à différents moments du cycle de vie de développement de votre application, en se connectant aux systèmes CI/CD pour vérifier les composants au moment de la construction et dans K8s pour s'assurer que le code n'a pas été modifié entre la construction et l'exécution. Il est important d'avoir un mode de vérification qui fonctionne sur l'ensemble de la pile pour éviter que les attaques de la chaîne arrivent jusqu'au code, au moment de la conception, dans les référentiels et les registres, et son exécution. Il est important de disposer d'un seul outil pour gérer la vérification tout au long du cycle de vie logiciel, car cela garantit que vous n'avez besoin d'écrire les politiques qu'une seule fois. Différents outils pour plusieurs scénarios ajoutent le risque d'erreurs de transcription et de traduction dans les politiques. Dans ce cas, disposer d'un seul outil et d'un seul format de politique permet de l'éviter. Il est également possible de disposer d'un outil de test de politique externe pour aider à mener des enquêtes préventives avant de livrer du code.

Définir une politique de règles

L'équipe Ratify de Microsoft a partagé son code dans un référentiel GitHub qui montre comment utiliser son framework avec Gatekeeper dans Kubernetes. Ratify s'installe à l'aide d'un graphique Helm, en apportant des exemples de modèles de configuration. On peut l'utiliser pour tester des opérations, par exemple, bloquer toutes les images K8S qui n'ont pas de signature. Gatekeeper refusera toutes les images de conteneurs qui ne sont pas signées, les empêchant de s'exécuter. Les fichiers de stratégie sont écrits en YAML, il est donc possible de les modifier dans Visual Studio Code ou d'autres outils, en tirant parti des outils de formatage de code. Ils constituent un moteur de règles simple, faisant passer les artefacts à travers une vérification. Sont-ils issus d'un registre agréé ? Doit-on vérifier plusieurs fois un artefact pour des signatures différentes ? Les composants de son propre registre privé sont-ils plus fiables que ceux des registres publics ? Tous ces moteurs de vérification fonctionnent-ils lorsque vous effectuez plusieurs vérifications ? Il s'avère que les règles d'une vérification d'exécution sont assez simples à définir, bien que ce ne soit probablement pas le cas pour une vérification exécutée dans un système CI/CD où vous devez déterminer l'état de nombreux artefacts différents et avec des signatures provenant de nombreuses racines différentes.

Ratify constitue actuellement un projet intéressant pour un ensemble d'outils permettant de gérer tous les éléments d'une nomenclature logicielle. Bien que cela n'empêche pas l'exploitation de failles zero day impactant des bugs cachés depuis longtemps, il peut rapidement déterminer quel code est affecté, en créant des règles pour empêcher son utilisation et son exécution. Les risques liés à la chaîne étant clairs, il est important que l'industrie examine attentivement des propositions comme celle-ci et y travaille dessus publiquement. Il est bon de voir que Microsoft s'est déjà engagé à partager Ratify avec la Cloud Native Computing Foundation dont les plus communautés de développeurs Kubernetes pourraient bien lui apporter le soutien dont il a besoin.