脆弱性のある古いライブラリを更新する時のTips集

# はじめに
本記事は、[エムスリーキャリア Advent Calendar 2022](https://qiita.com/advent-calendar/2022/m3c)の22日目の記事です。

弊社では医療従事者のキャリアに関する様々なサイト・Webアプリを提供しています。
長年開発を続けるにあたって、導入している依存関係ライブラリのバージョンが古いまま更新していないプロジェクトがありました。

古いバージョンのライブラリを使い続けると、ライブラリに脆弱性がある場合、**その修正が取り込まれない**というリスクがあります。
恐ろしい点は、あるプロジェクトが依存しているライブラリが、更に別のライブラリを依存関係に持っている場合、その脆弱性の影響も受けてしまうことです。

2021年12月ごろ報告された、[Apache Log4jの任意コード実行の脆弱性](https://www.jpcert.or.jp/newsflash/2021122401.html)(通称Log4Shell)は、その危険性・悪用の容易性に加えて、多くのアプリで導入されていた事による影響範囲の広さで話題となり、記憶に新しい方もいるでしょう。

弊社でも本格的に依存ライブラリの脆弱性問題に取り組むことになり、そのタスクフォースに参加した時のTips(もしくはポエム？）を公開します。

# 脆弱性修正の着手前に…

## 脆弱性をスキャンし、優先順位を決める
まず、自分のプロジェクトが脆弱性のあるバージョンのライブラリに依存しているかスキャンします。

脆弱性がスキャンされたライブラリは全て修正するのがベストですが、一度に全てを対応するのは現実的ではないと思います。
そこで、脆弱性の危険性をもとに修正の優先順位を決めます(=トリアージ）。

弊社ではVisional社が提供している[yamory](https://yamory.io)を利用しました。
yamoryは`Gemfile.lock`・`yarn.lock`などの依存関係マッピングファイルから脆弱性をスキャンします。
そして脆弱性の危険性をもとに`Immidiate`・`Delayed`などのトリアージレベルを付与してくれます。
![app.yamory.io_it-assets_projects.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986436/81ca8ed5-dd88-96df-d1ac-78d5dd499081.png)
※スクリーンショットは脆弱性に取り組む前のスキャン結果、プロジェクト名はマスク済み

## 脆弱性の内容を調査し、対応方法を決める
優先順位を決めたら、次にその脆弱性の内容を調査します。

### その脆弱性がプロジェクトに影響するか調査
yamory等で脆弱性が検知されても、そのプロジェクトには影響がない場合もあります。
例えば、`activerecord`で検知される[CVE-2021-22880](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22880)は「PostgreSQLを使用して、money型のカラムがある」場合のみ発生しますので、PostgreSQLでmoney型を使っていない場合この脆弱性は無視できます。

> [CVE-2021-22880](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22880)
This only impacts Rails applications that are using PostgreSQL along with money type columns that take user input.
(拙訳：これはPostgreSQLを使っていて、ユーザーの入力をmoney型のカラムで受け取るRailsアプリケーションのみ影響します。)

### 更新するライブラリと、更新先バージョンを決定
間接的に依存しているライブラリを更新する際は、大元となるライブラリそのものの更新が必要な場合もあります。
例えば上記の`activerecord`の場合は、同じバージョンの`rails`の間接依存として混入したケースがほとんどでしょう。
この場合は`rails`自体をバージョンアップします。

ライブラリを更新する場合は、そのChangelogを読んでアプリへの影響範囲を確認します。
参考：Railsのchangelog https://rubyonrails.org/category/releases

多くのライブラリは**セマンティックバージョニング**を採用しており、マイナーバージョンまでの更新で解決する場合はメジャーバージョンは上げなくてもいいでしょう(End of Lifeしてない限り)。
逆に、メジャーバージョンを上げないと解決しない場合は、その影響範囲を慎重に調査する必要があります。

:::note
**セマンティックバージョニング**は`メジャーバージョン.マイナーバージョン.パッチバージョン`の形式を取ります。
* メジャーバージョンの変更は、**破壊的な変更**(Breaking changes)が含まれ、その影響を確認する必要があることを示唆します。
* マイナーバージョンの変更は、新機能の追加および既存機能の軽微な変更が含まれます。
* パッチバージョンの更新は、バグの修正のみ含まれます。

~~なお筆者は騙されて「パッチバージョン上げただけなのにアプリ起動しないんだが！？」となった経験あり~~
:::

## 可能な限りテストコードを書いておく
多くのライブラリを一度に更新する時は、**「ライブラリを更新しては、動作確認」** を頻繁に繰り返すことになります。
手戻りを防ぐため、着手前に可能な限りテストコードのカバレッジを上げておくことを推奨します。

テストコードはアプリの品質を保証するだけでなく、積極的なリファクタリングを手助けする役割もあります。
~~テストコードのある機能をリファクタリングする時の安心感はダンチだぜ？~~
ライブラリの更新のみならず、技術的負債の返済に取り組む時はまずテストコードの充足させることをお勧めします。

# ライブラリ更新のTips

## 一気にではなく、一つずつ更新する
更新するライブラリと、そのバージョンが決まった、あとは`xxx update`するだけ。
そんな早る気持ちを抑えてちょっと待ってください。

一気に更新してしまうと、動作確認して動かなかった時にどのライブラリが原因なのか分からなくなります。
影響範囲の小さいライブラリから一つずつ更新して、都度動作確認しましょう。

また、フレームワークや巨大なライブラリは1バージョンあたりの差分が多いため、マイナーバージョンを地道に一つづつ上げて動作確認、を繰り返すと良いでしょう。

私は、Railsアプリは以下の順番で更新しました。
1. 更新対象のライブラリを一つだけ更新
1. railsコンソールが起動するか確認
1. railsサーバーが起動するか確認
1. ブラウザでトップページにアクセスできるか確認
1. 単体テスト(rspec)が合格するか確認
<!--
# その他よもやま話
* ライブラリの役割を理解する
* 未使用のライブラリは削除する
* 報告されている脆弱性を理解する
* 定期的に脆弱性をスキャン・修正する体制を作る
-->
# 最後に
エムスリーキャリアでは積極的にエンジニアを採用しております。
今回は長年続いているプロジェクトの技術負債ネタでしたが、新規事業も多くやっておりますので興味のある方はぜひご応募ください！

https://career.m3career.com/midcareer/engineer