The Go Blog

Go はサプライチェーン攻撃をどのように軽減するか

Filippo Valsorda
2022年3月31日

現代のソフトウェアエンジニアリングはコラボレーションに基づいており、オープンソースソフトウェアの再利用に基づいています。これは、ソフトウェアプロジェクトがその依存関係を侵害することによって攻撃されるサプライチェーン攻撃のターゲットを露出させます。

プロセスや技術的な対策に関わらず、すべての依存関係は必然的に信頼関係です。しかし、Go のツールと設計は、様々な段階でリスクを軽減するのに役立ちます。

すべてのビルドは「ロック」されています

外部の世界での変更(例えば、新しいバージョンの依存関係が公開されるなど)が自動的に Go ビルドに影響を与える方法はありません。

他のほとんどのパッケージマネージャーファイルとは異なり、Go モジュールには制約の個別のリストと特定のバージョンを固定するロックファイルはありません。Go ビルドに貢献するすべての依存関係のバージョンは、メインモジュールの go.mod ファイルによって完全に決定されます。

Go 1.16 以降、この決定論はデフォルトで強制され、ビルドコマンド (go build, go test, go install, go run, ...) は go.mod が不完全な場合に失敗しますgo.mod (したがってビルド) を変更する唯一のコマンドは go getgo mod tidy です。これらのコマンドは自動的に、または CI で実行されることは想定されていないため、依存関係ツリーへの変更は意図的に行われ、コードレビューを受ける機会がなければなりません。

これはセキュリティにとって非常に重要です。なぜなら、CI システムや新しいマシンが go build を実行するとき、チェックインされたソースは、何がビルドされるかについての究極的で完全な真実の源だからです。第三者がそれに影響を与える方法はありません。

さらに、go get で依存関係が追加されるとき、その推移的な依存関係は、Minimal version selection (最小バージョン選択) のおかげで、依存関係の go.mod ファイルに指定されたバージョンで追加され、最新バージョンでは追加されません。go install example.com/cmd/devtoolx@latest の呼び出しでも同じことが起こりますが、一部のエコシステムではこれに相当するものがピン留めをバイパスします。Go では、example.com/cmd/devtoolx の最新バージョンがフェッチされますが、その後のすべての依存関係はその go.mod ファイルによって設定されます。

モジュールが侵害され、新しい悪意のあるバージョンが公開された場合でも、明示的にその依存関係を更新するまで誰も影響を受けません。これにより、変更をレビューする機会と、エコシステムがイベントを検出する時間が提供されます。

バージョン内容は決して変更されません

第三者がビルドに影響を与えられないようにするためのもう一つの重要な特性は、モジュールバージョンの内容が不変であることです。攻撃者が依存関係を侵害して既存のバージョンを再アップロードできた場合、それに依存するすべてのプロジェクトを自動的に侵害する可能性があります。

それは go.sum ファイルの役割です。このファイルには、ビルドに貢献する各依存関係の暗号化ハッシュのリストが含まれています。繰り返しになりますが、不完全な go.sum はエラーを引き起こし、go getgo mod tidy のみがそれを変更するため、その変更は意図的な依存関係の変更に伴います。他のビルドは完全なチェックサムのセットを持つことが保証されます。

これはほとんどのロックファイルに共通する機能です。Go はそれを超えて、チェックサムデータベース (略して sumdb) を備えています。これは、go.sum エントリのグローバルで追加専用の暗号検証可能なリストです。go getgo.sum ファイルにエントリを追加する必要がある場合、sumdb からそれを取得し、sumdb の整合性の暗号証明も取得します。これにより、特定のモジュールのすべてのビルドが同じ依存関係の内容を使用するだけでなく、すべてのモジュールが同じ依存関係の内容を使用することが保証されます!

sumdb は、侵害された依存関係や、Google が運営する Go インフラストラクチャでさえも、変更された (例: バックドアが仕込まれた) ソースで特定の依存関係をターゲットにすることを不可能にします。あなたは、例えば example.com/modulex の v1.9.2 を使用している他のすべての人が使用し、レビューしたのとまったく同じコードを使用していることが保証されます。

最後に、sumdb の私のお気に入りの機能は、モジュール作成者がキー管理を行う必要がなく、Go モジュールの分散型とシームレスに連携することです。

VCS が真実の源

