ぶるーたるごぶりん

UI, UX, セキュリティとか😘

GitHubが狙う「ライブラリのバージョン管理問題」の解決と依存関係地獄の話

GitHubが狙う「ライブラリのバージョン管理問題」の解決と依存関係地獄の話

GithubOSS Security Foundation に入りましたね。 大変興味深くて 関連するドキュメント なりについて会社のチームで雑談していたところ、 GitHubの「DependaBot」が何を狙い、どういう「大きな課題」を解決するのか? という話において、点と点が結びついた感じがあるので言語化してみます。

「この大きな課題」を説明する前に Dependency Hell について国内で言及してる記事がそれほどないので その辺りをまずは書いていきます。

ここのあたりが国内の開発者の中でも認識が広まっていくと、より一歩先のステージにいくのかなと思うので、 比較的ラフな感じで書いていきます。 ​ ちなみに、このブログ記事は所属組織とかに関係なく個人で執筆しています。 なので1デベロッパーとして、Dependabotが何を成し遂げるのか(ようとしているのか)を個人的感想でまとめてみます。 (僕の所属する組織は近しい領域なのでバイアスが掛かり気味かもです。ご注意ください。) ​ ちなみにこんなこと言いながら僕自身は DependaBot つかってないです。 (機会がないだけ) ​

Dependabotでできること

​ ここがメインの記事ではないので、サクッと書いてしまいます。 Dependabot は、マニフェストファイルはトップページに書いてあるとおり

Dependabotは依存関係を安全かつ最新に保つためにプルリクエストを作成します。

というツールです。

Dependabot は対象のGitプロジェクトの中から、 マニフェストファイル( pom.xml, Gemfile などの、依存するライブラリなどの管理ファイル)をベースに、「古いライブラリ」「脆弱性を持ってるライブラリ」などを検知し、自動でプルリクを作ってくれるやつです。 ​ 「え、めっちゃいいじゃん、即導入!」と行けばいいんですが、こいつを適切にハンドリングするのは なんだかんだ難しかったりします。 ​

バージョンアップの地獄

​ さて、Dependabot を入れていざ脆弱性が出た・古いライブラリだ という状況が起きて最新版のPullReqが来たとしましょう。

ここで早速(割と目にする)問題が発生します。 それは 「え?私の(ライブラリのバージョン)低すぎ・・・」 問題です。 (実際はそれだけじゃないのですが、それはこの節の後半に・・・)

この時点で分かってもらえると思いますが、プログラマーは怠惰なので、全てのライブラリが最新になっているプロジェクト なんてものは思っているよりも多くはないはずです。

そんな中、いきなりメジャーバージョンがめっちゃ上がる(場合によっては特大の)ライブラリアップデートなんて、安易にできるわけがありません。

「えいや」でアップデートをしてしまった場合、そもそも今まで使っていたメソッドが廃止されていたり、 特定のメソッドの挙動が変わっていたり、内部実装の変更によってパフォーマンス上の問題が発生するなんてことがあるかもしれません。

こうなってくると、安全にテストを通すしかありません。 UTが落ちてないか確認し、ITなどで挙動の変更などが確認されていないことを確認し、 E2Eであったりロールバックプランを考えたり、カナリアリリースしたりとまあ大変です。

おっと、まだここで問題がありました。そもそもUTのカバレッジが xx%しかないそうです。 では何を持って「このアップデートは安全だ」と言えばいいんでしょう?

でも Dependabot は無邪気にアップデートを進めてくる・・・。

カナリアの環境もない・・・。 ​

Dependency Hell(依存地獄)

​ 問題がこれだけであればまだ対処の方法はありそうです(はちゃめちゃ頑張ってテストするとか・・・?)。 ただもっと大きく難しい問題が隠されています。 (そしてDependabot は今から記載するこの大きくて複雑な問題を(おそらく)解決しようと考えているように見えます)

それが Dependency Hell と呼ばれる問題です。 Dependency Hellは、ライブラリなどがさらに他のライブラリに依存すると言った形式の依存性地獄という物を指す言葉です。(間違ってないよね?)

そしてこの問題は言葉の通り「地獄」であり、色々な辛さがあります。が、詳細については Google の出しているライブラリ管理に関するベストプラクティスでかなりの量を触れているので是非読んでみてください。


Dependency Hell について理解するために、一つ例を出します。

例えばプロダクトで OAuth( HogeOAuth ) のライブラリを使うことになったとしましょう。 となればまずは マニフェストファイル(java の pom.xml とか npm の package.json とかです)に依存を追加しましょう。

(以下は依存性ツリーと思ってください)

HogeOAuth v1

