internal/frontend: display vulnerabilities on package page
Under an experiment, look up and display a package's vulnerabilities
on its main page using the client provided by the golang.org/x/vulndb
module.
For golang/go#48223
Change-Id: I310440db16f8ad5fe582fc8ab42999e874f3ca88
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/347949
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index 3bb9e9b..04bf2b5 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -28,6 +28,7 @@
"golang.org/x/pkgsite/internal/proxy"
"golang.org/x/pkgsite/internal/queue"
"golang.org/x/pkgsite/internal/source"
+ vulndbc "golang.org/x/vulndb/client"
)
var (
@@ -104,6 +105,10 @@
}
rc := cmdconfig.ReportingClient(ctx, cfg)
+ vc, err := vulndbc.NewClient([]string{cfg.VulnDB}, vulndbc.Options{})
+ if err != nil {
+ log.Fatalf(ctx, "vulndbc.NewClient: %v", err)
+ }
server, err := frontend.NewServer(frontend.ServerConfig{
DataSourceGetter: dsg,
Queue: fetchQueue,
@@ -115,6 +120,7 @@
GoogleTagManagerID: cfg.GoogleTagManagerID,
ServeStats: cfg.ServeStats,
ReportingClient: rc,
+ VulndbClient: vc,
})
if err != nil {
log.Fatalf(ctx, "frontend.NewServer: %v", err)
diff --git a/go.mod b/go.mod
index de77081..eec9f70 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@
github.com/golang-migrate/migrate/v4 v4.6.2
github.com/golang/protobuf v1.4.2
github.com/gomodule/redigo v2.0.0+incompatible // indirect
- github.com/google/go-cmp v0.5.2
+ github.com/google/go-cmp v0.5.4
github.com/google/go-replayers/httpreplay v0.1.0
github.com/google/licensecheck v0.3.1
github.com/google/safehtml v0.0.2
@@ -40,11 +40,12 @@
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect
go.opencensus.io v0.22.4
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
- golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449
+ golang.org/x/mod v0.4.1
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/text v0.3.6
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c
+ golang.org/x/vulndb v0.0.0-20210812203154-5d84be3c9e14
google.golang.org/api v0.32.0
google.golang.org/genproto v0.0.0-20200923140941-5646d36feee1
google.golang.org/grpc v1.32.0
diff --git a/go.sum b/go.sum
index 9c9559c..2b86c14 100644
--- a/go.sum
+++ b/go.sum
@@ -264,8 +264,9 @@
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk=
@@ -698,8 +699,9 @@
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ=
golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -885,6 +887,8 @@
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c h1:AQsh/7arPVFDBraQa8x7GoVnwnGg1kM7J2ySI0kF5WU=
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/vulndb v0.0.0-20210812203154-5d84be3c9e14 h1:fGz1pt31Ygv69LkbU9kkWMChI2ZPUeZ/IzqEce/NA7s=
+golang.org/x/vulndb v0.0.0-20210812203154-5d84be3c9e14/go.mod h1:xh7j0yEDggyETQM2RIfHFmzOcnAwzHg8j8heomkN1Dc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1017,8 +1021,9 @@
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
diff --git a/internal/config/config.go b/internal/config/config.go
index 2272d9e..7b1e934 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -181,6 +181,9 @@
// DisableErrorReporting disables sending errors to the GCP ErrorReporting system.
DisableErrorReporting bool
+
+ // VulnDB is the URL of the Go vulnerability DB.
+ VulnDB string
}
// AppVersionLabel returns the version label for the current instance. This is
@@ -379,6 +382,7 @@
LogLevel: os.Getenv("GO_DISCOVERY_LOG_LEVEL"),
ServeStats: os.Getenv("GO_DISCOVERY_SERVE_STATS") == "true",
DisableErrorReporting: os.Getenv("GO_DISCOVERY_DISABLE_ERROR_REPORTING") == "true",
+ VulnDB: GetEnv("GO_DISCOVERY_VULN_DB", "https://storage.googleapis.com/go-vulndb"),
}
log.SetLevel(cfg.LogLevel)
diff --git a/internal/experiment.go b/internal/experiment.go
index f36f6ac..6efe44c 100644
--- a/internal/experiment.go
+++ b/internal/experiment.go
@@ -14,6 +14,7 @@
ExperimentSkipInsertSymbols = "skip-insert-symbols"
ExperimentStyleGuide = "styleguide"
ExperimentSymbolSearch = "symbol-search"
+ ExperimentVulns = "vulns"
)
// Experiments represents all of the active experiments in the codebase and
@@ -27,6 +28,7 @@
ExperimentSkipInsertSymbols: "Don't insert data into symbols tables.",
ExperimentStyleGuide: "Enable the styleguide.",
ExperimentSymbolSearch: "Enable searching for symbols.",
+ ExperimentVulns: "Enable vulnerability reporting.",
}
// Experiment holds data associated with an experimental feature for frontend
diff --git a/internal/frontend/server.go b/internal/frontend/server.go
index 7e32c4f..570f9f7 100644
--- a/internal/frontend/server.go
+++ b/internal/frontend/server.go
@@ -32,6 +32,7 @@
"golang.org/x/pkgsite/internal/queue"
"golang.org/x/pkgsite/internal/static"
"golang.org/x/pkgsite/internal/version"
+ vulndbc "golang.org/x/vulndb/client"
)
// Server can be installed to serve the go discovery frontend.
@@ -50,6 +51,7 @@
serveStats bool
reportingClient *errorreporting.Client
fileMux *http.ServeMux
+ vulndbClient *vulndbc.Client
mu sync.Mutex // Protects all fields below
templates map[string]*template.Template
@@ -69,6 +71,7 @@
GoogleTagManagerID string
ServeStats bool
ReportingClient *errorreporting.Client
+ VulndbClient *vulndbc.Client
}
// NewServer creates a new Server for the given database and template directory.
@@ -95,6 +98,7 @@
serveStats: scfg.ServeStats,
reportingClient: scfg.ReportingClient,
fileMux: http.NewServeMux(),
+ vulndbClient: scfg.VulndbClient,
}
errorPageBytes, err := s.renderErrorPage(context.Background(), http.StatusInternalServerError, "error", nil)
if err != nil {
diff --git a/internal/frontend/unit.go b/internal/frontend/unit.go
index 5d66d40..1c4e610 100644
--- a/internal/frontend/unit.go
+++ b/internal/frontend/unit.go
@@ -17,10 +17,12 @@
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/cookie"
"golang.org/x/pkgsite/internal/derrors"
+ "golang.org/x/pkgsite/internal/experiment"
"golang.org/x/pkgsite/internal/log"
"golang.org/x/pkgsite/internal/middleware"
"golang.org/x/pkgsite/internal/stdlib"
"golang.org/x/pkgsite/internal/version"
+ "golang.org/x/vulndb/osv"
)
// UnitPage contains data needed to render the unit template.
@@ -85,6 +87,9 @@
// Details contains data specific to the type of page being rendered.
Details interface{}
+
+ // Vulns holds vulnerability information.
+ Vulns []Vuln
}
// serveUnitPage serves a unit page for a path.
@@ -204,6 +209,17 @@
if ok {
page.MetaDescription = metaDescription(main.DocSynopsis)
}
+
+ // Get vulnerability information.
+ var vulns []Vuln
+ if s.vulndbClient != nil && experiment.IsActive(ctx, internal.ExperimentVulns) {
+ getEntries := func(m string) ([]*osv.Entry, error) { return s.vulndbClient.Get([]string{m}) }
+ vulns, err = Vulns(um.ModulePath, um.Version, um.Path, getEntries)
+ if err != nil {
+ vulns = []Vuln{{Details: fmt.Sprintf("could not get vulnerability data: %v", err)}}
+ }
+ page.Vulns = vulns
+ }
s.servePage(ctx, w, tabSettings.TemplateName, page)
return nil
}
diff --git a/internal/frontend/vulns.go b/internal/frontend/vulns.go
new file mode 100644
index 0000000..e6aeabe
--- /dev/null
+++ b/internal/frontend/vulns.go
@@ -0,0 +1,53 @@
+// Copyright 2021 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 frontend
+
+import (
+ "golang.org/x/mod/semver"
+ "golang.org/x/pkgsite/internal/derrors"
+ "golang.org/x/vulndb/osv"
+)
+
+// A Vuln contains information to display about a vulnerability.
+type Vuln struct {
+ // A description of the vulnerability, or the problem in obtaining it.
+ Details string
+ // The version is which the vulnerability has been fixed.
+ FixedVersion string
+}
+
+// Vulns obtains vulnerability information for the given package.
+// the getVulnEntries function should have the same signature and
+// behavior as golang.org/x/vulndb/client.Client.Get.
+// It is passed to facilitate testing.
+func Vulns(modulePath, version, packagePath string, getVulnEntries func(string) ([]*osv.Entry, error)) (_ []Vuln, err error) {
+ defer derrors.Wrap(&err, "Vulns(%q, %q)", modulePath, version)
+
+ // Get all the vulns for this module.
+ entries, err := getVulnEntries(modulePath)
+ if err != nil {
+ return nil, err
+ }
+ // Each entry describes a single vuln. Select the ones that apply to this
+ // package at this version.
+ var vulns []Vuln
+ for _, e := range entries {
+ if e.Package.Name == packagePath && e.Affects.AffectsSemver(version) {
+ // Choose the latest fixed version, if any.
+ var fixed string
+ for _, r := range e.Affects.Ranges {
+ if r.Fixed != "" && (fixed == "" || semver.Compare(r.Fixed, fixed) > 0) {
+ fixed = r.Fixed
+ }
+ }
+ vulns = append(vulns, Vuln{
+ Details: e.Details,
+ // TODO(golang/go#48223): handle stdlib versions
+ FixedVersion: "v" + fixed,
+ })
+ }
+ }
+ return vulns, nil
+}
diff --git a/internal/frontend/vulns_test.go b/internal/frontend/vulns_test.go
new file mode 100644
index 0000000..f82e2a5
--- /dev/null
+++ b/internal/frontend/vulns_test.go
@@ -0,0 +1,55 @@
+// Copyright 2021 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 frontend
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/vulndb/osv"
+)
+
+func TestVulns(t *testing.T) {
+ e := osv.Entry{
+ Package: osv.Package{Name: "bad.com"},
+ Details: "bad",
+ Affects: osv.Affects{
+ Ranges: []osv.AffectsRange{{
+ Type: osv.TypeSemver,
+ Fixed: "1.2.3",
+ }},
+ },
+ }
+ get := func(modulePath string) ([]*osv.Entry, error) {
+ switch modulePath {
+ case "good.com":
+ return nil, nil
+ case "bad.com":
+ return []*osv.Entry{&e}, nil
+ default:
+ return nil, fmt.Errorf("unknown module %q", modulePath)
+ }
+ }
+
+ got, err := Vulns("good.com", "v1.0.0", "good.com", get)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != nil {
+ t.Errorf("got %v, want nil", got)
+ }
+ got, err = Vulns("bad.com", "v1.0.0", "bad.com", get)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := []Vuln{{
+ Details: "bad",
+ FixedVersion: "v1.2.3",
+ }}
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
diff --git a/static/frontend/unit/_header.tmpl b/static/frontend/unit/_header.tmpl
index 0deade6..146eb28 100644
--- a/static/frontend/unit/_header.tmpl
+++ b/static/frontend/unit/_header.tmpl
@@ -220,6 +220,17 @@
/> Redirected from <span data-test-id="redirected-banner-text">{{.}}</span>.
</div>
{{end}}
+ {{if .Experiments.IsActive "vulns"}}
+ {{with .Vulns}}
+ <div class="go-Message go-Message--alert">
+ This package has vulnerabilities.<br/>
+ {{range .}}
+ <p>{{.Details}}</p>
+ {{with .FixedVersion}}Fixed in {{.}}.{{end}}
+ {{end}}
+ </div>
+ {{end}}
+ {{end}}
{{if .Unit.Deprecated}}
<div class="go-Message go-Message--warning">
<img