blob: c4f82ab8c18a3d4dd96042368fe9425977e5f8e3 [file] [log] [blame]
// Copyright 2023 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 genericosv
import (
"flag"
"io/fs"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
osvschema "github.com/google/osv-scanner/pkg/models"
"golang.org/x/vulndb/internal/osv"
"golang.org/x/vulndb/internal/proxy"
"golang.org/x/vulndb/internal/report"
)
var (
realProxy = flag.Bool("proxy", false, "if true, contact the real module proxy and update expected responses")
update = flag.Bool("update", false, "if true, update test YAML reports to reflect new expected behavior")
)
var (
testdataDir = "testdata"
testOSVDir = filepath.Join(testdataDir, "osv")
testYAMLDir = filepath.Join(testdataDir, "yaml")
)
// To update test cases to reflect new expected behavior
// (only use -proxy if the calls to the proxy will change):
// go test ./internal/genericosv/... -update -proxy -run TestToReport
func TestToReport(t *testing.T) {
if err := filepath.WalkDir(testOSVDir, func(path string, f fs.DirEntry, err error) error {
if err != nil {
return err
}
if f.IsDir() || filepath.Ext(path) != ".json" {
return nil
}
ghsaID := strings.TrimSuffix(f.Name(), ".json")
t.Run(ghsaID, func(t *testing.T) {
t.Parallel()
pc, err := proxy.NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
osv := &Entry{}
if err := report.UnmarshalFromFile(path, osv); err != nil {
t.Fatal(err)
}
modulePath := "" // ignored
got := report.New(osv, "GO-TEST-ID", modulePath, pc)
// Keep record of what lints would apply to each generated report.
got.LintAsNotes(pc)
yamlFile := filepath.Join(testYAMLDir, ghsaID+".yaml")
if *update {
if err := got.Write(yamlFile); err != nil {
t.Fatal(err)
}
}
want, err := report.Read(yamlFile)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("ToReport() mismatch (-want +got)\n%s", diff)
}
})
return nil
}); err != nil {
t.Fatal(err)
}
}
// TODO(https://go.dev/issues/61769): unskip test cases as we add features.
// To update proxy responses:
// go test ./internal/genericosv/... -proxy -run TestAffectedToModules
func TestAffectedToModules(t *testing.T) {
for _, tc := range []struct {
name string
desc string
in []osvschema.Affected
want []*report.Module
skip bool
}{
{
name: "ok",
desc: "module is already OK",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/influxdata/influxdb",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "0.3.2",
},
{
Fixed: "1.7.6",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/influxdata/influxdb",
Versions: []report.VersionRange{
{
Introduced: "0.3.2",
Fixed: "1.7.6",
}},
VulnerableAt: "1.7.5",
}},
},
{
name: "import_path",
desc: "find module from import path",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/influxdata/influxdb/services/httpd",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "0.3.2",
},
{
Fixed: "1.7.6",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/influxdata/influxdb",
Versions: []report.VersionRange{
{
Introduced: "0.3.2",
Fixed: "1.7.6",
},
},
Packages: []*report.Package{
{
Package: "github.com/influxdata/influxdb/services/httpd",
},
},
VulnerableAt: "1.7.5",
}},
},
{
name: "major_version",
desc: "correct major version of module path",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/nats-io/nats-server",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "2.2.0",
},
{
Fixed: "2.8.3",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/nats-io/nats-server/v2",
Versions: []report.VersionRange{
{
Introduced: "2.2.0",
Fixed: "2.8.3",
},
},
VulnerableAt: "2.8.2",
}},
},
{
name: "canonicalize",
desc: "canonicalize module path",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/golang/vulndb",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Fixed: "0.0.0-20230712151357-4fee11d0f8f9",
},
},
}},
}},
want: []*report.Module{{
Module: "golang.org/x/vulndb",
Versions: []report.VersionRange{
{
Fixed: "0.0.0-20230712151357-4fee11d0f8f9",
},
},
}},
},
{
name: "add_incompatible",
desc: "add +incompatible",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/docker/docker",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Fixed: "23.0.0",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/docker/docker",
Versions: []report.VersionRange{
{
Fixed: "23.0.0+incompatible",
},
},
VulnerableAt: "23.0.0-rc.4+incompatible",
}},
},
{
name: "merge_modules",
desc: "merge modules that are the same except for versions",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/hashicorp/go-getter/v2",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "2.0.0",
},
{
Fixed: "2.0.2",
},
},
}},
},
{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/hashicorp/go-getter/v2",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "2.1.0",
},
{
Fixed: "2.1.1",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/hashicorp/go-getter/v2",
Versions: []report.VersionRange{
{
Introduced: "2.0.0",
Fixed: "2.0.2",
},
{
Introduced: "2.1.0",
Fixed: "2.1.1",
},
},
VulnerableAt: "2.1.0",
}},
},
{
name: "remove_duplicates",
desc: "remove major version duplicates",
in: []osvschema.Affected{{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/hashicorp/go-getter/v2",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "0",
},
{
Fixed: "2.1.0",
},
},
}},
},
{
Package: osvschema.Package{
Ecosystem: osvschema.EcosystemGo,
Name: "github.com/hashicorp/go-getter",
},
Ranges: []osvschema.Range{{
Type: osvschema.RangeEcosystem,
Events: []osvschema.Event{
{
Introduced: "2.0.0",
},
{
Fixed: "2.1.0",
},
},
}},
}},
want: []*report.Module{{
Module: "github.com/hashicorp/go-getter/v2",
Versions: []report.VersionRange{
{
Introduced: "2.0.0",
Fixed: "2.1.0",
},
},
}},
skip: true,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.skip {
t.Skip("skipping (not implemented yet)")
}
pc, err := proxy.NewTestClient(t, *realProxy)
if err != nil {
t.Fatal(err)
}
got := affectedToModules(tc.in, pc)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("%s: affectedToModules() mismatch (-want +got)\n%s", tc.desc, diff)
}
})
}
}
func TestFixRefs(t *testing.T) {
for _, tc := range []struct {
name string
in, want []*report.Reference
}{
{
// GHSA references are converted to advisory type
name: "to_advisory_ghsa",
in: []*report.Reference{
{
URL: "https://github.com/example/module/security/advisories/GHSA-xxxx-yyyy-zzzz",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/advisories/GHSA-gggg-hhhh-ffff",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/other/module/security/advisories/GHSA-xxxx-yyyy-zzzz",
Type: osv.ReferenceTypeWeb,
},
},
want: []*report.Reference{
{
URL: "https://github.com/example/module/security/advisories/GHSA-xxxx-yyyy-zzzz",
Type: osv.ReferenceTypeAdvisory,
},
{
URL: "https://github.com/advisories/GHSA-gggg-hhhh-ffff",
Type: osv.ReferenceTypeAdvisory,
},
{
URL: "https://github.com/other/module/security/advisories/GHSA-xxxx-yyyy-zzzz",
Type: osv.ReferenceTypeAdvisory, // different module OK, because GHSA matches
},
},
},
{
// CVE references are converted to advisory type
name: "to_advisory_cve",
in: []*report.Reference{
{
URL: "https://nvd.nist.gov/vuln/detail/CVE-1999-0001",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://nvd.nist.gov/vuln/detail/CVE-1999-2222",
Type: osv.ReferenceTypeWeb,
},
},
want: []*report.Reference{
{
URL: "https://nvd.nist.gov/vuln/detail/CVE-1999-0001",
Type: osv.ReferenceTypeAdvisory,
},
{
URL: "https://nvd.nist.gov/vuln/detail/CVE-1999-2222",
Type: osv.ReferenceTypeWeb, // different CVE, keep "web" type
},
},
},
{
// CVE references are removed if GHSA is present
name: "remove_cve",
in: []*report.Reference{
{
URL: "https://github.com/advisories/GHSA-gggg-hhhh-ffff",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://nvd.nist.gov/vuln/detail/CVE-1999-0001",
Type: osv.ReferenceTypeWeb,
},
},
want: []*report.Reference{
{
URL: "https://github.com/advisories/GHSA-gggg-hhhh-ffff",
Type: osv.ReferenceTypeAdvisory,
},
},
},
{
name: "to_fix_or_report",
in: []*report.Reference{
{
URL: "https://github.com/example/module/pull/123",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/example/module/commit/123",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/module/module/issues/123",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/example/module/issue/123",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/different/module/issue/123",
Type: osv.ReferenceTypeWeb,
},
},
want: []*report.Reference{
{
URL: "https://github.com/example/module/pull/123",
Type: osv.ReferenceTypeFix,
},
{
URL: "https://github.com/example/module/commit/123",
Type: osv.ReferenceTypeFix,
},
{
URL: "https://github.com/module/module/issues/123",
Type: osv.ReferenceTypeReport,
},
{
URL: "https://github.com/example/module/issue/123",
Type: osv.ReferenceTypeReport,
},
{
URL: "https://github.com/different/module/issue/123",
Type: osv.ReferenceTypeWeb, // different module, keep web type
},
},
},
{
// package references and go advisory references are deleted
name: "delete",
in: []*report.Reference{
{
URL: "https://pkg.go.dev/vuln/GO-0000-0000",
Type: osv.ReferenceTypeWeb,
},
{
URL: "https://github.com/anything",
Type: osv.ReferenceTypePackage,
},
{
URL: "https://example.com",
Type: osv.ReferenceTypePackage,
},
},
want: nil,
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := &report.Report{
Modules: []*report.Module{
{
Module: "github.com/example/module",
},
{
Module: "github.com/module/module",
},
},
GHSAs: []string{"GHSA-xxxx-yyyy-zzzz", "GHSA-gggg-hhhh-ffff"},
CVEs: []string{"CVE-1999-0001", "CVE-1999-0002"},
References: tc.in,
}
fixRefs(r)
got := r.References
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("fixRefs() mismatch (-want +got)\n%s", diff)
}
})
}
}