Skip to content
This repository has been archived by the owner on Mar 20, 2023. It is now read-only.

Commit

Permalink
actions: support proxying sum db urls (gomods#1208)
Browse files Browse the repository at this point in the history
* actions: support proxying sum db urls

* remove proxy prefix

* add docs for checksum db

* more docs

* typo

* typo

* typo

* typo

* typo

* typo

* typo

* typo

* move checksum db into its own section
  • Loading branch information
marwan-at-work authored and arschles committed May 9, 2019
1 parent e7d7749 commit 0cac0ed
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 27 deletions.
22 changes: 22 additions & 0 deletions cmd/proxy/actions/app_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package actions

import (
"fmt"
"net/http"
"net/url"
"path"
"strings"

"github.com/gomods/athens/pkg/config"
Expand All @@ -27,6 +30,25 @@ func addProxyRoutes(
r.HandleFunc("/version", versionHandler)
r.HandleFunc("/catalog", catalogHandler(s))

for _, sumdb := range c.SumDBs {
sumdbURL, err := url.Parse(sumdb)
if err != nil {
return err
}
if sumdbURL.Scheme != "https" {
return fmt.Errorf("sumdb: %v must have an https scheme", sumdb)
}
supportPath := path.Join("/sumdb", sumdbURL.Host, "/supported")
r.HandleFunc(supportPath, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
})
sumHandler := sumdbPoxy(sumdbURL, c.NoSumPatterns)
pathPrefix := "/sumdb/" + sumdbURL.Host
r.PathPrefix(pathPrefix + "/").Handler(
http.StripPrefix(pathPrefix, sumHandler),
)
}

// Download Protocol
// the download.Protocol and the stash.Stasher interfaces are composable
// in a middleware fashion. Therefore you can separate concerns
Expand Down
36 changes: 36 additions & 0 deletions cmd/proxy/actions/sumdb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package actions

import (
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
)

func sumdbPoxy(url *url.URL, nosumPatterns []string) http.Handler {
rp := httputil.NewSingleHostReverseProxy(url)
rp.Director = func(req *http.Request) {
req.Host = url.Host
req.URL.Scheme = url.Scheme
req.URL.Host = url.Host
}
if len(nosumPatterns) > 0 {
return noSumWrapper(rp, url.Host, nosumPatterns)
}
return rp
}

func noSumWrapper(h http.Handler, host string, patterns []string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/lookup/") {
for _, p := range patterns {
if isMatch, err := path.Match(p, r.URL.Path[len("/lookup/"):]); err == nil && isMatch {
w.WriteHeader(http.StatusForbidden)
return
}
}
}
h.ServeHTTP(w, r)
})
}
86 changes: 86 additions & 0 deletions cmd/proxy/actions/sumdb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package actions

import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
)

func TestSumdbProxy(t *testing.T) {
var givenURL string
expectedURL := "/latest"
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
givenURL = r.URL.Path
}))
defer s.Close()

surl, err := url.Parse(s.URL)
if err != nil {
panic(err)
}
pathPrefix := "/sumdb/" + surl.Host
h := sumdbPoxy(surl, nil)
h = http.StripPrefix(pathPrefix, h)

targetURL := "/sumdb/" + surl.Host + "/latest"
req := httptest.NewRequest("GET", targetURL, nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, req)

if w.Code != 200 {
t.Fatalf("expected to return 200 but got %v", w.Code)
}

if givenURL != expectedURL {
t.Fatalf("expected the URL to be %v but got %v", expectedURL, givenURL)
}
}

var noSumTestCases = []struct {
name string
patterns []string
given string
status int
}{
{
"no match",
[]string{"github.com/private/repo"},
"github.com/public/[email protected]",
http.StatusOK,
},
{
"exact match",
[]string{"github.com/private/[email protected]"},
"github.com/private/[email protected]",
http.StatusForbidden,
},
{
"star match",
[]string{"github.com/private/*"},
"github.com/private/[email protected]",
http.StatusForbidden,
},
{
"any version",
[]string{"github.com/private/repo*"},
"github.com/private/[email protected]",
http.StatusForbidden,
},
}

func TestNoSumPatterns(t *testing.T) {
for _, tc := range noSumTestCases {
t.Run(tc.name, func(t *testing.T) {
w := httptest.NewRecorder()
skipHandler := noSumWrapper(http.HandlerFunc(emptyHandler), "sum.golang.org", tc.patterns)
req := httptest.NewRequest("GET", "/lookup/"+tc.given, nil)
skipHandler.ServeHTTP(w, req)
if tc.status != w.Code {
t.Fatalf("expected NoSum wrapper to return %v but got %v", tc.status, w.Code)
}
})
}
}

func emptyHandler(w http.ResponseWriter, r *http.Request) {}
14 changes: 14 additions & 0 deletions config.dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ TraceExporterURL = "http://localhost:14268"
# Env override: ATHENS_STATS_EXPORTER
StatsExporter = "prometheus"

