Slide 1

Slide 1 text

Go Getでのchecksum不一致 に遭遇した話とその対応 Go Conference mini 2023 Winter, DEC 02 2023 Natsumi Kojima ANDPAD Inc. @replu5

Slide 2

Slide 2 text

小島 夏海 ● 株式会社アンドパッド ○ 最近は社内向けの通知プラットフォームの開発・運用 : replu : replu5 自己紹介

Slide 3

Slide 3 text

go getでのchecksum不一致

Slide 4

Slide 4 text

go get コマンドでモジュールを取得する時にchecksumの 不一致のエラーに遭遇したことある人いますか? 遭遇したことがない人は下記コマンドで確認できます GOPROXY=direct go get github.com/replu/[email protected]

Slide 5

Slide 5 text

遭遇したエラーについて checksum server の checksum と一致してないのは わかるがここからどうすればいいのかわからなかった

Slide 6

Slide 6 text

go get時にどういう動作をしているか モジュール配布元 checksum server go get xxx 1 : モジュールダウンロード 2 : checksum ダウンロード 3 : 計算したchecksum とダウンロードした checksumと比較 4 : go.modとgo.sum更新

Slide 7

Slide 7 text

go get時にどういう動作をしているか モジュール配布元 checksum server go get xxx 1 : モジュールダウンロード 2 : checksum ダウンロード 3 : 計算したchecksum とダウンロードした checksumと比較 4 : go.modとgo.sum更新

Slide 8

Slide 8 text

エラーの原因 エラーメッセージに考えられる理由が2つ書かれている ● オリジンサーバが上書きされている (The bits may have been replaced on the origin server) ● ダウンロードが攻撃者に傍受された可能性がある (An attacker may have intercepted the download attempt) オリジンサーバが上書きされているかがわかればよさそう

Slide 9

Slide 9 text

checksumを確認する オリジンサーバで上書きがあったならダウンロードして checksumを計算し、checksum serverのchecksumと比較した 時に異なるはずなので、実際に比較してみる checksumは https://pkg.go.dev/golang.org/x/mod/sumdb/dirhash を使用することで計算可能

Slide 10

Slide 10 text

fileのハッシュを求める open := func(fn string) (io.ReadCloser, error) { return os.Open(fn) } filePath := "go.mod" s, err := dirhash.Hash1([]string{filePath}, open) if err != nil { fmt.Println(err.Error()) } fmt.Println(s) https://go.dev/play/p/7aEARvckJjD

Slide 11

Slide 11 text

directoryのハッシュを求める dir := "[email protected]" prefix := "github.com/replu/[email protected]" s, err := dirhash.HashDir(dir, prefix, dirhash.DefaultHash) if err != nil { fmt.Println(err.Error()) } fmt.Println(s) https://go.dev/play/p/UofHVjffeuy

Slide 12

Slide 12 text

ツールを使ってchecksumを求める ツールを利用してもchecksumを確認できる https://github.com/vikyd/go-checksum ディレクトリのchecksumを求める例

Slide 13

Slide 13 text

checksum serverのchecksumを取得する checksum serverに保存されているchecksumは $base/lookup/$module@version で取得でき、デフォルトのchecksum serverの場合 $base = sum.golang.org

Slide 14

Slide 14 text

checksumが一致しないことが確認できたら ● checksum serverのchecksumは更新できない ○ そのバージョンは利用しないようにする ■ GONOSUMDB を指定することで checksum の確認をしない ようにできるが、攻撃を受けた時に気付けないのでおすすめは しない ○ 開発者に連絡して、新バージョンをリリースしてもらう ■ そのライブラリを利用している他ライブラリが新バージョンを 使っていないなら、そちらも更新してもらう必要がある 自分の場合は新バージョンがでていたが、他ライブラリが 新バージョンを使ってなかったので、そちらにissueを作成して連絡 した

Slide 15

Slide 15 text

どうして発生するのか

Slide 16

Slide 16 text

go get時の動作(再喝) モジュール配布元 checksum server go get xxx 1 : モジュールダウンロード 2 : checksum ダウンロード 3 : 計算したchecksum とダウンロードした checksumと比較 4 : go.modとgo.sum更新

Slide 17

Slide 17 text

go get時の動作(再喝) モジュール配布元 checksum server go get xxx 1 : モジュールダウンロード 2 : checksum ダウンロード 3 : 計算したchecksum とダウンロードした checksumと比較 4 : go.modとgo.sum更新 この2つの仕様の違いが原因

Slide 18

Slide 18 text

ダウンロード元とchecksum serverの指定 ● GOPROXY ○ モジュールのダウンロード元を指定する ○ デフォルトだと proxy.golang.org,direct ■ directを指定した場合は直接アクセスになる ■ カンマ区切りまたはパイプ区切りで指定し、前のものから 使用される ● GOSUBDB ○ checksum serverを指定する ■ デフォルトではsum.golang.org ○ offを指定した場合は検証を無効化できる

Slide 19

Slide 19 text

proxy.golang.orgとsum.golang.org ● proxy.golang.org ○ 明示的にキャッシュを削除することはできない ■ 無限に保存されるわけではなく適切なライセンスを 検出できない場合などは一定時間でキャッシュが削除される ● sum.golang.org ○ 一度記録されたら更新・削除されることはない sum.golang.orgに記録された後にオリジンサーバが上書き された場合、proxy.golang.orgのキャッシュが更新される or directでアクセスしているとchecksumが不一致になる

Slide 20

Slide 20 text

まとめ ● checksum不一致のエラーに遭遇したらchecksumを確認 して、オリジンサーバに上書きがあったのか確認する ○ 上書きがあった場合は新バージョンをリリースして もらう必要ある ● sum.golang.orgに記録された後で上書きすると問題が 発生するので、一度公開した後は上書きしないように すべき ● checksumは https://pkg.go.dev/golang.org/x/mod/sumdb/dirhash を用いることで計算できる

Slide 21

Slide 21 text

参考文献 ● Go Module Mirror, Index, and Checksum Database : https://sum.golang.org/ ● Go Modules Reference : https://go.dev/ref/mod

Slide 22

Slide 22 text

おまけ

Slide 23

Slide 23 text

アクセス制限しているモジュールの場合 ● GOPRIVATE環境変数を設定する ○ GOPRIVATEに設定されているモジュールの場合 ■ モジュールプロキシは利用されず、直接アクセス (GOPROXY=direct相当) ■ checksumの確認は実施されない

Slide 24

Slide 24 text

環境変数に設定する値まとめ ● GOPRIVATE ○ プライベートモジュールのパスの接頭辞を指定 ● GOPROXY ○ 公式のプロキシサーバを使いたくない理由がない限り デフォルトで問題なし ● GOSUMDB ○ 公式のchecksum serverを使いたくない理由がない限り デフォルト値で問題なし

Slide 25

Slide 25 text

ご清聴ありがとうございました