Skip to main content

Ruby gemの依存関係管理を徹底解説

著者
blog feature ruby security

2022年8月5日

0 分で読めます

Rubyは他のプログラミング言語と同様に、サードパーティ製オープンソースライブラリのエコシステムを持ち、それらはgem、またはRuby gemと呼ばれています。gemはコミュニティによって開発され、Rubyライブラリの公式レジストリであるRubyGems.orgから利用できます。他のオープンソースエコシステムと同様に、攻撃者が意図的に悪意のあるコードを公開したり、バックドアや認証情報を窃取するコードを含めたりする可能性があります。そのため、Ruby gemの管理と監査には細心の注意を払うことが重要です。

この記事では、Rubyの依存関係エコシステムを構成する概念やツールを紹介し、Ruby開発者からよく寄せられる質問にお答えします。

Rubyプログラミング言語

Rubyの成功と普及に大きく貢献した、人気のWeb開発フレームワークRuby on Rails(RoR)をご存じかもしれません。RoRはRubyをベースにしています。Rubyは動的で、クロスプラットフォームに対応したインタプリタ型言語です。ソースコードファイルは.rbという拡張子で簡単に見分けられます。

Ruby gemとは?

gemとは、パッケージ化されたソースコードライブラリです。モジュール化されて独立しており、プロジェクト間で簡単に再利用できます。

RubyGemsとは?Bundlerとはどう違う?

RubyGemsは、サードパーティ製オープンソースRuby gemを共有する中央パッケージレジストリであり、公式のRubyパッケージマネージャーでもあります。コマンドラインインターフェースからはgemとして利用できます。

検索欄、「RubyGemsを見つけて、インストール、公開しましょう」という見出し、ダウンロード数、「RubyGemsをインストール」ボタンが表示されたRubyGemsのホームページ。

RubyGemsを使うと、開発者は何十万ものオープンソースライブラリから選び、プロジェクトで活用できます。

RubyGem独自のgems CLIは、動作するRuby環境にあらかじめインストールされているため、開発者はすぐにお気に入りのオープンソースの依存関係をインストールできます。例を見てみましょう。

sh
gem install rails

ただし、gems CLIはレジストリから依存関係を個別に取得し、ファイルシステムにインストールして管理します。こうすることで、Rubyのコードプロジェクトから簡単にインポートできるようになります。

Bundlerプロジェクトは、それ自体がRuby gemであり、gemとしてインストールします。gemを1つずつインストールする手間を軽減し、Gemfileというマニフェストファイルと、対応するロックファイルGemfile.lockを使って、決定論的で一貫性のある依存関係ツリーを実現します。後者はツリー全体の依存関係を固定し、時間が経ってもインストール結果の一貫性を保ちます。

Bundlerはgemなので、次のように簡単に使い始められます。

sh
gem install bundler

このコマンドを実行すると、開発者がbundlerコマンドラインツールを使えるようになります

RubyのGemfile

Gemfileというファイルは、プロジェクトで使用するすべての依存関係と、bundlerツールが提供するその他のメタデータや指示を記述するマニフェストファイルです。

シンプルなGemfileは次のとおりです。

ruby
source 'https://rubygems.org'

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

上記のGemfileでは、Bundlerがすべてのgemをダウンロードするリモートリポジトリとしてsourceを定義し、それぞれのgem行でオープンソースライブラリ、その取得するバージョン、取得元を指定しています。

以下は、Ruby gemをさまざまな場所から取得する、より複雑な例です。

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’

これらのソースについては、公式のBundlerのGitドキュメントで詳しく説明されています。

Ruby gemの構成要素

Ruby gemはパッケージ化されたモジュール形式のRubyソースコードで、他のRuby gemやRubyアプリケーションから再利用できます。また、この記事で先ほど紹介したBundlerのように、コマンドライン経由で利用することもできます。

Ruby gemは一般に、Rubyのソースコード、テスト、ユーザーのファイルパスから利用できる実行可能ファイルなどで構成されます。特に重要なのが、Gemspecと呼ばれるRuby gem仕様ファイルです。

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

test/やbin/などのディレクトリは、それぞれこの依存関係のテストコードと、パッケージに含めてインストールする実行可能ファイルを表しているため、わかりやすいでしょう。ほかに注目すべきファイルはGemfileとGemfile.lockです。これらは、Ruby gemの管理にBundlerを使う方法を紹介した際に説明しました。ロックファイルを使うことで、インストールを決定論的にし、安全に繰り返せるようにします。