# SumDBs specifies a list of fully qualified URLs that Athens will proxy
# so that the go command can use as a checksum verifier.
# See NoSumDB for enforcing the go command to use
# GONOSUMDB.
# Env override: ATHENS_SUM_DBS
SumDBs = ["https://sum.golang.org"]

# NoSumPatterns specifies a list of patterns that will make the
# Sum DB proxy return a 403 if any of those patterns match.
# This will enforce the client to run GONOSUMDB
# Example pattern: NoSumPatterns = ["github.com/mycompany/*"]
# Env override: ATHENS_GONOSUM_PATTERNS
NoSumPatterns = []

# SingleFlightType determines what mechanism Athens uses
# to manage concurrency flowing into the Athens Backend.
# This is important for the following scenario: if two concurrent requests
Expand Down
5 changes: 5 additions & 0 deletions docs/content/configuration/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ In Athens we support many storage options. In this section we'll describe how th
In this section we'll describe how the upstream proxy can be configured to fetch all modules from a Go Modules Repository such as [GoCenter](https://gocenter.io) or another Athens Server.

- [Upstream](/configuration/upstream)

### Proxying A Checksum DB
In this section we'll describe how to proxy a Checksum DB as per https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md

- [Checksum](/configuration/sumdb)
45 changes: 45 additions & 0 deletions docs/content/configuration/sumdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Checksum DB
description: Proxying A Checksum DB API
weight: 2
---

## Proxying A Checksum DB
The Athens Proxy has the ability to proxy a Checksum Database as defined by [this proposal](https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md) by the Go team.

Athens by defualt will accept proxying `https://sum.golang.org`. However, if you'd like to override that behavior or proxy more Checksum DBs you can do so through the `SumDBs` config or its equivalent Environment Variable: `ATHENS_SUM_DBS`

So for example, if you run the following command:

```bash
GOPROXY=<athens-url> go build
```

The Go command will proxy requests to `sum.golang.org` like this: `<athens-url>/sumdb/sum.golang.org`. Feel free to read the linked proposal above for the exact requests that makes Athens successfully proxy Checksum DB APIs.

Note that as of this documentation (May 2019), you need to explicitly set `GOSUMDB=https://sum.golang.org`, but the Go team is planning on enabling this by defualt.

### Why a Checksum DB?

The reasons for needing a Checksum DB is explained in the linked proposal above. However, the reasons for proxying a Checksum DB are more explained below.

### Why Proxy a Checksum DB?

This is quite important. Say you are a company that is running an Athens instance, and you don't want the world to konw about where your
repositories live. For example, say you have a private repo under `github.com/mycompany/secret-repo`. In order to ensure that the Go client
does not send a request to `https://sum.golang.org/lookup/github.com/mycompany/[email protected]` and therefore leaking your private import path to the public, you need to ensure that you tell Go to skip particular import paths as such:

```
GONOSUMDB=github.com/mycompany/* go build
```

This will make sure that Go does not send any requests to the Checksum DB for your private import paths.
However, how can you ensure that all of your employees are building private code with the right configuration?

Athens, in this case can help ensure that all private code flowing through it never goes to the Checksum DB. So as long as your employees are using Athens, then they will get a helpful reminder to ensure Their GONOSUMDB is rightly configured.

As the Athens company maintainer, you can run Athens with the following configuration:

`NoSumPatterns = ["github.com/mycompany/*] # or comma separted env var: ATHENS_GONOSUM_PATTERNS`

