Skip to main content

Guide complet de la gestion des dépendances des gems Ruby

Écrit par
blog feature ruby security

5 août 2022

0 minutes de lecture

Comme d’autres langages de programmation, Ruby dispose de tout un écosystème de bibliothèques open source tierces, appelées gems ou parfois gems Ruby. Ces gems sont créées par la communauté et disponibles sur RubyGems.org, le registre officiel des bibliothèques Ruby. Comme dans les autres écosystèmes open source, des acteurs malveillants peuvent publier intentionnellement du code malveillant, par exemple avec des portes dérobées ou des mécanismes de collecte d’identifiants. Il est donc essentiel de gérer et d’auditer avec soin vos gems Ruby open source.

Dans cet article, je passe en revue les concepts et les outils qui composent l’écosystème des dépendances Ruby et réponds aux questions courantes des développeurs Ruby.

Le langage de programmation Ruby

Vous connaissez peut-être Ruby on Rails (RoR), le framework de développement web populaire qui a largement contribué au succès et à la popularité de Ruby. RoR repose sur Ruby, un langage dynamique, multiplateforme et interprété. Ses fichiers de code source sont facilement reconnaissables à leur extension .rb.

Que sont les gems Ruby ?

Les gems sont des bibliothèques de code source empaquetées, modulaires, indépendantes et facilement réutilisables dans différents projets.

Qu’est-ce que RubyGems et en quoi diffère-t-il de Bundler ?

RubyGems est le registre central de packages où sont partagées les gems Ruby open source tierces. C’est également le gestionnaire de packages officiel de Ruby, appelé gem lorsqu’on l’utilise via l’interface en ligne de commande.

Page d’accueil de RubyGems avec un champ de recherche, le titre « Trouvez, installez et publiez des RubyGems », le nombre de téléchargements et un bouton « Installer RubyGems ».

RubyGems permet aux développeurs d’accéder à un écosystème de centaines de milliers de bibliothèques open source et de les utiliser dans leurs projets.

L’interface CLI gems de RubyGems est préinstallée dans un environnement Ruby fonctionnel. Les développeurs peuvent donc installer immédiatement leurs dépendances open source préférées, par exemple :

sh
gem install rails

L’interface CLI gems gère toutefois chaque dépendance individuellement : elle les récupère depuis un registre et les installe sur le système de fichiers, afin qu’elles soient disponibles et faciles à importer dans les projets de code Ruby.

Le projet Bundler, qui est techniquement une gem Ruby et s’installe comme telle, simplifie l’installation des gems une par une. Il permet également d’obtenir un arbre de dépendances déterministe et cohérent grâce à un fichier manifeste appelé Gemfile et à son fichier de verrouillage associé, Gemfile.lock. Ce dernier verrouille les dépendances dans tout l’arbre et garantit la cohérence des installations au fil du temps.

Bundler étant une gem, il est très facile de commencer à l’utiliser :

sh
gem install bundler

Cette commande met l’outil en ligne de commande bundler à la disposition des développeurs.

Le Gemfile Ruby

Le fichier nommé Gemfile est un fichier manifeste texte qui décrit toutes les dépendances à utiliser dans le projet, ainsi que d’autres instructions de métadonnées prises en charge par l’outil bundler.

Voici un exemple de Gemfile simple :

ruby
source 'https://rubygems.org'

gem ‘rails’, ‘4.2.3’
gem ‘sqlite3’

Le Gemfile ci-dessus définit l’entrée source, qui indique le dépôt distant depuis lequel Bundler téléchargera les gems. Chaque ligne gem définit une bibliothèque open source, la version à récupérer et la source où la trouver.

Voici un exemple plus complexe qui montre comment les gems Ruby peuvent provenir de différents emplacements :

ruby

gem 'nokogiri'
gem 'nokogiri', git: 'https://github.com/sparklemotion/nokogiri.git', branch: main'
gem 'nokogiri', github: ‘sparklemotion/nokogiri’
gem 'nokogiri', path: '/path/to/local/nokogiri’