Rakefileは、Rubyプロジェクト向けのmakeに似たビルドユーティリティであるオープンソースのRakeプロジェクトに関連するファイルです。そのため、名前もMakefileに似ています。内容の例を見てみましょう。

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

RakefileはRake gemから必要なクラスをインポートし、testタスクを定義します。このディレクトリでrake testコマンドを実行すると、Rakeプログラムはglobパターンに一致するtest/フォルダ内のすべてのテストファイルを検索し、実行します。Rakefileの形式について詳しくは、Rakeプロジェクトリポジトリのドキュメントをご覧ください。

上のファイルツリーにある、もう1つの見慣れないファイルはgemspecファイルです。通常はパッケージと同じ名前(たとえばbase64ness.gemspec)が付けられます。gemspecはRuby gemについて記述し、この依存関係のメタデータを提供します。RubyGemsレジストリがこの情報を追跡、分析し、Ruby gemを探しているユーザーに提供するうえで役立ちます。

先ほど触れたbase64 Ruby gem、base64ness.gemspecの例を見てみましょう。

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

RubyGemsのWebサイトには、先ほど紹介したSpecificationクラスのドキュメントがすべて掲載されています。Ruby gemに付加できるメタデータ、Rubyのメンテナーが実行可能ファイルを定義できる拡張機能、クロスコンパイルによってネイティブモジュール機能を提供するC拡張のRuby gemへのバンドルなどについても説明されています。

ヒント:特定のRuby gemの中身が気になる場合は、次のようにディレクトリに展開してソースコードを確認できます。

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

Ruby gemの依存関係インストールを制御する

依存関係は、関連性のある特定のカテゴリに分けて管理できます。たとえば、Ruby gemの本番環境用依存関係と、ローカルでの開発時のみインストールする開発用gemに分けられます。

区別するには、次のようにGemfileマニフェストを定義します。

ruby
group :production do
  gem ‘rails’,
end

group :development do
  gem ‘rspec-rails’
end

Bundlerを使うと、カテゴリ別に依存関係をインストールできます。たとえば、次のコマンドでは本番環境用の依存関係だけをインストールします。

sh
bundle install --without development

Gemfile.lockが必要な理由

BundlerのGemfileパッケージマニフェストで依存関係を定義する方法しか使えないとしたら、次のような制約があります。

  1. 記録されるのは、直接依存するRuby gemだけです。

  2. 直接依存するRuby gemのバージョン指定は、大まかで曖昧です。最新バージョンが取得されたり、セマンティックバージョンの範囲内で最新のものが取得されたりする可能性があります。

  3. 直接依存するRuby gemのバージョンを固定しても、新たにインストールするたびに、予期しないバージョンの依存gemが解決される可能性があります。

つまり、プロジェクトにインストールされるRuby gemのネストされた依存関係全体を固定するロックファイルがなければ、インストールされるgemのバージョンが予測できなくなります。自動化されたCIやビルド環境で、あるいはプロジェクトを一緒に進める他のRuby開発者にとって、これは避けたい事態でしょう。

そこで登場するのが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

このgem依存関係ロックファイルの仕様と形式を見ていきましょう。

  • GEMディレクティブは、ネストされたツリー形式ですべてのRuby gem依存関係を一覧にするブロックの開始を示します。この形式では、直接依存と推移的依存、およびそれらのバージョンが示されます。これらはspecsディレクティブの下に記述されます。remoteディレクティブは、インストールするRuby gemの取得元をBundlerツールに指示します。

  • PLATFORMSディレクティブは、Cで書かれたRuby拡張などで必要となるクロスコンパイルのため、ネイティブRuby gemをビルドする対象プラットフォームを列挙します。

  • RUBY VERSIONディレクティブは省略可能で、このGemfile.lockロックファイルの作成時に使用したRubyランタイムのバージョンを指定します。

  • BUNDLED WITHディレクティブは、ロックファイルの作成に使われたBundlerのバージョンを指定します。古いバージョンを使っている開発者は、最新の状態を保ち、一貫性のあるRuby gemのインストール結果を維持できるよう、アップグレードを検討してください。

bundler-auditとRuby gemのセキュリティ強化の必要性

優れたエコシステムには、大きな責任が伴います。そのとおりです。ここで登場するのが、コミュニティ主導のプロジェクトbundler-auditです。これはRuby gemで、プロジェクトのロックファイル(Gemfile.lock)に記載された依存関係をスキャンし、脆弱なRuby gemのデータベースと照合して、1つ以上の脆弱性に該当するバージョンが見つかった場合に報告します。

