Skip to content

Commit 167e6b8

Browse files
authored
Merge pull request #462 from pinguinfuss/issue-409-go-sumdb
Serve the Go checksum database through the proxy
2 parents 53c03c9 + 42df87e commit 167e6b8

12 files changed

Lines changed: 1641 additions & 16 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ export GOPROXY=http://localhost:8080/go,direct
202202

203203
Or in your shell profile for persistence.
204204

205+
Checksum lookups go through the same URL: the proxy serves `sum.golang.org` at
206+
`/go/sumdb/sum.golang.org/`, and the `go` command uses it without further
207+
setup, so clients need no route of their own to `sum.golang.org`. If the
208+
proxy loses `https://sum.golang.org`, it keeps answering until its cached
209+
copy of the database's signed tree is older than `metadata_ttl`; after that
210+
the `go` command contacts the database directly as before. Set
211+
`upstream.go_sumdbs_offline` on networks where clients cannot do that. Other
212+
checksum databases are listed under `upstream.go_sumdbs`, see
213+
[Go checksum database](docs/configuration.md#go-checksum-database).
214+
205215
### Homebrew
206216

207217
Point Homebrew's JSON API and artifact domain at the proxy:

‎docs/configuration.md‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ Each upstream used by a built-in package route can be set in YAML or JSON under
237237
| `upstream.cargo_download` | `PROXY_UPSTREAM_CARGO_DOWNLOAD` | `https://static.crates.io/crates` |
238238
| `upstream.gem` | `PROXY_UPSTREAM_GEM` | `https://rubygems.org` |
239239
| `upstream.go` | `PROXY_UPSTREAM_GO` | `https://proxy.golang.org` |
240+
| `upstream.go_sumdbs_offline` | `PROXY_UPSTREAM_GO_SUMDBS_OFFLINE` | `false` |
240241
| `upstream.hex` | `PROXY_UPSTREAM_HEX` | `https://repo.hex.pm` |
241242
| `upstream.hex_api` | `PROXY_UPSTREAM_HEX_API` | `https://hex.pm` |
242243
| `upstream.pub` | `PROXY_UPSTREAM_PUB` | `https://pub.dev` |
@@ -495,6 +496,63 @@ When `upstream.arch` is empty, a single repository named `archlinux` pointing at
495496
that default. Arch Linux ARM has no default: pick a regional HTTPS mirror, not
496497
the `mirror.archlinuxarm.org` redirector.
497498

499+
### Go checksum database
500+
501+
```yaml
502+
upstream:
503+
504+
# Named Go checksum databases, served at /go/sumdb/{name}/.
505+
go_sumdbs:
506+
sum.golang.org: "https://sum.golang.org"
507+
508+
# Keep answering for a database from its cached tree, however old, when
509+
# the database cannot be reached. For networks where clients have no
510+
# route to the database of their own. Requires cache_metadata.
511+
go_sumdbs_offline: false
512+
```
513+
514+
The `go` command asks `GOPROXY` for the checksum database before contacting it
515+
directly. The proxy answers for every name listed here and fetches lookups and
516+
tiles from the database's URL, not from `upstream.go`. Unknown names get 404,
517+
so the `go` command falls back to contacting that database itself. When
518+
`upstream.go_sumdbs` is empty, `sum.golang.org` is served; configuring any
519+
entry replaces that default.
520+
521+
Once the proxy says it serves a database, the `go` command no longer contacts
522+
that database directly. The proxy therefore only says so when it can fetch the
523+
database's latest signed tree, or has a fresh one cached (fetched within
524+
`metadata_ttl`). If it cannot, it answers 404 as for an unknown name, and
525+
clients go to the database themselves, as they would without the proxy. The
526+
check waits five seconds at most, and after a failed check the proxy waits a
527+
minute before it tries the database again.
528+
529+
Set `upstream.go_sumdbs_offline: true` when clients have no route to the
530+
database of their own. The proxy then keeps answering for it from the cached
531+
tree for as long as the outage lasts and serves cached lookups and tiles.
532+
That does not mean every cached module still verifies: the `go` command
533+
checks each lookup against tiles of the signed tree that came with it, the
534+
database attaches a different tree to almost every lookup, and a tile no
535+
client fetched through the proxy before cannot be served during the outage.
536+
Lookups for modules that were never cached fail as well. Builds whose
537+
`go.sum` already lists every module do not ask the database at all and are
538+
not affected. This requires `cache_metadata`.
539+
540+
With `cache_metadata` enabled, lookups, partial hash tiles and the latest
541+
signed tree follow `metadata_ttl` and are served from cache when the database
542+
is unreachable. Complete hash tiles never change and are not revalidated once
543+
cached; a tile of the wrong size is refused and not stored. Data tiles are
544+
always forwarded and never stored: the `go` command does not fetch them, only
545+
mirrors and auditors do.
546+
Without `cache_metadata` the requests are forwarded and nothing is stored.
547+
548+
A 404 from the database keeps its text, which the `go` command prints to
549+
explain why a version is missing. With `cache_metadata` enabled the text is
550+
cut at 4 KiB; without it the response is passed on unchanged.
551+
552+
If `upstream.go` points at a module proxy that serves the checksum database
553+
itself, such as Athens, point the entry there instead, for example
554+
`sum.golang.org: "https://athens.example/sumdb/sum.golang.org"`.
555+
498556
### Debian archives
499557
500558
```yaml

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ require (
2929
github.com/spdx/tools-golang v0.5.7
3030
github.com/swaggo/swag v1.16.6
3131
gocloud.dev v0.46.0
32+
golang.org/x/mod v0.41.0
3233
golang.org/x/net v0.59.0
3334
golang.org/x/sync v0.23.0
3435
google.golang.org/protobuf v1.36.12
@@ -117,7 +118,6 @@ require (
117118
go.opentelemetry.io/otel/trace v1.45.0 // indirect
118119
go.yaml.in/yaml/v3 v3.0.5 // indirect
119120
golang.org/x/crypto v0.57.0 // indirect
120-
golang.org/x/mod v0.41.0 // indirect
121121
golang.org/x/oauth2 v0.36.0 // indirect
122122
golang.org/x/sys v0.48.0 // indirect
123123
golang.org/x/text v0.42.0 // indirect

‎internal/config/config.go‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,21 @@ type UpstreamConfig struct {
573573
// Default: https://proxy.golang.org
574574
Go string `json:"go" yaml:"go"`
575575

576+
// GoSumDBs maps Go checksum database names to their base URLs, served at
577+
// /go/sumdb/{name}/ so the go command reaches the database through
578+
// GOPROXY. Unknown names get 404, which makes the go command contact the
579+
// database directly.
580+
// Default when empty: {"sum.golang.org": "https://sum.golang.org"}.
581+
GoSumDBs map[string]string `json:"go_sumdbs" yaml:"go_sumdbs"`
582+
583+
// GoSumDBsOffline keeps answering for a checksum database from its
584+
// cached signed tree, however old, when the database cannot be reached.
585+
// Set this when clients have no route to the database of their own;
586+
// otherwise the proxy stops answering once the cached tree is older than
587+
// metadata_ttl, and the go command contacts the database directly.
588+
// Requires cache_metadata. Default: false.
589+
GoSumDBsOffline bool `json:"go_sumdbs_offline" yaml:"go_sumdbs_offline"`
590+
576591
// Hex is the upstream Hex repository URL.
577592
// Default: https://repo.hex.pm
578593
Hex string `json:"hex" yaml:"hex"`
@@ -773,6 +788,9 @@ func (u *UpstreamConfig) Validate() error {
773788
if err := validateNamedUpstreams("upstream.arch", u.Arch); err != nil {
774789
return err
775790
}
791+
if err := validateNamedUpstreams("upstream.go_sumdbs", u.GoSumDBs); err != nil {
792+
return err
793+
}
776794
if err := validateNamedUpstreams("upstream.oci", u.OCI); err != nil {
777795
return err
778796
}
@@ -813,6 +831,18 @@ func (u *UpstreamConfig) validateGenericArtifacts() error {
813831
return nil
814832
}
815833

834+
// validateUpstream checks the upstream block, including the one setting in
835+
// it that depends on a top-level option.
836+
func (c *Config) validateUpstream() error {
837+
if err := c.Upstream.Validate(); err != nil {
838+
return err
839+
}
840+
if c.Upstream.GoSumDBsOffline && !c.CacheMetadata {
841+
return fmt.Errorf("upstream.go_sumdbs_offline requires cache_metadata")
842+
}
843+
return nil
844+
}
845+
816846
// CompileGenericArtifactPattern compiles an upstream.generic_artifacts pattern
817847
// so that it only matches a whole path, and checks it has the `version` and
818848
// `file` groups.
@@ -1140,6 +1170,7 @@ func (c *Config) LoadFromEnv() {
11401170
setEnvBool(&c.Upstream.AllowLoopback, "PROXY_UPSTREAM_ALLOW_LOOPBACK")
11411171
setEnvString(&c.Upstream.NPM, "PROXY_UPSTREAM_NPM")
11421172
setEnvBool(&c.Upstream.NPMFullMetadata, "PROXY_UPSTREAM_NPM_FULL_METADATA")
1173+
setEnvBool(&c.Upstream.GoSumDBsOffline, "PROXY_UPSTREAM_GO_SUMDBS_OFFLINE")
11431174
setEnvString(&c.Upstream.Cargo, "PROXY_UPSTREAM_CARGO")
11441175
setEnvString(&c.Upstream.CargoDownload, "PROXY_UPSTREAM_CARGO_DOWNLOAD")
11451176
setEnvString(&c.Upstream.Gem, "PROXY_UPSTREAM_GEM")
@@ -1315,7 +1346,7 @@ func (c *Config) validateComponents() error {
13151346
return err
13161347
}
13171348

1318-
if err := c.Upstream.Validate(); err != nil {
1349+
if err := c.validateUpstream(); err != nil {
13191350
return err
13201351
}
13211352

‎internal/config/config_test.go‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,61 @@ func TestValidateNamedUpstreams(t *testing.T) {
16121612
}
16131613
}
16141614

1615+
func TestValidateGoSumDBs(t *testing.T) {
1616+
tests := []struct {
1617+
name string
1618+
modify func(*Config)
1619+
wantErr bool
1620+
}{
1621+
{
1622+
name: "valid Go checksum database",
1623+
modify: func(cfg *Config) {
1624+
cfg.Upstream.GoSumDBs = map[string]string{"sum.golang.org": "https://sum.golang.org"}
1625+
},
1626+
},
1627+
{
1628+
name: "Go checksum database name contains path separator",
1629+
modify: func(cfg *Config) {
1630+
cfg.Upstream.GoSumDBs = map[string]string{"sum.golang.org/x": "https://sum.golang.org"}
1631+
},
1632+
wantErr: true,
1633+
},
1634+
{
1635+
name: "Go checksum database URL is not absolute",
1636+
modify: func(cfg *Config) {
1637+
cfg.Upstream.GoSumDBs = map[string]string{"sum.golang.org": "sum.golang.org"}
1638+
},
1639+
wantErr: true,
1640+
},
1641+
{
1642+
name: "Go checksum database offline mode requires metadata caching",
1643+
modify: func(cfg *Config) {
1644+
cfg.CacheMetadata = false
1645+
cfg.Upstream.GoSumDBsOffline = true
1646+
},
1647+
wantErr: true,
1648+
},
1649+
{
1650+
name: "Go checksum database offline mode with metadata caching",
1651+
modify: func(cfg *Config) {
1652+
cfg.CacheMetadata = true
1653+
cfg.Upstream.GoSumDBsOffline = true
1654+
},
1655+
},
1656+
}
1657+
1658+
for _, tt := range tests {
1659+
t.Run(tt.name, func(t *testing.T) {
1660+
cfg := Default()
1661+
tt.modify(cfg)
1662+
err := cfg.Validate()
1663+
if (err != nil) != tt.wantErr {
1664+
t.Errorf("Validate() error = %v, wantErr %t", err, tt.wantErr)
1665+
}
1666+
})
1667+
}
1668+
}
1669+
16151670
func TestValidateCacheArtifactsDisabled(t *testing.T) {
16161671
tests := []struct {
16171672
name string

‎internal/handler/download_test.go‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ func TestGoHandler_UpstreamProxy(t *testing.T) {
330330
"/golang.org/x/text/@v/v0.14.0.info",
331331
"/golang.org/x/text/@v/v0.14.0.mod",
332332
"/golang.org/x/text/@latest",
333-
"/sumdb/sum.golang.org/lookup/golang.org/x/text@v0.14.0",
334333
}
335334

336335
for _, path := range tests {

‎internal/handler/go.go‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ func init() {
2828

2929
// GoHandler handles Go module proxy protocol requests.
3030
type GoHandler struct {
31-
proxy *Proxy
32-
upstreamURL string
33-
proxyURL string
31+
proxy *Proxy
32+
upstreamURL string
33+
proxyURL string
34+
sumDBs map[string]string
35+
sumDBOffline bool
36+
sumDBProbes goSumDBProbes
3437
}
3538

3639
// NewGoHandler creates a new Go module proxy handler.
@@ -39,6 +42,7 @@ func NewGoHandler(proxy *Proxy, proxyURL string) *GoHandler {
3942
proxy: proxy,
4043
upstreamURL: goUpstream,
4144
proxyURL: strings.TrimSuffix(proxyURL, "/"),
45+
sumDBs: map[string]string{defaultGoSumDBName: defaultGoSumDBURL},
4246
}
4347
}
4448

@@ -65,9 +69,9 @@ func (h *GoHandler) Routes() http.Handler {
6569
func (h *GoHandler) handleRequest(w http.ResponseWriter, r *http.Request) {
6670
path := strings.TrimPrefix(r.URL.Path, "/")
6771

68-
// Sumdb requests - proxy through
69-
if strings.HasPrefix(path, "sumdb/") {
70-
h.proxyUpstream(w, r)
72+
// Checksum database requests go to the database, not the module upstream
73+
if rest, ok := strings.CutPrefix(path, "sumdb/"); ok {
74+
h.handleSumDB(w, r, rest)
7175
return
7276
}
7377

@@ -141,11 +145,6 @@ func (h *GoHandler) handleDownload(w http.ResponseWriter, r *http.Request, modul
141145
ServeArtifactRequest(w, r, result)
142146
}
143147

144-
// proxyUpstream forwards a request to proxy.golang.org without caching.
145-
func (h *GoHandler) proxyUpstream(w http.ResponseWriter, r *http.Request) {
146-
h.proxy.ProxyUpstream(w, r, h.upstreamURL+r.URL.Path, nil)
147-
}
148-
149148
// proxyCached forwards a request with metadata caching.
150149
func (h *GoHandler) proxyCached(w http.ResponseWriter, r *http.Request, cacheKey string) {
151150
h.proxy.ProxyCached(w, r, h.upstreamURL+r.URL.Path, "golang", cacheKey, "*/*")

0 commit comments

Comments
 (0)