ほとんどのプロジェクトはバージョン管理システム (VCS) を通じて開発され、その後、他のエコシステムではパッケージリポジトリにアップロードされます。これは、VCS ホストとパッケージリポジトリという、侵害される可能性のある2つのアカウントがあることを意味します。後者はめったに使用されず、見落とされやすいです。また、特にアップロードの一環としてソースが日常的に変更される場合(例えば、最小化するためなど)、リポジトリにアップロードされたバージョンに悪意のあるコードを隠すのが容易になります。

Go では、パッケージリポジトリのアカウントという概念はありません。パッケージのインポートパスには、go mod downloadVCS から直接モジュールを取得するために必要な情報が埋め込まれており、タグがバージョンを定義します。

Go には Go モジュールミラーがありますが、それは単なるプロキシです。モジュールの作成者はアカウントを登録したり、バージョンをプロキシにアップロードしたりしません。プロキシは、go ツールが使用するのと同じロジック (実際には、プロキシは go mod download を実行します) を使用してバージョンを取得し、キャッシュします。チェックサムデータベースが特定のモジュールバージョンに対して1つのソースツリーしか存在できないことを保証しているため、プロキシを使用する誰もが、プロキシをバイパスして VCS から直接取得するすべての人と同じ結果を見ることになります。(VCS でバージョンが利用できなくなったり、内容が変更された場合、直接取得するとエラーになりますが、プロキシから取得すると引き続き機能する可能性があり、可用性を向上させ、エコシステムを 「left-pad」問題から保護します。)

クライアント上で VCS ツールを実行することは、かなり大きな攻撃対象領域を露出させます。それも Go モジュールミラーが役立つもう一つの場所です。プロキシ上の go ツールは堅牢なサンドボックス内で実行され、すべての VCS ツールをサポートするように設定されていますが、デフォルトでは主要な2つの VCS システムのみをサポートしています(git と Mercurial)。プロキシを使用する誰もが、デフォルトでオフになっている VCS システムを使用して公開されたコードを取得できますが、攻撃者はほとんどのインストールでそのコードに到達できません。

コードをビルドしても実行されない

Go ツールチェインのセキュリティ設計目標として、コードの取得もビルドも、たとえそれが信頼されていない悪意のあるコードであっても、そのコードを実行させないことが明確にされています。これは他のほとんどのエコシステムとは異なります。多くのエコシステムでは、パッケージ取得時にコードを実行するためのファーストクラスのサポートがあります。これらの「インストール後フック」は、過去に、侵害された依存関係を侵害された開発者マシンに変えたり、モジュール作成者を介して ワームを広げたりするための最も便利な方法として使用されてきました。

公平に言えば、コードを取得する場合、開発者マシンでのテストの一部として、または本番環境でのバイナリの一部として、その直後に実行することが多いため、インストール後フックがないことは攻撃者を遅らせるだけです。(ビルド内にはセキュリティ境界はありません。ビルドに貢献するどのパッケージも init 関数を定義できます。)しかし、モジュールの依存関係の一部のみを使用するバイナリを実行したりパッケージをテストしたりする可能性があるため、これは意味のあるリスク軽減策となり得ます。たとえば、macOS で example.com/cmd/devtoolx をビルドして実行する場合、Windows 専用の依存関係や example.com/cmd/othertool の依存関係があなたのマシンを危険にさらすことはありません。

Go では、特定のビルドにコードを貢献しないモジュールは、そのビルドに対してセキュリティ上の影響を与えません。

「少しのコピーは少しの依存関係よりも良い」

Go エコシステムにおけるソフトウェアサプライチェーンのリスク軽減策の最後にしておそらく最も重要なものは、最も技術的でないものです。Go には、大きな依存関係ツリーを拒否し、新しい依存関係を追加するよりも少しのコピーを好む文化があります。これは Go の格言の一つにまで遡ります。「少しのコピーは少しの依存関係よりも良い」。高品質の再利用可能な Go モジュールは「依存関係ゼロ」というラベルを誇り高く掲げています。ライブラリが必要な場合、他の作者や所有者による何十もの他のモジュールに依存することはないでしょう。

これは、豊富な標準ライブラリと追加モジュール (golang.org/x/... のもの) によっても可能になっています。これらは、HTTP スタック、TLS ライブラリ、JSON エンコーディングなど、一般的に使用される高レベルのビルディングブロックを提供します。

これらすべてを合わせると、ほんの数個の依存関係で豊富で複雑なアプリケーションを構築できることを意味します。ツールがどれほど優れていても、コードの再利用に伴うリスクを排除することはできません。したがって、最も強力な軽減策は常に小さな依存関係ツリーであると言えます。

次の記事: ワークスペースに慣れ親しむ
前の記事: ジェネリックス入門
ブログインデックス