Les détails de ces sources sont décrits dans la documentation Git officielle de Bundler.

De quoi se compose une gem Ruby ?

Une gem Ruby est du code source Ruby empaqueté et modulaire, réutilisable par d’autres gems Ruby, par des applications Ruby ou, plus généralement, par les utilisateurs qui interagissent avec elle en ligne de commande, comme nous l’avons vu plus haut avec Bundler.

Les gems Ruby suivent généralement une convention qui comprend le code source Ruby, les tests, d’éventuels fichiers exécutables mis à la disposition de l’utilisateur dans son chemin d’accès et, surtout, le fichier de spécification de la gem Ruby, appelé Gemspec.

sh 
base64ness/
├─ lib/
│  ├─ base64.rb
├─ bin/
│  ├─ base64ness
│  ├─ index.html
│  ├─ robots.txt
├─ test/
│  ├─ base64
├─ base64ness.gemspec
├─ Rakefile
├─ Gemfile
├─ Gemfile.lock
├─ README.md

Certains répertoires, comme test/ et bin/, sont assez explicites : ils contiennent respectivement le code de test de cette dépendance et le fichier exécutable à distribuer et à installer avec celle-ci. On peut également citer les fichiers Gemfile et Gemfile.lock, que nous avons découverts en présentant Bundler pour gérer les gems Ruby et son fichier de verrouillage, qui garantit des installations déterministes et reproductibles en toute sécurité.

Le fichier Rakefile est associé au projet open source Rake, un outil de compilation pour les projets Ruby similaire à make. Son nom rappelle donc celui de Makefile. En voici un exemple de contenu :

ruby
require "rake/testtask"

Rake::TestTask.new do |t|
  t.libs << "test"
  t.verbose = true
  t.test_files = FileList["test/**/*.rb"]
end

desc "Run tests"
task default: :test

Le fichier Rakefile importe les classes nécessaires depuis la gem Rake et définit une tâche test. Lorsque la commande rake test est exécutée dans ce répertoire, elle demande au programme Rake de rechercher, à l’aide d’un motif glob, tous les fichiers de test du dossier test/, puis de les exécuter. Pour en savoir plus sur le format du Rakefile, consultez la documentation du dépôt du projet Rake.

Un autre fichier, que vous avez peut-être remarqué dans l’arborescence ci-dessus, est le fichier gemspec, qui porte souvent le même nom que le package, par exemple base64ness.gemspec. Le fichier gemspec décrit la gem Ruby et fournit des métadonnées sur cette dépendance. Il aide le registre RubyGems à suivre, analyser et mettre ces informations à la disposition des utilisateurs qui recherchent des gems Ruby.

Voici un exemple du fichier base64ness.gemspec de la gem Ruby base64 mentionnée plus haut :

ruby
Gem::Specification.new do |s|
  s.name        = "base64ness"
  s.version     = "1.0.0"
  s.summary     = "It does magic with base64!"
  s.description = "This Ruby gem adds extended utilities for base64 encoding and decoding tasks such as making them URL compliant."
  s.authors     = ["Liran Tal"]
  s.email       = "liran@snyk.io"
  s.files       = ["lib/base64.rb"]
  s.homepage    =
    "https://rubygems.org/gems/base64ness"
  s.license       = "MIT"
end

Le site RubyGems propose une documentation complète sur la classe Specification mentionnée plus haut. Elle présente notamment les métadonnées supplémentaires associées à une gem Ruby, les fonctionnalités avancées permettant aux responsables de la maintenance Ruby de définir des exécutables, l’ajout d’extensions C aux gems Ruby qui, grâce à la compilation croisée, prennent en charge les modules natifs, et bien plus encore.

Conseil : si vous souhaitez découvrir le contenu d’une gem Ruby en particulier, vous pouvez la décompresser dans un répertoire et examiner son code source comme suit :

