Skip to content

Commit

Permalink
internal/frontend: collect vulns for versions page
Browse files Browse the repository at this point in the history
Store the vulnerabilities for each version in the
structs that are handed to the rendering templates.

Later CLs will display them on the versions page.

For golang/go#48223

Change-Id: Icbc541b5d981ea84d5b97b142c48d312219f3aba
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/347971
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
  • Loading branch information
jba committed Sep 7, 2021
1 parent 1b07102 commit a741209
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 19 deletions.
5 changes: 3 additions & 2 deletions internal/frontend/server.go
Expand Up @@ -33,6 +33,7 @@ import (
"golang.org/x/pkgsite/internal/static"
"golang.org/x/pkgsite/internal/version"
vulndbc "golang.org/x/vulndb/client"
"golang.org/x/vulndb/osv"
)

// Server can be installed to serve the go discovery frontend.
Expand All @@ -51,7 +52,7 @@ type Server struct {
serveStats bool
reportingClient *errorreporting.Client
fileMux *http.ServeMux
vulndbClient *vulndbc.Client
getVulnEntries vulnEntriesFunc

mu sync.Mutex // Protects all fields below
templates map[string]*template.Template
Expand Down Expand Up @@ -98,7 +99,7 @@ func NewServer(scfg ServerConfig) (_ *Server, err error) {
serveStats: scfg.ServeStats,
reportingClient: scfg.ReportingClient,
fileMux: http.NewServeMux(),
vulndbClient: scfg.VulndbClient,
getVulnEntries: func(m string) ([]*osv.Entry, error) { return scfg.VulndbClient.Get([]string{m}) },
}
errorPageBytes, err := s.renderErrorPage(context.Background(), http.StatusInternalServerError, "error", nil)
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions internal/frontend/tabs.go
Expand Up @@ -76,14 +76,15 @@ func init() {
// fetchDetailsForPackage returns tab details by delegating to the correct detail
// handler.
func fetchDetailsForUnit(ctx context.Context, r *http.Request, tab string, ds internal.DataSource, um *internal.UnitMeta,
requestedVersion string, bc internal.BuildContext) (_ interface{}, err error) {
requestedVersion string, bc internal.BuildContext,
getVulnEntries vulnEntriesFunc) (_ interface{}, err error) {
defer derrors.Wrap(&err, "fetchDetailsForUnit(r, %q, ds, um=%q,%q,%q)", tab, um.Path, um.ModulePath, um.Version)
switch tab {
case tabMain:
_, expandReadme := r.URL.Query()["readme"]
return fetchMainDetails(ctx, ds, um, requestedVersion, expandReadme, bc)
case tabVersions:
return fetchVersionsDetails(ctx, ds, um)
return fetchVersionsDetails(ctx, ds, um, getVulnEntries)
case tabImports:
return fetchImportsDetails(ctx, ds, um.Path, um.ModulePath, um.Version)
case tabImportedBy:
Expand Down
8 changes: 3 additions & 5 deletions internal/frontend/unit.go
Expand Up @@ -22,7 +22,6 @@ import (
"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.
Expand Down Expand Up @@ -123,7 +122,7 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht
// It's also okay to provide just one (e.g. GOOS=windows), which will select
// the first doc with that value, ignoring the other one.
bc := internal.BuildContext{GOOS: r.FormValue("GOOS"), GOARCH: r.FormValue("GOARCH")}
d, err := fetchDetailsForUnit(ctx, r, tab, ds, um, info.requestedVersion, bc)
d, err := fetchDetailsForUnit(ctx, r, tab, ds, um, info.requestedVersion, bc, s.getVulnEntries)
if err != nil {
return err
}
Expand Down Expand Up @@ -212,9 +211,8 @@ func (s *Server) serveUnitPage(ctx context.Context, w http.ResponseWriter, r *ht

// 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 s.getVulnEntries != nil && experiment.IsActive(ctx, internal.ExperimentVulns) {
vulns, err = Vulns(um.ModulePath, um.Version, um.Path, s.getVulnEntries)
if err != nil {
vulns = []Vuln{{Details: fmt.Sprintf("could not get vulnerability data: %v", err)}}
}
Expand Down
19 changes: 15 additions & 4 deletions internal/frontend/versions.go
Expand Up @@ -14,6 +14,7 @@ import (

"golang.org/x/mod/semver"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/experiment"
"golang.org/x/pkgsite/internal/log"
"golang.org/x/pkgsite/internal/postgres"
"golang.org/x/pkgsite/internal/stdlib"
Expand Down Expand Up @@ -78,9 +79,10 @@ type VersionSummary struct {
RetractionRationale string
IsMinor bool
Symbols [][]*Symbol
Vulns []Vuln
}

func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta) (*VersionsDetails, error) {
func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta, getVulnEntries vulnEntriesFunc) (*VersionsDetails, error) {
db, ok := ds.(*postgres.DB)
if !ok {
// The proxydatasource does not support the imported by page.
Expand Down Expand Up @@ -109,7 +111,7 @@ func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, um *inter
}
return constructUnitURL(versionPath, mi.ModulePath, linkVersion(mi.ModulePath, mi.Version, mi.Version))
}
return buildVersionDetails(um.ModulePath, versions, sh, linkify), nil
return buildVersionDetails(ctx, um.ModulePath, versions, sh, linkify, getVulnEntries), nil
}

// pathInVersion constructs the full import path of the package corresponding
Expand All @@ -136,10 +138,12 @@ func pathInVersion(v1Path string, mi *internal.ModuleInfo) string {
// versions tab, organizing major versions into those that have the same module
// path as the package version under consideration, and those that don't. The
// given versions MUST be sorted first by module path and then by semver.
func buildVersionDetails(currentModulePath string,
func buildVersionDetails(ctx context.Context, currentModulePath string,
modInfos []*internal.ModuleInfo,
sh *internal.SymbolHistory,
linkify func(v *internal.ModuleInfo) string) *VersionsDetails {
linkify func(v *internal.ModuleInfo) string,
getVulnEntries vulnEntriesFunc,
) *VersionsDetails {
// lists organizes versions by VersionListKey. Note that major version isn't
// sufficient as a key: there are packages contained in the same major
// version of different modules, for example github.com/hashicorp/vault/api,
Expand Down Expand Up @@ -190,6 +194,13 @@ func buildVersionDetails(currentModulePath string,
if sv := sh.SymbolsAtVersion(mi.Version); sv != nil {
vs.Symbols = symbolsForVersion(linkify(mi), sv)
}
if experiment.IsActive(ctx, internal.ExperimentVulns) {
vulns, err := Vulns(mi.ModulePath, mi.Version, "", getVulnEntries)
if err != nil {
vulns = []Vuln{{Details: fmt.Sprintf("could not get vulnerability data: %v", err)}}
}
vs.Vulns = vulns
}
if _, ok := lists[key]; !ok {
seenLists = append(seenLists, key)
}
Expand Down
31 changes: 29 additions & 2 deletions internal/frontend/versions_test.go
Expand Up @@ -10,10 +10,12 @@ import (

"github.com/google/go-cmp/cmp"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/experiment"
"golang.org/x/pkgsite/internal/postgres"
"golang.org/x/pkgsite/internal/stdlib"
"golang.org/x/pkgsite/internal/testing/sample"
"golang.org/x/pkgsite/internal/version"
"golang.org/x/vulndb/osv"
)

var (
Expand Down Expand Up @@ -87,6 +89,23 @@ func TestFetchPackageVersionsDetails(t *testing.T) {
}
}

vulnEntry := &osv.Entry{
Details: "vuln",
Affects: osv.Affects{
Ranges: []osv.AffectsRange{{
Type: osv.TypeSemver,
Introduced: "1.2.0",
Fixed: "1.2.3",
}},
},
}
getVulnEntries := func(m string) ([]*osv.Entry, error) {
if m == modulePath1 {
return []*osv.Entry{vulnEntry}, nil
}
return nil, nil
}

for _, tc := range []struct {
name string
pkg *internal.Unit
Expand Down Expand Up @@ -121,7 +140,14 @@ func TestFetchPackageVersionsDetails(t *testing.T) {
},
wantDetails: &VersionsDetails{
ThisModule: []*VersionList{
makeList(v1Path, modulePath1, "v1", []string{"v1.3.0", "v1.2.3", "v1.2.1"}, false),
func() *VersionList {
vl := makeList(v1Path, modulePath1, "v1", []string{"v1.3.0", "v1.2.3", "v1.2.1"}, false)
vl.Versions[2].Vulns = []Vuln{{
Details: vulnEntry.Details,
FixedVersion: "v" + vulnEntry.Affects.Ranges[0].Fixed,
}}
return vl
}(),
},
IncompatibleModules: []*VersionList{
makeList(v1Path, modulePath1, "v2", []string{"v2.1.0+incompatible"}, true),
Expand Down Expand Up @@ -162,13 +188,14 @@ func TestFetchPackageVersionsDetails(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout*2)
defer cancel()
ctx = experiment.NewContext(ctx, internal.ExperimentVulns)
defer postgres.ResetTestDB(testDB, t)

for _, v := range tc.modules {
postgres.MustInsertModule(ctx, t, testDB, v)
}

got, err := fetchVersionsDetails(ctx, testDB, &tc.pkg.UnitMeta)
got, err := fetchVersionsDetails(ctx, testDB, &tc.pkg.UnitMeta, getVulnEntries)
if err != nil {
t.Fatalf("fetchVersionsDetails(ctx, db, %q, %q): %v", tc.pkg.Path, tc.pkg.ModulePath, err)
}
Expand Down
10 changes: 6 additions & 4 deletions internal/frontend/vulns.go
Expand Up @@ -18,11 +18,13 @@ type Vuln struct {
FixedVersion string
}

type vulnEntriesFunc func(string) ([]*osv.Entry, error)

// 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.
// If packagePath is empty, it returns all entries for the module at version.
// The getVulnEntries function should retrieve all entries for the given module path.
// It is passed to facilitate testing.
func Vulns(modulePath, version, packagePath string, getVulnEntries func(string) ([]*osv.Entry, error)) (_ []Vuln, err error) {
func Vulns(modulePath, version, packagePath string, getVulnEntries vulnEntriesFunc) (_ []Vuln, err error) {
defer derrors.Wrap(&err, "Vulns(%q, %q)", modulePath, version)

// Get all the vulns for this module.
Expand All @@ -34,7 +36,7 @@ func Vulns(modulePath, version, packagePath string, getVulnEntries func(string)
// package at this version.
var vulns []Vuln
for _, e := range entries {
if e.Package.Name == packagePath && e.Affects.AffectsSemver(version) {
if (packagePath == "" || e.Package.Name == packagePath) && e.Affects.AffectsSemver(version) {
// Choose the latest fixed version, if any.
var fixed string
for _, r := range e.Affects.Ranges {
Expand Down

0 comments on commit a741209

Please sign in to comment.