Ruby gemの依存関係管理を徹底解説
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を使うと、開発者は何十万ものオープンソースライブラリから選び、プロジェクトで活用できます。
RubyGem独自のgems CLIは、動作するRuby環境にあらかじめインストールされているため、開発者はすぐにお気に入りのオープンソースの依存関係をインストールできます。例を見てみましょう。
ただし、gems CLIはレジストリから依存関係を個別に取得し、ファイルシステムにインストールして管理します。こうすることで、Rubyのコードプロジェクトから簡単にインポートできるようになります。
Bundlerプロジェクトは、それ自体がRuby gemであり、gemとしてインストールします。gemを1つずつインストールする手間を軽減し、Gemfileというマニフェストファイルと、対応するロックファイルGemfile.lockを使って、決定論的で一貫性のある依存関係ツリーを実現します。後者はツリー全体の依存関係を固定し、時間が経ってもインストール結果の一貫性を保ちます。
Bundlerはgemなので、次のように簡単に使い始められます。
このコマンドを実行すると、開発者がbundlerコマンドラインツールを使えるようになります
RubyのGemfile
Gemfileというファイルは、プロジェクトで使用するすべての依存関係と、bundlerツールが提供するその他のメタデータや指示を記述するマニフェストファイルです。
シンプルなGemfileは次のとおりです。
上記のGemfileでは、Bundlerがすべてのgemをダウンロードするリモートリポジトリとしてsourceを定義し、それぞれのgem行でオープンソースライブラリ、その取得するバージョン、取得元を指定しています。
以下は、Ruby gemをさまざまな場所から取得する、より複雑な例です。
これらのソースについては、公式のBundlerのGitドキュメントで詳しく説明されています。
Ruby gemの構成要素
Ruby gemはパッケージ化されたモジュール形式のRubyソースコードで、他のRuby gemやRubyアプリケーションから再利用できます。また、この記事で先ほど紹介したBundlerのように、コマンドライン経由で利用することもできます。
Ruby gemは一般に、Rubyのソースコード、テスト、ユーザーのファイルパスから利用できる実行可能ファイルなどで構成されます。特に重要なのが、Gemspecと呼ばれるRuby gem仕様ファイルです。
test/やbin/などのディレクトリは、それぞれこの依存関係のテストコードと、パッケージに含めてインストールする実行可能ファイルを表しているため、わかりやすいでしょう。ほかに注目すべきファイルはGemfileとGemfile.lockです。これらは、Ruby gemの管理にBundlerを使う方法を紹介した際に説明しました。ロックファイルを使うことで、インストールを決定論的にし、安全に繰り返せるようにします。
Rakefileは、Rubyプロジェクト向けのmakeに似たビルドユーティリティであるオープンソースのRakeプロジェクトに関連するファイルです。そのため、名前もMakefileに似ています。内容の例を見てみましょう。
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の例を見てみましょう。
RubyGemsのWebサイトには、先ほど紹介したSpecificationクラスのドキュメントがすべて掲載されています。Ruby gemに付加できるメタデータ、Rubyのメンテナーが実行可能ファイルを定義できる拡張機能、クロスコンパイルによってネイティブモジュール機能を提供するC拡張のRuby gemへのバンドルなどについても説明されています。
ヒント:特定のRuby gemの中身が気になる場合は、次のようにディレクトリに展開してソースコードを確認できます。
Ruby gemの依存関係インストールを制御する
依存関係は、関連性のある特定のカテゴリに分けて管理できます。たとえば、Ruby gemの本番環境用依存関係と、ローカルでの開発時のみインストールする開発用gemに分けられます。
区別するには、次のようにGemfileマニフェストを定義します。
Bundlerを使うと、カテゴリ別に依存関係をインストールできます。たとえば、次のコマンドでは本番環境用の依存関係だけをインストールします。
Gemfile.lockが必要な理由
BundlerのGemfileパッケージマニフェストで依存関係を定義する方法しか使えないとしたら、次のような制約があります。
記録されるのは、直接依存するRuby gemだけです。
直接依存するRuby gemのバージョン指定は、大まかで曖昧です。最新バージョンが取得されたり、セマンティックバージョンの範囲内で最新のものが取得されたりする可能性があります。
直接依存するRuby gemのバージョンを固定しても、新たにインストールするたびに、予期しないバージョンの依存gemが解決される可能性があります。
つまり、プロジェクトにインストールされるRuby gemのネストされた依存関係全体を固定するロックファイルがなければ、インストールされるgemのバージョンが予測できなくなります。自動化されたCIやビルド環境で、あるいはプロジェクトを一緒に進める他のRuby開発者にとって、これは避けたい事態でしょう。
そこで登場するのがGemfile.lockです。
この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と同じようにインストールします。
インストール後、bundler-auditはコミュニティが管理するruby-adivsory-dbなどのリソースからセキュリティアドバイザリをダウンロードする必要があります。このデータベース自体も、GitHub Advisoryと、Googleが管理するオープンソース脆弱性データベースからデータを取得しています。
正常に完了すると、bundler-auditコマンドは、すべてが最新であることを示す次のような結果を出力します。
次に、追加の引数なしでbundle-auditを実行し、セキュリティ脆弱性をテストできます。現在、私のローカルのRuby on Railsプロジェクトで実行すると、次のように表示されました。
問題なさそうに見えますが、誤解を招き、不必要なリスクにつながる可能性があります。実際、インストール済みの依存関係の一部にはセキュリティ脆弱性が存在するため、これは偽陰性です。
コミュニティがセキュリティツールの開発に取り組んできたことには敬意を表しますが、脆弱性を十分に網羅するには、人員やリソースが不足している可能性があります。そこで役立つのが、Snyk Open Sourceのセキュリティスキャナーです。
Snykには、開発者を第一に考えるセキュリティ企業が支える(無料の)CLIツールがあります。Ruby gemの脆弱性レポートを豊富に収録したデータベースを活用しており、インストールして依存関係をスキャンできます。
SnykでRuby gemを無料で保護する
Snykを試して、Railsプロジェクトでどのようなセキュリティ脆弱性が見つかるか確認しましょう。私と同じM1 MacBook Proをお使いの場合は、次のコマンドを実行してください。
これにより、スタンドアロン版のSnykバイナリがダウンロードされ、実行可能になり、パスから利用できるようになります。macOSのbrewやWindowsのscoopなど、別のインストール方法が必要な場合は、Snyk CLIの利用開始ガイドをご覧ください。
Snyk CLIをインストールしたら、次のように実行できます。
初めて実行する場合は、次のメッセージが表示されます。
その後、snyk authと入力し、手順に従って認証用URLをブラウザーにコピーします。ログインまたはサインアップするとAPIキーが発行されます。すぐにコマンドラインのプロンプトに戻り、スキャンを続けられます。
なるほど、問題が2件、脆弱なパスが59件です。思っていたほど安全ではありませんでした。現時点では、ネストされた依存関係ツリー全体に、これらのライブラリを更新できる新しいバージョンはないようです。そこで、プロジェクトのGemfileをSnykで継続的に監視し、新しい修正バージョンを探せるようにするには、次のいずれかを実行します。
https://apps.snyk.ioにアクセスし、GitHubなどのソースコードリポジトリにSnykを接続します。これにより、Snykがバックグラウンドでこのプロジェクトを監視できるようになります。
snyk monitorコマンドを実行すると、現在のGemfileとGemfile.lockのスナップショットが作成され、継続的に監視されます。Rubyパッケージのマニフェストファイルが更新されるたびに、Snykが監視して追跡できるよう新しいスナップショットを送信する必要があるため、CIまたは自動ビルドから実行してください。
bundle-auditとSnyk、どちらを使うべきですか?
両方使いましょう。これは無難な回答というわけではなく、実際に次のような点で互いを補完します。
SnykはRubyエコシステムのセキュリティ脆弱性を網羅したデータベースを提供しています。手作業で精査され、最新の状態に保たれており、多くの場合、bundle-auditなどに先駆けて情報が掲載されます。
Bundle-auditは脆弱性スキャン以外にもチェックを実行します。たとえば、
http://やgit://のような安全でないソースからRuby gemを取得していないかを確認します。こうしたソースは、中間者攻撃やロックファイルへのコード挿入攻撃によって、受け取って実行するソースコードを改ざんされるおそれがあります。
まとめ
プロジェクトの依存関係としてRuby gemを管理する方法や、ロックファイルが重要な理由について学びました。また、Snykやbundler-auditなどのツールでスキャンしてRubyの依存関係を安全に利用するなど、その他の側面や推奨されるベストプラクティスについても紹介しました。
Ruby関連のその他のリソース:
JavaScriptの開発も行っている方には、package-lock.jsonとは何か、yarnとnpmのパッケージでロックファイルがどのように機能するかを読むことを強くおすすめします。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。