sh
gem unpack your-ruby-gem.gem --target /path/to/gem-directory

Gérer l’installation des dépendances des gems Ruby

Les dépendances peuvent être regroupées logiquement en catégories spécifiques selon leur usage. Par exemple, vous pouvez avoir des dépendances de gems Ruby pour la production, ainsi que des gems Ruby de développement, à installer uniquement lorsque vous développez en local.

Pour les distinguer, vous pouvez définir le manifeste Gemfile suivant :

ruby
group :production do
  gem ‘rails’,
end

group :development do
  gem ‘rspec-rails’
end

Vous pouvez ensuite installer les dépendances par catégorie avec Bundler. Par exemple, la commande ci-dessous n’installera que les dépendances de production :

sh
bundle install --without development

Pourquoi avons-nous besoin d’un Gemfile.lock ?

Si vous étiez limité à la définition de vos dépendances à l’aide du manifeste de packages Gemfile de Bundler, vous seriez confronté aux contraintes suivantes :

  1. Seules les dépendances directes des gems Ruby sont documentées.

  2. Les versions des packages de ces dépendances directes des gems Ruby sont définies de manière imprécise et peu restrictive. Il peut s’agir de la dernière version disponible ou simplement de la dernière version dans une plage sémantique donnée.

  3. Même si vous verrouillez ces dépendances directes des gems Ruby sur des versions précises, chaque nouvelle installation peut malgré tout résoudre vers une version inattendue d’une gem dépendante.

En conséquence, sans fichier de verrouillage pour figer l’ensemble des dépendances imbriquées des gems Ruby installées dans votre projet, vous risquez d’obtenir des versions indéterminées des gems installées. C’est probablement la dernière chose que vous souhaitez dans un environnement d’intégration continue (CI) ou de compilation automatisé, ou pour les autres développeurs Ruby qui collaborent avec vous sur le projet.

Voici donc le Gemfile.lock :

ruby
GEM
  remote: https://rubygems.org/
  specs:
    actioncable (7.0.3.1)
      actionpack (= 7.0.3.1)
      activesupport (= 7.0.3.1)
      nio4r (~> 2.0)
      websocket-driver (>= 0.6.1)
    actionmailbox (7.0.3.1)
      actionpack (= 7.0.3.1)
      activejob (= 7.0.3.1)
      activerecord (= 7.0.3.1)
      activestorage (= 7.0.3.1)
      activesupport (= 7.0.3.1)
      mail (>= 2.7.1)
      net-imap
      net-pop
      net-smtp

PLATFORMS
  aarch64-linux

DEPENDENCIES
  bootsnap
  capybara
  debug
  importmap-rails
  jbuilder
  puma (~> 5.0)
  rails (~> 7.0.3, >= 7.0.3.1)
  rails_admin!
  selenium-webdriver
  sprockets-rails
  sqlite3 (~> 1.4)
  stimulus-rails
  turbo-rails
  tzinfo-data
  web-console
  webdrivers

RUBY VERSION
   ruby 3.1.2p20

BUNDLED WITH
   2.3.18

Examinons la spécification et le format de ce fichier de verrouillage des dépendances des gems :

  • La directive GEM ouvre le bloc qui répertorie toutes les dépendances des gems Ruby sous la forme d’un arbre imbriqué, avec les dépendances directes et transitives ainsi que leurs versions. Elles sont indiquées sous la directive specs. La directive remote indique à l’outil bundler la source où récupérer ces gems Ruby pour les installer.

  • La directive PLATFORMS contient la liste des plateformes cibles pour lesquelles il faut compiler des gems Ruby natives, en raison notamment de la chaîne de compilation croisée requise pour les extensions Ruby écrites en C.

  • La directive RUBY VERSION est facultative et indique la version de l’environnement d’exécution Ruby utilisée lors de la création du fichier de verrouillage Gemfile.lock.

  • La directive BUNDLED WITH indique la version de Bundler utilisée pour créer le fichier de verrouillage. Les développeurs utilisant des versions plus anciennes devraient envisager une mise à niveau afin de rester à jour et de garantir des résultats d’installation de gems Ruby cohérents et fiables.