よし、これでプロジェクトにHogeOAuthが追加されました。 と思いましたが、よくみてみると、依存性は以下のようになっています。 ​

HogeOAuth v1 -- HogeHttpClient v2
             |-- ...
             |-- ...

どうやら HogeOAuth v1 では、 HogeHttpClient v2 なるやつを内部で使っているようです。

おっと、さらに機能追加が必要で、今度は FooConnecter なるやつを入れる必要が出てきました。入れちゃいましょう。

HogeOAuth v1 -- HogeHttpClient v1
             |-- ...
             |-- ...

FooConnector v1 -- HogeHttpClient v1
                |-- ...
                |-- ...

よし、 FooConnector v1 を入れました。 むむ、 FooConnector もどうやら HogeHttpClient を使っていたようです。

と思いましたが何か変です。 なぜか先ほどまで v2 だったはずの HogeHttpClient のバージョンが v2 から v1 になっています。

これは親となっているライブラリ FooConnector が、マニフェストファイル内で 明示的に「HogeHttpClient の v1バージョンを使うこと!」と指定しているためです。

一方、HogeOAuthは v1 - v2 のどれかを使うことと指定しているため、 両者を揃えるため、パッケージ管理ツールが規則に沿って「内部ライブラリのバージョンを揃えた」ということを行ったためです。

この辺りは(僕は割と素人なので詳しくないですが)、各言語・各パッケージ管理ツールや設定方法によって 「無理やり上に揃える」とかできるらしいです(が、もちろんコンパイルで落ちるとか例外発生したりとか起きる可能性があります)。

そしてここでようやく Dependabot の話に入ってくるわけです。

仮に Dependabot が 「 HogeOAuth脆弱性があったから上げてくれ!」と言っており、 「 HogeOAuth のバージョンを v1 -> v2 」となった時、以下のようになったらどうしましょうか?

HogeOAuth v1 は   HogeHttpClient v1 - v2 を求めている
HogeOAuth v2 は   HogeHttpClient v3      を求めている(今回アップデートしたい ver)

FooConnector v1は HogeHttpClient v1 - v2 を求めている

この場合、片方のライブラリは、両方で使っているライブラリバージョンを引っ張り合うような状況に陥ります。 HogeOAuth v2 にあげようにも内部依存している HogeHttpClient のバージョンが下側で ( FooConnector によって)束縛されているため、あげようにも上げられず、(設定によっては)コンパイルが通らなくなってしまいます。

これが Dependency Hellです。

こうなってくるとアップデートはとても大変です。 どのように対応すればいいんでしょうか?

(先に言っておきますが最高に簡単でいい感じの方法はないです。詳しくは冒頭に貼った Googleのライブラリ管理ベストプラクティス 読んでください)

これで問題は以上となればいいのですが、さらに大きな爆弾(であり、Dependabot が解決を狙っていそうな問題)があります。

それは以下のようなケースの問題です

「内部の依存ライブラリ ( HogeHttpClient v1 - v2 )に脆弱性があった場合」 どう対応すればいいでしょう? ​​ 仮に親ライブラリである HogeOAuth が、最新版であっても依存ライブラリ HogeHttpClient v2 を使い続けると言った場合、 どうやっても(元のライブラリにPullReqを送るなりしないと)脆弱性は治りません。

これは困り果てました・・・。 こうなると直す方法は自分たちで親元のライブラリのバージョンアップ PullReq を送るくらいしかないでしょう。

アップデートの判断、ハンドリング

ここまでどうしようもないような問題が数多くありました。 そして残念ながらこれらの問題を解決する「めっちゃ簡単で、銀の弾丸で、みんなハッピーになる手段」というのは(僕の知る限り)存在しません。

そうなった場合に取れる対応は限りがあります。

例えば根本解決で問題のライブラリの最新版を使うために、本体にPullReqを送るとか、 別のライブラリを使うようにするとか、 アップデートしない理由を探すとかです。

これがセキュリティのリスクがあるためのバージョンアップであれば、別の方法もあるかもしれません。

