Ruby フルタイムコミッタの仕事報告 2023年Q1

こんにちは、hsbt です。前回のエントリで触れたウィッチャー3は一段落しましたが、気の迷いから原神を初めてしまい無限に時間が溶けています。RubyKaigi 2023 が近づいて来ているのにこれはまずい。

今日は前回の Ruby フルタイムコミッタになってからやったこと、の定期報告シリーズとして、2023年のQ1にフルタイムコミッタとして行った仕事の一部をご紹介します。

Ruby のリリースについてのご紹介

まず、今回の仕事内容に入る前に2023年2月18日に開催された福岡Rubyist会議03で発表した、Ruby のリリースにまつわる課題をまとめたスライドをご紹介します。

上記スライドでは、毎年、または不定期に行っている安定バージョンのリリース時に発生していた、発生している課題について原因と対策、対策の結果生まれた新たな課題のループについて解説をしています。今回は発表では深くは触れなかったセキュリティリリースの裏側についてご紹介します。

Ruby の開発チームは2023年3月末に以下の2件のセキュリティの問題、いわゆる脆弱性が Ruby に発見され、修正したことを公開しました。

上記2件の脆弱性の修正と公開に加えて、私 hsbt がリリースマネージャとして各安定バージョンへのバックポートとリリースまでハンドリングしました。このリリースマネジメントの中から、ソフトウェアのバグを脆弱性として扱い CVE ID を取得して不具合の修正と公開までの手続きや進め方を解説します。

セキュリティリリースの難しいところ

脆弱性の修正を含むリリース(セキュリティリリース)を行う上で、一番重要なことは「いつ、誰が責任を持ってリリースするか」を決めることです。人間だれもがみな忙しいので、リリースできると良いね、というものはリリースされません。最後まで面倒見る人がやる気を持って進める覚悟をもってやる必要があります。

今回は3月末-4月頭には Ruby 2.7 の EOL が予告されているので、もし脆弱性を修正したいのであれば、その時期までに必要なプロセスをすべて消化した上で安定版ブランチのメンテナと連絡をとってリリースを行う必要があります。

Ruby 2.7 のブランチメンテナである unak さんと協議したところ、いくつかの脆弱性リポートについては Ruby 2.7 で修正したいということだったので、今回時間をとることができるhsbt がリリースマネージャに立候補しました。

なお、このセキュリティリリースを担当するリリースマネージャは持ち回り、というわけでもなく hsbt または mame のいずれかが担当することが多いです。かろうじてトラックナンバーは2ですが、あまり良い状態ではないですね...。

覚悟を持ってリリースマネージャをやることにしたので、後は必要となるプロセスを淡々と進めていくわけですが、主な作業としては以下のようになります。

  • 脆弱性リポートについて CVE を取得するか否かを決める
  • 脆弱性リポートの修正を行い、問題が解決されていることを確認する
  • 解決された修正をパッチとして、メンテナンス対象である安定バージョンすべてに適用できることを確認する
  • パッチを適用したブランチを決定したリリース日に新しい Ruby のバージョンとしてリリースする

上記の作業が脆弱性リポートの数だけ倍々で増えることになります。今回は2件だったので、単純にはパッチは2件*4ブランチの8つ存在します。3,4つとなったときの複雑度についてはご想像のとおりです。この作業を何かしらの課題管理システムやドキュメントなどで進捗管理しながら進めることになります。

CVE とは何なのか

一旦ここで脆弱性について何か対応を行う際に必ず目にすることになる CVE について、OSS のメンテナとして発行するときの扱いも含めてご紹介します。

  • MITRE が発行している「脆弱性となりうる事象に対して世界で一意な識別子」である
  • あくまでも識別子なので CVE が示す事象すべてが脆弱性かどうかは保証されてない、内容の再現容易性やシステムへの影響の深刻度は各自で評価を行う必要がある
  • 一般的に CVE をアサインする事象は「ソフトウェアの利用者に対してアクションを起こすべき」事象である

所属している組織のコンプライアンス対応などで CVE が発行されたものはすべてアップデートなり緩和策を講じる、というようなポリシーがある場合、あらゆる CVE が脆弱性に見えてくるかもしれませんが、CVE の中には再現が極めて難しいものや、中には脆弱性とは言えないようなものも時には混ざっています。

理想は CVE が発行されたときに内容を吟味し、自分の責任範囲において対応は不要、必要などを判断して取捨選択することが望ましいですが、誰もがリソースは有限なため、増え続けるソフトウェアと依存関係にたいして取捨選択の時間が取れず、CVE が発行されたものはすべて対応するというポリシーにならざるを得ないという状況は近年の課題ではないかと個人的には思います。

CVE を取得する基準の難しさ

Ruby では HackerOne (h1)というサービスを用いて脆弱性のレポートを一元管理しています。

HackerOne の Ruby プロジェクトのページ

HackerOne を介さないメールでも受け付けていますが内部的には h1 に集約しています。私 hsbt を始めとして、Ruby の開発メンバーの中では以下のような判断基準で CVE を取得するか否かを判断することが多いです。

  • 前提に従ってユーザーに対してアップデートしたほうが良いよ、ということを特に知らせたいものは CVE を取得することが多い。ユーザーに Ruby をアップデートしても解決できない、というようなどうしようもないものは CVE を取得したところでどうしようもないので取得しない。
  • 他の言語、例えば Python では同じ事象について CVE を発行したというケースなどは大いに参考にする。ただし絶対ではなく、Ruby では CVE を採番しないとしたものもある。主にライブラリのメンテナや先に説明したようなセキュリティリリースを担当するリリースマネージャなどの決断を尊重する。
  • 過去に発生した類似のケースで CVE を採番したものは取得することが多い。類似のケースであってもあえて取得しない、と決めるにはそれなりの理由が必要なのでロジックの組み立てを行う。