bundler-audit et la nécessité de renforcer la sécurité des gems Ruby

Un vaste écosystème implique de grandes responsabilités. C’est bien vrai. C’est là qu’intervient le projet communautaire bundler-audit. Cette gem Ruby analyse les dépendances répertoriées dans le fichier de verrouillage du projet (Gemfile.lock), les compare à une base de données de gems Ruby vulnérables et signale les versions correspondant à une ou plusieurs vulnérabilités.

Premiers pas avec bundler-audit pour détecter les vulnérabilités des gems Ruby

Pour commencer à détecter les vulnérabilités avec bundler-audit, nous devons d’abord l’installer, comme n’importe quelle autre gem Ruby :

sh
gem install bundler-audit

Une fois installé, bundler-audit doit télécharger des avis de sécurité depuis des ressources telles que ruby-adivsory-db, une base de données gérée par la communauté qui puise elle-même ses données dans GitHub Advisory et dans la base de données Open Source Vulnerability gérée par Google :

sh
bundler-audit update

Si tout se passe bien, la commande bundler-audit devrait afficher un résultat similaire à celui-ci, indiquant que tout est à jour :

sh
Updating ruby-advisory-db ...
hint: Pulling without specifying how to reconcile divergent branches is
hint: discouraged. You can squelch this message by running one of the following
hint: commands sometime before your next pull:
hint:
hint:   git config pull.rebase false  # merge (the default strategy)
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
From https://github.com/rubysec/ruby-advisory-db
 * branch            master     -> FETCH_HEAD
Already up to date.
Updated ruby-advisory-db
ruby-advisory-db:
  advisories:596 advisories
  last updated:2022-07-18 13:57:38 -0700
  commit:66f047bdcb4bbda76857f9ba668b7d71b641b28b

Nous pouvons ensuite exécuter bundle-audit sans argument supplémentaire pour rechercher des vulnérabilités de sécurité. Voici le résultat obtenu pour mon projet Ruby on Rails local :

No vulnerabilities found

Le résultat semble satisfaisant, mais il peut être trompeur et vous exposer à des risques inutiles. En réalité, il s’agit d’un faux négatif : des vulnérabilités de sécurité existent bel et bien dans certaines versions des dépendances que j’ai installées.

Tout le respect dû au travail de la communauté pour créer des outils de sécurité, ses équipes disposent probablement de moyens et de ressources insuffisants pour assurer une couverture adéquate des vulnérabilités. C’est là qu’intervient le scanner de sécurité de Snyk Open Source.

Snyk propose un outil CLI gratuit, soutenu par une entreprise de sécurité qui place les développeurs au premier plan et dispose d’une base de données très complète de rapports sur les vulnérabilités des gems Ruby. Vous pouvez l’installer pour analyser vos dépendances.

Sécurisez gratuitement vos gems Ruby avec Snyk

Essayons Snyk et voyons quelles vulnérabilités de sécurité il détecte dans notre projet Rails. Si vous utilisez un MacBook Pro M1 comme moi, exécutez la commande suivante :

sh
curl https://static.snyk.io/cli/latest/snyk-linux-arm64 -o snyk
chmod +x snyk
mv snyk /usr/local/bin

Cette commande télécharge le binaire Snyk autonome, le rend exécutable et l’ajoute à votre chemin d’accès. Si vous préférez une autre méthode d’installation (par exemple brew sur macOS, scoop sur Windows ou une autre méthode), consultez la documentation de prise en main de la CLI Snyk.

Une fois la CLI Snyk installée, nous pouvons l’exécuter comme suit :

sh
snyk test

Si c’est la première fois que vous l’utilisez, le message suivant s’affiche :

sh
`snyk` requires an authenticated account. Please run `snyk auth` and try again.

