Commit 5bf1853e authored by Andrew Gerrand's avatar Andrew Gerrand

cmd/go: don't fetch from insecure repositories without -insecure

Fixes #9637
Fixes #10120

Change-Id: I3728239089efb94d04cd4115c9f840afd7badeaf
Reviewed-on: https://go-review.googlesource.com/9715Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent d8c6dac7
...@@ -424,7 +424,7 @@ Download and install packages and dependencies ...@@ -424,7 +424,7 @@ Download and install packages and dependencies
Usage: Usage:
go get [-d] [-f] [-fix] [-t] [-u] [build flags] [packages] go get [-d] [-f] [-fix] [-insecure] [-t] [-u] [build flags] [packages]
Get downloads and installs the packages named by the import paths, Get downloads and installs the packages named by the import paths,
along with their dependencies. along with their dependencies.
...@@ -440,6 +440,9 @@ of the original. ...@@ -440,6 +440,9 @@ of the original.
The -fix flag instructs get to run the fix tool on the downloaded packages The -fix flag instructs get to run the fix tool on the downloaded packages
before resolving dependencies or building the code. before resolving dependencies or building the code.
The -insecure flag permits fetching from repositories and resolving
custom domains using insecure schemes such as HTTP. Use with caution.
The -t flag instructs get to also download the packages required to build The -t flag instructs get to also download the packages required to build
the tests for the specified packages. the tests for the specified packages.
......
...@@ -29,7 +29,7 @@ func httpGET(url string) ([]byte, error) { ...@@ -29,7 +29,7 @@ func httpGET(url string) ([]byte, error) {
return nil, errHTTP return nil, errHTTP
} }
func httpsOrHTTP(importPath string) (string, io.ReadCloser, error) { func httpsOrHTTP(importPath string, security securityMode) (string, io.ReadCloser, error) {
return "", nil, errHTTP return "", nil, errHTTP
} }
......
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
) )
var cmdGet = &Command{ var cmdGet = &Command{
UsageLine: "get [-d] [-f] [-fix] [-t] [-u] [build flags] [packages]", UsageLine: "get [-d] [-f] [-fix] [-insecure] [-t] [-u] [build flags] [packages]",
Short: "download and install packages and dependencies", Short: "download and install packages and dependencies",
Long: ` Long: `
Get downloads and installs the packages named by the import paths, Get downloads and installs the packages named by the import paths,
...@@ -33,6 +33,9 @@ of the original. ...@@ -33,6 +33,9 @@ of the original.
The -fix flag instructs get to run the fix tool on the downloaded packages The -fix flag instructs get to run the fix tool on the downloaded packages
before resolving dependencies or building the code. before resolving dependencies or building the code.
The -insecure flag permits fetching from repositories and resolving
custom domains using insecure schemes such as HTTP. Use with caution.
The -t flag instructs get to also download the packages required to build The -t flag instructs get to also download the packages required to build
the tests for the specified packages. the tests for the specified packages.
...@@ -62,6 +65,7 @@ var getF = cmdGet.Flag.Bool("f", false, "") ...@@ -62,6 +65,7 @@ var getF = cmdGet.Flag.Bool("f", false, "")
var getT = cmdGet.Flag.Bool("t", false, "") var getT = cmdGet.Flag.Bool("t", false, "")
var getU = cmdGet.Flag.Bool("u", false, "") var getU = cmdGet.Flag.Bool("u", false, "")
var getFix = cmdGet.Flag.Bool("fix", false, "") var getFix = cmdGet.Flag.Bool("fix", false, "")
var getInsecure = cmdGet.Flag.Bool("insecure", false, "")
func init() { func init() {
addBuildFlags(cmdGet) addBuildFlags(cmdGet)
...@@ -279,6 +283,12 @@ func downloadPackage(p *Package) error { ...@@ -279,6 +283,12 @@ func downloadPackage(p *Package) error {
repo, rootPath string repo, rootPath string
err error err error
) )
security := secure
if *getInsecure {
security = insecure
}
if p.build.SrcRoot != "" { if p.build.SrcRoot != "" {
// Directory exists. Look for checkout along path to src. // Directory exists. Look for checkout along path to src.
vcs, rootPath, err = vcsForDir(p) vcs, rootPath, err = vcsForDir(p)
...@@ -288,19 +298,23 @@ func downloadPackage(p *Package) error { ...@@ -288,19 +298,23 @@ func downloadPackage(p *Package) error {
repo = "<local>" // should be unused; make distinctive repo = "<local>" // should be unused; make distinctive
// Double-check where it came from. // Double-check where it came from.
if *getU && vcs.remoteRepo != nil && !*getF { if *getU && vcs.remoteRepo != nil {
dir := filepath.Join(p.build.SrcRoot, rootPath) dir := filepath.Join(p.build.SrcRoot, rootPath)
if remote, err := vcs.remoteRepo(vcs, dir); err == nil { if remote, err := vcs.remoteRepo(vcs, dir); err == nil {
if rr, err := repoRootForImportPath(p.ImportPath); err == nil { repo = remote
repo := rr.repo
if rr.vcs.resolveRepo != nil { if !*getF {
resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo) if rr, err := repoRootForImportPath(p.ImportPath, security); err == nil {
if err == nil { repo := rr.repo
repo = resolved if rr.vcs.resolveRepo != nil {
resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
if err == nil {
repo = resolved
}
}
if remote != repo {
return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.root, repo, dir, remote)
} }
}
if remote != repo {
return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.root, repo, dir, remote)
} }
} }
} }
...@@ -308,12 +322,15 @@ func downloadPackage(p *Package) error { ...@@ -308,12 +322,15 @@ func downloadPackage(p *Package) error {
} else { } else {
// Analyze the import path to determine the version control system, // Analyze the import path to determine the version control system,
// repository, and the import path for the root of the repository. // repository, and the import path for the root of the repository.
rr, err := repoRootForImportPath(p.ImportPath) rr, err := repoRootForImportPath(p.ImportPath, security)
if err != nil { if err != nil {
return err return err
} }
vcs, repo, rootPath = rr.vcs, rr.repo, rr.root vcs, repo, rootPath = rr.vcs, rr.repo, rr.root
} }
if !vcs.isSecure(repo) && !*getInsecure {
return fmt.Errorf("cannot download, %v uses insecure protocol", repo)
}
if p.build.SrcRoot == "" { if p.build.SrcRoot == "" {
// Package not found. Put in first directory of $GOPATH. // Package not found. Put in first directory of $GOPATH.
......
...@@ -530,6 +530,17 @@ func (tg *testgoData) cleanup() { ...@@ -530,6 +530,17 @@ func (tg *testgoData) cleanup() {
} }
} }
// failSSH puts an ssh executable in the PATH that always fails.
// This is to stub out uses of ssh by go get.
func (tg *testgoData) failSSH() {
wd, err := os.Getwd()
if err != nil {
tg.t.Fatal(err)
}
fail := filepath.Join(wd, "testdata/failssh")
tg.setenv("PATH", fmt.Sprintf("%v%c%v", fail, filepath.ListSeparator, os.Getenv("PATH")))
}
func TestFileLineInErrorMessages(t *testing.T) { func TestFileLineInErrorMessages(t *testing.T) {
tg := testgo(t) tg := testgo(t)
defer tg.cleanup() defer tg.cleanup()
...@@ -1848,3 +1859,61 @@ func TestIssue4210(t *testing.T) { ...@@ -1848,3 +1859,61 @@ func TestIssue4210(t *testing.T) {
tg.runFail("build", "y") tg.runFail("build", "y")
tg.grepBoth("is a program", `did not find expected error message ("is a program")`) tg.grepBoth("is a program", `did not find expected error message ("is a program")`)
} }
func TestGoGetInsecure(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tg := testgo(t)
defer tg.cleanup()
tg.makeTempdir()
tg.setenv("GOPATH", tg.path("."))
tg.failSSH()
const repo = "wh3rd.net/git.git"
// Try go get -d of HTTP-only repo (should fail).
tg.runFail("get", "-d", repo)
// Try again with -insecure (should succeed).
tg.run("get", "-d", "-insecure", repo)
// Try updating without -insecure (should fail).
tg.runFail("get", "-d", "-u", "-f", repo)
}
func TestGoGetUpdateInsecure(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tg := testgo(t)
defer tg.cleanup()
tg.makeTempdir()
tg.setenv("GOPATH", tg.path("."))
const repo = "github.com/golang/example"
// Clone the repo via HTTP manually.
cmd := exec.Command("git", "clone", "-q", "http://"+repo, tg.path("src/"+repo))
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("cloning %v repo: %v\n%s", repo, err, out)
}
// Update without -insecure should fail.
// Update with -insecure should succeed.
// We need -f to ignore import comments.
const pkg = repo + "/hello"
tg.runFail("get", "-d", "-u", "-f", pkg)
tg.run("get", "-d", "-u", "-f", "-insecure", pkg)
}
func TestGoGetInsecureCustomDomain(t *testing.T) {
testenv.MustHaveExternalNetwork(t)
tg := testgo(t)
defer tg.cleanup()
tg.makeTempdir()
tg.setenv("GOPATH", tg.path("."))
const repo = "wh3rd.net/repo"
tg.runFail("get", "-d", repo)
tg.run("get", "-d", "-insecure", repo)
}
...@@ -18,11 +18,15 @@ import ( ...@@ -18,11 +18,15 @@ import (
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"time"
) )
// httpClient is the default HTTP client, but a variable so it can be // httpClient is the default HTTP client, but a variable so it can be
// changed by tests, without modifying http.DefaultClient. // changed by tests, without modifying http.DefaultClient.
var httpClient = http.DefaultClient var httpClient = http.DefaultClient
var impatientHTTPClient = &http.Client{
Timeout: time.Duration(5 * time.Second),
}
type httpError struct { type httpError struct {
status string status string
...@@ -55,7 +59,7 @@ func httpGET(url string) ([]byte, error) { ...@@ -55,7 +59,7 @@ func httpGET(url string) ([]byte, error) {
// httpsOrHTTP returns the body of either the importPath's // httpsOrHTTP returns the body of either the importPath's
// https resource or, if unavailable, the http resource. // https resource or, if unavailable, the http resource.
func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) { func httpsOrHTTP(importPath string, security securityMode) (urlStr string, body io.ReadCloser, err error) {
fetch := func(scheme string) (urlStr string, res *http.Response, err error) { fetch := func(scheme string) (urlStr string, res *http.Response, err error) {
u, err := url.Parse(scheme + "://" + importPath) u, err := url.Parse(scheme + "://" + importPath)
if err != nil { if err != nil {
...@@ -66,7 +70,11 @@ func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err erro ...@@ -66,7 +70,11 @@ func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err erro
if buildV { if buildV {
log.Printf("Fetching %s", urlStr) log.Printf("Fetching %s", urlStr)
} }
res, err = httpClient.Get(urlStr) if security == insecure && scheme == "https" { // fail earlier
res, err = impatientHTTPClient.Get(urlStr)
} else {
res, err = httpClient.Get(urlStr)
}
return return
} }
closeBody := func(res *http.Response) { closeBody := func(res *http.Response) {
...@@ -84,7 +92,9 @@ func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err erro ...@@ -84,7 +92,9 @@ func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err erro
} }
} }
closeBody(res) closeBody(res)
urlStr, res, err = fetch("http") if security == insecure {
urlStr, res, err = fetch("http")
}
} }
if err != nil { if err != nil {
closeBody(res) closeBody(res)
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"internal/singleflight" "internal/singleflight"
"log" "log"
"net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
...@@ -40,6 +41,22 @@ type vcsCmd struct { ...@@ -40,6 +41,22 @@ type vcsCmd struct {
resolveRepo func(v *vcsCmd, rootDir, remoteRepo string) (realRepo string, err error) resolveRepo func(v *vcsCmd, rootDir, remoteRepo string) (realRepo string, err error)
} }
var isSecureScheme = map[string]bool{
"https": true,
"git+ssh": true,
"bzr+ssh": true,
"svn+ssh": true,
}
func (v *vcsCmd) isSecure(repo string) bool {
u, err := url.Parse(repo)
if err != nil {
// If repo is not a URL, it's not secure.
return false
}
return isSecureScheme[u.Scheme]
}
// A tagCmd describes a command to list available tags // A tagCmd describes a command to list available tags
// that can be passed to tagSyncCmd. // that can be passed to tagSyncCmd.
type tagCmd struct { type tagCmd struct {
...@@ -134,10 +151,17 @@ func gitRemoteRepo(vcsGit *vcsCmd, rootDir string) (remoteRepo string, err error ...@@ -134,10 +151,17 @@ func gitRemoteRepo(vcsGit *vcsCmd, rootDir string) (remoteRepo string, err error
if err != nil { if err != nil {
return "", err return "", err
} }
repoUrl := strings.TrimSpace(string(outb)) repoURL, err := url.Parse(strings.TrimSpace(string(outb)))
if err != nil {
return "", err
}
// Iterate over insecure schemes too, because this function simply
// reports the state of the repo. If we can't see insecure schemes then
// we can't report the actual repo URL.
for _, s := range vcsGit.scheme { for _, s := range vcsGit.scheme {
if strings.HasPrefix(repoUrl, s) { if repoURL.Scheme == s {
return repoUrl, nil return repoURL.String(), nil
} }
} }
return "", errParse return "", errParse
...@@ -460,10 +484,20 @@ type repoRoot struct { ...@@ -460,10 +484,20 @@ type repoRoot struct {
var httpPrefixRE = regexp.MustCompile(`^https?:`) var httpPrefixRE = regexp.MustCompile(`^https?:`)
// securityMode specifies whether a function should make network
// calls using insecure transports (eg, plain text HTTP).
// The zero value is "secure".
type securityMode int
const (
secure securityMode = iota
insecure
)
// repoRootForImportPath analyzes importPath to determine the // repoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use. // version control system, and code repository to use.
func repoRootForImportPath(importPath string) (*repoRoot, error) { func repoRootForImportPath(importPath string, security securityMode) (*repoRoot, error) {
rr, err := repoRootForImportPathStatic(importPath, "") rr, err := repoRootForImportPathStatic(importPath, "", security)
if err == errUnknownSite { if err == errUnknownSite {
// If there are wildcards, look up the thing before the wildcard, // If there are wildcards, look up the thing before the wildcard,
// hoping it applies to the wildcarded parts too. // hoping it applies to the wildcarded parts too.
...@@ -472,7 +506,7 @@ func repoRootForImportPath(importPath string) (*repoRoot, error) { ...@@ -472,7 +506,7 @@ func repoRootForImportPath(importPath string) (*repoRoot, error) {
if i := strings.Index(lookup, "/.../"); i >= 0 { if i := strings.Index(lookup, "/.../"); i >= 0 {
lookup = lookup[:i] lookup = lookup[:i]
} }
rr, err = repoRootForImportDynamic(lookup) rr, err = repoRootForImportDynamic(lookup, security)
// repoRootForImportDynamic returns error detail // repoRootForImportDynamic returns error detail
// that is irrelevant if the user didn't intend to use a // that is irrelevant if the user didn't intend to use a
...@@ -502,7 +536,7 @@ var errUnknownSite = errors.New("dynamic lookup required to find mapping") ...@@ -502,7 +536,7 @@ var errUnknownSite = errors.New("dynamic lookup required to find mapping")
// containing its VCS type (foo.com/repo.git/dir) // containing its VCS type (foo.com/repo.git/dir)
// //
// If scheme is non-empty, that scheme is forced. // If scheme is non-empty, that scheme is forced.
func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) { func repoRootForImportPathStatic(importPath, scheme string, security securityMode) (*repoRoot, error) {
// A common error is to use https://packagepath because that's what // A common error is to use https://packagepath because that's what
// hg and git require. Diagnose this helpfully. // hg and git require. Diagnose this helpfully.
if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil { if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil {
...@@ -552,6 +586,9 @@ func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) { ...@@ -552,6 +586,9 @@ func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
match["repo"] = scheme + "://" + match["repo"] match["repo"] = scheme + "://" + match["repo"]
} else { } else {
for _, scheme := range vcs.scheme { for _, scheme := range vcs.scheme {
if security == secure && !isSecureScheme[scheme] {
continue
}
if vcs.ping(scheme, match["repo"]) == nil { if vcs.ping(scheme, match["repo"]) == nil {
match["repo"] = scheme + "://" + match["repo"] match["repo"] = scheme + "://" + match["repo"]
break break
...@@ -573,7 +610,7 @@ func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) { ...@@ -573,7 +610,7 @@ func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
// statically known by repoRootForImportPathStatic. // statically known by repoRootForImportPathStatic.
// //
// This handles custom import paths like "name.tld/pkg/foo". // This handles custom import paths like "name.tld/pkg/foo".
func repoRootForImportDynamic(importPath string) (*repoRoot, error) { func repoRootForImportDynamic(importPath string, security securityMode) (*repoRoot, error) {
slash := strings.Index(importPath, "/") slash := strings.Index(importPath, "/")
if slash < 0 { if slash < 0 {
return nil, errors.New("import path does not contain a slash") return nil, errors.New("import path does not contain a slash")
...@@ -582,9 +619,13 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) { ...@@ -582,9 +619,13 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
if !strings.Contains(host, ".") { if !strings.Contains(host, ".") {
return nil, errors.New("import path does not begin with hostname") return nil, errors.New("import path does not begin with hostname")
} }
urlStr, body, err := httpsOrHTTP(importPath) urlStr, body, err := httpsOrHTTP(importPath, security)
if err != nil { if err != nil {
return nil, fmt.Errorf("http/https fetch: %v", err) msg := "https fetch: %v"
if security == insecure {
msg = "http/" + msg
}
return nil, fmt.Errorf(msg, err)
} }
defer body.Close() defer body.Close()
imports, err := parseMetaGoImports(body) imports, err := parseMetaGoImports(body)
...@@ -614,7 +655,7 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) { ...@@ -614,7 +655,7 @@ func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
} }
urlStr0 := urlStr urlStr0 := urlStr
var imports []metaImport var imports []metaImport
urlStr, imports, err = metaImportsForPrefix(mmi.Prefix) urlStr, imports, err = metaImportsForPrefix(mmi.Prefix, security)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -652,7 +693,7 @@ var ( ...@@ -652,7 +693,7 @@ var (
// It is an error if no imports are found. // It is an error if no imports are found.
// urlStr will still be valid if err != nil. // urlStr will still be valid if err != nil.
// The returned urlStr will be of the form "https://golang.org/x/tools?go-get=1" // The returned urlStr will be of the form "https://golang.org/x/tools?go-get=1"
func metaImportsForPrefix(importPrefix string) (urlStr string, imports []metaImport, err error) { func metaImportsForPrefix(importPrefix string, security securityMode) (urlStr string, imports []metaImport, err error) {
setCache := func(res fetchResult) (fetchResult, error) { setCache := func(res fetchResult) (fetchResult, error) {
fetchCacheMu.Lock() fetchCacheMu.Lock()
defer fetchCacheMu.Unlock() defer fetchCacheMu.Unlock()
...@@ -668,7 +709,7 @@ func metaImportsForPrefix(importPrefix string) (urlStr string, imports []metaImp ...@@ -668,7 +709,7 @@ func metaImportsForPrefix(importPrefix string) (urlStr string, imports []metaImp
} }
fetchCacheMu.Unlock() fetchCacheMu.Unlock()
urlStr, body, err := httpsOrHTTP(importPrefix) urlStr, body, err := httpsOrHTTP(importPrefix, security)
if err != nil { if err != nil {
return setCache(fetchResult{urlStr: urlStr, err: fmt.Errorf("fetch %s: %v", urlStr, err)}) return setCache(fetchResult{urlStr: urlStr, err: fmt.Errorf("fetch %s: %v", urlStr, err)})
} }
......
...@@ -99,7 +99,7 @@ func TestRepoRootForImportPath(t *testing.T) { ...@@ -99,7 +99,7 @@ func TestRepoRootForImportPath(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
got, err := repoRootForImportPath(test.path) got, err := repoRootForImportPath(test.path, secure)
want := test.want want := test.want
if want == nil { if want == nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment