技術的負債:私のRustライブラリ、今やCDOへ転換
(lucumr.pocoo.org)- Rustエコシステムでは、メンテナンスが止まった依存関係がRUSTSECに載った瞬間、直接問題がなかったライブラリもユーザーとCIを通じて技術的負債になる
instaが依存していたyaml-rustは、元の作者の関心が薄れた後、機能リクエストとバグが積み上がった状態だった- RUSTSEC登録後、直接・間接のユーザーのCIが失敗し、金融の比喩で言えば格下げとマージンコールが発生した格好になった
- 代替ライブラリやフォークも明確な解決策ではなく、依存関係を変えてもメンテナンス負担と新たな依存関係のリスクは残る
- 最終的な対応は
yaml-rustのコードをinsta内にベンダリングすることで、不良な技術的負債をAAAに包み直したCDOに近いという批判につながった
yaml-rust依存関係が技術的負債として露呈した経緯
instaはyaml-rustに依存しており、yaml-rustは元の作者が関心を失った後、Issueが積み上がり続けていた- 一部は機能リクエストで、一部は実際のバグだった
instaのメンテナーはその問題を直接経験していなかったが、メンテナンスが止まった依存関係という点で技術的負債だった
yaml-rustがRUSTSECデータベースに追加される議論に上がったことで状況が変わった- RUSTSECは金融の比喩では格付け機関の役割を果たす
- 登録後、
yaml-rustを直接または間接的に使う多くのプロジェクトのCIが数分以内に失敗し始めた - ユーザーが
instaのメンテナーにyaml-rust利用の問題を指摘し、金融の比喩ではマージンコールが発生した形になった
選択肢と実際の対応
- 代替へ乗り換える方法は魅力的ではなかった
- ある代替は
yaml-rustのフォークで、メンテナーは1人だけであり、依存関係を3つ追加する - そのうち1つはすでに「B-」格付けを受けていた
- エコシステムの別の選択肢は、指摘される前にデフォルト値を変える決定をしていた
- ある代替は
- 自分でフォークする方法も根本的な解決策ではなかった
- フォークしたライブラリには同じメンテナンス要求が伴う
- バグレポートに対応しなければ、最終的には既存の
yaml-rustのように指摘されることになる - したがってフォークは時間稼ぎにはなるが、問題をなくすわけではない
- 実際の対応は、
yaml-rustのコードをinsta内へ取り込むベンダリングだったinstaは今や、instaのコードとyaml-rustが統合された形になった- これは不良な技術的負債を
AAAへ格上げしたような構造である - タイトルのCDOは、2007年の金融危機で悪名高くなった債務担保証券を指す
- 最終的な評価は「誰も勝てなかった」に近い
- 問題のコードは消えておらず、場所が
insta内部へ移っただけだった - 外部依存関係として見える場合の圧力は減ったが、メンテナンス負担そのものは残っている
- 問題のコードは消えておらず、場所が
1件のコメント
Hacker News のコメント
率直に言うと、最も人気のある YAML パーサーである serde_yaml(https://lib.rs/crates/serde_yaml) の作者が、予告も後継メンテナーの指名もなく突然手を引き、deprecated および unmaintained と表示した状況です。
left-pad とまったく同じではありません。パッケージは今も動作し、crates.io は削除も許可していませんが、このパッケージは他の 4,000 個のクレートで使われています。
監査ツールや自動更新ツールが、メンテナンスされていないクレートの使用を問題視するようになるでしょう。
同時に、人々が大挙してそちらへ移るのを防ぐために serde_yaml も unmaintained と表示されたのです。
監査ツールや会社のポリシーには、最近のコミットがないという理由だけで「メンテナンスされていない」と警告し続けるのではなく、こうしたケースを見分けられるくらい賢くあってほしいです。
もっと詳しい情報があるのか気になります。
説明なしに出てきた CDO という略語がすぐには理解できませんでしたが、記事で collateralized という表現が何度も出てくることから、債務担保証券(collateralized debt obligation) のことだと思われます。
https://en.wikipedia.org/wiki/Collateralized_debt_obligation
最初は chief data officer を思い浮かべました。
具体的には、2008年には複数の住宅ローンに対する部分的な所有権のようなもので、不良住宅ローンが債務不履行に陥ると CDO も一緒に壊れました [1]。
いずれにせよ、筆者が考えているのは債務ベースの比喩というより、leftpad や連鎖的な DNS 障害のようなシステムリスクに近いものです。2008年があれほど大きな混乱になった大きな理由も、債務を商品化したという事実そのものより、システム的な問題のほうが大きかったためです [2]。
規制役を担うべき格付け機関がすでに攻略されている、という意味でもあります。
これが「勝利」かどうかは「勝利」の定義にあまりに左右されるので議論したくはありませんが、利点があるのは確かです。実行されることもなく、外部ライブラリから到達されることもない脆弱なコードパスは、今や安全なコードパスになります。
もちろん気になる状態ではありますが、安全ではあります。
この種のベンダリングには別の利点もあります。自分のライブラリに堅実なテストカバレッジがあるなら、新しく取り込んだライブラリに対してコードカバレッジツールを走らせられます。
ライブラリの修正は難しいかもしれませんが、自分のコードが触れない部分を比較的簡単に削っていくこともできます。構造次第ですが、脆弱なコードがすべて削除されるなら明確な利益ですし、実際には一部を使っていたと分かることも、よりはっきりした利益になり得ます。
つまり、公開メンテナンスのためにライブラリをフォークするのは大きな責任ですが、必要な部分だけをベンダリングして手を入れるのは、はるかに小さな負担です。実際に枝刈りをしなくても、それを容易にするという事実自体が前進です。
欠点も確かにありますが、すべてが悪いわけではありません。
しかし、その外部依存関係をもう誰も見守っていないなら、その論理は消え、その依存関係は負担になります。
依存関係が abandoned と表示され、セキュリティレポートに引っかかり始めたのは、望ましい動作です。おかげで、ベンダリングするのか、つまり「悪意ある者が何かをこっそり入れる」リスクを取り除くのか、それとも別の選択をするのかを、情報に基づいて判断できます。
Rust のビルドシステムがこれを簡単に許している点も良いです。
通常、巨大なモノレポ全体でその依存関係のバージョンも 1 つしか許可されません。私がいた後に変わっているかもしれません。
そして各 third_party 依存関係ごとに、指定された責任者や OWNERS がいます。
これによって、比較的秩序が保たれています。
ただし、こうした強制された規律は、時間と資金が十分にあり、「素早く動いて壊せ」という哲学に縛られていない Google のような組織には合うかもしれません。スタートアップでもどれほど合うかは分かりません。
ある意味では、他のライブラリ群がランダムに近い呼び出しを提供する、方向性のあるファズテストなので、興味深いテスト戦略になり得ます。
JS の npm エコシステムでも同じパターンを見たことがある
npm audit はセキュリティ問題についてはたいてい「オオカミ少年」のように振る舞うし、ライセンスが許すならコードを内部に取り込むことは、ユーザーからの偽のイシューに圧倒されないための最も安定した方法の一つだ
ユーザーが文脈を理解していなかったり、雇用主のポリシーが現実離れした場所で作られていたりするため、気にしない場合が多い
ビルドパイプラインの推移的依存コードベースの一部で使われている正規表現が、実際にサービス拒否攻撃に悪用できるわけではない
深い推移的依存の「イシュー」は、特に回避が面倒なことがある。構造上、「そのコードパスには絶対に入らないので欠陥の影響を受けない」や「そのパスに入る唯一のケースは、オフライン環境の信頼された入力だ」といった事実を技術的に立証するのが難しい場合が多い
こうした問題が重要になるシナリオが存在するのは確かだ。たとえば、侵害されたビルドツールがビルド中のライブラリに悪意あるコードを注入する場合がある
しかしそうしたケースは極めてまれで、ビルドで呼び出すだけなので実際には重要でない、サービス拒否の可能性がある正規表現の波に埋もれている
そこに、一般的なビルドツールが推移的依存を500億個ほど持つツリーを抱えている点まで加わると、本当に骨が折れる
こうしたイシューを報告するツールは、「再配布すれば悪用可能」と「ビルドパイプラインで使えば悪用可能」を区別すべきだと思う
「不良な技術的負債が突然 AAA 格付けになった」というところの「突然」という表現は、同じコードが vendoring されたという理由で以前より良い負債格付けを受けるのはおかしい、という意味に見える
しかしそれはコード自体の価値だけを見ていて、全体の価値提案で最も重要な部分を見落としている
メンテナがコードを内部に取り込むと、そのコードはそのメンテナの所有物になる。死んだプロジェクトのコードを活動中のメンテナが vendoring するなら、イシューに対応し、プルリクエストをレビューし、バグを直せる 活動中の人 が生まれるので、そのコードの価値は上がる
別のたとえで言えば、放置されたペットを新しい飼い主に渡すと、よりよく世話され、健康になり、長く生きられるため価値が上がるのと同じだ
メンテナも大きく見慣れないコードベースに慣れる必要があり、修正の実装やレビューには 参入障壁 が生じるだろう
少し脇道で議論を呼ぶ考えだが、ソースベースのパッケージマネージャが、公開済みパッケージのメンテナンスをレジストリが強制的に引き継ぐ 法的権利 を保証しないなら、ひどい問題を避けるのは難しいと思う
放置、悪意ある変更、悪意ある削除、なりすましといった問題だ
あるパッケージがより大きなコミュニティにとって十分重要だと判断されるなら、元の所有者の手からパッケージレジストリ項目を取り上げ、フォークを指すようにする手段が必要だ
当然、こうした措置には多くの騒動が伴うだろうが、下流ユーザーを積極的に保護できる
実際の問題は、オープンソースプロジェクトをメンテナンスするには時間と労力が必要で、余暇のある別の人を見つけるのが簡単ではない点だ
すべての貢献が自動的に特定集団の所有物になる著作権モデルよりも魅力が薄い
GitHub が自由ソフトウェアをホストしながら、そのソフトウェアの著作権侵害に特化した場所だと主張する余地はあるかもしれないが、少なくとも今のところ、パッケージの名目上の所有権を奪おうとはしていない
コミュニティの話である以上、顧客と供給者の関係ではなく、ほとんどのパッケージは無料で提供される程度にしか重要ではない
「月10ドル払うから、われわれのために10万個のパッケージを管理してくれ」のような屈辱的な金額になる可能性もある
インターネットには管理されていないソースコードが多く、単にある時点でそのまま公開されただけだ。無料のコードについて、誰も更新を保証されてはいない
すでに誰かが yaml-rust をフォークして yaml-rust2(https://github.com/Ethiraric/yaml-rust2/blob/master/document...) を作っていたのは、かなり幸運だった
そのフォークが YAML テストスイートを完全に通過し、ベンチマークでもより高速なのも素晴らしい。移行も簡単そうだ
結局、問題は残っている。私たちは今のところ無償労働を進んで提供してくれる他人の作業に依存しているが、それが永遠に続くとは限らない
彼らの時間と労力に報い、良い仕事を続けてくれるよう願う以外に、回避策があるのかは分からない
“A pure rust YAML implementation.”
むしろ serde_yaml は libyaml を c2rust で変換した unsafe-libyaml に依存していたため、純粋な Rust とは見なしにくかった
十分に長い時間軸では、オープンソースのメンテナがプロジェクトを辞める確率は 1 だ
これを避ける唯一の方法は、1人メンテナのプロジェクトをタブー視することだ
Rust 標準ライブラリを作った人たちは標準ライブラリを貧弱に保ちたがっているので、Rust プロジェクトではおそらく選択肢にならないかもしれない
しかし機能が十分な標準ライブラリを持つ他の言語を使うときには、間違いなく可能な選択肢だ
必要なものが組み込まれている言語を意図的に使うため、データベースドライバ以外の外部ライブラリはほとんど使わない
この状況全体が少し滑稽に見える。コードが動作していて、何年もそうだったのなら、管理されていないことがなぜ問題なのか分からない。
修正する必要がなく、限界と機能を分かっているなら問題ない。
コードは勝手に腐ったりしない。何十年も前のコードを借りたり統合したりして、うまく使ったことは何度もある。
個人的には、そのライブラリへの不満は全部そのまま無視して先に進むと思う。
すでに多くのバグが積み上がっていて、修正が存在しているのに決してパッチされない。https://github.com/chyh1990/yaml-rust/issues と /pulls を見ればよい。
自分が使うコード片のメンテナンスを引き受けるのと同じ。
場合によっては悪いこともある。コピー&ペーストコーディングや重複作業になり得るから。
だが、サードパーティ製コンポーネントが完全に管理されていない状態なら、かなり理解できる。
その通り、依存関係はベンダリングすればいい。「ほぼ完成」に近く、開発とメンテナンスが遅くなった依存関係については、この20年間たいていそうしてきた。
ただし、「batteries are not included」な言語で働いたことはない。
cargo vendor --aggressiveのようなものを作って、自分のクレートを基準に依存関係内のデッドコードをすべて刈り込む方法はあるだろうか?「依存関係をレビューせよ」という問題を、もっと扱いやすくできるのか気になっている。
記事の本題からは外れるが、結局のところ依存関係の選択と、それに伴うすべての責任は自分たちにあるという点では関係している。
実際にクレートにコンパイルされて入るものについて、より責任を持てるよう支援するツールが入る余地はありそうだ。
そうでないなら、何が改善されるのか分からない。