mirror of https://github.com/golang/go.git
cmd/go: implement Go checksum database support
This CL adds support for consulting the Go checksum database when downloading a module that is not already listed in go.sum. The overall system is described at golang.org/design/25530-sumdb, and this CL implements the functionality described specifically in golang.org/design/25530-sumdb#command-client. Although the eventual plan is to set GOPROXY and GOSUMDB to default to a Google-run proxy serving the public Go ecosystem, this CL leaves them off by default. Fixes #30601. Change-Id: Ie46140f93c6cc2d85573fbce0878a258819ff44d Reviewed-on: https://go-review.googlesource.com/c/go/+/173951 Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com> Reviewed-by: Jay Conrod <jayconrod@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
203b80ab86
commit
3cf1d77080
|
|
@ -2742,33 +2742,46 @@
|
|||
// you want to use the same code you used yesterday.
|
||||
//
|
||||
// If a downloaded module is not yet included in go.sum and it is a publicly
|
||||
// available module, the go command consults the Go notary server to fetch
|
||||
// available module, the go command consults the Go checksum database to fetch
|
||||
// the expected go.sum lines. If the downloaded code does not match those
|
||||
// lines, the go command reports the mismatch and exits. Note that the
|
||||
// notary is not consulted for module versions already listed in go.sum.
|
||||
// database is not consulted for module versions already listed in go.sum.
|
||||
//
|
||||
// The GONOVERIFY environment variable is a comma-separated list of
|
||||
// If a go.sum mismatch is reported, it is always worth investigating why
|
||||
// the code downloaded today differs from what was downloaded yesterday.
|
||||
//
|
||||
// The GOSUMDB environment variable identifies the name of checksum database
|
||||
// to use and optionally its public key and URL, as in:
|
||||
//
|
||||
// GOSUMDB="sum.golang.org"
|
||||
// GOSUMDB="sum.golang.org+<publickey>"
|
||||
// GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"
|
||||
//
|
||||
// The go command knows the public key of sum.golang.org; use of any other
|
||||
// database requires giving the public key explicitly. The URL defaults to
|
||||
// "https://" followed by the database name.
|
||||
//
|
||||
// GOSUMDB defaults to "sum.golang.org" when GOPROXY="https://proxy.golang.org"
|
||||
// and otherwise defaults to "off". NOTE: The GOSUMDB will later default to
|
||||
// "sum.golang.org" unconditionally.
|
||||
//
|
||||
// If GOSUMDB is set to "off", or if "go get" is invoked with the -insecure flag,
|
||||
// the checksum database is never consulted, but at the cost of giving up the
|
||||
// security guarantee of verified repeatable downloads for all modules.
|
||||
// A better way to bypass the checksum database for specific modules is
|
||||
// to use the GONOSUMDB environment variable.
|
||||
//
|
||||
// The GONOSUMDB environment variable is a comma-separated list of
|
||||
// patterns (in the syntax of Go's path.Match) of module path prefixes
|
||||
// that should not be verified using the notary. For example,
|
||||
// that should not be compared against the checksum database.
|
||||
// For example,
|
||||
//
|
||||
// GONOVERIFY=*.corp.example.com,rsc.io/private
|
||||
// GONOSUMDB=*.corp.example.com,rsc.io/private
|
||||
//
|
||||
// disables notary verification for modules with path prefixes matching
|
||||
// disables checksum database lookups for modules with path prefixes matching
|
||||
// either pattern, including "git.corp.example.com/xyzzy", "rsc.io/private",
|
||||
// and "rsc.io/private/quux".
|
||||
//
|
||||
// As a special case, if GONOVERIFY is set to "off", or if "go get" was invoked
|
||||
// with the -insecure flag, the notary is never consulted, but note that this
|
||||
// defeats the security provided by the notary. A better course of action is
|
||||
// to set a narrower GONOVERIFY and, in the case of go.sum mismatches,
|
||||
// investigate why the code downloaded code differs from what was
|
||||
// downloaded yesterday.
|
||||
//
|
||||
// NOTE: Early in the Go 1.13 dev cycle, the notary is being simulated by
|
||||
// a whitelist of known hashes for popular Go modules, to expose any
|
||||
// problems arising from knowing the expected hashes.
|
||||
// TODO(rsc): This note should be removed once the real notary is used instead. See #30601.
|
||||
//
|
||||
//
|
||||
// Testing flags
|
||||
//
|
||||
|
|
|
|||
|
|
@ -265,12 +265,14 @@ var knownEnv = `
|
|||
GOHOSTOS
|
||||
GOMIPS
|
||||
GOMIPS64
|
||||
GONOVERIFY
|
||||
GONOPROXY
|
||||
GONOSUMDB
|
||||
GOOS
|
||||
GOPATH
|
||||
GOPPC64
|
||||
GOPROXY
|
||||
GOROOT
|
||||
GOSUMDB
|
||||
GOTMPDIR
|
||||
GOTOOLDIR
|
||||
GOWASM
|
||||
|
|
@ -293,8 +295,45 @@ var (
|
|||
GOMIPS64 = envOr("GOMIPS64", objabi.GOMIPS64)
|
||||
GOPPC64 = envOr("GOPPC64", fmt.Sprintf("%s%d", "power", objabi.GOPPC64))
|
||||
GOWASM = envOr("GOWASM", fmt.Sprint(objabi.GOWASM))
|
||||
|
||||
GOPROXY = goproxy()
|
||||
GOSUMDB = gosumdb()
|
||||
GONOPROXY = Getenv("GONOPROXY")
|
||||
GONOSUMDB = Getenv("GONOSUMDB")
|
||||
)
|
||||
|
||||
func goproxy() string {
|
||||
v := Getenv("GOPROXY")
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
// Proxy is off by default for now.
|
||||
// TODO(rsc): Remove this condition, turning it on always.
|
||||
// (But do NOT do this without approval from rsc.)
|
||||
if true {
|
||||
return "direct"
|
||||
}
|
||||
|
||||
return "https://proxy.golang.org"
|
||||
}
|
||||
|
||||
func gosumdb() string {
|
||||
v := Getenv("GOSUMDB")
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
// Checksum database is off by default except when GOPROXY is proxy.golang.org.
|
||||
// TODO(rsc): Remove this condition, turning it on always.
|
||||
// (But do NOT do this without approval from rsc.)
|
||||
if !strings.HasPrefix(GOPROXY, "https://proxy.golang.org") {
|
||||
return "off"
|
||||
}
|
||||
|
||||
return "sum.golang.org"
|
||||
}
|
||||
|
||||
// GetArchEnv returns the name and setting of the
|
||||
// GOARCH-specific architecture environment variable.
|
||||
// If the current architecture has no GOARCH-specific variable,
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ package envcmd
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unicode/utf8"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cache"
|
||||
|
|
@ -56,7 +56,7 @@ func init() {
|
|||
|
||||
var (
|
||||
envJson = CmdEnv.Flag.Bool("json", false, "")
|
||||
envU = CmdEnv.Flag.Bool("u", false, "")
|
||||
envU = CmdEnv.Flag.Bool("u", false, "")
|
||||
envW = CmdEnv.Flag.Bool("w", false, "")
|
||||
)
|
||||
|
||||
|
|
@ -74,10 +74,13 @@ func MkEnv() []cfg.EnvVar {
|
|||
{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
|
||||
{Name: "GOHOSTARCH", Value: runtime.GOARCH},
|
||||
{Name: "GOHOSTOS", Value: runtime.GOOS},
|
||||
{Name: "GONOPROXY", Value: cfg.GONOPROXY},
|
||||
{Name: "GONOSUMDB", Value: cfg.GONOSUMDB},
|
||||
{Name: "GOOS", Value: cfg.Goos},
|
||||
{Name: "GOPATH", Value: cfg.BuildContext.GOPATH},
|
||||
{Name: "GOPROXY", Value: cfg.Getenv("GOPROXY")},
|
||||
{Name: "GOPROXY", Value: cfg.GOPROXY},
|
||||
{Name: "GOROOT", Value: cfg.GOROOT},
|
||||
{Name: "GOSUMDB", Value: cfg.GOSUMDB},
|
||||
{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
|
||||
{Name: "GOTOOLDIR", Value: base.ToolDir},
|
||||
}
|
||||
|
|
@ -387,7 +390,7 @@ func updateEnvFile(add map[string]string, del map[string]bool) {
|
|||
}
|
||||
}
|
||||
for key, val := range add {
|
||||
lines = append(lines, key + "=" + val + "\n")
|
||||
lines = append(lines, key+"="+val+"\n")
|
||||
}
|
||||
|
||||
// Delete requested variables (go env -u).
|
||||
|
|
@ -404,7 +407,7 @@ func updateEnvFile(add map[string]string, del map[string]bool) {
|
|||
for i := 0; i <= len(lines); i++ {
|
||||
if i == len(lines) || lineToKey(lines[i]) == "" {
|
||||
sortKeyValues(lines[start:i])
|
||||
start = i+1
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build cmd_go_bootstrap
|
||||
|
||||
package modfetch
|
||||
|
||||
import "cmd/go/internal/module"
|
||||
|
||||
func useSumDB(mod module.Version) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func lookupSumDB(mod module.Version) (string, []string, error) {
|
||||
panic("bootstrap")
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ func Download(mod module.Version) (dir string, err error) {
|
|||
if err := download(mod, dir); err != nil {
|
||||
return cached{"", err}
|
||||
}
|
||||
checkSum(mod)
|
||||
checkMod(mod)
|
||||
return cached{dir, nil}
|
||||
}).(cached)
|
||||
return c.dir, c.err
|
||||
|
|
@ -246,7 +246,7 @@ func downloadZip(mod module.Version, zipfile string) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
checkOneSum(mod, hash)
|
||||
checkModSum(mod, hash)
|
||||
|
||||
if err := renameio.WriteFile(zipfile+"hash", []byte(hash)); err != nil {
|
||||
return err
|
||||
|
|
@ -305,7 +305,7 @@ func initGoSum() bool {
|
|||
readGoSum(migrate, alt, data)
|
||||
for mod, sums := range migrate {
|
||||
for _, sum := range sums {
|
||||
checkOneSumLocked(mod, sum)
|
||||
addModSumLocked(mod, sum)
|
||||
}
|
||||
}
|
||||
goSum.modverify = alt
|
||||
|
|
@ -348,8 +348,8 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// checkSum checks the given module's checksum.
|
||||
func checkSum(mod module.Version) {
|
||||
// checkMod checks the given module's checksum.
|
||||
func checkMod(mod module.Version) {
|
||||
if PkgMod == "" {
|
||||
// Do not use current directory.
|
||||
return
|
||||
|
|
@ -373,7 +373,7 @@ func checkSum(mod module.Version) {
|
|||
base.Fatalf("verifying %s@%s: unexpected ziphash: %q", mod.Path, mod.Version, h)
|
||||
}
|
||||
|
||||
checkOneSum(mod, h)
|
||||
checkModSum(mod, h)
|
||||
}
|
||||
|
||||
// goModSum returns the checksum for the go.mod contents.
|
||||
|
|
@ -391,52 +391,63 @@ func checkGoMod(path, version string, data []byte) {
|
|||
base.Fatalf("verifying %s %s go.mod: %v", path, version, err)
|
||||
}
|
||||
|
||||
checkOneSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
|
||||
checkModSum(module.Version{Path: path, Version: version + "/go.mod"}, h)
|
||||
}
|
||||
|
||||
// checkOneSum checks that the recorded hash for mod is h.
|
||||
func checkOneSum(mod module.Version, h string) {
|
||||
// checkModSum checks that the recorded checksum for mod is h.
|
||||
func checkModSum(mod module.Version, h string) {
|
||||
// We lock goSum when manipulating it,
|
||||
// but we arrange to release the lock when calling checkSumDB,
|
||||
// so that parallel calls to checkModHash can execute parallel calls
|
||||
// to checkSumDB.
|
||||
|
||||
// Check whether mod+h is listed in go.sum already. If so, we're done.
|
||||
goSum.mu.Lock()
|
||||
defer goSum.mu.Unlock()
|
||||
if initGoSum() {
|
||||
checkOneSumLocked(mod, h)
|
||||
} else if useNotary(mod) {
|
||||
checkNotarySum(mod, h)
|
||||
}
|
||||
}
|
||||
inited := initGoSum()
|
||||
done := inited && haveModSumLocked(mod, h)
|
||||
goSum.mu.Unlock()
|
||||
|
||||
func checkOneSumLocked(mod module.Version, h string) {
|
||||
goSum.checked[modSum{mod, h}] = true
|
||||
|
||||
checkGoSum := func() bool {
|
||||
for _, vh := range goSum.m[mod] {
|
||||
if h == vh {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(vh, "h1:") {
|
||||
base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v"+goSumMismatch, mod.Path, mod.Version, h, vh)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if checkGoSum() {
|
||||
if done {
|
||||
return
|
||||
}
|
||||
|
||||
if useNotary(mod) {
|
||||
goSum.mu.Unlock()
|
||||
checkNotarySum(mod, h) // dies if h is wrong
|
||||
goSum.mu.Lock()
|
||||
|
||||
// Because we dropped the lock, a racing goroutine
|
||||
// may have already added this entry to go.sum.
|
||||
// Check again.
|
||||
if checkGoSum() {
|
||||
return
|
||||
}
|
||||
// Not listed, so we want to add them.
|
||||
// Consult checksum database if appropriate.
|
||||
if useSumDB(mod) {
|
||||
// Calls base.Fatalf if mismatch detected.
|
||||
checkSumDB(mod, h)
|
||||
}
|
||||
|
||||
// Add mod+h to go.sum, if it hasn't appeared already.
|
||||
if inited {
|
||||
goSum.mu.Lock()
|
||||
addModSumLocked(mod, h)
|
||||
goSum.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// haveModSumLocked reports whether the pair mod,h is already listed in go.sum.
|
||||
// If it finds a conflicting pair instead, it calls base.Fatalf.
|
||||
// goSum.mu must be locked.
|
||||
func haveModSumLocked(mod module.Version, h string) bool {
|
||||
goSum.checked[modSum{mod, h}] = true
|
||||
for _, vh := range goSum.m[mod] {
|
||||
if h == vh {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(vh, "h1:") {
|
||||
base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tgo.sum: %v"+goSumMismatch, mod.Path, mod.Version, h, vh)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addModSumLocked adds the pair mod,h to go.sum.
|
||||
// goSum.mu must be locked.
|
||||
func addModSumLocked(mod module.Version, h string) {
|
||||
if haveModSumLocked(mod, h) {
|
||||
return
|
||||
}
|
||||
if len(goSum.m[mod]) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "warning: verifying %s@%s: unknown hashes in go.sum: %v; adding %v"+hashVersionMismatch, mod.Path, mod.Version, strings.Join(goSum.m[mod], ", "), h)
|
||||
}
|
||||
|
|
@ -444,16 +455,22 @@ func checkOneSumLocked(mod module.Version, h string) {
|
|||
goSum.dirty = true
|
||||
}
|
||||
|
||||
// checkNotarySum checks the mod, h pair against the Go notary.
|
||||
// checkSumDB checks the mod, h pair against the Go checksum database.
|
||||
// It calls base.Fatalf if the hash is to be rejected.
|
||||
func checkNotarySum(mod module.Version, h string) {
|
||||
hashes := notaryHashes(mod)
|
||||
for _, vh := range hashes {
|
||||
if h == vh {
|
||||
func checkSumDB(mod module.Version, h string) {
|
||||
db, lines, err := lookupSumDB(mod)
|
||||
if err != nil {
|
||||
base.Fatalf("verifying %s@%s: %v", mod.Path, mod.Version, err)
|
||||
}
|
||||
|
||||
have := mod.Path + " " + mod.Version + " " + h
|
||||
prefix := mod.Path + " " + mod.Version + " h1:"
|
||||
for _, line := range lines {
|
||||
if line == have {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(vh, "h1:") {
|
||||
base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\tnotary: %v"+notarySumMismatch, mod.Path, mod.Version, h, vh)
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
base.Fatalf("verifying %s@%s: checksum mismatch\n\tdownloaded: %v\n\t%s: %v"+sumdbMismatch, mod.Path, mod.Version, h, db, line[len(prefix)-len("h1:"):])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -526,7 +543,8 @@ func WriteGoSum() {
|
|||
goSum.m = make(map[module.Version][]string, len(goSum.m))
|
||||
readGoSum(goSum.m, GoSumFile, data)
|
||||
for ms := range goSum.checked {
|
||||
checkOneSumLocked(ms.mod, ms.sum)
|
||||
addModSumLocked(ms.mod, ms.sum)
|
||||
goSum.dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -587,10 +605,10 @@ have intercepted the download attempt.
|
|||
For more information, see 'go help module-auth'.
|
||||
`
|
||||
|
||||
const notarySumMismatch = `
|
||||
const sumdbMismatch = `
|
||||
|
||||
SECURITY ERROR
|
||||
This download does NOT match the expected download known to the notary.
|
||||
This download does NOT match the one reported by the checksum server.
|
||||
The bits may have been replaced on the origin server, or an attacker may
|
||||
have intercepted the download attempt.
|
||||
|
||||
|
|
@ -662,31 +680,44 @@ go.sum is wrong or the downloaded code is wrong. Usually go.sum is right:
|
|||
you want to use the same code you used yesterday.
|
||||
|
||||
If a downloaded module is not yet included in go.sum and it is a publicly
|
||||
available module, the go command consults the Go notary server to fetch
|
||||
available module, the go command consults the Go checksum database to fetch
|
||||
the expected go.sum lines. If the downloaded code does not match those
|
||||
lines, the go command reports the mismatch and exits. Note that the
|
||||
notary is not consulted for module versions already listed in go.sum.
|
||||
database is not consulted for module versions already listed in go.sum.
|
||||
|
||||
The GONOVERIFY environment variable is a comma-separated list of
|
||||
If a go.sum mismatch is reported, it is always worth investigating why
|
||||
the code downloaded today differs from what was downloaded yesterday.
|
||||
|
||||
The GOSUMDB environment variable identifies the name of checksum database
|
||||
to use and optionally its public key and URL, as in:
|
||||
|
||||
GOSUMDB="sum.golang.org"
|
||||
GOSUMDB="sum.golang.org+<publickey>"
|
||||
GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"
|
||||
|
||||
The go command knows the public key of sum.golang.org; use of any other
|
||||
database requires giving the public key explicitly. The URL defaults to
|
||||
"https://" followed by the database name.
|
||||
|
||||
GOSUMDB defaults to "sum.golang.org" when GOPROXY="https://proxy.golang.org"
|
||||
and otherwise defaults to "off". NOTE: The GOSUMDB will later default to
|
||||
"sum.golang.org" unconditionally.
|
||||
|
||||
If GOSUMDB is set to "off", or if "go get" is invoked with the -insecure flag,
|
||||
the checksum database is never consulted, but at the cost of giving up the
|
||||
security guarantee of verified repeatable downloads for all modules.
|
||||
A better way to bypass the checksum database for specific modules is
|
||||
to use the GONOSUMDB environment variable.
|
||||
|
||||
The GONOSUMDB environment variable is a comma-separated list of
|
||||
patterns (in the syntax of Go's path.Match) of module path prefixes
|
||||
that should not be verified using the notary. For example,
|
||||
that should not be compared against the checksum database.
|
||||
For example,
|
||||
|
||||
GONOVERIFY=*.corp.example.com,rsc.io/private
|
||||
GONOSUMDB=*.corp.example.com,rsc.io/private
|
||||
|
||||
disables notary verification for modules with path prefixes matching
|
||||
disables checksum database lookups for modules with path prefixes matching
|
||||
either pattern, including "git.corp.example.com/xyzzy", "rsc.io/private",
|
||||
and "rsc.io/private/quux".
|
||||
|
||||
As a special case, if GONOVERIFY is set to "off", or if "go get" was invoked
|
||||
with the -insecure flag, the notary is never consulted, but note that this
|
||||
defeats the security provided by the notary. A better course of action is
|
||||
to set a narrower GONOVERIFY and, in the case of go.sum mismatches,
|
||||
investigate why the code downloaded code differs from what was
|
||||
downloaded yesterday.
|
||||
|
||||
NOTE: Early in the Go 1.13 dev cycle, the notary is being simulated by
|
||||
a whitelist of known hashes for popular Go modules, to expose any
|
||||
problems arising from knowing the expected hashes.
|
||||
TODO(rsc): This note should be removed once the real notary is used instead. See #30601.
|
||||
`,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package modfetch
|
||||
|
||||
var knownGOSUMDB = map[string]string{
|
||||
"sum.golang.org": "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8",
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package modfetch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
pathpkg "path"
|
||||
"strings"
|
||||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/get"
|
||||
"cmd/go/internal/module"
|
||||
)
|
||||
|
||||
// notaryShouldVerify reports whether the notary should be used for path,
|
||||
// given the GONOVERIFY setting.
|
||||
func notaryShouldVerify(path, GONOVERIFY string) (bool, error) {
|
||||
if GONOVERIFY == "off" {
|
||||
return false, nil
|
||||
}
|
||||
for GONOVERIFY != "" {
|
||||
var pattern string
|
||||
i := strings.Index(GONOVERIFY, ",")
|
||||
if i < 0 {
|
||||
pattern, GONOVERIFY = GONOVERIFY, ""
|
||||
} else {
|
||||
pattern, GONOVERIFY = GONOVERIFY[:i], GONOVERIFY[i+1:]
|
||||
}
|
||||
if pattern == "" {
|
||||
continue
|
||||
}
|
||||
n := strings.Count(pattern, "/") + 1
|
||||
prefix := path
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if prefix[i] == '/' {
|
||||
n--
|
||||
if n == 0 {
|
||||
prefix = prefix[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if n > 1 {
|
||||
continue
|
||||
}
|
||||
matched, err := pathpkg.Match(pattern, prefix)
|
||||
if err != nil {
|
||||
// Note that path.Match does not guarantee to detect
|
||||
// pattern errors. It usually depends on whether the
|
||||
// given text (prefix in this case) matches enough of
|
||||
// the pattern to reach the error. So this will only
|
||||
// trigger on malformed patterns that are "close enough" to prefix.
|
||||
return false, fmt.Errorf("malformed GONOVERIFY pattern: %s", pattern)
|
||||
}
|
||||
if matched {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// useNotary reports whether to use the notary for the given module.
|
||||
func useNotary(mod module.Version) bool {
|
||||
if get.Insecure {
|
||||
return false
|
||||
}
|
||||
wantNotary, err := notaryShouldVerify(mod.Path, cfg.Getenv("GONOVERIFY"))
|
||||
if err != nil {
|
||||
base.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
// TODO(rsc): return wantNotary. See #30601.
|
||||
//
|
||||
// This code must be deleted when goSumPin is deleted.
|
||||
// goSumPin is only a partial notary simulation, so we don't return true from
|
||||
// useNotary when we don't have an entry for that module.
|
||||
// This differs from the real notary, which will be authoritative
|
||||
// for everything it is asked for. When goSumPin is removed,
|
||||
// this function body should end here with "return wantNotary".
|
||||
|
||||
_ = goSumPin // read TODO above if goSumPin is gone
|
||||
return wantNotary && notaryHashes(mod) != nil
|
||||
}
|
||||
|
||||
// notaryHashes fetches hashes for mod from the notary.
|
||||
// The caller must have checked that useNotary(mod) is true.
|
||||
func notaryHashes(mod module.Version) []string {
|
||||
// For testing, hard-code this result.
|
||||
if mod.Path == "rsc.io/badsum" {
|
||||
switch mod.Version {
|
||||
case "v1.0.0":
|
||||
return []string{"h1:6/o+QJfe6mFSNuegDihphabcvR94anXQk/qq7Enr19U="}
|
||||
case "v1.0.0/go.mod":
|
||||
return []string{"h1:avOsLUJaHavllihBU9qCTW37z64ypkZjqZg8O16JLVY="}
|
||||
case "v1.0.1":
|
||||
return []string{"h1:S7G9Ikksx7htnFivDrUOv8xI0kIdAf15gLt97Gy//Zk="}
|
||||
case "v1.0.1/go.mod":
|
||||
return []string{"h1:avOsLUJaHavllihBU9qCTW37z64ypkZjqZg8O16JLVY="}
|
||||
}
|
||||
}
|
||||
|
||||
// Until the notary is ready, simulate contacting the notary by
|
||||
// looking in the known hash list goSumPin in pin.go.
|
||||
// Entries not listed in goSumPin are treated as "not for the notary",
|
||||
// but once the real notary is added, they should be treated as
|
||||
// "failed to verify".
|
||||
//
|
||||
// TODO(rsc): Once the notary is ready, this function should be
|
||||
// rewritten to use it. See #30601.
|
||||
i := strings.Index(goSumPin, "\n"+mod.Path+"\n")
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
wantGoSum := false
|
||||
if strings.HasSuffix(mod.Version, "/go.mod") {
|
||||
wantGoSum = true
|
||||
mod.Version = strings.TrimSuffix(mod.Version, "/go.mod")
|
||||
}
|
||||
versions := goSumPin[i+1+len(mod.Path)+1:]
|
||||
var lastSum, lastGoSum string
|
||||
for {
|
||||
i := strings.Index(versions, "\n")
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
line := versions[:i]
|
||||
versions = versions[i+1:]
|
||||
if !strings.HasPrefix(line, " ") {
|
||||
break
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 3 {
|
||||
break
|
||||
}
|
||||
if f[1] == "-" {
|
||||
f[1] = lastSum
|
||||
} else {
|
||||
lastSum = f[1]
|
||||
}
|
||||
if f[2] == "-" {
|
||||
f[2] = lastGoSum
|
||||
} else {
|
||||
lastGoSum = f[2]
|
||||
}
|
||||
if f[0] == mod.Version {
|
||||
if wantGoSum {
|
||||
return []string{f[2]}
|
||||
}
|
||||
return []string{f[1]}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package modfetch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var notaryShouldVerifyTests = []struct {
|
||||
modPath string
|
||||
GONOVERIFY string
|
||||
result int // -1 = bad GONOVERIFY, 0 = wantNotary=false, 1 = wantNotary=true
|
||||
}{
|
||||
{"anything", "off", 0},
|
||||
{"anything", "", 1},
|
||||
{"anything", ",", 1},
|
||||
{"anything", ",foo,", 1},
|
||||
{"anything", "[malformed", -1},
|
||||
{"anything", "malformed[", 1},
|
||||
{"my.corp.example.com", "*.[c]orp.*", 0},
|
||||
{"my.corp.example.com/foo", "*.c[^a]rp.*", 0},
|
||||
{"my.corp.example.com", "*.corp.*,bar.com", 0},
|
||||
{"my.corp.example.com/foo", "*.corp.*,bar.com", 0},
|
||||
{"my.corp.example.com", "bar.com,*.corp.*", 0},
|
||||
{"my.corp.example.com/foo", "bar.com,*.corp.*", 0},
|
||||
{"bar.com", "*.corp.*", 1},
|
||||
{"bar.com/foo", "*.corp.*", 1},
|
||||
{"bar.com", "*.corp.*,bar.com", 0},
|
||||
{"bar.com/foo", "*.corp.*,bar.com", 0},
|
||||
{"bar.com", "bar.com,*.corp.*", 0},
|
||||
{"bar.com/foo", "bar.com,*.corp.*", 0},
|
||||
}
|
||||
|
||||
func TestNotaryShouldVerify(t *testing.T) {
|
||||
for _, tt := range notaryShouldVerifyTests {
|
||||
wantNotary, err := notaryShouldVerify(tt.modPath, tt.GONOVERIFY)
|
||||
if wantNotary != (tt.result > 0) || (err != nil) != (tt.result < 0) {
|
||||
wantErr := "nil"
|
||||
if tt.result < 0 {
|
||||
wantErr = "non-nil error"
|
||||
}
|
||||
t.Errorf("notaryShouldVerify(%q, %q) = %v, %v, want %v, %s", tt.modPath, tt.GONOVERIFY, wantNotary, err, tt.result > 0, wantErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,7 +10,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
url "net/url"
|
||||
"net/url"
|
||||
"os"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"cmd/go/internal/modfetch/codehost"
|
||||
"cmd/go/internal/par"
|
||||
"cmd/go/internal/semver"
|
||||
"cmd/go/internal/str"
|
||||
web "cmd/go/internal/web"
|
||||
)
|
||||
|
||||
|
|
@ -205,10 +206,9 @@ func lookup(path string) (r Repo, err error) {
|
|||
if proxyURL == "off" {
|
||||
return nil, fmt.Errorf("module lookup disabled by GOPROXY=%s", proxyURL)
|
||||
}
|
||||
if proxyURL != "" && proxyURL != "direct" {
|
||||
if proxyURL != "" && proxyURL != "direct" && !str.GlobsMatchPath(cfg.GONOPROXY, path) {
|
||||
return lookupProxy(path)
|
||||
}
|
||||
|
||||
return lookupDirect(path)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Go checksum database lookup
|
||||
|
||||
// +build !cmd_go_bootstrap
|
||||
|
||||
package modfetch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cmd/go/internal/base"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/get"
|
||||
"cmd/go/internal/lockedfile"
|
||||
"cmd/go/internal/module"
|
||||
"cmd/go/internal/note"
|
||||
"cmd/go/internal/str"
|
||||
"cmd/go/internal/sumweb"
|
||||
"cmd/go/internal/web"
|
||||
)
|
||||
|
||||
// useSumDB reports whether to use the Go checksum database for the given module.
|
||||
func useSumDB(mod module.Version) bool {
|
||||
return cfg.GOSUMDB != "off" && !get.Insecure && !str.GlobsMatchPath(cfg.GONOSUMDB, mod.Path)
|
||||
}
|
||||
|
||||
// lookupSumDB returns the Go checksum database's go.sum lines for the given module,
|
||||
// along with the name of the database.
|
||||
func lookupSumDB(mod module.Version) (dbname string, lines []string, err error) {
|
||||
dbOnce.Do(func() {
|
||||
dbName, db, dbErr = dbDial()
|
||||
})
|
||||
if dbErr != nil {
|
||||
return "", nil, dbErr
|
||||
}
|
||||
lines, err = db.Lookup(mod.Path, mod.Version)
|
||||
return dbName, lines, err
|
||||
}
|
||||
|
||||
var (
|
||||
dbOnce sync.Once
|
||||
dbName string
|
||||
db *sumweb.Conn
|
||||
dbErr error
|
||||
)
|
||||
|
||||
func dbDial() (dbName string, db *sumweb.Conn, err error) {
|
||||
// $GOSUMDB can be "key" or "key url",
|
||||
// and the key can be a full verifier key
|
||||
// or a host on our list of known keys.
|
||||
key := strings.Fields(cfg.Getenv("GOSUMDB"))
|
||||
if len(key) >= 1 {
|
||||
if k := knownGOSUMDB[key[0]]; k != "" {
|
||||
key[0] = k
|
||||
}
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return "", nil, fmt.Errorf("missing GOSUMDB")
|
||||
}
|
||||
if len(key) > 2 {
|
||||
return "", nil, fmt.Errorf("invalid GOSUMDB: too many fields")
|
||||
}
|
||||
vkey, err := note.NewVerifier(key[0])
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid GOSUMDB: %v", err)
|
||||
}
|
||||
name := vkey.Name()
|
||||
|
||||
// No funny business in the database name.
|
||||
direct, err := url.Parse("https://" + name)
|
||||
if err != nil || strings.HasSuffix(name, "/") || *direct != (url.URL{Scheme: "https", Host: direct.Host, Path: direct.Path, RawPath: direct.RawPath}) || direct.RawPath != "" || direct.Host == "" {
|
||||
return "", nil, fmt.Errorf("invalid sumdb name (must be host[/path]): %s %+v", name, *direct)
|
||||
}
|
||||
|
||||
// Determine how to get to database.
|
||||
var base *url.URL
|
||||
if len(key) >= 2 {
|
||||
// Use explicit alternate URL listed in $GOSUMDB,
|
||||
// bypassing both the default URL derivation and any proxies.
|
||||
u, err := url.Parse(key[1])
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid GOSUMDB URL: %v", err)
|
||||
}
|
||||
base = u
|
||||
}
|
||||
|
||||
return name, sumweb.NewConn(&dbClient{key: key[0], name: name, direct: direct, base: base}), nil
|
||||
}
|
||||
|
||||
type dbClient struct {
|
||||
key string
|
||||
name string
|
||||
direct *url.URL
|
||||
|
||||
once sync.Once
|
||||
base *url.URL
|
||||
baseErr error
|
||||
}
|
||||
|
||||
func (c *dbClient) ReadRemote(path string) ([]byte, error) {
|
||||
c.once.Do(c.initBase)
|
||||
if c.baseErr != nil {
|
||||
return nil, c.baseErr
|
||||
}
|
||||
|
||||
var data []byte
|
||||
start := time.Now()
|
||||
targ := web.Join(c.base, path)
|
||||
data, err := web.GetBytes(targ)
|
||||
if false {
|
||||
fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), web.Redacted(targ))
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// initBase determines the base URL for connecting to the database.
|
||||
// Determining the URL requires sending network traffic to proxies,
|
||||
// so this work is delayed until we need to download something from
|
||||
// the database. If everything we need is in the local cache and
|
||||
// c.ReadRemote is never called, we will never do this work.
|
||||
func (c *dbClient) initBase() {
|
||||
if c.base != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Try proxies in turn until we find out how to connect to this database.
|
||||
urls, err := proxyURLs()
|
||||
if err != nil {
|
||||
c.baseErr = err
|
||||
return
|
||||
}
|
||||
for _, proxyURL := range urls {
|
||||
if proxyURL == "direct" {
|
||||
break
|
||||
}
|
||||
proxy, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
c.baseErr = err
|
||||
return
|
||||
}
|
||||
// Quoting https://golang.org/design/25530-sumdb#proxying-a-checksum-database:
|
||||
//
|
||||
// Before accessing any checksum database URL using a proxy,
|
||||
// the proxy client should first fetch <proxyURL>/sumdb/<sumdb-name>/supported.
|
||||
// If that request returns a successful (HTTP 200) response, then the proxy supports
|
||||
// proxying checksum database requests. In that case, the client should use
|
||||
// the proxied access method only, never falling back to a direct connection to the database.
|
||||
// If the /sumdb/<sumdb-name>/supported check fails with a “not found” (HTTP 404)
|
||||
// or “gone” (HTTP 410) response, the proxy is unwilling to proxy the checksum database,
|
||||
// and the client should connect directly to the database.
|
||||
// Any other response is treated as the database being unavailable.
|
||||
_, err = web.GetBytes(web.Join(proxy, "sumdb/"+c.name+"/supported"))
|
||||
if err == nil {
|
||||
// Success! This proxy will help us.
|
||||
c.base = web.Join(proxy, "sumdb/"+c.name)
|
||||
return
|
||||
}
|
||||
// If the proxy serves a non-404/410, give up.
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
c.baseErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// No proxies, or all proxies said 404, or we reached an explicit "direct".
|
||||
c.base = c.direct
|
||||
}
|
||||
|
||||
// ReadConfig reads the key from c.key
|
||||
// and otherwise reads the config (a latest tree head) from GOPATH/pkg/sumdb/<file>.
|
||||
func (c *dbClient) ReadConfig(file string) (data []byte, err error) {
|
||||
if file == "key" {
|
||||
return []byte(c.key), nil
|
||||
}
|
||||
|
||||
// GOPATH/pkg is PkgMod/..
|
||||
targ := filepath.Join(PkgMod, "../sumdb/"+file)
|
||||
data, err = lockedfile.Read(targ)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Treat non-existent as empty, to bootstrap the "latest" file
|
||||
// the first time we connect to a given database.
|
||||
return []byte{}, nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// WriteConfig rewrites the latest tree head.
|
||||
func (*dbClient) WriteConfig(file string, old, new []byte) error {
|
||||
if file == "key" {
|
||||
// Should not happen.
|
||||
return fmt.Errorf("cannot write key")
|
||||
}
|
||||
targ := filepath.Join(PkgMod, "../sumdb/"+file)
|
||||
os.MkdirAll(filepath.Dir(targ), 0777)
|
||||
f, err := lockedfile.Edit(targ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) > 0 && !bytes.Equal(data, old) {
|
||||
return sumweb.ErrWriteConflict
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(new); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// ReadCache reads cached lookups or tiles from
|
||||
// GOPATH/pkg/mod/download/cache/sumdb,
|
||||
// which will be deleted by "go clean -modcache".
|
||||
func (*dbClient) ReadCache(file string) ([]byte, error) {
|
||||
targ := filepath.Join(PkgMod, "download/cache/sumdb", file)
|
||||
return lockedfile.Read(targ)
|
||||
}
|
||||
|
||||
// WriteCache updates cached lookups or tiles.
|
||||
func (*dbClient) WriteCache(file string, data []byte) {
|
||||
targ := filepath.Join(PkgMod, "download/cache/sumdb", file)
|
||||
os.MkdirAll(filepath.Dir(targ), 0777)
|
||||
lockedfile.Write(targ, bytes.NewReader(data), 0666)
|
||||
}
|
||||
|
||||
func (*dbClient) Log(msg string) {
|
||||
// nothing for now
|
||||
}
|
||||
|
||||
func (*dbClient) SecurityError(msg string) {
|
||||
base.Fatalf("%s", msg)
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ var Paths = []string{
|
|||
"/tile/",
|
||||
}
|
||||
|
||||
var modVerRE = regexp.MustCompile(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?$`)
|
||||
var modVerRE = regexp.MustCompile(`^[^@]+@v[0-9]+\.[0-9]+\.[0-9]+(-[^@]*)?(\+incompatible)?$`)
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, err := h.Server.NewContext(r)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SecurityMode specifies whether a function should make network
|
||||
|
|
@ -118,3 +119,15 @@ func Redacted(u *url.URL) string {
|
|||
func OpenBrowser(url string) (opened bool) {
|
||||
return openBrowser(url)
|
||||
}
|
||||
|
||||
// Join returns the result of adding the slash-separated
|
||||
// path elements to the end of u's path.
|
||||
func Join(u *url.URL, path string) *url.URL {
|
||||
j := *u
|
||||
if path == "" {
|
||||
return &j
|
||||
}
|
||||
j.Path = strings.TrimSuffix(u.Path, "/") + "/" + strings.TrimPrefix(path, "/")
|
||||
j.RawPath = strings.TrimSuffix(u.RawPath, "/") + "/" + strings.TrimPrefix(path, "/")
|
||||
return &j
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
|
|
@ -21,11 +22,13 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"cmd/go/internal/dirhash"
|
||||
"cmd/go/internal/modfetch"
|
||||
"cmd/go/internal/modfetch/codehost"
|
||||
"cmd/go/internal/module"
|
||||
"cmd/go/internal/par"
|
||||
"cmd/go/internal/semver"
|
||||
"cmd/go/internal/sumweb"
|
||||
"cmd/go/internal/txtar"
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +46,6 @@ var proxyOnce sync.Once
|
|||
// The proxy serves from testdata/mod. See testdata/mod/README.
|
||||
func StartProxy() {
|
||||
proxyOnce.Do(func() {
|
||||
fmt.Fprintf(os.Stderr, "go test proxy starting\n")
|
||||
readModList()
|
||||
addr := *proxyAddr
|
||||
if addr == "" {
|
||||
|
|
@ -59,6 +61,11 @@ func StartProxy() {
|
|||
go func() {
|
||||
log.Fatalf("go proxy: http.Serve: %v", http.Serve(l, http.HandlerFunc(proxyHandler)))
|
||||
}()
|
||||
|
||||
// Prepopulate main sumdb.
|
||||
for _, mod := range modList {
|
||||
sumdbHandler.Server.Lookup(nil, mod.Path+"@"+mod.Version)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +89,9 @@ func readModList() {
|
|||
encPath := strings.ReplaceAll(name[:i], "_", "/")
|
||||
path, err := module.DecodePath(encPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
|
||||
if encPath != "example.com/invalidpath/v1" {
|
||||
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
encVers := name[i+1:]
|
||||
|
|
@ -97,6 +106,15 @@ func readModList() {
|
|||
|
||||
var zipCache par.Cache
|
||||
|
||||
const (
|
||||
testSumDBName = "localhost.localdev/sumdb"
|
||||
testSumDBVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
|
||||
testSumDBSignerKey = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
|
||||
)
|
||||
|
||||
var sumdbHandler = &sumweb.Handler{Server: sumweb.NewTestServer(testSumDBSignerKey, proxyGoSum)}
|
||||
var sumdbWrongHandler = &sumweb.Handler{Server: sumweb.NewTestServer(testSumDBSignerKey, proxyGoSumWrong)}
|
||||
|
||||
// proxyHandler serves the Go module proxy protocol.
|
||||
// See the proxy section of https://research.swtch.com/vgo-module.
|
||||
func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -104,17 +122,66 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
path := strings.TrimPrefix(r.URL.Path, "/mod/")
|
||||
path := r.URL.Path[len("/mod/"):]
|
||||
|
||||
// If asked for 404/abc, serve a 404.
|
||||
// /mod/quiet/ does not print errors.
|
||||
quiet := false
|
||||
if strings.HasPrefix(path, "quiet/") {
|
||||
path = path[len("quiet/"):]
|
||||
quiet = true
|
||||
}
|
||||
|
||||
// Next element may opt into special behavior.
|
||||
if j := strings.Index(path, "/"); j >= 0 {
|
||||
n, err := strconv.Atoi(path[:j])
|
||||
if err == nil && n >= 200 {
|
||||
w.WriteHeader(n)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(path, "sumdb-") {
|
||||
n, err := strconv.Atoi(path[len("sumdb-"):j])
|
||||
if err == nil && n >= 200 {
|
||||
if strings.HasPrefix(path[j:], "/sumdb/") {
|
||||
w.WriteHeader(n)
|
||||
return
|
||||
}
|
||||
path = path[j+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request for $GOPROXY/sumdb-direct is direct sumdb access.
|
||||
// (Client thinks it is talking directly to a sumdb.)
|
||||
if strings.HasPrefix(path, "sumdb-direct/") {
|
||||
r.URL.Path = path[len("sumdb-direct"):]
|
||||
sumdbHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Request for $GOPROXY/sumdb-wrong is direct sumdb access
|
||||
// but all the hashes are wrong.
|
||||
// (Client thinks it is talking directly to a sumdb.)
|
||||
if strings.HasPrefix(path, "sumdb-wrong/") {
|
||||
r.URL.Path = path[len("sumdb-wrong"):]
|
||||
sumdbWrongHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Request for $GOPROXY/sumdb/<name>/supported
|
||||
// is checking whether it's OK to access sumdb via the proxy.
|
||||
if path == "sumdb/"+testSumDBName+"/supported" {
|
||||
w.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
|
||||
// Request for $GOPROXY/sumdb/<name>/... goes to sumdb.
|
||||
if sumdbPrefix := "sumdb/" + testSumDBName + "/"; strings.HasPrefix(path, sumdbPrefix) {
|
||||
r.URL.Path = path[len(sumdbPrefix)-1:]
|
||||
sumdbHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Module proxy request: /mod/path/@v/version[.suffix]
|
||||
i := strings.Index(path, "/@v/")
|
||||
if i < 0 {
|
||||
http.NotFound(w, r)
|
||||
|
|
@ -123,7 +190,9 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
enc, file := path[:i], path[i+len("/@v/"):]
|
||||
path, err := module.DecodePath(enc)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
|
||||
if !quiet {
|
||||
fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
|
@ -179,9 +248,11 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
a := readArchive(path, vers)
|
||||
if a == nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: no archive %s %s\n", path, vers)
|
||||
a, err := readArchive(path, vers)
|
||||
if err != nil {
|
||||
if !quiet {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: no archive %s %s: %v\n", path, vers, err)
|
||||
}
|
||||
http.Error(w, "cannot load archive", 500)
|
||||
return
|
||||
}
|
||||
|
|
@ -229,7 +300,9 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}).(cached)
|
||||
|
||||
if c.err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: %v\n", c.err)
|
||||
if !quiet {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: %v\n", c.err)
|
||||
}
|
||||
http.Error(w, c.err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
|
@ -241,8 +314,8 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func findHash(m module.Version) string {
|
||||
a := readArchive(m.Path, m.Version)
|
||||
if a == nil {
|
||||
a, err := readArchive(m.Path, m.Version)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var data []byte
|
||||
|
|
@ -261,16 +334,14 @@ var archiveCache par.Cache
|
|||
|
||||
var cmdGoDir, _ = os.Getwd()
|
||||
|
||||
func readArchive(path, vers string) *txtar.Archive {
|
||||
func readArchive(path, vers string) (*txtar.Archive, error) {
|
||||
enc, err := module.EncodePath(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
encVers, err := module.EncodeVersion(vers)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefix := strings.ReplaceAll(enc, "/", "_")
|
||||
|
|
@ -285,5 +356,51 @@ func readArchive(path, vers string) *txtar.Archive {
|
|||
}
|
||||
return a
|
||||
}).(*txtar.Archive)
|
||||
return a
|
||||
if a == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// proxyGoSum returns the two go.sum lines for path@vers.
|
||||
func proxyGoSum(path, vers string) ([]byte, error) {
|
||||
a, err := readArchive(path, vers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var names []string
|
||||
files := make(map[string][]byte)
|
||||
var gomod []byte
|
||||
for _, f := range a.Files {
|
||||
if strings.HasPrefix(f.Name, ".") {
|
||||
if f.Name == ".mod" {
|
||||
gomod = f.Data
|
||||
}
|
||||
continue
|
||||
}
|
||||
name := path + "@" + vers + "/" + f.Name
|
||||
names = append(names, name)
|
||||
files[name] = f.Data
|
||||
}
|
||||
h1, err := dirhash.Hash1(names, func(name string) (io.ReadCloser, error) {
|
||||
data := files[name]
|
||||
return ioutil.NopCloser(bytes.NewReader(data)), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h1mod, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(gomod)), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := []byte(fmt.Sprintf("%s %s %s\n%s %s/go.mod %s\n", path, vers, h1, path, vers, h1mod))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// proxyGoSumWrong returns the wrong lines.
|
||||
func proxyGoSumWrong(path, vers string) ([]byte, error) {
|
||||
data := []byte(fmt.Sprintf("%s %s %s\n%s %s/go.mod %s\n", path, vers, "h1:wrong", path, vers, "h1:wrong"))
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ func (ts *testScript) setup() {
|
|||
"GOPATH=" + filepath.Join(ts.workdir, "gopath"),
|
||||
"GOPROXY=" + proxyURL,
|
||||
"GOROOT=" + testGOROOT,
|
||||
"GONOVERIFY=*",
|
||||
"GOSUMDB=" + testSumDBVerifierKey,
|
||||
tempEnvName() + "=" + filepath.Join(ts.workdir, "tmp"),
|
||||
"devnull=" + os.DevNull,
|
||||
"goversion=" + goVersion(ts),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
env GO111MODULE=on
|
||||
env GOPROXY=direct
|
||||
env GOSUMDB=off
|
||||
|
||||
# Without credentials, downloading a module from a path that requires HTTPS
|
||||
# basic auth should fail.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
env GO111MODULE=on
|
||||
env GOPROXY=$GOPROXY/quiet
|
||||
|
||||
# download with version should print nothing
|
||||
go mod download rsc.io/quote@v1.5.0
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ env GO111MODULE=on
|
|||
# Testing mod download with non semantic versions; turn off proxy.
|
||||
[!net] skip
|
||||
[!exec:git] skip
|
||||
env GOPROXY=
|
||||
env GOPROXY=direct
|
||||
env GOSUMDB=off
|
||||
|
||||
go mod download rsc.io/quote@a91498bed0a73d4bb9c1fb2597925f7883bc40a7
|
||||
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v0.0.0-20180709162918-a91498bed0a7.info
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
env GO111MODULE=on
|
||||
env GOPROXY=$GOPROXY/quiet
|
||||
|
||||
# @commit should resolve
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
env GO111MODULE=on
|
||||
env GOPROXY=$GOPROXY/quiet
|
||||
|
||||
# A 'go get' that worked at a previous version should continue to work at that version,
|
||||
# even if the package was subsequently moved into a submodule.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ env GO111MODULE=on
|
|||
# Testing git->module converter's generation of +incompatible tags; turn off proxy.
|
||||
[!net] skip
|
||||
[!exec:git] skip
|
||||
env GOPROXY=
|
||||
env GOPROXY=direct
|
||||
env GOSUMDB=off
|
||||
|
||||
# We can resolve the @master branch without unshallowing the local repository
|
||||
# (even with older gits), so try that before we do anything else.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ go list
|
|||
|
||||
[!net] skip
|
||||
|
||||
env GOPROXY=
|
||||
env GOPROXY=direct
|
||||
env GOSUMDB=off
|
||||
go get gopkg.in/macaroon-bakery.v2-unstable/bakery
|
||||
go list -m all
|
||||
stdout 'gopkg.in/macaroon-bakery.v2-unstable v2.0.0-[0-9]+-[0-9a-f]+$'
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
env GO111MODULE=on
|
||||
env GONOVERIFY= # default in test scripts is *
|
||||
|
||||
# notary should reject rsc.io/badsum v1.0.0 with good go.mod, bad tree
|
||||
cp go.mod.orig go.mod
|
||||
! go get rsc.io/badsum@v1.0.0
|
||||
stderr 'verifying rsc.io/badsum@v1.0.0: checksum mismatch'
|
||||
stderr 'notary: +h1:6/o\+QJfe6mFSNuegDihphabcvR94anXQk/qq7Enr19U='
|
||||
stderr 'SECURITY ERROR'
|
||||
stderr 'NOT match the expected download known to the notary'
|
||||
stderr 'go help module-auth'
|
||||
grep 'rsc.io/badsum v1.0.0/go.mod' go.sum
|
||||
! grep 'rsc.io/badsum v1.0.0 ' go.sum
|
||||
rm go.sum
|
||||
|
||||
# notary should reject rsc.io/badsum v1.0.1 with bad go.mod, good tree
|
||||
cp go.mod.orig go.mod
|
||||
! go get rsc.io/badsum@v1.0.1
|
||||
stderr 'verifying rsc.io/badsum@v1.0.1/go.mod: checksum mismatch'
|
||||
stderr 'notary: +h1:avOsLUJaHavllihBU9qCTW37z64ypkZjqZg8O16JLVY='
|
||||
stderr 'SECURITY ERROR'
|
||||
stderr 'NOT match the expected download known to the notary'
|
||||
stderr 'go help module-auth'
|
||||
! exists go.sum # failed at go.mod, did not get to the tree
|
||||
|
||||
# notary checks should run even without explicit go.mod
|
||||
rm go.mod
|
||||
rm go.sum
|
||||
! go get rsc.io/badsum@v1.0.0
|
||||
stderr 'verifying rsc.io/badsum@v1.0.0: checksum mismatch'
|
||||
stderr 'notary: +h1:6/o\+QJfe6mFSNuegDihphabcvR94anXQk/qq7Enr19U='
|
||||
stderr 'SECURITY ERROR'
|
||||
stderr 'NOT match the expected download known to the notary'
|
||||
stderr 'go help module-auth'
|
||||
! go get rsc.io/badsum@v1.0.1
|
||||
stderr 'verifying rsc.io/badsum@v1.0.1/go.mod: checksum mismatch'
|
||||
stderr 'notary: +h1:avOsLUJaHavllihBU9qCTW37z64ypkZjqZg8O16JLVY='
|
||||
stderr 'SECURITY ERROR'
|
||||
stderr 'NOT match the expected download known to the notary'
|
||||
stderr 'go help module-auth'
|
||||
! exists go.mod
|
||||
! exists go.sum
|
||||
|
||||
# go get -insecure should skip notary without go.mod
|
||||
go get -insecure rsc.io/badsum@v1.0.0
|
||||
go get -insecure rsc.io/badsum@v1.0.1
|
||||
! exists go.mod
|
||||
! exists go.sum
|
||||
|
||||
# GONOVERIFY should skip notary too
|
||||
env GONOVERIFY=rsc.i[aeiou]
|
||||
go get rsc.io/badsum@v1.0.0
|
||||
go get rsc.io/badsum@v1.0.1
|
||||
! exists go.mod
|
||||
! exists go.sum
|
||||
env GONOVERIFY=
|
||||
|
||||
# go get -insecure should skip notary with go.mod and update go.sum
|
||||
cp go.mod.orig go.mod
|
||||
go get -insecure rsc.io/badsum@v1.0.0
|
||||
go get -insecure rsc.io/badsum@v1.0.1
|
||||
|
||||
# go.sum should override notary
|
||||
cp go.mod.orig go.mod
|
||||
cp go.sum.wrong go.sum
|
||||
go get rsc.io/badsum@v1.0.0
|
||||
go get rsc.io/badsum@v1.0.1
|
||||
|
||||
-- go.mod.orig --
|
||||
module m
|
||||
-- go.sum.wrong --
|
||||
rsc.io/badsum v1.0.0 h1:kZaLRhqz4LgsHPQddRMr+124lTgJfm28AxghGw3vLB0=
|
||||
rsc.io/badsum v1.0.1/go.mod h1:xwKWaN8OFR80Kg5/vdt+V2b+2P5kaLH+wWo5CL+pwHs=
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
env GO111MODULE=on
|
||||
env GOPROXY=$GOPROXY/quiet
|
||||
|
||||
# get excluded version
|
||||
cp go.mod1 go.mod
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
env GO111MODULE=on
|
||||
env sumdb=$GOSUMDB
|
||||
env proxy=$GOPROXY
|
||||
env GOPROXY GONOPROXY GOSUMDB GONOSUMDB
|
||||
env dbname=localhost.localdev/sumdb
|
||||
|
||||
# disagreeing with the sumdb produces security errors
|
||||
# (this also populates tiles on the sumdb server).
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb' '$proxy/sumdb-wrong
|
||||
! go get rsc.io/quote
|
||||
stderr 'verifying rsc.io/quote@v1.5.2/go.mod: checksum mismatch'
|
||||
stderr 'downloaded: h1:LzX7'
|
||||
stderr 'localhost.localdev/sumdb: h1:wrong'
|
||||
stderr 'SECURITY ERROR\nThis download does NOT match the one reported by the checksum server.'
|
||||
! go get rsc.io/sampler
|
||||
! go get golang.org/x/text
|
||||
rm go.sum
|
||||
|
||||
# switching to truthful sumdb detects timeline inconsistency
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb
|
||||
! go get -m rsc.io/fortune
|
||||
stderr 'SECURITY ERROR\ngo.sum database server misbehavior detected!'
|
||||
stderr 'proof of misbehavior:'
|
||||
|
||||
# removing the cached wrong tree head and cached tiles clears the bad data
|
||||
rm $GOPATH/pkg/sumdb/$dbname/latest
|
||||
go clean -modcache
|
||||
go get -m rsc.io/fortune
|
||||
|
||||
-- go.mod.orig --
|
||||
module m
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
env GO111MODULE=on
|
||||
env sumdb=$GOSUMDB
|
||||
env proxy=$GOPROXY
|
||||
env GOPROXY GONOPROXY GOSUMDB GONOSUMDB
|
||||
|
||||
# rejected proxy fails verification
|
||||
cp go.mod.orig go.mod
|
||||
rm go.sum
|
||||
env GOPROXY=$proxy/sumdb-503
|
||||
! go get -m rsc.io/quote
|
||||
stderr 503
|
||||
|
||||
# fetch through working proxy is OK
|
||||
cp go.mod.orig go.mod
|
||||
rm go.sum
|
||||
env GOPROXY=$proxy
|
||||
go get -m rsc.io/quote
|
||||
|
||||
# repeated fetch works entirely from cache, does not consult sumdb
|
||||
cp go.mod.orig go.mod
|
||||
rm go.sum
|
||||
env GOPROXY=$proxy/sumdb-503
|
||||
go get -m rsc.io/quote
|
||||
rm go.sum
|
||||
|
||||
# fetch specific module can work without proxy, using cache or go.sum
|
||||
cp go.mod.orig go.mod
|
||||
rm go.sum
|
||||
env GOPROXY=off
|
||||
go get -m rsc.io/quote@v1.5.2 # using cache
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb/localhost.localdev/sumdb/lookup/rsc.io/quote@v1.5.2
|
||||
go get -m rsc.io/quote@v1.5.2 # using go.sum
|
||||
|
||||
# fetch fails once we lose access to both cache and go.sum
|
||||
rm go.sum
|
||||
env GOPROXY=$proxy/sumdb-504
|
||||
! go get -m rsc.io/quote@v1.5.2
|
||||
stderr 504
|
||||
|
||||
# but -insecure bypasses the checksum lookup entirely
|
||||
go get -m -insecure rsc.io/quote@v1.5.2
|
||||
|
||||
# and then it is in go.sum again
|
||||
go get -m rsc.io/quote@v1.5.2
|
||||
|
||||
-- go.mod.orig --
|
||||
module m
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[!net] skip
|
||||
|
||||
env GOPROXY=
|
||||
env GOSUMDB=
|
||||
go env GOPROXY
|
||||
stdout '^direct$'
|
||||
go env GOSUMDB
|
||||
stdout '^off$'
|
||||
env GOPROXY=https://proxy.golang.org
|
||||
go env GOSUMDB
|
||||
stdout '^sum.golang.org$'
|
||||
|
||||
# download direct from github
|
||||
env GOSUMDB=sum.golang.org
|
||||
env GOPROXY=direct
|
||||
go get -m rsc.io/quote
|
||||
|
||||
# download from proxy.golang.org
|
||||
go clean -modcache
|
||||
env GOSUMDB='sum.golang.org https://sum.golang.org' # TODO remove URL
|
||||
env GOPROXY=https://proxy.golang.org
|
||||
go get -m rsc.io/quote
|
||||
|
||||
-- go.mod --
|
||||
module m
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
env GO111MODULE=on
|
||||
env sumdb=$GOSUMDB
|
||||
env proxy=$GOPROXY
|
||||
env GOPROXY GONOPROXY GOSUMDB GONOSUMDB
|
||||
|
||||
# basic fetch (through proxy) works
|
||||
cp go.mod.orig go.mod
|
||||
go get -m rsc.io/fortune@v1.0.0 # note: must use test proxy, does not exist in real world
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb # rm sumdb cache but NOT package download cache
|
||||
rm go.sum
|
||||
|
||||
# can fetch by explicit URL
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb' '$proxy/sumdb-direct
|
||||
go get -m rsc.io/fortune@v1.0.0
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb
|
||||
rm go.sum
|
||||
|
||||
# direct access fails (because localhost.localdev does not exist)
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb
|
||||
env GOPROXY=direct
|
||||
! go get -m rsc.io/fortune@v1.0.0
|
||||
stderr 'verifying.*localhost.localdev.*no such host'
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb
|
||||
rm go.sum
|
||||
|
||||
# proxy 404 falls back to direct access (which fails)
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb
|
||||
env GOPROXY=$proxy/sumdb-404
|
||||
! go get -m rsc.io/fortune@v1.0.0
|
||||
stderr 'verifying.*localhost.localdev.*no such host'
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb
|
||||
rm go.sum
|
||||
|
||||
# proxy non-200/404/410 stops direct access
|
||||
cp go.mod.orig go.mod
|
||||
env GOSUMDB=$sumdb
|
||||
env GOPROXY=$proxy/sumdb-503
|
||||
! go get -m rsc.io/fortune@v1.0.0
|
||||
stderr '503 Service Unavailable'
|
||||
rm $GOPATH/pkg/mod/download/cache/sumdb
|
||||
rm go.sum
|
||||
|
||||
-- go.mod.orig --
|
||||
module m
|
||||
Loading…
Reference in New Issue