This will ensure that when Go sends a request to `<athens-url/sumdb/sum.golang.org/github.com/mycompany/[email protected]>`, Athens will return a 403 and failing the build ensuring that the client knows something is not configured correctly and also never leaking those import paths
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ require (
github.com/bsm/redis-lock v8.0.0+incompatible
github.com/codegangsta/negroni v1.0.0 // indirect
github.com/fatih/color v1.7.0
github.com/globalsign/mgo v0.0.0-20180828104044-6f9f54af1356
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/go-redis/redis v6.15.2+incompatible
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180828104044-6f9f54af1356 h1:5bNaeqHyuxTGYlx42mevVN+R0TGdOrwj8MQl0yo1260=
github.com/globalsign/mgo v0.0.0-20180828104044-6f9f54af1356/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
Expand Down
52 changes: 28 additions & 24 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,32 @@ const defaultConfigFile = "athens.toml"
// Config provides configuration values for all components
type Config struct {
TimeoutConf
GoEnv string `validate:"required" envconfig:"GO_ENV"`
GoBinary string `validate:"required" envconfig:"GO_BINARY_PATH"`
GoGetWorkers int `validate:"required" envconfig:"ATHENS_GOGET_WORKERS"`
ProtocolWorkers int `validate:"required" envconfig:"ATHENS_PROTOCOL_WORKERS"`
LogLevel string `validate:"required" envconfig:"ATHENS_LOG_LEVEL"`
CloudRuntime string `validate:"required" envconfig:"ATHENS_CLOUD_RUNTIME"`
FilterFile string `envconfig:"ATHENS_FILTER_FILE"`
TraceExporterURL string `envconfig:"ATHENS_TRACE_EXPORTER_URL"`
TraceExporter string `envconfig:"ATHENS_TRACE_EXPORTER"`
StatsExporter string `envconfig:"ATHENS_STATS_EXPORTER"`
StorageType string `validate:"required" envconfig:"ATHENS_STORAGE_TYPE"`
GlobalEndpoint string `envconfig:"ATHENS_GLOBAL_ENDPOINT"` // This feature is not yet implemented
Port string `envconfig:"ATHENS_PORT"`
BasicAuthUser string `envconfig:"BASIC_AUTH_USER"`
BasicAuthPass string `envconfig:"BASIC_AUTH_PASS"`
ForceSSL bool `envconfig:"PROXY_FORCE_SSL"`
ValidatorHook string `envconfig:"ATHENS_PROXY_VALIDATOR"`
PathPrefix string `envconfig:"ATHENS_PATH_PREFIX"`
NETRCPath string `envconfig:"ATHENS_NETRC_PATH"`
GithubToken string `envconfig:"ATHENS_GITHUB_TOKEN"`
HGRCPath string `envconfig:"ATHENS_HGRC_PATH"`
TLSCertFile string `envconfig:"ATHENS_TLSCERT_FILE"`
TLSKeyFile string `envconfig:"ATHENS_TLSKEY_FILE"`
SingleFlightType string `envconfig:"ATHENS_SINGLE_FLIGHT_TYPE"`
GoEnv string `validate:"required" envconfig:"GO_ENV"`
GoBinary string `validate:"required" envconfig:"GO_BINARY_PATH"`
GoGetWorkers int `validate:"required" envconfig:"ATHENS_GOGET_WORKERS"`
ProtocolWorkers int `validate:"required" envconfig:"ATHENS_PROTOCOL_WORKERS"`
LogLevel string `validate:"required" envconfig:"ATHENS_LOG_LEVEL"`
CloudRuntime string `validate:"required" envconfig:"ATHENS_CLOUD_RUNTIME"`
FilterFile string `envconfig:"ATHENS_FILTER_FILE"`
TraceExporterURL string `envconfig:"ATHENS_TRACE_EXPORTER_URL"`
TraceExporter string `envconfig:"ATHENS_TRACE_EXPORTER"`
StatsExporter string `envconfig:"ATHENS_STATS_EXPORTER"`
StorageType string `validate:"required" envconfig:"ATHENS_STORAGE_TYPE"`
GlobalEndpoint string `envconfig:"ATHENS_GLOBAL_ENDPOINT"` // This feature is not yet implemented
Port string `envconfig:"ATHENS_PORT"`
BasicAuthUser string `envconfig:"BASIC_AUTH_USER"`
BasicAuthPass string `envconfig:"BASIC_AUTH_PASS"`
ForceSSL bool `envconfig:"PROXY_FORCE_SSL"`
ValidatorHook string `envconfig:"ATHENS_PROXY_VALIDATOR"`
PathPrefix string `envconfig:"ATHENS_PATH_PREFIX"`
NETRCPath string `envconfig:"ATHENS_NETRC_PATH"`
GithubToken string `envconfig:"ATHENS_GITHUB_TOKEN"`
HGRCPath string `envconfig:"ATHENS_HGRC_PATH"`
TLSCertFile string `envconfig:"ATHENS_TLSCERT_FILE"`
TLSKeyFile string `envconfig:"ATHENS_TLSKEY_FILE"`
SumDBs []string `envconfig:"ATHENS_SUM_DBS"`
NoSumPatterns []string `envconfig:"ATHENS_GONOSUM_PATTERNS"`
SingleFlightType string `envconfig:"ATHENS_SINGLE_FLIGHT_TYPE"`
SingleFlight *SingleFlight
Storage *StorageConfig
}
Expand Down Expand Up @@ -80,6 +82,8 @@ func defaultConfig() *Config {
SingleFlightType: "memory",
GlobalEndpoint: "http://localhost:3001",
TraceExporterURL: "http://localhost:14268",
SumDBs: []string{"https://sum.golang.org"},
NoSumPatterns: []string{},
SingleFlight: &SingleFlight{
Etcd: &Etcd{"localhost:2379,localhost:22379,localhost:32379"},
Redis: &Redis{"127.0.0.1:6379"},
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ func TestParseExampleConfig(t *testing.T) {
StatsExporter: "prometheus",
SingleFlightType: "memory",
SingleFlight: &SingleFlight{},
SumDBs: []string{"https://sum.golang.org"},
NoSumPatterns: []string{},
}

absPath, err := filepath.Abs(testConfigFile(t))
Expand Down

0 comments on commit 0cac0ed

Please sign in to comment.