Since the infamous SolarWinds attack, supply chain integrity is something a lot of people are discussing and working on.

In this post we’ll see how we can verify a binary built with Go is indeed what it says it is.

Building from Go mod proxy

Using go install

The easiest way of doing that is using go install:

$ go install github.com/caarlos0/svu@v1.7.0

And then we can verify with go version -m:

$ go version -m $(which svu)
/go/bin/svu: go1.17
	path	github.com/caarlos0/svu
	mod	github.com/caarlos0/svu	v1.7.0	h1:Aqk2q+qPGRoigQWgWkMeFlsjM0cZin7QL4oCPL++xUI=
	dep	github.com/Masterminds/semver	v1.5.0	h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
	dep	github.com/alecthomas/kingpin	v2.2.6+incompatible	h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
	dep	github.com/alecthomas/template	v0.0.0-20190718012654-fb15b899a751	h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
	dep	github.com/alecthomas/units	v0.0.0-20210208195552-ff826a37aa15	h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
	dep	github.com/gobwas/glob	v0.2.3	h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=

In the output, we can see this line:

mod	github.com/caarlos0/svu	v1.7.0	h1:Aqk2q+qPGRoigQWgWkMeFlsjM0cZin7QL4oCPL++xUI=

Which is the built module, its version and its hash.

We can then verify if its the correct hash doing something like this:

$ mkdir /tmp/test
$ cd /tmp/test
$ go mod init test
$ go get -d github.com/caarlos0/svu@v1.7.0
$ grep svu go.sum
github.com/caarlos0/svu v1.7.0 h1:Aqk2q+qPGRoigQWgWkMeFlsjM0cZin7QL4oCPL++xUI=
github.com/caarlos0/svu v1.7.0/go.mod h1:x4iUUt1lKyYx8o8uKfiaRZjJSt/PHaYTSUgR13e6Zy0=

And we can see that the hashes match, so it is verified.

Creating a go.mod and main.go

This is the way GoReleaser does it, it’s a bit more convoluted than using go install, with the main difference being that the binary is not installed to the GOPATH.

$ mkdir proxy
$ cd proxy/
$ go mod init proxy
$ go get -d github.com/caarlos0/svu@v1.7.0
$ echo '// +build main
package main

import _ "github.com/caarlos0/svu"
' > main.go
$ go build github.com/caarlos0/svu
$ go version -m svu
svu: go1.17
	path	github.com/caarlos0/svu
	mod	github.com/caarlos0/svu	v1.7.0	h1:Aqk2q+qPGRoigQWgWkMeFlsjM0cZin7QL4oCPL++xUI=
	dep	github.com/Masterminds/semver	v1.5.0	h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
	dep	github.com/alecthomas/kingpin	v2.2.6+incompatible	h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
	dep	github.com/alecthomas/template	v0.0.0-20190718012654-fb15b899a751	h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
	dep	github.com/alecthomas/units	v0.0.0-20210208195552-ff826a37aa15	h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
	dep	github.com/gobwas/glob	v0.2.3	h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=

We can see here that the hashes still match, success!

Using it within GoReleaser

To proxy the module when building with GoReleaser, you only need 2 lines in your goreleaser.yml:

gomod:
  proxy: true

And that’s it! GoReleaser hides all the complexity from you and figure most of the needed parts out by itself!

Conclusion

Assuming your tool is OpenSource, it doesn’t matter where it is built, as long as its built from the Go Mod proxy, it is verifiable.

You can take this further by signing the binaries and making the result binary more reproducible by trimming the path and using a fixed timestamp (see an example).

GoReleaser OSS itself is verifiable this way, you can try it with:

go version -m $(which goreleaser)

GoReleaser Pro in the other hand is not, as it is not OpenSource.

Still, since GoReleaser uses go build underneath, as long as you have gomod.proxy set to true, it shouldn’t matter much — your builds are still verifiable. If GoReleaser Pro gets somehow corrupted and starts doing “something funny” with your builds, you can easily verify them using this technique.

Another side effect of using the gomod.proxy feature: your module gets hashed right away, so if someone deletes the tag and recreates it, the previous tag would still be used to compile.

Hope this is somewhat helpful/informative to you.

I’ll see you in the next one!

Edit

After some more discussion in this issue, Marcos Nils pointed out a way to hack this using go mod vendor:

$ go mod vendor
# change some dependency code
$ go build -mod vendor github.com/caarlos0/svu
$ go version -m svu
# same output as before

So, while it prevents the attacker from changing the target code, they could still change the dependencies, which renders the idea of this post pretty much useless.

There’s also an open issue in the Go repository regarding this problem.