Carpe Diem

備忘録

Go Module MirrorとChecksum Database

概要

Go 1.13から、goコマンドはデフォルトでGo Module MirrorとChecksum Databaseを使用してモジュールをダウンロード&認証するようになりました。

環境

  • go 1.14.1

Module Mirror (Module Proxy)

Module Mirrorは公開モジュールをキャッシュし、go modでのダウンロードを高速化するためのProxyです。
ProxyのURLはGOPROXYにセットされています。

$ go env GOPROXY
https://proxy.golang.org,direct

メリット

大きく以下のメリットがあります。

  • モジュールを高速にダウンロードできる
  • オリジンのVCSGithubなど)が落ちていてもダウンロードできる
  • URLが変更、リポジトリが削除されてもダウンロードできる

ダウンロードの高速化

1つ目は実際に検証すると分かりますが、proxyを経由しない場合はVCS特有(プロトコル、履歴管理など)のオーバーヘッドの影響を受けるので遅くなります。

proxy使わないダウンロード

git@github.com:hashicorp/vault.gitリポジトリで検証してみます。
まずはproxyを使わない方。

$ time GO111MODULE=on GOPROXY=direct go mod download

real    2m37.808s
user    1m55.232s
sys     0m37.981s
proxy使ったダウンロード

go clean -modcacheでmoduleを消してから今度はproxy経由(デフォルト)でダウンロードしてみます。

$ time GO111MODULE=on go mod download

real    0m51.068s
user    0m27.813s
sys     0m22.059s

3倍以上速くなってますね。
低速な環境だと7倍速くなったということもあるようです。

vendorディレクトリを消せるようになった

個人的には3つ目が嬉しくて、これまでだと依存ライブラリが気づいたら消えていて困ることが何度かありました。
そのためバージョン管理にvendorディレクトリを含ませるというプラクティスを取っていました。

しかし自分たちのコード以外に大量のファイルが追加されるため、以下のような問題が頭を悩ませていました。

  • PRが見にくい
    • レビューのため開くのも重い
    • コミットを分けたりしたがそうするとViewedなど一部機能が使えない
  • 検索でノイズが大量に出る
  • git cloneが遅い
    • CIではdepthを変更しておかないとどんどん遅くなる

また

といった問題もありました。

Module Mirrorの登場でこういったデメリットのあるvendorディレクトリ削除できるようになりました。

注意点

Module Mirrorを使う上で以下の点に注意して下さい。

これはPrivateリポジトリのデータが漏れないという意味で嬉しいですが、開発する際はそのままだとエラーになってしまいます。

対応策としては以下の2つがあります。

  • GOPRIVATEでPrivateリポジトリをModule Mirrorの対象外とする
  • 自前のProxyを用意する

GOPRIVATEにPrivateリポを指定

GOPRIVATEという環境変数を利用します。
GOPRIVATEglobパターンで記述します。また複数ある場合はカンマで区切ります。

例えば以下のように設定した場合

GOPRIVATE=*.corp.example.com,rsc.io/private
  • git.corp.example.com/xyzzy
  • rsc.io/private
  • rsc.io/private/quux

といったリポジトリが対象になります。

path prefixマッチングが効くので、後ろにワイルドカード*をつける必要はありません。

自前のProxyを用意する

GOPROXYは自分でProxyを設定できるので、Privateリポジトリ対応したProxyを自前で用意する、という対応です。

以下の記事が参考になります。

Go Modulesに対応しつつ、Dockerコンテナの中でプライベートリポジトリを go get する - Qiita

.gitconfigをいじらないといけなくてつらい、、といったケースで有用だと思います。

Checksum Database

go.modはバージョンのロックに使われますが、そこで固定したバージョンはあとからダウンロードした時に同一とは限りません。

  1. タグv1.0.1付けてをリリースする
  2. バージョンv1.0.1を指定したロックファイルを作る
  3. 悪意ある人物がタグv1.0.1削除、不正なコードを埋め込み、新たにタグv1.0.1を作成
  4. ロックファイルからv1.0.1をダウンロードすると悪意あるコードになっている

といった感じで、タグ自体は一貫性を保証できません。

そこでgo.sumというChecksumを用いた検証をしていますが、初めてgo.sumを作成した時は各モジュールのChecksumを持っていないので検証できないという課題がありました。

そこでGoチームは sum.golang.org というグローバルな追記型のChecksumデータベースを用意して

$ go env GOSUMDB
sum.golang.org

go getした際にこのデータベースのChecksumと比較し、検証するという対応を行いました。

メリット

このChecksumデータベースによるメリットは以下です。

  • モジュールの一貫性を保証できる
    • 信頼性のないProxyを経由しても問題ない
  • 追記型なので改竄されない

注意点

Checksumデータベースを使う上で以下の点に注意して下さい。

  • バージョンの付け替えをするとエラーになる
  • Privateリポジトリは保存されない

バージョンの付け替えをするとエラー

先に述べたようにChecksumデータベースは追記型であり、既存データの変更はできません。
つまり一度 sum.golang.org に登録されると取り消すことができないので、手動でタグを変更してしまったりするとChecksumの値が異なるようになり、以下のエラーが発生するようになります。

SECURITY ERROR This download does NOT match the one reported by the checksum server. The bits may have been replaced on the origin server, or an attacker may have intercepted the download attempt.

Privateリポジトリ

PrivateリポジトリはChecksumデータベースに記録されません。
そのため、普通にgo getしようとするとChecksumデータベースの検証が走り404 NotFound410 Goneがレスポンスとして返ります。

Module Mirrorと同様にGOPRIVATEを指定することでChecksumデータベースによる検証の対象外とすることができます。

まとめ

Module MirrorによるProxyのおかげで

  • ダウンロードの高速化
  • オリジンが消えてもビルド可能

となり、Checksumデータベースにより

  • コードの真正性を保証

できるようになりました。

Privateリポジトリはこの仕組みから外れるため、GOPRIVATEで対象外にするようにしましょう。

参考