📚

Golang における依存関係解消の方法が思ってたのと違うというお話

に公開

はじめに

プロダクトセキュリティの文脈で、脆弱性のあるパッケージへの依存への対策を話し合っています。その中で https://pkg.go.dev/golang.org/x/crypto がさまざまなパッケージ経由で孫依存してるということが話題になりました。その流れでGolangにおけるパッケージの依存関係について改めて調べてみて、自身が勘違いしていたなという部分があったので紹介したいと思います。

最初に思っていた依存関係

パッケージmain,a,b が次のように依存しているとします。

  • パッケージmainのgo.mod にはa@v0.1.0 と b@v0.2.0が書かれている
  • パッケージa のgo.mod にはb@v0.1.0 と書かれている

この時、以下のような挙動になると誤解していました

  1. パッケージa はパッケージb@v0.1.0 を参照してビルドされる
  2. パッケージmain は1の生成物とパッケージb@v0.2.0 を参照してビルドされる
    • なので、a経由でbに依存する時にはb@v0.1.0が利用されるし、bを直接参照する時はb@v0.2.0 が参照される

実際の挙動

main のビルド全体として、各パッケージに対するバージョンは一つ(最新のもの)になります。上の例だとパッケージb @v0.2.0 が採用され、パッケージa も(go.modの記載に関わらず) b@v0.2.0 を参照します。

最初は信じられなかったので実験してみました

パッケージmain

package main

import "github.com/hacobu-inc/lance_sec_a"

func main() {
	lance_sec_a.F()
}
module github.com/hacobu-inc/lance_sec_main

go 1.23.3

require github.com/hacobu-inc/lance_sec_a v0.1.0

require github.com/hacobu-inc/lance_sec_b v0.2.0 // indirect

パッケージa

package lance_sec_a

import (
	"fmt"

	"github.com/hacobu-inc/lance_sec_b"
)

func F() {
	fmt.Println("lance_sec_a: 0.1.0")
	lance_sec_b.F()
}
module github.com/hacobu-inc/lance_sec_a

go 1.23.3

require github.com/hacobu-inc/lance_sec_b v0.1.0

パッケージb @v0.1.0

package lance_sec_b

import "fmt"

func F() {
	fmt.Println("lance_sec_b: 0.1.0")
}

パッケージb @v0.2.0

package lance_sec_b

import "fmt"

func F() {
	fmt.Println("lance_sec_b: 0.2.0")
}

実験結果

mainパッケージを実行すると、以下のようにb@v0.2.0 が採用されていることが分かります

lance_sec_a: 0.1.0
lance_sec_b: 0.2.0

go mod で依存関係を出力してみると

% go mod graph
github.com/hacobu-inc/lance_sec_main github.com/hacobu-inc/lance_sec_a@v0.1.0
github.com/hacobu-inc/lance_sec_main github.com/hacobu-inc/lance_sec_b@v0.2.0
github.com/hacobu-inc/lance_sec_main go@1.23.3
github.com/hacobu-inc/lance_sec_a@v0.1.0 github.com/hacobu-inc/lance_sec_b@v0.1.0
github.com/hacobu-inc/lance_sec_a@v0.1.0 go@1.23.3
github.com/hacobu-inc/lance_sec_b@v0.2.0 go@1.23.3
go@1.23.3 toolchain@go1.23.3

となっていて、aはb@v0.1.0に確かに依存している

採用されるパッケージのバージョン一覧はこちら

% go list -m all
github.com/hacobu-inc/lance_sec_main
github.com/hacobu-inc/lance_sec_a v0.1.0
github.com/hacobu-inc/lance_sec_b v0.2.0

最後に

golang におけるパッケージの依存関係の挙動が思ってのと違うというお話でした。ちなみにこのアルゴリズムはMinimal Version Selection という名前がついているそうです。

参考

https://go.dev/ref/mod#minimal-version-selection
https://research.swtch.com/vgo-mvs

Hacobuテックブログ

Discussion