pkgproxy design: Go modules
The Go module proxy (the GOPROXY protocol) and checksum database verification. Part of the pkgproxy design, whose index says which file holds each § number.
16. Go module proxy (GOPROXY protocol)
The Go side lives in internal/gomod (protocol parsing) and
internal/gomodproxy (the handler); gitlab.com/zygoon/pkgproxy#13.
16.1 Why this is a good fit
CI systems that rebuild from a fresh disk image re-download every Go
module from the internet on every build. The GOPROXY protocol
(https://go.dev/ref/mod#goproxy-protocol) is a closed, five-endpoint
GET-only API ($base/$module/@v/{list,$version.info,$version.mod, $version.zip} and $base/$module/@latest) with no query params and no
required headers — even a static file server is a legal proxy. Crucially,
.mod/.zip content for a given module@version is immutable by
protocol: it must be byte-identical for all time, and clients pin it via
go.sum plus the checksum database. So, like apt’s pool/** and snap’s
downloads, versioned content is fetch-once-verify-cache-forever; only
list/@latest/.info (which change as new versions get published) need
a short TTL.
16.2 Shape: a third reverse-origin handler on the same listener
Like internal/snapproxy (§11.1) and unlike the apt forward proxy, the go
client addresses the proxy URL as an origin — GOPROXY=http://host:3142/go
— so internal/gomodproxy.Handler is a reverse-origin handler that chains
to an upstream module proxy (-go-upstream, default
https://proxy.golang.org) on a cache miss: a proxy-of-proxy, the same
model corporate proxies use. Direct VCS fetching (git tags/commits,
pseudo-versions, zip creation) is explicitly deferred — the chained
upstream covers all public modules, and it’s a much larger job.
internal/gateway routes a request whose path starts with -go-base-path
(default /go) here. It cannot collide with snap’s fixed /v2/... paths:
a module path’s first element must contain a dot (it names a host).
Cache layout mirrors apt/snap: a third fslocal.Store rooted at
<cache-dir>/go, sibling to apt and snap — see §11.4’s “each gets its
own top-level directory” precedent.
A 404 from the upstream (the go command probing a module boundary, such as
rsc.io before rsc.io/quote) is not cached, a known minor gap.
16.3 Checksum database verification
Fetched .mod/.zip are hash-checked and, by default, verified against
the checksum database (sum.golang.org, via golang.org/x/mod/sumdb
with the public key embedded exactly as the go command embeds it) before
being admitted to the cache. A mismatch refuses to cache and returns 502
to the client — this matters more than apt’s InRelease trust-root gap
(§4.2) because a poisoned cached zip would break every future build until
manually deleted, not just one client’s one request. -go-sumdb=off
disables verification entirely; a sumdb lookup failure (unreachable, or
the version not yet recorded) is logged as a gap and cached anyway — the
same stance apt’s InRelease trust root takes, and the client’s own
go.sum/sumdb check remains the backstop either way.
The checksum database’s hash of a .mod file is not a plain sha256 of its
bytes but dirhash.Hash1 over a single file named go.mod
(golang.org/x/mod/sumdb/dirhash), as the go command’s own goModSum
computes it.
16.4 Client enablement
pkgproxyctl setup go (and the daemon’s startup banner) prints copy-paste
instructions. Unlike apt (an apt.conf.d
drop-in) and snapd (a systemd drop-in), the go command has no system-wide
config file; go env -w writes the invoking user’s own $GOENV. So the
guidance offers go env -w GOPROXY=http://host:3142/go,direct for a user
or export GOPROXY=... for one job. The trailing ,direct keeps modules
pkgproxy can’t serve (private modules once GOPRIVATE excludes them)
reachable from their source. GOSUMDB is left at its default: pkgproxy’s
own verification (§16.3) adds to the client’s, it does not replace it.
The piped setup go -undo runs go env -u GOPROXY only when the file
go env GOENV names exists: without one, as for a user who never ran go env -w, go env -u fails although there is nothing to undo, so undoing
before setting up, or twice, changes nothing and succeeds.