このように脆弱性なのか不具合なのか、ということは一律の基準があって決められるものではなく、リリースまで行う実行者の裁量によって決められています。

昨今のサプライチェーンアタックやセキュリティへの関心のたかまりもあり、開発者である我々が CVE をとってリリースすることで、それらの企業と所属している人々に対して仕事を増やしてしまうのではないか、という迷いも決断へ負担を増やす要因となっています。また最近は GitHub の dependabot が pull-request を作成し、CI を実行するなど、CVE 1つで世界中のあらゆるリソースが使われています。

DevSecOps などの取り組みの結果としては迅速に対処できるようになった、というのは喜ばしい一方で、各々の決断一つで他の人々や組織を動かすことになる時代になってしまいました。CVE を取得して修正したソフトウェアをリリースする、ということは、色んな人に影響が出たとしてもそれでもソフトウェアを直したほうが良い、という技術者倫理に基づいた重い決断の一つと考えています。

CVE の取得と脆弱性の修正とリリース

さて、CVE を取得する、ということを決めた場合は後は淡々と進めることになります。実は CVE の取得は誰にでもできる簡単な内容になっています。 https://cveform.mitre.org/ のフォームに必要な事項を入力後に 1-2日すると、PGP 暗号化されたメールが届きます。そのメールには予約された CVE番号が CVE-yyyy-XXXX という形式で記載されているのでこの番号を使うことになります。

続いて、脆弱性の修正です。OSS は GitHub などに広く公開されている場合が多いので、不具合の修正フローやリリースはついつい GitHub の上で行ってしまいがちですが、脆弱性の修正は修正された、ということが確認されるまでは非公開で行う必要があります。

修正する手段がない状態で公開されてしまっては、それらはゼロデイの脆弱性となり、多くの組織にとってダメージを受ける結果となります。そのため、Ruby でも GitHub のようなプラットフォームを使いながらも非公開で不具合の修正を行えるような仕組みを準備しています。

HackerOne でパッチをやり取りしている様子

昨今では、GitHub の Security Advisary 機能を使うことで CVE ごとに private repository を作成し、pull-request などを作成することはできるようになりましたが、h1 や MITRE に入力したような情報をもう一度繰り返し入力する必要があるなど、煩雑になってしまうため上の図のように h1 で課題と進捗管理、パッチをやり取りした後、GitHub の CI 専用のリポジトリでブランチごとにテストというワークフローを採用しています。これらのサービス間での情報とデータのやりとりは人力で漏れが発生するため、もう少しオートメーションを進めたいとは考えています。

脆弱性の公開とリリース

Ruby には hsbt が進めてきた default gems という仕組みがあり、default gems となっているものについては、Ruby のリリースとは独立して gem update などで個別にライブラリを修正することができます。そのため、例えば今回のように URI に脆弱性があるという場合、URI は Ruby 2.7 から default gems になっているので、脆弱性を修正した URI をリリースすれば、ユーザーは Ruby をバージョンアップすることなく RubyGems や Bundler を経由するだけで脆弱性を対応することができます。

しかしながら、default gems 化は Ruby 3.1 まで継続して進めていたため、Ruby 3.1 以降のみ default gems となっているようなものは Ruby も同時にリリースしなければ、Ruby 3.0 ユーザーにとっては回避しようがなくなってしまいます。そのようなライブラリの脆弱性修正の場合は、Ruby の各バージョンのメンテナ全員とリリース時期を調整し、ライブラリの修正と Ruby の修正、そしてそれぞれのリリースを同時に行う必要があります。今回の time などが対象です。

また、最初に決めることではありますが、リリース日は JST の火-木を優先して選ぶようにしています。JSTでいう 月曜は PDT では日曜だったり、JST に金曜にリリースすると内容や組織によっては週末に突入してしまうからです。しかし、Ruby の各ブランチメンテナやセキュリティリリース時のリリースマネージャそれぞれの都合や脆弱性の内容によっては、曜日を考慮している場合ではない、ということもあります。そのような場合は早く修正版を出さなければ、という強い気持ちをもってリリースを行っています。

後は事前に用意していた修正パッチをマージ、またはコミットして、バージョンを上げた後 rubygems.org または www.ruby-lang.org へリリースし、事前に用意していたリリースアナウンスを公開して一連の作業は終了となります。

まとめ

今回は脆弱性の修正を伴うリリースの際に考えなければならないこと、考えたことなどをご紹介しました。なお、最後の最後に1行のみで書いた「リリースします」の裏側にも、事前にテストしていなかった GitHub Actions が壊れていてリアルタイムに直していたり、OpenSSL 3 しか提供されていない Ubuntu 22.04 の環境で Ruby 2.7 と 3.0 のテストが落ちて、「パッチ壊れていた?」などと焦ったりなど、何度やっても必ずなにか障害が起きるのは続いているという状態でした。

これらについては福岡Rubyist会議03でも発表しましたが、誰かが強い気持ちをもって改善し続けるしかないと思うので、引き続き CI を安定化したり、複数のサービスをつなぐようなコードを書いて人の手による判断や作業を減らす、ということを進めていきたいと思います。乞うご期待。

おわりに

アンドパッドではRubyやセキュリティエンジニアをはじめ、さまざまなポジションのエンジニアを募集しています。 ご興味がある方は、ぜひ話を聞きにきてください。

engineer.andpad.co.jp

hrmos.co