Go Proxy Security, Part 1: A critical piece of infrastructure
When pulling dependencies for Go via go mod, the dataflows are not obvious for the user. You import
a github.com/…
and the rest happens automatically.
In the default settings, go pulls these dependencies from proxy.golang.org, not from github.com.
This solves one issue in Software Supply Chains: Vanishing dependencies.
When the npm package left-pad was removed from npm, this led to failing builds around the globe. It was one of many supply chain disasters.
In Go, you have three major options to prevent the dependency on inaccessible packages:
- Use a
/vendor
folder and store the dependencies together with your source code - Selfhost a goproxy server
- Use the proxy.golang.org proxy server
Let’s shine more light on the proxy.golang.org proxy server since it is the only option that is not under our control.
With the command
go env
you can find the settings for GOPROXY, GOSUMDB and GOPRIVATE.
Security considerations
An attacker on a proxy server is the classical man-in-the-middle. They can modify the content that is sent to clients and they can send different versions of content to different clients for targeted attacks.
Without further protection, clients put their ultimate trust in the proxy.
The implementation of proxies in go mod is secured with gosumdb. The integrity of dependencies is verified with multiple cryptographical secure hash sums that are stored in gosumdb. Gosumdb has an API to provide the contents of the underlying data structure, which is a Merkle Tree.
Merkle Trees are append-only data structures. Once inserted, the data becomes immutable. This can be cryptographically proven. Go locally verifies the integrity of each and every dependency it pulls with the help of gosumdb.
A typical dependency request looks like this:
- Client requests the dependency from the proxy server (Proxy server requests the dependency from the upstream server, if not cached already)
- Client requests the checksums from gosumdb (Gosumdb will calculate the checksum on-the-fly if it is not stored already)
- Client verifies parts of the Merkle Tree locally
- If the cryptographic checks are successful, and the checksum of the dependency is valid too, the dependency is used
There is much more to say about the details of how go verifies the integrity of dependencies. In Part 2 of this series, I’ll explain the cryptographic concepts of gosumdb.