L'usine logicielle, atout ou talon d'achille pour la sécurité ?

samedi 01 mai 2021

Blog

L’usine logicielle catalyse la collaboration entre équipes et facilite la mise en place des pratiques DevOps.

Elle industrialise une grande partie des contrôles qualité via les tests logiciels et les outils de qualité de code. Enfin, elle automatise le déploiement sur les différents environnements.

L’étendue de ses responsabilités l’expose à un ensemble de menaces directes: introduction de vulnérabilités au travers de dépendances tierces, dévoiement des agents de CI/ CD. Les équipes de développement contribuent à maintenir la sécurité du code source, au travers du respect de bonnes pratiques. Le manque d’un seul maillon de cette chaîne est suffisant pour introduire une faille de sécurité.

Les dépendances fournies par des tiers

Ces dépendances prennent plusieurs formes: code source ou paquets de librairies, images de containers, modules de déploiement d’infrastructure, etc. L’introduction d’une dépendance tierce requiert un niveau de confiance mesuré à son origine et une analyse raisonnée du risque encouru.

Les librairies open source les plus matures et disposant d’une équipe de contributeurs permanents sont, en général, mises à jour régulièrement et peuvent bénéficier de processus de validation et d’audit propres. Parfois, il est nécessaire de s’appuyer sur un projet moins abouti, ou maintenu par peu de contributeurs. Ici, la prudence est de mise car, en fonction de la complexité du code, il peut s’avérer ardu d’y détecter une faille. Quand une librairie ne dispose pas de contrôles suffisants de son référentiel de code, celui-ci peut être compromis. Et du code malveillant risque de se propager à la prochaine mise à jour des consommateurs de cette librairie. A méditer!

Pour se protéger, il convient de ne pas succomber aux sirènes de la facilité et introduire uniquement les dépendances indispensables. De plus, ces dernières requièrent, au moins, un suivi méthodique de leurs mises à jour.

Avec les images de containers (provenant par exemple du Docker Hub), le problème est encore plus vaste: sans avoir accès à la source des images, il est extrêmement difficile de valider leur contenu. Les images officielles1 sont triées sur le volet, validées et les librairies et applicatifs installés sont scannés pour la recherche de vulnérabilités. Les images des éditeurs de logiciels bénéficient a priori d’un traitement similaire. Ainsi, pour éviter les images malveillantes, il est préférable d’utiliser les images docker officielles ou d’un éditeur et, dans les autres cas, de reconstruire depuis les sources et de maintenir soi-même.

Les recettes d’infrastructure as code, notamment Terraform, n’étant pas compilées, leur inspection est plus ai- sée, donc d’autant plus nécessaire. Un point d’attention concerne Terraform et l’utilisation de providers: il s’agit d’exécutables Go qui appellent les API du fournisseur d’infrastructure. Ces exécutables sont automatiquement télé- chargés et exécutés lors de la validation et application du code d’infrastructure.

Pour appréhender leur impact, il importe d’inspecter les recettes d’infrastructure pour bien comprendre leur fonc- tionnement et, au passage, apprendre de nouvelles fonctionnalités de l’outil ou de son fournisseur cloud. Enfin, conserver une copie locale du code source des dépendances permet aussi de se sortir d’une mauvaise passe, si le code d’origine venait à disparaître.

Intégration et déploiement continus

Code et dépendances ne se compilent et ne se déploient pas tous seuls comme par magie. Toute usine digne de ce nom dispose d’une chaîne d’assemblage et de conditionnement pour la livraison.

L’intégration continue, la compilation, l’exécution de tests et la production d’images docker sont réalisées par un build agent ou runner. Pour ce faire, il télécharge les dépendances et, potentiellement, exécute du code pour ensuite déployer le produit fini.

Le déploiement est conditionné par plusieurs sas de validation des fonctionnalités développées. Certains sont automatiques, comme par exemple l’exécution d’un plan de tests. D’autres sont manuels, comme la création d’une merge request, qui exécutera des tests automatisés sur la fonctionnalité, et sera validée suite à une série de tests manuels, en nombre plus restreints (cf. pyramide des tests4). Dans les équipes les plus avancées, cette étape génère un environnement éphémère. La validation de la merge request entraîne le déploiement en production et la destruction de l’environnement éphémère.

Ici, la présence d’au moins deux environnements exige la séparation des rôles des agents (ou runners). Un agent destiné à déployer sur un environnement de développement ne doit pas avoir accès à un environnement de contrôle qualité, ou pire, de production, et ce, pour plusieurs raisons.

L’agent exécute du code potentiellement non digne de confiance; en restreignant ses accès à un environnement bien défini, on réduit le risque d’éventuels dégâts, intentionnels ou, le plus souvent, accidentels. Dans le cadre d’un projet où seul un petit nombre de développeurs peut déployer en production, mais accepte les contributions d’un plus grand nombre, la séparation des rôles garantit qu’un agent dévoyé n’accède pas à un environnement nécessitant des privilèges plus élevés. La compromission d’un agent ayant accès à un seul environnement limitera les mouvements latéraux jusqu’à cet environnement uniquement.

Equipes de développement

Les équipes de développement contribuent activement à la sécurité du produit, sous réserve de mettre en place ou de suivre les bonnes pratiques.

Ces équipes produisent du code, ou s’appuient sur des dépendances tierces, comme vu précédemment. Les outils d’intégration continue facilitent l’automatisation des scans de sécurité. Autant ces outils remontent les défauts évidents, autant ils ne se substituent pas à une revue de code systématique.

Ces revues collaboratives, via merge request, assurent que les changements sont examinés par plusieurs contributeurs: deux paires d’yeux valent mieux qu’une. Elles offrent un avantage supplémentaire: le partage de connaissances. En général, lors des revues de code, le développeur senior discute de ce qui a été fait, des bonnes ou mauvaises pratiques et aide ainsi les plus juniors à monter en compétence. Par exemple pour éviter l’introduction par inadvertance d’une dépendance non nécessaire, voire malicieuse. Dans ce cas, la merge request joue davantage un rôle éducatif, le contrôle, au demeurant nécessaire, ne servant que de prétexte. Un bémol tout de même, si un nombre trop restreint de personnes sont habilitées à approuver les merge request, il se forme alors un goulot d’étranglement qui ralentit d’autant la fréquence de livraison du produit.

Enfin, sous réserve d’utiliser Git, ces pratiques ont également un effet sur l’ordonnancement de l’historique, le rendant bien plus lisible et donc, plus aisément auditable.

Conclusion

La confiance dans les équipes de développement constitue la clé de voûte du succès: ces dernières sont les garantes de l’intégrité du code, qu’elles produisent ou qu’elles intègrent au travers de dépendances tierces.

Les agents automatisant les compilations, tests et déploiements constituent une cible de choix, du fait de leurs accès autant au code qu’aux environnements où celui-ci sera déployé. La segmentation des rôles d’accès aux différents environnements constitue un garde-fou, plus qu’une panacée.

Le bon sens et les revues de code à plusieurs mitigent le risque d’introduction d’une faille de sécurité dans le code – qui a lieu bien plus souvent par inadvertance que par malice. Dans tous les cas, la vérité en cybersécurité n’est pas absolue et dépend du modèle de menace de l’entreprise. Pour aller plus loin, Google propose SLSA: Supply-chain Levels for Software Artifacts5, un framework qui formalise l’implémentation de ces pratiques. Une lecture recommandée!

© 2024 Kleis Technology Sàrl.