Gestion des dépendances

Ce document décrit les dépendances d'applications et les bonnes pratiques à appliquer pour les gérer, y compris la surveillance des failles, la vérification des artefacts, la réduction de l'encombrement de vos dépendances et la compatibilité avec les builds reproductibles.

Une dépendance logicielle est un logiciel dont votre application a besoin pour fonctionner, comme une bibliothèque logicielle ou un plug-in. La résolution des dépendances peut se produire lorsque vous compilez du code, compilez, exécutez, téléchargez ou installez votre logiciel.

Les dépendances peuvent inclure les composants que vous créez, des logiciels tiers propriétaires et des logiciels Open Source. L'approche que vous adoptez pour gérer les dépendances peut avoir un impact sur la sécurité et la fiabilité de vos applications.

Les détails de la mise en œuvre des bonnes pratiques peuvent varier selon le format des artefacts et les outils que vous utilisez, mais les principes généraux s'appliquent toujours.

Dépendances directes et transitives

Vos applications peuvent inclure des dépendances directes et transitives:

Dépendances directes
Composants logiciels auxquels une application fait directement référence.
Dépendances transitives
Composants logiciels requis par les fonctionnalités directes des applications. Chaque dépendance peut avoir ses propres dépendances directes et indirectes, ce qui crée un arbre récursif de dépendances transitives qui affectent toutes l'application.

Les différents langages de programmation offrent différents niveaux de visibilité sur les dépendances et leurs relations. En outre, certains langages utilisent des gestionnaires de packages pour résoudre l'arborescence des dépendances lors de l'installation ou du déploiement d'un package.

Dans l'écosystème Node.js, les gestionnaires de packages npm et yarn utilisent des fichiers de verrouillage pour identifier les versions de dépendance permettant de créer un module, ainsi que les versions de dépendances téléchargées par un gestionnaire de packages pour une installation spécifique du module. Dans d'autres écosystèmes de langage tels que Java, l'introspection des dépendances est plus limitée. En outre, les systèmes de compilation doivent utiliser des gestionnaires de dépendances spécifiques pour gérer les dépendances de manière systématique.

Prenons l'exemple du module npm glob version 8.0.2. Vous déclarez les dépendances directes pour les modules npm dans le fichier package.json. Dans le fichier package.json pour glob, la section dependencies indique les dépendances directes du package publié. La section devDepdencies indique les dépendances pour le développement local et les tests par les responsables et les contributeurs de glob.

  • Sur le site Web npm, la page glob présente les dépendances directes et les dépendances de développement, mais n'indique pas si ces modules ont leurs propres dépendances.

  • Vous trouverez des informations supplémentaires sur les dépendances concernant glob sur le site Open Source Insights. La liste de dépendances pour glob inclut les dépendances directes et les dépendances indirectes (transitives).

    Une dépendance transitive peut être constituée de plusieurs couches profondes dans l'arborescence des dépendances. Exemple :

    1. glob 8.0.2 dépend directement de minimatch 5.0.1.
    2. minimatch 5.0.1 a une dépendance directe brace-expression 2.0.1.
    3. brace-expression 2.0.1 a une dépendance directe à balanced-match 1.0.2.

Sans visibilité sur les dépendances indirectes, il est très difficile d'identifier et de répondre aux failles et aux autres problèmes provenant d'un composant auquel votre code ne fait pas directement référence.

Lorsque vous installez le package glob, npm résout l'intégralité de l'arborescence des dépendances et enregistre la liste des versions téléchargées spécifiques dans le fichier package.lock.json afin de conserver un enregistrement de toutes les dépendances. Les installations suivantes récupéreront les mêmes versions.

Outils d'insights sur les dépendances

Vous pouvez utiliser les outils suivants pour mieux comprendre vos dépendances Open Source et évaluer le niveau de sécurité de vos projets. Ces outils fournissent des informations sur tous les formats de package.

Software Delivery Shield
Solution de sécurité sur la chaîne d'approvisionnement logicielle entièrement gérée sur Google Cloud qui vous permet d'afficher des insights de sécurité pour vos artefacts dans Cloud Build, Cloud Run et GKE, y compris les failles, les informations sur les dépendances, la facture logicielle (SBOM) et la provenance des builds. Software Delivery Shield fournit également d'autres services et fonctionnalités permettant d'améliorer votre stratégie de sécurité tout au long du cycle de développement logiciel.
Outils Open Source

Plusieurs outils Open Source sont disponibles, par exemple:

  • Open Source Insights: site Web qui fournit des informations sur les dépendances directes et indirectes connues, les failles connues et les informations sur les licences des logiciels Open Source. Le projet Open Source Insights met également ces données à disposition en tant qu'ensemble de données Google Cloud. Vous pouvez utiliser BigQuery pour explorer et analyser les données.

  • Base de données des failles Open Source: une base de données de failles indexable regroupant les failles d'autres bases de données en un seul endroit.

  • Tableaux de données : outil automatisé que vous pouvez utiliser pour identifier les pratiques risquées de la chaîne d'approvisionnement logicielle dans vos projets GitHub. Il effectue des vérifications par rapport aux dépôts et attribue un score compris entre 0 et 10 à chaque contrôle. Vous pouvez ensuite utiliser les scores pour évaluer le niveau de sécurité de votre projet.

  • Allstar: application GitHub qui surveille en permanence les organisations ou les dépôts GitHub afin de vérifier qu'ils respectent les règles configurées. Par exemple, vous pouvez appliquer à votre organisation GitHub une stratégie qui recherche les collaborateurs externes à l'organisation qui disposent d'un accès administrateur ou push.

Méthodes d'inclusion des dépendances

Il existe plusieurs méthodes courantes pour inclure des dépendances avec votre application:

Installer directement à partir de sources publiques
Installez des dépendances Open Source directement à partir de dépôts publics tels que Docker Hub, npm, PyPI ou Maven Central. Cette approche est pratique, car vous n'avez pas besoin de gérer vos dépendances externes. Cependant, comme vous ne contrôlez pas ces dépendances externes, votre chaîne d'approvisionnement logicielle est plus sujette aux attaques de la chaîne d'approvisionnement Open Source.
Stocker des copies des dépendances dans votre dépôt source
Cette approche est également appelée fournisseur. Au lieu d'installer une dépendance externe à partir d'un dépôt public pendant vos compilations, vous la téléchargez et la copiez dans l'arborescence source de votre projet. Vous avez davantage de contrôle sur les dépendances fournies par les fournisseurs que vous utilisez, mais il existe plusieurs inconvénients :
  • Les dépendances des fournisseurs augmentent la taille de votre dépôt source et augmentent la perte d'utilisateurs.
  • Vous devez fournir les mêmes dépendances dans chaque application. Si votre dépôt source ou votre processus de compilation n'est pas compatible avec les modules sources réutilisables, vous devrez peut-être gérer plusieurs copies de vos dépendances.
  • La mise à niveau des dépendances fournies par les fournisseurs peut s'avérer plus difficile.
Stocker des dépendances dans un registre privé
Un registre privé, tel que Artifact Registry, permet d'installer facilement un dépôt public et de contrôler vos dépendances. Artifact Registry vous permet d'effectuer les opérations suivantes :
  • Centralisez les artefacts et les dépendances de compilation pour toutes vos applications.
  • Configurez vos clients Docker et les packages de langages pour qu'ils interagissent avec les dépôts privés dans Artifact Registry de la même manière que pour les dépôts publics.
  • Bénéficiez d'un contrôle accru sur vos dépendances dans les dépôts privés:
  • Limitez l'accès à chaque dépôt avec Identity and Access Management.
  • Utilisez des dépôts distants pour mettre en cache les dépendances à partir de sources publiques en amont et les rechercher pour les détecter (aperçu privé).
  • Utilisez des dépôts virtuels pour regrouper les dépôts distants et privés sous un seul point de terminaison. Définissez une priorité sur chaque dépôt pour contrôler l'ordre de recherche lors du téléchargement ou de l'installation d'un artefact (aperçu privé).
  • Utilisez Artifact Registry avec d'autres services Google Cloud dans Software Delivery Shield, y compris Cloud Build, Cloud Run et Google Kubernetes Engine. Utilisez l'analyse automatique des failles tout au long du cycle de développement des logiciels, générez une provenance de compilation, contrôlez les déploiements et consultez des informations sur votre stratégie de sécurité.

Si possible, utilisez un registre privé pour vos dépendances. Dans les cas où vous ne pouvez pas utiliser de registre privé, envisagez de provisionner vos dépendances afin de contrôler le contenu de votre chaîne d'approvisionnement logicielle.

Épinglage

Le blocage des versions consiste à limiter une dépendance d'application à une version ou une plage de versions spécifique. Idéalement, vous devez épingler une version unique d'une dépendance.

Épingler la version d'une dépendance permet de s'assurer que les builds de votre application sont reproductibles. Toutefois, cela signifie également que vos builds n'incluent pas de mises à jour de la dépendance, y compris les correctifs de sécurité, les corrections de bugs ou les améliorations.

Vous pouvez atténuer ce problème à l'aide d'outils de gestion des dépendances automatisées qui surveillent les dépendances dans vos dépôts sources pour détecter les nouvelles versions. Ces outils mettent à jour vos fichiers d'exigences pour mettre à niveau les dépendances si nécessaire, y compris souvent les informations du journal des modifications ou des détails supplémentaires.

Le blocage des versions ne s'applique qu'aux dépendances directes, pas aux dépendances transitives. Par exemple, si vous épinglez la version du package my-library, elle limite la version de my-library, mais pas les versions du logiciel qui dépendent d'my-library. Vous pouvez limiter l'arborescence des dépendances pour un package dans certaines langues à l'aide d'un fichier de verrouillage.

Validation de la signature et du hachage

Il existe plusieurs méthodes pour vérifier l'authenticité d'un artefact que vous utilisez en tant que dépendance.

Validation du hachage

Un hachage est une valeur générée pour un fichier qui sert d'identifiant unique. Vous pouvez comparer le hachage d'un artefact avec la valeur de hachage calculée par le fournisseur de l'artefact pour confirmer l'intégrité du fichier. La vérification du hachage vous aide à identifier le remplacement, la falsification ou la corruption des dépendances par le biais d'une attaque MITM ("man in the middle") ou d'un piratage du dépôt d'artefacts.

L'utilisation de la vérification du hachage nécessite de vérifier que le hachage reçu du dépôt d'artefacts n'est pas compromis.

Validation de la signature

La validation de la signature renforce la sécurité du processus de validation. Le dépôt d'artefacts, les responsables de la gestion logicielle ou les deux peuvent signer des artefacts.

Des services tels que sigstore permettent aux responsables de maintenance de signer des artefacts logiciels et aux consommateurs de valider ces signatures.

L'autorisation binaire peut vérifier que les images de conteneurs déployées dans les environnements d'exécution Google Cloud sont signées avec des attestations répondant à divers critères.

Verrouiller les fichiers et les dépendances compilées

Les fichiers de verrouillage sont des fichiers d'exigences entièrement résolus spécifiant la version exacte de chaque dépendance à installer pour une application. Généralement produit automatiquement par les outils d'installation, les fichiers de verrouillage associent l'épinglage de version et la vérification de la signature ou du hachage avec une arborescence de dépendances complète pour votre application.

Les outils d'installation créent des arborescences de dépendances en résolvant entièrement les dépendances transitives en aval de vos dépendances de niveau supérieur, puis incluent l'arborescence de dépendances dans votre fichier de verrouillage. Par conséquent, seules ces dépendances peuvent être installées, ce qui rend les builds plus reproductibles et cohérents.

Combiner des dépendances privées et publiques

Les applications cloud natives modernes dépendent souvent de code Open Source, tiers, ainsi que de bibliothèques internes Open Source. Artifact Registry vous permet de partager votre logique métier entre plusieurs applications et de réutiliser les mêmes outils pour installer des bibliothèques externes et internes.

Toutefois, lorsque vous mélangez des dépendances privées et publiques, votre chaîne d'approvisionnement logicielle est plus vulnérable aux attaques de confus de dépendance. En publiant des projets portant le même nom que votre projet interne dans des dépôts Open Source, des pirates informatiques peuvent exploiter des programmes d'installation mal configurés pour installer leur code malveillant au lieu de votre dépendance interne.

Pour éviter une attaque par confusion sur les dépendances, vous pouvez prendre différentes mesures:

  • Vérifiez la signature ou les hachages de vos dépendances en les incluant dans un fichier verrouillé.
  • Séparez l'installation des dépendances tierces et internes en deux étapes distinctes.
  • Mettez en miroir explicitement les dépendances tierces dont vous avez besoin dans votre dépôt privé, manuellement ou avec un proxy de transfert. Les dépôts distants Artifact Registry sont des proxys pull-pus pour les dépôts publics en amont.
  • Utilisez des dépôts virtuels pour regrouper les dépôts Artifact Registry distants et standards derrière un seul point de terminaison. Vous pouvez configurer les priorités des dépôts en amont afin que les versions d'artefacts privés soient toujours prioritaires sur les artefacts publics portant le même nom.
  • Utilisez des sources fiables pour les packages publics et les images de base.

Supprimer les dépendances inutilisées

À mesure que vos besoins évoluent et que votre application évolue, vous pouvez modifier ou arrêter d'utiliser certaines de vos dépendances. Continuer à installer les dépendances inutilisées avec votre application augmente votre empreinte de dépendance et augmente le risque de compromission de ces dépendances.

Une fois que l'application fonctionne localement, il est courant de copier toutes les dépendances installées pendant le processus de développement dans le fichier de configuration de votre application. Vous déployez ensuite l'application avec toutes ces dépendances. Cette approche permet de garantir le bon fonctionnement de l'application déployée. Toutefois, elle introduit également probablement des dépendances dont vous n'avez pas besoin en production.

Soyez prudent lorsque vous ajoutez des dépendances à votre application. Chacun d'eux peut introduire davantage de code sur lequel vous n'avez aucun contrôle total. Dans le cadre de votre pipeline d'analyse lint et de test standard, intégrez des outils qui auditent vos fichiers d'exigences pour déterminer si vous utilisez ou importez vos dépendances.

Certains langages disposent d'outils pour vous aider à gérer vos dépendances. Par exemple, vous pouvez utiliser le plug-in de dépendance Maven pour analyser et gérer les dépendances Java.

Analyse des failles

En réagissant rapidement aux failles de vos dépendances, vous pouvez protéger votre chaîne d'approvisionnement logicielle.

L'analyse des failles vous permet d'évaluer automatiquement et de manière cohérente si les dépendances introduisent des failles dans votre application. Les outils d'analyse des failles consomment les fichiers de verrouillage pour déterminer exactement les artefacts dont vous dépendez et vous informent de l'apparition de nouvelles failles, parfois même avec les chemins de mise à niveau suggérés.

Par exemple, Artifact Analysis identifie les failles du package OS dans les images de conteneurs. Il peut analyser les images lors de leur importation dans Artifact Registry et les surveille en continu pour détecter les nouvelles failles pendant 30 jours maximum après le transfert de l'image.

Vous pouvez également utiliser l'analyse à la demande pour analyser les images de conteneurs en local afin de détecter les failles OS, Go et Java. Cela vous permet d'identifier les failles de façon anticipée afin de les corriger avant de les stocker dans Artifact Registry.

Étapes suivantes