Vous devrez alors saisir snyk auth, puis suivre les instructions pour copier une URL d’authentification dans votre navigateur et vous connecter ou vous inscrire afin de recevoir votre clé API. Vous reviendrez rapidement à l’invite de commande pour poursuivre l’analyse :

sh
snyk test

Testing /home/app/myapp...

Tested 80 dependencies for known issues, found 2 issues, 59 vulnerable paths.

Issues with no direct upgrade or patch:
  ✗ Information Exposure [Medium Severity][https://security.snyk.io/vuln/SNYK-RUBY-ACTIONCABLE-20338] in actioncable@7.0.3.1
    introduced by rails@7.0.3.1 > actioncable@7.0.3.1 and 1 other path(s)
  No upgrade or patch available
  ✗ Web Cache Poisoning [Medium Severity][https://security.snyk.io/vuln/SNYK-RUBY-RACK-1061917] in rack@2.2.4
    introduced by capybara@3.37.1 > rack@2.2.4 and 56 other path(s)
  No upgrade or patch available

Organization:      snyk-demo-567
Package manager:   rubygems
Target file:       Gemfile
Project name:      myapp
Open source:       no
Project path:      /home/app/myapp
Licenses:          enabled

Eh bien, deux problèmes et 59 chemins vulnérables… Notre projet est moins sûr que nous le pensions. Pour le moment, il semble qu’aucune version plus récente ne permette de mettre à niveau ces bibliothèques dans l’arbre de dépendances imbriquées. Pour que Snyk surveille en continu le Gemfile de notre projet et recherche les nouvelles versions correctives à appliquer, nous pouvons procéder de l’une des façons suivantes :

  1. Rendez-vous sur https://apps.snyk.io et connectez Snyk au dépôt de code source sur GitHub ou ailleurs. Snyk pourra ainsi surveiller ce projet en arrière-plan.

  2. Exécutez la commande snyk monitor pour créer un instantané des fichiers Gemfile et Gemfile.lock actuels et les surveiller en continu. Veuillez noter que vous devrez l’exécuter dans un environnement d’intégration continue ou de compilation automatisée afin qu’à chaque mise à jour des fichiers manifestes de packages Ruby, un nouvel instantané soit envoyé à Snyk, qui pourra ainsi les surveiller et en assurer le suivi.

Dois-je utiliser bundle-audit ou Snyk ?

Utilisez les deux. Ce n’est pas une réponse diplomatique : ils sont véritablement complémentaires de plusieurs façons :

  1. Snyk propose une base de données complète de vulnérabilités de sécurité dans l’écosystème Ruby, organisée manuellement et tenue à jour, souvent même avant bundle-audit et les autres outils.

  2. Bundle-audit effectue d’autres vérifications en plus de l’analyse des vulnérabilités, notamment pour s’assurer que vos gems Ruby ne sont pas récupérées depuis des sources non sécurisées telles que http:// et git://, qui pourraient permettre une attaque de l’intercepteur ou une injection dans le fichier de verrouillage, et ainsi altérer le code source que vous recevez et exécutez.

En résumé

Nous avons vu comment gérer les gems Ruby en tant que dépendances de projet, pourquoi il est important d’utiliser un fichier de verrouillage, ainsi que d’autres aspects et bonnes pratiques recommandées, comme analyser vos dépendances Ruby avec des outils tels que Snyk et bundler-audit pour les utiliser en toute sécurité.

Autres ressources Ruby :

Si vous pratiquez également le développement JavaScript, je vous recommande vivement de lire ce qu’est package-lock.json et comment fonctionne un fichier de verrouillage pour les packages Yarn et npm.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis de capture du drapeau en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Pourquoi les agents de codage IA créent-ils sans cesse des failles de contrôle d’accès ?

Les agents de codage IA peuvent générer une logique d’autorisation qui compile et passe la revue, tout en exposant les données d’un tenant à un autre. Découvrez pourquoi les failles de contrôle d’accès sont difficiles à détecter et comment les prévenir.