bundler-auditでRuby gemの脆弱性スキャンを始める

bundler-auditで脆弱性スキャンを始めるには、まずほかのRuby gemと同じようにインストールします。

sh
gem install bundler-audit

インストール後、bundler-auditはコミュニティが管理するruby-adivsory-dbなどのリソースからセキュリティアドバイザリをダウンロードする必要があります。このデータベース自体も、GitHub Advisoryと、Googleが管理するオープンソース脆弱性データベースからデータを取得しています。

sh
bundler-audit update

正常に完了すると、bundler-auditコマンドは、すべてが最新であることを示す次のような結果を出力します。

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

次に、追加の引数なしでbundle-auditを実行し、セキュリティ脆弱性をテストできます。現在、私のローカルのRuby on Railsプロジェクトで実行すると、次のように表示されました。

No vulnerabilities found

問題なさそうに見えますが、誤解を招き、不必要なリスクにつながる可能性があります。実際、インストール済みの依存関係の一部にはセキュリティ脆弱性が存在するため、これは偽陰性です。

コミュニティがセキュリティツールの開発に取り組んできたことには敬意を表しますが、脆弱性を十分に網羅するには、人員やリソースが不足している可能性があります。そこで役立つのが、Snyk Open Sourceのセキュリティスキャナーです。

Snykには、開発者を第一に考えるセキュリティ企業が支える(無料の)CLIツールがあります。Ruby gemの脆弱性レポートを豊富に収録したデータベースを活用しており、インストールして依存関係をスキャンできます。

SnykでRuby gemを無料で保護する

Snykを試して、Railsプロジェクトでどのようなセキュリティ脆弱性が見つかるか確認しましょう。私と同じM1 MacBook Proをお使いの場合は、次のコマンドを実行してください。

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

これにより、スタンドアロン版のSnykバイナリがダウンロードされ、実行可能になり、パスから利用できるようになります。macOSのbrewやWindowsのscoopなど、別のインストール方法が必要な場合は、Snyk CLIの利用開始ガイドをご覧ください。

Snyk CLIをインストールしたら、次のように実行できます。

sh
snyk test

初めて実行する場合は、次のメッセージが表示されます。

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

その後、snyk authと入力し、手順に従って認証用URLをブラウザーにコピーします。ログインまたはサインアップするとAPIキーが発行されます。すぐにコマンドラインのプロンプトに戻り、スキャンを続けられます。

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

なるほど、問題が2件、脆弱なパスが59件です。思っていたほど安全ではありませんでした。現時点では、ネストされた依存関係ツリー全体に、これらのライブラリを更新できる新しいバージョンはないようです。そこで、プロジェクトのGemfileをSnykで継続的に監視し、新しい修正バージョンを探せるようにするには、次のいずれかを実行します。

  1. https://apps.snyk.ioにアクセスし、GitHubなどのソースコードリポジトリにSnykを接続します。これにより、Snykがバックグラウンドでこのプロジェクトを監視できるようになります。

  2. snyk monitorコマンドを実行すると、現在のGemfileとGemfile.lockのスナップショットが作成され、継続的に監視されます。Rubyパッケージのマニフェストファイルが更新されるたびに、Snykが監視して追跡できるよう新しいスナップショットを送信する必要があるため、CIまたは自動ビルドから実行してください。

bundle-auditとSnyk、どちらを使うべきですか?

両方使いましょう。これは無難な回答というわけではなく、実際に次のような点で互いを補完します。

  1. SnykはRubyエコシステムのセキュリティ脆弱性を網羅したデータベースを提供しています。手作業で精査され、最新の状態に保たれており、多くの場合、bundle-auditなどに先駆けて情報が掲載されます。

  2. Bundle-auditは脆弱性スキャン以外にもチェックを実行します。たとえば、http://やgit://のような安全でないソースからRuby gemを取得していないかを確認します。こうしたソースは、中間者攻撃やロックファイルへのコード挿入攻撃によって、受け取って実行するソースコードを改ざんされるおそれがあります。

まとめ

プロジェクトの依存関係としてRuby gemを管理する方法や、ロックファイルが重要な理由について学びました。また、Snykやbundler-auditなどのツールでスキャンしてRubyの依存関係を安全に利用するなど、その他の側面や推奨されるベストプラクティスについても紹介しました。

Ruby関連のその他のリソース:

JavaScriptの開発も行っている方には、package-lock.jsonとは何か、yarnとnpmのパッケージでロックファイルがどのように機能するかを読むことを強くおすすめします。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。