例えば 「有償サポートがある!」 「別のライブラリを使うようにしよう」 「脆弱性はうちには影響しない(という判断をする」 「独自のパッチを作ろう」 「WAFでリスク軽減だ」 ・・・

ライブラリ管理にはこのような地獄が積み重なり、開発者の精神をどんどん追い詰めています。

Dependabot が解決する問題

そしてようやく話が本題に来ます。

じゃあ Dependabot って何を結局解決してくれるのか? という話ですが、それは先ほど書いたこの問題です。

仮に親ライブラリである `HogeOAuth` が、最新版であっても依存ライブラリ `HogeHttpClient v2` を使い続けると言った場合、
どうやっても(元のライブラリにPullReqを送るなりしないと)脆弱性は治りません。

そう、これです。この問題を Dependabot は解決しようと目論んでいるように見えます。 順を追ってみていきましょう。

まず、最初に、挙げたアップデートし辛い問題ですが、仮に以下のような環境が整っていた場合、 割とアップデートを気楽にできるはずです。

  • めっちゃテストがされてる
  • 使っているライブラリは常に最新になっている(ため、1回1回のアップデートは軽微)

このような理想的な環境は一般的なプロダクトでは(そう多くはない)と思います。 ただ、OSSの「著名なライブラリ・フレームワーク」などにおいては、なんだかんだ言ってカバレッジが 90% くらいになってるのが多くあります。

すると Dependabot が毎回最新のPullReqを送ってくれて、テストもCIでPassしており、 (アップデートの粒度が)小さいので、メンテナーが容易に Mergeボタンをクリックできるでしょう。

隠れた問題として、OSSライブラリなどにおいて、 利用しているライブラリのVerを上げるタイミングってどういう時があるんだっけ?と言った、 「誰が」「いつやるのか」と言った問題もあります。

が、この問題も Dependabot がやってくれれば解決です。

するとどうでしょう。

ほとんどのライブラリが常に最新のverを保ち、 Dependency Hell で、依存ライブラリの ver を下に引っ張り続けるような物もいなくなり、 「子依存のライブラリが脆弱性を持ってるけど、他のライブラリがバージョンを固定していて(引っ張っていて)上げられない。脆弱性を持ったままになる」と言った問題が少しは解決するはずです。 ​

攻撃が盛んな脆弱性OSSライブラリ

また、上記の Dependabot がどうにかしてくれそうなソリューションというのは、 「理にかなった(ように見える)」対応だと自分は思っています。 その理由としては「特に対処が必要な脆弱性」というのは、有名なライブラリーほど発生する という傾向に起因します。

当たり前ですが利用している人口・スター数などが多いほど、攻撃者も狙ってきます。 また、CVEが欲しいセキュリティエンジニアもいっぱい群がってきて、 こぞって脆弱性を探し、CVEを報告しようとしていきます。(いいことですよ)

有名ライブラリほど脆弱性の報告数が増え、 そのライブラリを使っている(他の有名な)ライブラリは、マニフェストファイルで バージョンアップの作業をしなければならないということになります。

攻撃者からしたら、有名なライブラリの脆弱性なので、刺さりやすいと思うでしょう。 そうなってくると比較的アップデートの対応を早めにやらなければならなくなります。 なんせ「今すぐにでも攻撃がくるかもしれないから」です。

しかし、OSSライブラリにも Dependency Hell の問題がつきまとってきます。 「有名ライブラリが使っているライブラリ、が使っているライブラリ、が使っているライブラリ」がバージョンを引っ張っていたりといった問題が発生したら・・・。

ただ、Dependabot があれば、ここのライブラリが依存しているライブラリのアップデートというのも解決していくはずです。

しかも、有名ライブラリほどテストがしっかりしている傾向にあるため、容易にプルリクをMergeできるでしょう。 これはとても凄いことです。

残った課題

これまでのような問題が解決していくと、OSSのライブラリ管理・脆弱性管理は一つ先のステップにいくはずです。

ただ、まだ以下のような課題が残っていると思っています。

  • そもそもテストがないような(弱いような)ライブラリはDependabotで容易にアップデートできるのか?
  • (一般的なプロダクトは)危険な脆弱性があった場合、どう対処するのか?
  • そもそもこの辺りの話を理解してないと、Dependabot 入れてくれなかったり、安易に(テストもなしにアップデートを)Mergeとかしちゃわない・・・?

そもそもこれらの問題はあまり開発者に認識されていないため、まずは啓蒙からなのかもしれません。 ​

まとめ

まとめに入ります。 本記事で言いたいこととしては大きく以下になります。

  • Dependabot は、(よくメンテナンスされ、攻撃に晒される可能性が特に高そうな)ライブラリにおいて、脆弱性を直してくれる世界を作ってくれる(かも)
  • 依然、プロダクトなどに良いてはアップデートの地獄があり、リスク管理とかが必要になったりする
  • いい感じの銀の弾丸はないので、開発は泥水啜るしかない。(またはそうならないように 頑張る しかない)

最後に、この記事は全部自分の時間で書いたので、所属組織関係ありません! (会社として出す場合、レビューいるとか面倒なので一応念のため)

これらの問題が世間一般にもっと認識され、もっともっと議論されることを祈ってます(合掌の絵文字)