mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
Optionally orient results by CVE (#1020)
Co-authored-by: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
ef82b33465
commit
a869480f89
58 changed files with 1781 additions and 613 deletions
150
cmd/root.go
150
cmd/root.go
|
@ -1,6 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/matcher/golang"
|
||||
"github.com/anchore/grype/grype/matcher/java"
|
||||
"github.com/anchore/grype/grype/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/matcher/python"
|
||||
"github.com/anchore/grype/grype/matcher/ruby"
|
||||
|
@ -167,6 +169,11 @@ func setRootFlags(flags *pflag.FlagSet) {
|
|||
"ignore matches for vulnerabilities that are fixed",
|
||||
)
|
||||
|
||||
flags.BoolP(
|
||||
"by-cve", "", false,
|
||||
"orient results by CVE instead of the original vulnerability ID when possible",
|
||||
)
|
||||
|
||||
flags.BoolP(
|
||||
"show-suppressed", "", false,
|
||||
"show suppressed/ignored vulnerabilities in the output (only supported with table output format)",
|
||||
|
@ -229,6 +236,10 @@ func bindRootConfigOptions(flags *pflag.FlagSet) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := viper.BindPFlag("by-cve", flags.Lookup("by-cve")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := viper.BindPFlag("show-suppressed", flags.Lookup("show-suppressed")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -298,28 +309,13 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
|
|||
return
|
||||
}
|
||||
|
||||
if appConfig.CheckForAppUpdate {
|
||||
isAvailable, newVersion, err := version.IsUpdateAvailable()
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
if isAvailable {
|
||||
log.Infof("new version of %s is available: %s (currently running: %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)
|
||||
checkForAppUpdate()
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.AppUpdateAvailable,
|
||||
Value: newVersion,
|
||||
})
|
||||
} else {
|
||||
log.Debugf("No new %s update available", internal.ApplicationName)
|
||||
}
|
||||
}
|
||||
|
||||
var store *store.Store
|
||||
var str *store.Store
|
||||
var status *db.Status
|
||||
var dbCloser *db.Closer
|
||||
var packages []pkg.Package
|
||||
var context pkg.Context
|
||||
var pkgContext pkg.Context
|
||||
var wg = &sync.WaitGroup{}
|
||||
var loadedDB, gatheredPackages bool
|
||||
|
||||
|
@ -328,7 +324,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
log.Debug("loading DB")
|
||||
store, status, dbCloser, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
|
||||
str, status, dbCloser, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
|
||||
if err = validateDBLoad(err, status); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
|
@ -339,7 +335,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
log.Debugf("gathering packages")
|
||||
packages, context, err = pkg.Provide(userInput, getProviderConfig())
|
||||
packages, pkgContext, err = pkg.Provide(userInput, getProviderConfig())
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to catalog: %w", err)
|
||||
return
|
||||
|
@ -364,35 +360,27 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
|
|||
appConfig.Ignore = append(appConfig.Ignore, ignoreFixedMatches...)
|
||||
}
|
||||
|
||||
applyDistroHint(packages, &context, appConfig)
|
||||
applyDistroHint(packages, &pkgContext, appConfig)
|
||||
|
||||
matchers := matcher.NewDefaultMatchers(matcher.Config{
|
||||
Java: appConfig.ExternalSources.ToJavaMatcherConfig(appConfig.Match.Java),
|
||||
Ruby: ruby.MatcherConfig(appConfig.Match.Ruby),
|
||||
Python: python.MatcherConfig(appConfig.Match.Python),
|
||||
Dotnet: dotnet.MatcherConfig(appConfig.Match.Dotnet),
|
||||
Javascript: javascript.MatcherConfig(appConfig.Match.Javascript),
|
||||
Golang: golang.MatcherConfig(appConfig.Match.Golang),
|
||||
Stock: stock.MatcherConfig(appConfig.Match.Stock),
|
||||
})
|
||||
|
||||
allMatches := grype.FindVulnerabilitiesForPackage(*store, context.Distro, matchers, packages)
|
||||
remainingMatches, ignoredMatches := match.ApplyIgnoreRules(allMatches, appConfig.Ignore)
|
||||
|
||||
if count := len(ignoredMatches); count > 0 {
|
||||
log.Infof("ignoring %d matches due to user-provided ignore rules", count)
|
||||
vulnMatcher := grype.VulnerabilityMatcher{
|
||||
Store: *str,
|
||||
IgnoreRules: appConfig.Ignore,
|
||||
NormalizeByCVE: appConfig.ByCVE,
|
||||
FailSeverity: failOnSeverity,
|
||||
Matchers: getMatchers(),
|
||||
}
|
||||
|
||||
// determine if there are any severities >= to the max allowable severity (which is optional).
|
||||
// note: until the shared file lock in sqlittle is fixed the sqlite DB cannot be access concurrently,
|
||||
// implying that the fail-on-severity check must be done before sending the presenter object.
|
||||
if hitSeverityThreshold(failOnSeverity, remainingMatches, store) {
|
||||
errs <- grypeerr.ErrAboveSeverityThreshold
|
||||
remainingMatches, ignoredMatches, err := vulnMatcher.FindMatches(packages, pkgContext)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
if !errors.Is(err, grypeerr.ErrAboveSeverityThreshold) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.VulnerabilityScanningFinished,
|
||||
Value: presenter.GetPresenter(presenterConfig, remainingMatches, ignoredMatches, packages, context, store, appConfig, status),
|
||||
Value: presenter.GetPresenter(presenterConfig, *remainingMatches, ignoredMatches, packages, pkgContext, str, appConfig, status),
|
||||
})
|
||||
}()
|
||||
return errs
|
||||
|
@ -434,15 +422,57 @@ func applyDistroHint(pkgs []pkg.Package, context *pkg.Context, appConfig *config
|
|||
}
|
||||
}
|
||||
|
||||
func checkForAppUpdate() {
|
||||
if !appConfig.CheckForAppUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
isAvailable, newVersion, err := version.IsUpdateAvailable()
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
if isAvailable {
|
||||
log.Infof("new version of %s is available: %s (currently running: %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.AppUpdateAvailable,
|
||||
Value: newVersion,
|
||||
})
|
||||
} else {
|
||||
log.Debugf("no new %s update available", internal.ApplicationName)
|
||||
}
|
||||
}
|
||||
|
||||
func getMatchers() []matcher.Matcher {
|
||||
return matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Java: java.MatcherConfig{
|
||||
ExternalSearchConfig: appConfig.ExternalSources.ToJavaMatcherConfig(),
|
||||
UseCPEs: appConfig.Match.Java.UseCPEs,
|
||||
},
|
||||
Ruby: ruby.MatcherConfig(appConfig.Match.Ruby),
|
||||
Python: python.MatcherConfig(appConfig.Match.Python),
|
||||
Dotnet: dotnet.MatcherConfig(appConfig.Match.Dotnet),
|
||||
Javascript: javascript.MatcherConfig(appConfig.Match.Javascript),
|
||||
Golang: golang.MatcherConfig(appConfig.Match.Golang),
|
||||
Stock: stock.MatcherConfig(appConfig.Match.Stock),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func getProviderConfig() pkg.ProviderConfig {
|
||||
return pkg.ProviderConfig{
|
||||
RegistryOptions: appConfig.Registry.ToOptions(),
|
||||
Exclusions: appConfig.Exclusions,
|
||||
CatalogingOptions: appConfig.Search.ToConfig(),
|
||||
GenerateMissingCPEs: appConfig.GenerateMissingCPEs,
|
||||
Platform: appConfig.Platform,
|
||||
AttestationPublicKey: appConfig.Attestation.PublicKey,
|
||||
AttestationIgnoreVerification: appConfig.Attestation.SkipVerification,
|
||||
SyftProviderConfig: pkg.SyftProviderConfig{
|
||||
RegistryOptions: appConfig.Registry.ToOptions(),
|
||||
Exclusions: appConfig.Exclusions,
|
||||
CatalogingOptions: appConfig.Search.ToConfig(),
|
||||
Platform: appConfig.Platform,
|
||||
AttestationPublicKey: appConfig.Attestation.PublicKey,
|
||||
AttestationIgnoreVerification: appConfig.Attestation.SkipVerification,
|
||||
},
|
||||
SynthesisConfig: pkg.SynthesisConfig{
|
||||
GenerateMissingCPEs: appConfig.GenerateMissingCPEs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -476,25 +506,3 @@ func validateRootArgs(cmd *cobra.Command, args []string) error {
|
|||
|
||||
return cobra.MaximumNArgs(1)(cmd, args)
|
||||
}
|
||||
|
||||
// hitSeverityThreshold indicates if there are any severities >= to the max allowable severity (which is optional)
|
||||
func hitSeverityThreshold(thresholdSeverity *vulnerability.Severity, matches match.Matches, metadataProvider vulnerability.MetadataProvider) bool {
|
||||
if thresholdSeverity != nil {
|
||||
var maxDiscoveredSeverity vulnerability.Severity
|
||||
for m := range matches.Enumerate() {
|
||||
metadata, err := metadataProvider.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
severity := vulnerability.ParseSeverity(metadata.Severity)
|
||||
if severity > maxDiscoveredSeverity {
|
||||
maxDiscoveredSeverity = severity
|
||||
}
|
||||
}
|
||||
|
||||
if maxDiscoveredSeverity >= *thresholdSeverity {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
110
cmd/root_test.go
110
cmd/root_test.go
|
@ -3,122 +3,12 @@ package cmd
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/grype/grype/db"
|
||||
grypeDB "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/grype/internal/config"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type mockMetadataStore struct {
|
||||
data map[string]map[string]*grypeDB.VulnerabilityMetadata
|
||||
}
|
||||
|
||||
func newMockStore() *mockMetadataStore {
|
||||
d := mockMetadataStore{
|
||||
data: make(map[string]map[string]*grypeDB.VulnerabilityMetadata),
|
||||
}
|
||||
d.stub()
|
||||
return &d
|
||||
}
|
||||
|
||||
func (d *mockMetadataStore) stub() {
|
||||
d.data["CVE-2014-fake-1"] = map[string]*grypeDB.VulnerabilityMetadata{
|
||||
"source-1": {
|
||||
Severity: "medium",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *mockMetadataStore) GetVulnerabilityMetadata(id, recordSource string) (*grypeDB.VulnerabilityMetadata, error) {
|
||||
return d.data[id][recordSource], nil
|
||||
}
|
||||
|
||||
func (d *mockMetadataStore) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestAboveAllowableSeverity(t *testing.T) {
|
||||
thePkg := pkg.Package{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
Name: "the-package",
|
||||
Version: "v0.1",
|
||||
Type: syftPkg.RpmPkg,
|
||||
}
|
||||
|
||||
matches := match.NewMatches()
|
||||
matches.Add(match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2014-fake-1",
|
||||
Namespace: "source-1",
|
||||
},
|
||||
Package: thePkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
failOnSeverity string
|
||||
matches match.Matches
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "no-severity-set",
|
||||
failOnSeverity: "",
|
||||
matches: matches,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "below-threshold",
|
||||
failOnSeverity: "high",
|
||||
matches: matches,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "at-threshold",
|
||||
failOnSeverity: "medium",
|
||||
matches: matches,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "above-threshold",
|
||||
failOnSeverity: "low",
|
||||
matches: matches,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
metadataProvider := db.NewVulnerabilityMetadataProvider(newMockStore())
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var failOnSeverity *vulnerability.Severity
|
||||
if test.failOnSeverity != "" {
|
||||
sev := vulnerability.ParseSeverity(test.failOnSeverity)
|
||||
if sev == vulnerability.UnknownSeverity {
|
||||
t.Fatalf("could not parse severity")
|
||||
}
|
||||
failOnSeverity = &sev
|
||||
}
|
||||
|
||||
actual := hitSeverityThreshold(failOnSeverity, test.matches, metadataProvider)
|
||||
|
||||
if test.expectedResult != actual {
|
||||
t.Errorf("expected: %v got : %v", test.expectedResult, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_applyDistroHint(t *testing.T) {
|
||||
ctx := pkg.Context{}
|
||||
cfg := config.Application{}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -269,7 +269,7 @@ require (
|
|||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -2323,8 +2323,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
@ -95,8 +95,26 @@ func (s *store) GetVulnerabilityNamespaces() ([]string, error) {
|
|||
return names, result.Error
|
||||
}
|
||||
|
||||
// GetVulnerability retrieves vulnerabilities by namespace and package
|
||||
func (s *store) GetVulnerability(namespace, packageName string) ([]v5.Vulnerability, error) {
|
||||
// GetVulnerability retrieves vulnerabilities by namespace and id
|
||||
func (s *store) GetVulnerability(namespace, id string) ([]v5.Vulnerability, error) {
|
||||
var models []model.VulnerabilityModel
|
||||
|
||||
result := s.db.Where("namespace = ? AND id = ?", namespace, id).Find(&models)
|
||||
|
||||
var vulnerabilities = make([]v5.Vulnerability, len(models))
|
||||
for idx, m := range models {
|
||||
vulnerability, err := m.Inflate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vulnerabilities[idx] = vulnerability
|
||||
}
|
||||
|
||||
return vulnerabilities, result.Error
|
||||
}
|
||||
|
||||
// SearchForVulnerabilities retrieves vulnerabilities by namespace and package
|
||||
func (s *store) SearchForVulnerabilities(namespace, packageName string) ([]v5.Vulnerability, error) {
|
||||
var models []model.VulnerabilityModel
|
||||
|
||||
result := s.db.Where("namespace = ? AND package_name = ?", namespace, packageName).Find(&models)
|
||||
|
|
|
@ -55,7 +55,7 @@ func TestStore_GetID_SetID(t *testing.T) {
|
|||
}
|
||||
|
||||
func assertVulnerabilityReader(t *testing.T, reader v5.VulnerabilityStoreReader, namespace, name string, expected []v5.Vulnerability) {
|
||||
if actual, err := reader.GetVulnerability(namespace, name); err != nil {
|
||||
if actual, err := reader.SearchForVulnerabilities(namespace, name); err != nil {
|
||||
t.Fatalf("failed to get Vulnerability: %+v", err)
|
||||
} else {
|
||||
if len(actual) != len(expected) {
|
||||
|
|
|
@ -10,8 +10,10 @@ type VulnerabilityStore interface {
|
|||
type VulnerabilityStoreReader interface {
|
||||
// GetVulnerabilityNamespaces retrieves unique list of vulnerability namespaces
|
||||
GetVulnerabilityNamespaces() ([]string, error)
|
||||
// GetVulnerability retrieves vulnerabilities by namespace and package
|
||||
GetVulnerability(namespace, packageName string) ([]Vulnerability, error)
|
||||
// GetVulnerability retrieves vulnerabilities by namespace and id
|
||||
GetVulnerability(namespace, id string) ([]Vulnerability, error)
|
||||
// SearchForVulnerabilities retrieves vulnerabilities by namespace and package
|
||||
SearchForVulnerabilities(namespace, packageName string) ([]Vulnerability, error)
|
||||
GetAllVulnerabilities() (*[]Vulnerability, error)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,26 @@ func NewVulnerabilityProvider(reader grypeDB.VulnerabilityStoreReader) (*Vulnera
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (pr *VulnerabilityProvider) Get(id, namespace string) ([]vulnerability.Vulnerability, error) {
|
||||
// note: getting a vulnerability record by id doesn't necessarily return a single record
|
||||
// since records are duplicated by the set of fixes they have.
|
||||
vulns, err := pr.reader.GetVulnerability(namespace, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace=%q pkg=%q: %w", namespace, id, err)
|
||||
}
|
||||
|
||||
var results []vulnerability.Vulnerability
|
||||
for _, vuln := range vulns {
|
||||
vulnObj, err := vulnerability.NewVulnerability(vuln)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to inflate vulnerability record (namespace=%q id=%q): %w", vuln.Namespace, vuln.ID, err)
|
||||
}
|
||||
|
||||
results = append(results, *vulnObj)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (pr *VulnerabilityProvider) GetByDistro(d *distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
|
||||
if d == nil {
|
||||
return nil, nil
|
||||
|
@ -57,16 +77,16 @@ func (pr *VulnerabilityProvider) GetByDistro(d *distro.Distro, p pkg.Package) ([
|
|||
for _, n := range namespaces {
|
||||
for _, packageName := range n.Resolver().Resolve(p) {
|
||||
nsStr := n.String()
|
||||
allPkgVulns, err := pr.reader.GetVulnerability(nsStr, packageName)
|
||||
allPkgVulns, err := pr.reader.SearchForVulnerabilities(nsStr, packageName)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace='%s' pkg='%s': %w", nsStr, packageName, err)
|
||||
return nil, fmt.Errorf("provider failed to search for vulnerabilities (namespace=%q pkg=%q): %w", nsStr, packageName, err)
|
||||
}
|
||||
|
||||
for _, vuln := range allPkgVulns {
|
||||
vulnObj, err := vulnerability.NewVulnerability(vuln)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to parse distro='%s': %w", d, err)
|
||||
return nil, fmt.Errorf("provider failed to inflate vulnerability record (namespace=%q id=%q distro=%q): %w", vuln.Namespace, vuln.ID, d, err)
|
||||
}
|
||||
|
||||
vulnerabilities = append(vulnerabilities, *vulnObj)
|
||||
|
@ -91,16 +111,16 @@ func (pr *VulnerabilityProvider) GetByLanguage(l syftPkg.Language, p pkg.Package
|
|||
for _, n := range namespaces {
|
||||
for _, packageName := range n.Resolver().Resolve(p) {
|
||||
nsStr := n.String()
|
||||
allPkgVulns, err := pr.reader.GetVulnerability(nsStr, packageName)
|
||||
allPkgVulns, err := pr.reader.SearchForVulnerabilities(nsStr, packageName)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace='%s' pkg='%s': %w", nsStr, packageName, err)
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace=%q pkg=%q: %w", nsStr, packageName, err)
|
||||
}
|
||||
|
||||
for _, vuln := range allPkgVulns {
|
||||
vulnObj, err := vulnerability.NewVulnerability(vuln)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to parse language='%s': %w", l, err)
|
||||
return nil, fmt.Errorf("provider failed to inflate vulnerability record (namespace=%q id=%q language=%q): %w", vuln.Namespace, vuln.ID, l, err)
|
||||
}
|
||||
|
||||
vulnerabilities = append(vulnerabilities, *vulnObj)
|
||||
|
@ -125,9 +145,9 @@ func (pr *VulnerabilityProvider) GetByCPE(requestCPE syftPkg.CPE) ([]vulnerabili
|
|||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
allPkgVulns, err := pr.reader.GetVulnerability(ns.String(), ns.Resolver().Normalize(requestCPE.Product))
|
||||
allPkgVulns, err := pr.reader.SearchForVulnerabilities(ns.String(), ns.Resolver().Normalize(requestCPE.Product))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace='%s' product='%s': %w", ns, requestCPE.Product, err)
|
||||
return nil, fmt.Errorf("provider failed to fetch namespace=%q product=%q: %w", ns, requestCPE.Product, err)
|
||||
}
|
||||
|
||||
normalizedRequestCPE, err := syftPkg.NewCPE(ns.Resolver().Normalize(requestCPE.BindToFmtString()))
|
||||
|
@ -148,7 +168,7 @@ func (pr *VulnerabilityProvider) GetByCPE(requestCPE syftPkg.CPE) ([]vulnerabili
|
|||
if len(candidateMatchCpes) > 0 {
|
||||
vulnObj, err := vulnerability.NewVulnerability(vuln)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provider failed to parse cpe='%s': %w", requestCPE.BindToFmtString(), err)
|
||||
return nil, fmt.Errorf("provider failed to inflate vulnerability record (namespace=%q id=%q cpe=%q): %w", vuln.Namespace, vuln.ID, requestCPE.BindToFmtString(), err)
|
||||
}
|
||||
|
||||
vulnObj.CPEs = candidateMatchCpes
|
||||
|
|
|
@ -79,7 +79,19 @@ func (d *mockStore) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (d *mockStore) GetVulnerability(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
func (d *mockStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
var results []grypeDB.Vulnerability
|
||||
for _, vulns := range d.data[namespace] {
|
||||
for _, vuln := range vulns {
|
||||
if vuln.ID == id {
|
||||
results = append(results, vuln)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *mockStore) SearchForVulnerabilities(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
return d.data[namespace][name], nil
|
||||
}
|
||||
|
||||
|
|
|
@ -16,14 +16,12 @@ import (
|
|||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestGetByDistro(t *testing.T) {
|
||||
func Test_GetByDistro(t *testing.T) {
|
||||
provider, err := NewVulnerabilityProvider(newMockStore())
|
||||
require.NoError(t, err)
|
||||
|
||||
d, err := distro.New(distro.Debian, "8", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create distro: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
p := pkg.Package{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
|
@ -31,9 +29,7 @@ func TestGetByDistro(t *testing.T) {
|
|||
}
|
||||
|
||||
actual, err := provider.GetByDistro(d, p)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get by distro: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []vulnerability.Vulnerability{
|
||||
{
|
||||
|
@ -63,7 +59,7 @@ func TestGetByDistro(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetByDistro_nilDistro(t *testing.T) {
|
||||
func Test_GetByDistro_nilDistro(t *testing.T) {
|
||||
provider, err := NewVulnerabilityProvider(newMockStore())
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -85,7 +81,7 @@ func must(c syftPkg.CPE, e error) syftPkg.CPE {
|
|||
return c
|
||||
}
|
||||
|
||||
func TestGetByCPE(t *testing.T) {
|
||||
func Test_GetByCPE(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -183,3 +179,30 @@ func TestGetByCPE(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_Get(t *testing.T) {
|
||||
provider, err := NewVulnerabilityProvider(newMockStore())
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := provider.Get("CVE-2014-fake-1", "debian:distro:debian:8")
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []vulnerability.Vulnerability{
|
||||
{
|
||||
Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
|
||||
ID: "CVE-2014-fake-1",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
CPEs: []syftPkg.CPE{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
}
|
||||
|
||||
require.Len(t, actual, len(expected))
|
||||
|
||||
for idx, vuln := range actual {
|
||||
for _, d := range deep.Equal(expected[idx], vuln) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
37
grype/deprecated.go
Normal file
37
grype/deprecated.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package grype
|
||||
|
||||
import (
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// TODO: deprecated, remove in v1.0.0
|
||||
func FindVulnerabilities(store store.Store, userImageStr string, scopeOpt source.Scope, registryOptions *image.RegistryOptions) (match.Matches, pkg.Context, []pkg.Package, error) {
|
||||
providerConfig := pkg.ProviderConfig{
|
||||
SyftProviderConfig: pkg.SyftProviderConfig{
|
||||
RegistryOptions: registryOptions,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
},
|
||||
}
|
||||
providerConfig.CatalogingOptions.Search.Scope = scopeOpt
|
||||
|
||||
packages, context, err := pkg.Provide(userImageStr, providerConfig)
|
||||
if err != nil {
|
||||
return match.Matches{}, pkg.Context{}, nil, err
|
||||
}
|
||||
|
||||
matchers := matcher.NewDefaultMatchers(matcher.Config{})
|
||||
|
||||
return FindVulnerabilitiesForPackage(store, context.Distro, matchers, packages), context, packages, nil
|
||||
}
|
||||
|
||||
// TODO: deprecated, remove in v1.0.0
|
||||
func FindVulnerabilitiesForPackage(store store.Store, d *linux.Release, matchers []matcher.Matcher, packages []pkg.Package) match.Matches {
|
||||
return matcher.FindMatches(store, d, matchers, packages)
|
||||
}
|
67
grype/lib.go
67
grype/lib.go
|
@ -4,77 +4,10 @@ import (
|
|||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/go-logger"
|
||||
"github.com/anchore/grype/grype/db"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func FindVulnerabilities(store store.Store, userImageStr string, scopeOpt source.Scope, registryOptions *image.RegistryOptions) (match.Matches, pkg.Context, []pkg.Package, error) {
|
||||
providerConfig := pkg.ProviderConfig{
|
||||
RegistryOptions: registryOptions,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
}
|
||||
providerConfig.CatalogingOptions.Search.Scope = scopeOpt
|
||||
|
||||
packages, context, err := pkg.Provide(userImageStr, providerConfig)
|
||||
if err != nil {
|
||||
return match.Matches{}, pkg.Context{}, nil, err
|
||||
}
|
||||
|
||||
matchers := matcher.NewDefaultMatchers(matcher.Config{})
|
||||
|
||||
return FindVulnerabilitiesForPackage(store, context.Distro, matchers, packages), context, packages, nil
|
||||
}
|
||||
|
||||
func FindVulnerabilitiesForPackage(store store.Store, d *linux.Release, matchers []matcher.Matcher, packages []pkg.Package) match.Matches {
|
||||
return matcher.FindMatches(store, d, matchers, packages)
|
||||
}
|
||||
|
||||
func LoadVulnerabilityDB(cfg db.Config, update bool) (*store.Store, *db.Status, *db.Closer, error) {
|
||||
dbCurator, err := db.NewCurator(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if update {
|
||||
log.Debug("looking for updates on vulnerability database")
|
||||
_, err := dbCurator.Update()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
storeReader, dbCloser, err := dbCurator.GetStore()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
status := dbCurator.Status()
|
||||
|
||||
p, err := db.NewVulnerabilityProvider(storeReader)
|
||||
if err != nil {
|
||||
return nil, &status, nil, err
|
||||
}
|
||||
|
||||
s := &store.Store{
|
||||
Provider: p,
|
||||
MetadataProvider: db.NewVulnerabilityMetadataProvider(storeReader),
|
||||
ExclusionProvider: db.NewMatchExclusionProvider(storeReader),
|
||||
}
|
||||
|
||||
closer := &db.Closer{DBCloser: dbCloser}
|
||||
|
||||
return s, &status, closer, nil
|
||||
}
|
||||
|
||||
func SetLogger(logger logger.Logger) {
|
||||
log.Log = logger
|
||||
}
|
||||
|
|
44
grype/load_vulnerability_db.go
Normal file
44
grype/load_vulnerability_db.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package grype
|
||||
|
||||
import (
|
||||
"github.com/anchore/grype/grype/db"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
func LoadVulnerabilityDB(cfg db.Config, update bool) (*store.Store, *db.Status, *db.Closer, error) {
|
||||
dbCurator, err := db.NewCurator(cfg)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if update {
|
||||
log.Debug("looking for updates on vulnerability database")
|
||||
_, err := dbCurator.Update()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
storeReader, dbCloser, err := dbCurator.GetStore()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
status := dbCurator.Status()
|
||||
|
||||
p, err := db.NewVulnerabilityProvider(storeReader)
|
||||
if err != nil {
|
||||
return nil, &status, nil, err
|
||||
}
|
||||
|
||||
s := &store.Store{
|
||||
Provider: p,
|
||||
MetadataProvider: db.NewVulnerabilityMetadataProvider(storeReader),
|
||||
ExclusionProvider: db.NewMatchExclusionProvider(storeReader),
|
||||
}
|
||||
|
||||
closer := &db.Closer{DBCloser: dbCloser}
|
||||
|
||||
return s, &status, closer, nil
|
||||
}
|
|
@ -65,6 +65,7 @@ func (r *Matches) Add(matches ...Match) {
|
|||
log.Warnf("unable to merge matches: original=%q new=%q : %w", existingMatch.String(), newMatch.String(), err)
|
||||
// TODO: dropped match in this case, we should figure a way to handle this
|
||||
}
|
||||
r.byFingerprint[fingerprint] = existingMatch
|
||||
} else {
|
||||
r.byFingerprint[fingerprint] = newMatch
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ package apk
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -29,7 +30,12 @@ type mockStore struct {
|
|||
backend map[string]map[string][]grypeDB.Vulnerability
|
||||
}
|
||||
|
||||
func (s *mockStore) GetVulnerability(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
func (s *mockStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (s *mockStore) SearchForVulnerabilities(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
namespaceMap := s.backend[namespace]
|
||||
if namespaceMap == nil {
|
||||
return nil, nil
|
||||
|
@ -111,6 +117,7 @@ func TestSecDBOnlyMatch(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"vulnerabilityID": "CVE-2020-2",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -121,10 +128,7 @@ func TestSecDBOnlyMatch(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestBothSecdbAndNvdMatches(t *testing.T) {
|
||||
|
@ -200,6 +204,7 @@ func TestBothSecdbAndNvdMatches(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"vulnerabilityID": "CVE-2020-1",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -210,9 +215,7 @@ func TestBothSecdbAndNvdMatches(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) {
|
||||
|
@ -289,6 +292,7 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"vulnerabilityID": "CVE-2020-1",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -299,9 +303,7 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNvdOnlyMatches(t *testing.T) {
|
||||
|
@ -358,6 +360,7 @@ func TestNvdOnlyMatches(t *testing.T) {
|
|||
Found: search.CPEResult{
|
||||
CPEs: []string{vulnFound.CPEs[0].BindToFmtString()},
|
||||
VersionConstraint: vulnFound.Constraint.String(),
|
||||
VulnerabilityID: "CVE-2020-1",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -368,10 +371,7 @@ func TestNvdOnlyMatches(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNvdMatchesWithSecDBFix(t *testing.T) {
|
||||
|
@ -423,9 +423,7 @@ func TestNvdMatchesWithSecDBFix(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNvdMatchesNoConstraintWithSecDBFix(t *testing.T) {
|
||||
|
@ -478,9 +476,7 @@ func TestNvdMatchesNoConstraintWithSecDBFix(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestDistroMatchBySourceIndirection(t *testing.T) {
|
||||
|
@ -545,6 +541,7 @@ func TestDistroMatchBySourceIndirection(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"vulnerabilityID": "CVE-2020-2",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -555,10 +552,7 @@ func TestDistroMatchBySourceIndirection(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
}
|
||||
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestNVDMatchBySourceIndirection(t *testing.T) {
|
||||
|
@ -620,6 +614,7 @@ func TestNVDMatchBySourceIndirection(t *testing.T) {
|
|||
Found: search.CPEResult{
|
||||
CPEs: []string{vulnFound.CPEs[0].BindToFmtString()},
|
||||
VersionConstraint: vulnFound.Constraint.String(),
|
||||
VulnerabilityID: "CVE-2020-1",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
|
@ -630,8 +625,17 @@ func TestNVDMatchBySourceIndirection(t *testing.T) {
|
|||
actual, err := m.Match(provider, d, p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, diff := range deep.Equal(expected, actual) {
|
||||
t.Errorf("diff: %+v", diff)
|
||||
assertMatches(t, expected, actual)
|
||||
}
|
||||
|
||||
func assertMatches(t *testing.T, expected, actual []match.Match) {
|
||||
t.Helper()
|
||||
var opts = []cmp.Option{
|
||||
cmpopts.IgnoreFields(vulnerability.Vulnerability{}, "Constraint"),
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "Locations"),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expected, actual, opts...); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -19,7 +19,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewDotnetMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -21,7 +21,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewGolangMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa
|
|||
}
|
||||
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -46,6 +46,11 @@ type mockProvider struct {
|
|||
data map[syftPkg.Language]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func (mp *mockProvider) Get(id, namespace string) ([]vulnerability.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mp *mockProvider) populateData() {
|
||||
mp.data[syftPkg.Go] = map[string][]vulnerability.Vulnerability{
|
||||
"istio.io/istio": {
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package java
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/grype/grype/distro"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
|
@ -21,101 +18,27 @@ const (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
SearchMavenUpstream bool
|
||||
MavenSearcher
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
// MavenSearcher is the interface that wraps the GetMavenPackageBySha method.
|
||||
type MavenSearcher interface {
|
||||
// GetMavenPackageBySha provides an interface for building a package from maven data based on a sha1 digest
|
||||
GetMavenPackageBySha(string) (*pkg.Package, error)
|
||||
}
|
||||
|
||||
// mavenSearch implements the MavenSearcher interface
|
||||
type mavenSearch struct {
|
||||
client *http.Client
|
||||
baseURL string
|
||||
}
|
||||
|
||||
type mavenAPIResponse struct {
|
||||
Response struct {
|
||||
NumFound int `json:"numFound"`
|
||||
Docs []struct {
|
||||
ID string `json:"id"`
|
||||
GroupID string `json:"g"`
|
||||
ArtifactID string `json:"a"`
|
||||
Version string `json:"v"`
|
||||
P string `json:"p"`
|
||||
VersionCount int `json:"versionCount"`
|
||||
} `json:"docs"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func (ms *mavenSearch) GetMavenPackageBySha(sha1 string) (*pkg.Package, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, ms.baseURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize HTTP client: %w", err)
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("q", fmt.Sprintf(sha1Query, sha1))
|
||||
q.Set("rows", "1")
|
||||
q.Set("wt", "json")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := ms.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sha1 search error: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("status %s from %s", resp.Status, req.URL.String())
|
||||
}
|
||||
|
||||
var res mavenAPIResponse
|
||||
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return nil, fmt.Errorf("json decode error: %w", err)
|
||||
}
|
||||
|
||||
if len(res.Response.Docs) == 0 {
|
||||
return nil, fmt.Errorf("digest %s: %w", sha1, errors.New("no artifact found"))
|
||||
}
|
||||
|
||||
// artifacts might have the same SHA-1 digests.
|
||||
// e.g. "javax.servlet:jstl" and "jstl:jstl"
|
||||
docs := res.Response.Docs
|
||||
sort.Slice(docs, func(i, j int) bool {
|
||||
return docs[i].ID < docs[j].ID
|
||||
})
|
||||
d := docs[0]
|
||||
|
||||
return &pkg.Package{
|
||||
Name: fmt.Sprintf("%s:%s", d.GroupID, d.ArtifactID),
|
||||
Version: d.Version,
|
||||
Language: syftPkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomArtifactID: d.ArtifactID,
|
||||
PomGroupID: d.GroupID,
|
||||
},
|
||||
}, nil
|
||||
type ExternalSearchConfig struct {
|
||||
SearchMavenUpstream bool
|
||||
MavenBaseURL string
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
SearchMavenUpstream bool
|
||||
MavenBaseURL string
|
||||
UseCPEs bool
|
||||
ExternalSearchConfig
|
||||
UseCPEs bool
|
||||
}
|
||||
|
||||
func NewJavaMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
cfg.SearchMavenUpstream,
|
||||
&mavenSearch{
|
||||
cfg: cfg,
|
||||
MavenSearcher: &mavenSearch{
|
||||
client: http.DefaultClient,
|
||||
baseURL: cfg.MavenBaseURL,
|
||||
},
|
||||
cfg.UseCPEs,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +52,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
var matches []match.Match
|
||||
if m.SearchMavenUpstream {
|
||||
if m.cfg.SearchMavenUpstream {
|
||||
upstreamMatches, err := m.matchUpstreamMavenPackages(store, p)
|
||||
if err != nil {
|
||||
log.Debugf("failed to match against upstream data for %s: %v", p.Name, err)
|
||||
|
@ -138,7 +61,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa
|
|||
}
|
||||
}
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
criteriaMatches, err := search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -12,6 +12,11 @@ type mockProvider struct {
|
|||
data map[syftPkg.Language]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func (mp *mockProvider) Get(id, namespace string) ([]vulnerability.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mp *mockProvider) populateData() {
|
||||
mp.data[syftPkg.Java] = map[string][]vulnerability.Vulnerability{
|
||||
"org.springframework.spring-webmvc": {
|
||||
|
|
|
@ -31,8 +31,13 @@ func TestMatcherJava_matchUpstreamMavenPackage(t *testing.T) {
|
|||
},
|
||||
}
|
||||
matcher := Matcher{
|
||||
SearchMavenUpstream: true,
|
||||
MavenSearcher: newMockSearcher(p),
|
||||
cfg: MatcherConfig{
|
||||
ExternalSearchConfig: ExternalSearchConfig{
|
||||
SearchMavenUpstream: true,
|
||||
},
|
||||
UseCPEs: false,
|
||||
},
|
||||
MavenSearcher: newMockSearcher(p),
|
||||
}
|
||||
store := newMockProvider()
|
||||
actual, _ := matcher.matchUpstreamMavenPackages(store, p)
|
||||
|
|
88
grype/matcher/java/maven_search.go
Normal file
88
grype/matcher/java/maven_search.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package java
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// MavenSearcher is the interface that wraps the GetMavenPackageBySha method.
|
||||
type MavenSearcher interface {
|
||||
// GetMavenPackageBySha provides an interface for building a package from maven data based on a sha1 digest
|
||||
GetMavenPackageBySha(string) (*pkg.Package, error)
|
||||
}
|
||||
|
||||
// mavenSearch implements the MavenSearcher interface
|
||||
type mavenSearch struct {
|
||||
client *http.Client
|
||||
baseURL string
|
||||
}
|
||||
|
||||
type mavenAPIResponse struct {
|
||||
Response struct {
|
||||
NumFound int `json:"numFound"`
|
||||
Docs []struct {
|
||||
ID string `json:"id"`
|
||||
GroupID string `json:"g"`
|
||||
ArtifactID string `json:"a"`
|
||||
Version string `json:"v"`
|
||||
P string `json:"p"`
|
||||
VersionCount int `json:"versionCount"`
|
||||
} `json:"docs"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func (ms *mavenSearch) GetMavenPackageBySha(sha1 string) (*pkg.Package, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, ms.baseURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize HTTP client: %w", err)
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Set("q", fmt.Sprintf(sha1Query, sha1))
|
||||
q.Set("rows", "1")
|
||||
q.Set("wt", "json")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := ms.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sha1 search error: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("status %s from %s", resp.Status, req.URL.String())
|
||||
}
|
||||
|
||||
var res mavenAPIResponse
|
||||
if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return nil, fmt.Errorf("json decode error: %w", err)
|
||||
}
|
||||
|
||||
if len(res.Response.Docs) == 0 {
|
||||
return nil, fmt.Errorf("digest %s: %w", sha1, errors.New("no artifact found"))
|
||||
}
|
||||
|
||||
// artifacts might have the same SHA-1 digests.
|
||||
// e.g. "javax.servlet:jstl" and "jstl:jstl"
|
||||
docs := res.Response.Docs
|
||||
sort.Slice(docs, func(i, j int) bool {
|
||||
return docs[i].ID < docs[j].ID
|
||||
})
|
||||
d := docs[0]
|
||||
|
||||
return &pkg.Package{
|
||||
Name: fmt.Sprintf("%s:%s", d.GroupID, d.ArtifactID),
|
||||
Version: d.Version,
|
||||
Language: syftPkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomArtifactID: d.ArtifactID,
|
||||
PomGroupID: d.GroupID,
|
||||
},
|
||||
}, nil
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -19,7 +19,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewJavascriptMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -114,7 +114,7 @@ func FindMatches(store interface {
|
|||
packagesProcessed, vulnerabilitiesDiscovered := trackMatcher()
|
||||
|
||||
if defaultMatcher == nil {
|
||||
defaultMatcher = &stock.Matcher{UseCPEs: true}
|
||||
defaultMatcher = stock.NewStockMatcher(stock.MatcherConfig{UseCPEs: true})
|
||||
}
|
||||
for _, p := range packages {
|
||||
packagesProcessed.N++
|
||||
|
|
|
@ -19,7 +19,12 @@ type mockStore struct {
|
|||
backend map[string]map[string][]grypeDB.Vulnerability
|
||||
}
|
||||
|
||||
func (s *mockStore) GetVulnerability(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
func (s *mockStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (s *mockStore) SearchForVulnerabilities(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
namespaceMap := s.backend[namespace]
|
||||
if namespaceMap == nil {
|
||||
return nil, nil
|
||||
|
|
|
@ -14,6 +14,11 @@ type mockProvider struct {
|
|||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func (pr *mockProvider) Get(id, namespace string) ([]vulnerability.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newMockProvider() *mockProvider {
|
||||
pr := mockProvider{
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -19,7 +19,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewPythonMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -16,6 +16,11 @@ type mockProvider struct {
|
|||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func (pr *mockProvider) Get(id, namespace string) ([]vulnerability.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newMockProvider(packageName, indirectName string, withEpoch bool, withPackageQualifiers bool) *mockProvider {
|
||||
pr := mockProvider{
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -19,7 +19,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewRubyMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type Matcher struct {
|
||||
UseCPEs bool
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
|
@ -19,7 +19,7 @@ type MatcherConfig struct {
|
|||
|
||||
func NewStockMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
UseCPEs: cfg.UseCPEs,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ func (m *Matcher) Type() match.MatcherType {
|
|||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
|
||||
criteria := search.CommonCriteria
|
||||
if m.UseCPEs {
|
||||
if m.cfg.UseCPEs {
|
||||
criteria = append(criteria, search.ByCPE)
|
||||
}
|
||||
return search.ByCriteria(store, d, p, m.Type(), criteria...)
|
||||
|
|
|
@ -58,10 +58,14 @@ func New(p pkg.Package) Package {
|
|||
}
|
||||
}
|
||||
|
||||
func FromCatalog(catalog *pkg.Catalog, config ProviderConfig) []Package {
|
||||
result := make([]Package, 0, catalog.PackageCount())
|
||||
missingCPEs := false
|
||||
for _, p := range catalog.Sorted() {
|
||||
func FromCatalog(catalog *pkg.Catalog, config SynthesisConfig) []Package {
|
||||
return FromPackages(catalog.Sorted(), config)
|
||||
}
|
||||
|
||||
func FromPackages(syftpkgs []pkg.Package, config SynthesisConfig) []Package {
|
||||
var pkgs []Package
|
||||
var missingCPEs bool
|
||||
for _, p := range syftpkgs {
|
||||
if len(p.CPEs) == 0 {
|
||||
// For SPDX (or any format, really) we may have no CPEs
|
||||
if config.GenerateMissingCPEs {
|
||||
|
@ -71,12 +75,12 @@ func FromCatalog(catalog *pkg.Catalog, config ProviderConfig) []Package {
|
|||
missingCPEs = true
|
||||
}
|
||||
}
|
||||
result = append(result, New(p))
|
||||
pkgs = append(pkgs, New(p))
|
||||
}
|
||||
if missingCPEs {
|
||||
log.Warnf("some package(s) are missing CPEs. This may result in missing vulnerabilities. You may autogenerate these using: --add-cpes-if-none")
|
||||
}
|
||||
return result
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// Stringer to represent a package.
|
||||
|
|
|
@ -415,7 +415,7 @@ func TestFromCatalog_DoesNotPanic(t *testing.T) {
|
|||
catalog.Add(examplePackage)
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
_ = FromCatalog(catalog, ProviderConfig{})
|
||||
_ = FromCatalog(catalog, SynthesisConfig{})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -436,12 +436,12 @@ func TestFromCatalog_GeneratesCPEs(t *testing.T) {
|
|||
})
|
||||
|
||||
// doesn't generate cpes when no flag
|
||||
pkgs := FromCatalog(catalog, ProviderConfig{})
|
||||
pkgs := FromCatalog(catalog, SynthesisConfig{})
|
||||
assert.Len(t, pkgs[0].CPEs, 1)
|
||||
assert.Len(t, pkgs[1].CPEs, 0)
|
||||
|
||||
// does generate cpes with the flag
|
||||
pkgs = FromCatalog(catalog, ProviderConfig{
|
||||
pkgs = FromCatalog(catalog, SynthesisConfig{
|
||||
GenerateMissingCPEs: true,
|
||||
})
|
||||
assert.Len(t, pkgs[0].CPEs, 1)
|
||||
|
|
|
@ -6,11 +6,19 @@ import (
|
|||
)
|
||||
|
||||
type ProviderConfig struct {
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Exclusions []string
|
||||
SyftProviderConfig
|
||||
SynthesisConfig
|
||||
}
|
||||
|
||||
type SyftProviderConfig struct {
|
||||
CatalogingOptions cataloger.Config
|
||||
GenerateMissingCPEs bool
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Platform string
|
||||
Exclusions []string
|
||||
AttestationPublicKey string
|
||||
AttestationIgnoreVerification bool
|
||||
}
|
||||
|
||||
type SynthesisConfig struct {
|
||||
GenerateMissingCPEs bool
|
||||
}
|
||||
|
|
|
@ -46,8 +46,10 @@ func TestProviderLocationExcludes(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cfg := ProviderConfig{
|
||||
Exclusions: test.excludes,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
Exclusions: test.excludes,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
},
|
||||
}
|
||||
pkgs, _, _ := Provide(test.fixture, cfg)
|
||||
|
||||
|
@ -99,8 +101,10 @@ func TestSyftLocationExcludes(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
userInput := imagetest.GetFixtureImageTarPath(t, test.fixture)
|
||||
cfg := ProviderConfig{
|
||||
Exclusions: test.excludes,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
Exclusions: test.excludes,
|
||||
CatalogingOptions: cataloger.DefaultConfig(),
|
||||
},
|
||||
}
|
||||
pkgs, _, err := Provide(userInput, cfg)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ func syftProvider(userInput string, config ProviderConfig) ([]Package, Context,
|
|||
return nil, Context{}, err
|
||||
}
|
||||
|
||||
return FromCatalog(catalog, config), Context{
|
||||
return FromCatalog(catalog, config.SynthesisConfig), Context{
|
||||
Source: &src.Metadata,
|
||||
Distro: theDistro,
|
||||
}, nil
|
||||
|
|
|
@ -41,7 +41,7 @@ func syftSBOMProvider(userInput string, config ProviderConfig) ([]Package, Conte
|
|||
return nil, Context{}, err
|
||||
}
|
||||
|
||||
return FromCatalog(s.Artifacts.PackageCatalog, config), Context{
|
||||
return FromCatalog(s.Artifacts.PackageCatalog, config.SynthesisConfig), Context{
|
||||
Source: &s.Source,
|
||||
Distro: s.Artifacts.LinuxDistribution,
|
||||
}, nil
|
||||
|
|
|
@ -80,7 +80,11 @@ func TestDecodeStdin(t *testing.T) {
|
|||
t.Run(tt.Name, func(t *testing.T) {
|
||||
f, err := os.Open(tt.Input)
|
||||
require.NoError(t, err)
|
||||
r, info, err := decodeStdin(f, ProviderConfig{AttestationPublicKey: tt.Key})
|
||||
r, info, err := decodeStdin(f, ProviderConfig{
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
AttestationPublicKey: tt.Key,
|
||||
},
|
||||
})
|
||||
tt.WantErr(t, err)
|
||||
|
||||
if err == nil {
|
||||
|
@ -88,7 +92,7 @@ func TestDecodeStdin(t *testing.T) {
|
|||
sbom, format, err := syft.Decode(r)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, format)
|
||||
assert.Len(t, FromCatalog(sbom.Artifacts.PackageCatalog, ProviderConfig{}), tt.PkgsLen)
|
||||
assert.Len(t, FromCatalog(sbom.Artifacts.PackageCatalog, SynthesisConfig{}), tt.PkgsLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -189,7 +193,11 @@ func TestParseAttestation(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
pkgs, _, err := syftSBOMProvider(tt.Input, ProviderConfig{AttestationPublicKey: tt.Key})
|
||||
pkgs, _, err := syftSBOMProvider(tt.Input, ProviderConfig{
|
||||
SyftProviderConfig: SyftProviderConfig{
|
||||
AttestationPublicKey: tt.Key,
|
||||
},
|
||||
})
|
||||
tt.WantErr(t, err)
|
||||
require.Len(t, pkgs, tt.PkgsLen)
|
||||
})
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:091c3e78-d915-4f58-b6f6-53c04d68967b",
|
||||
"serialNumber": "urn:uuid:e9fe8739-7197-44bd-b5e8-050ed14af1e0",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-11-23T16:23:09-05:00",
|
||||
"timestamp": "2022-12-08T13:54:36-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
|
@ -19,13 +19,13 @@
|
|||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "633e200f-bb1a-40fb-a14f-0adddf988155",
|
||||
"bom-ref": "1759de0b-fbb0-4a8b-a085-2a2216c642b5",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"bom-ref": "c60f424b-c274-4790-9209-89125f504579",
|
||||
"bom-ref": "92b2e9c3-87d4-49b9-ae04-70ee075e970f",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.2.2",
|
||||
|
@ -67,7 +67,7 @@
|
|||
},
|
||||
"affects": [
|
||||
{
|
||||
"ref": "633e200f-bb1a-40fb-a14f-0adddf988155"
|
||||
"ref": "1759de0b-fbb0-4a8b-a085-2a2216c642b5"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
|
@ -100,7 +100,7 @@
|
|||
},
|
||||
"affects": [
|
||||
{
|
||||
"ref": "c60f424b-c274-4790-9209-89125f504579"
|
||||
"ref": "92b2e9c3-87d4-49b9-ae04-70ee075e970f"
|
||||
}
|
||||
],
|
||||
"properties": []
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:834850de-2bee-401f-ac47-2ad2fa642ec6" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2a342d7a-8965-45c0-a71a-af3bcca303e3" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-23T16:23:09-05:00</timestamp>
|
||||
<timestamp>2022-12-08T13:54:36-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
|
@ -14,11 +14,11 @@
|
|||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="46c7e55a-84e6-49c3-ad1e-bf22bf273ea5" type="library">
|
||||
<component bom-ref="1fb6b8a4-2ef6-4176-8c0d-e95dd63d5e1f" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.1.1</version>
|
||||
</component>
|
||||
<component bom-ref="d50db824-5559-46ba-8972-8f832e4e31f9" type="library">
|
||||
<component bom-ref="7b81a896-f88e-40c4-bf30-1b909b028441" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.2.2</version>
|
||||
<licenses>
|
||||
|
@ -55,7 +55,7 @@
|
|||
</analysis>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>46c7e55a-84e6-49c3-ad1e-bf22bf273ea5</ref>
|
||||
<ref>1fb6b8a4-2ef6-4176-8c0d-e95dd63d5e1f</ref>
|
||||
</target>
|
||||
</affects>
|
||||
<properties>
|
||||
|
@ -85,7 +85,7 @@
|
|||
</analysis>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>d50db824-5559-46ba-8972-8f832e4e31f9</ref>
|
||||
<ref>7b81a896-f88e-40c4-bf30-1b909b028441</ref>
|
||||
</target>
|
||||
</affects>
|
||||
<properties></properties>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:66755c26-b74b-4e0a-91cb-553fdf4e7153",
|
||||
"serialNumber": "urn:uuid:58ce0fc9-d5c2-47b7-885b-4d97a3b3e335",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2022-11-23T16:23:09-05:00",
|
||||
"timestamp": "2022-12-08T13:54:36-05:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
|
@ -20,13 +20,13 @@
|
|||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "4ab475af-df40-4a79-82fb-9c463788f56c",
|
||||
"bom-ref": "52a05cbc-fb99-4571-b222-57e0ea5031b5",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.1.1"
|
||||
},
|
||||
{
|
||||
"bom-ref": "5c5f8e26-00bc-4d8e-9d47-0dc26c4c5fd3",
|
||||
"bom-ref": "d039d2f2-00fe-46c2-959c-797f9572ac65",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.2.2",
|
||||
|
@ -68,7 +68,7 @@
|
|||
},
|
||||
"affects": [
|
||||
{
|
||||
"ref": "4ab475af-df40-4a79-82fb-9c463788f56c"
|
||||
"ref": "52a05cbc-fb99-4571-b222-57e0ea5031b5"
|
||||
}
|
||||
],
|
||||
"properties": [
|
||||
|
@ -101,7 +101,7 @@
|
|||
},
|
||||
"affects": [
|
||||
{
|
||||
"ref": "5c5f8e26-00bc-4d8e-9d47-0dc26c4c5fd3"
|
||||
"ref": "d039d2f2-00fe-46c2-959c-797f9572ac65"
|
||||
}
|
||||
],
|
||||
"properties": []
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:0c4aaa8f-5858-43a6-ab4a-2c5b7856b627" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:9252cfa6-c1bb-4577-969f-6426a9576a4b" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-23T16:23:09-05:00</timestamp>
|
||||
<timestamp>2022-12-08T13:54:36-05:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
|
@ -15,11 +15,11 @@
|
|||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="3079bc39-2dc9-4ac3-8981-04e6f4175d0d" type="library">
|
||||
<component bom-ref="34c838f5-21cb-4366-80ae-cc2592319a81" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.1.1</version>
|
||||
</component>
|
||||
<component bom-ref="98d49da2-7878-41c3-a55a-5841b8da967e" type="library">
|
||||
<component bom-ref="19b6f279-29f9-4193-b84e-5e5bc3a32267" type="library">
|
||||
<name>package-2</name>
|
||||
<version>2.2.2</version>
|
||||
<licenses>
|
||||
|
@ -56,7 +56,7 @@
|
|||
</analysis>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>3079bc39-2dc9-4ac3-8981-04e6f4175d0d</ref>
|
||||
<ref>34c838f5-21cb-4366-80ae-cc2592319a81</ref>
|
||||
</target>
|
||||
</affects>
|
||||
<properties>
|
||||
|
@ -86,7 +86,7 @@
|
|||
</analysis>
|
||||
<affects>
|
||||
<target>
|
||||
<ref>98d49da2-7878-41c3-a55a-5841b8da967e</ref>
|
||||
<ref>19b6f279-29f9-4193-b84e-5e5bc3a32267</ref>
|
||||
</target>
|
||||
</affects>
|
||||
<properties></properties>
|
||||
|
|
|
@ -41,6 +41,7 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
|
||||
func TestJsonDirsPresenter(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, source.DirectoryScheme)
|
||||
pres := NewPresenter(matches, nil, packages, context, metadataProvider, nil, nil)
|
||||
|
||||
|
|
|
@ -101,7 +101,6 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||
|
||||
func removeDuplicateRows(items [][]string) [][]string {
|
||||
seen := map[string][]string{}
|
||||
//nolint:prealloc
|
||||
var result [][]string
|
||||
|
||||
for _, v := range items {
|
||||
|
|
|
@ -33,6 +33,7 @@ func (i *CPEParameters) Merge(other CPEParameters) error {
|
|||
}
|
||||
|
||||
type CPEResult struct {
|
||||
VulnerabilityID string `json:"vulnerabilityID"`
|
||||
VersionConstraint string `json:"versionConstraint"`
|
||||
CPEs []string `json:"cpes"`
|
||||
}
|
||||
|
@ -124,6 +125,7 @@ func addNewMatch(matchesByFingerprint map[match.Fingerprint]match.Match, vuln vu
|
|||
},
|
||||
},
|
||||
Found: CPEResult{
|
||||
VulnerabilityID: vuln.ID,
|
||||
VersionConstraint: vuln.Constraint.String(),
|
||||
CPEs: cpesToString(filterCPEsByVersion(searchVersion, vuln.CPEs)),
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ package search
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -29,6 +30,11 @@ type mockVulnStore struct {
|
|||
data map[string]map[string][]grypeDB.Vulnerability
|
||||
}
|
||||
|
||||
func (pr *mockVulnStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newMockStore() *mockVulnStore {
|
||||
pr := mockVulnStore{
|
||||
data: make(map[string]map[string][]grypeDB.Vulnerability),
|
||||
|
@ -138,7 +144,7 @@ func (pr *mockVulnStore) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockVulnStore) GetVulnerability(namespace, pkg string) ([]grypeDB.Vulnerability, error) {
|
||||
func (pr *mockVulnStore) SearchForVulnerabilities(namespace, pkg string) ([]grypeDB.Vulnerability, error) {
|
||||
return pr.data[namespace][pkg], nil
|
||||
}
|
||||
|
||||
|
@ -201,6 +207,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Found: CPEResult{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
VersionConstraint: "< 3.7.6 (semver)",
|
||||
VulnerabilityID: "CVE-2017-fake-1",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -250,6 +257,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Found: CPEResult{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
VersionConstraint: "< 3.7.6 (semver)",
|
||||
VulnerabilityID: "CVE-2017-fake-1",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -282,6 +290,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Found: CPEResult{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*"},
|
||||
VersionConstraint: "< 3.7.4 (semver)",
|
||||
VulnerabilityID: "CVE-2017-fake-2",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -326,6 +335,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Found: CPEResult{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:4.0.1:*:*:*:*:*:*:*"},
|
||||
VersionConstraint: "= 4.0.1 (semver)",
|
||||
VulnerabilityID: "CVE-2017-fake-3",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -378,6 +388,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Found: CPEResult{
|
||||
CPEs: []string{"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*"},
|
||||
VersionConstraint: "< 98SP3 (unknown)",
|
||||
VulnerabilityID: "CVE-2017-fake-4",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -426,6 +437,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
VersionConstraint: "< 4.0 (unknown)",
|
||||
VulnerabilityID: "CVE-2017-fake-5",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -484,6 +496,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
"cpe:2.3:*:sw:sw:*:*:*:*:*:puppet:*:*",
|
||||
},
|
||||
VersionConstraint: "< 1.0 (unknown)",
|
||||
VulnerabilityID: "CVE-2017-fake-7",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -536,6 +549,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
"cpe:2.3:*:funfun:funfun:5.2.1:*:*:*:*:python:*:*",
|
||||
},
|
||||
VersionConstraint: "= 5.2.1 (python)",
|
||||
VulnerabilityID: "CVE-2017-fake-6",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -581,6 +595,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:node.js:*:*",
|
||||
},
|
||||
VersionConstraint: "< 4.7.7 (unknown)",
|
||||
VulnerabilityID: "CVE-2021-23369",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -626,6 +641,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:node.js:*:*",
|
||||
},
|
||||
VersionConstraint: "< 4.7.7 (unknown)",
|
||||
VulnerabilityID: "CVE-2021-23369",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
|
@ -643,7 +659,9 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assertMatchesUsingIDsForVulnerabilities(t, test.expected, actual)
|
||||
for idx, e := range test.expected {
|
||||
assert.Equal(t, e.Details, actual[idx].Details)
|
||||
if d := cmp.Diff(e.Details, actual[idx].Details); d != "" {
|
||||
t.Errorf("unexpected match details (-want +got):\n%s", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ func ByPackageDistro(store vulnerability.ProviderByDistro, d *distro.Distro, p p
|
|||
"namespace": vuln.Namespace,
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"vulnerabilityID": vuln.ID,
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
Confidence: 1.0, // TODO: this is hard coded for now
|
||||
|
|
|
@ -96,6 +96,7 @@ func TestFindMatchesByPackageDistro(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": "< 2014.1.5-6 (deb)",
|
||||
"vulnerabilityID": "CVE-2014-fake-1",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
|
@ -151,6 +152,7 @@ func TestFindMatchesByPackageDistroSles(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": "< 2014.1.5-6 (rpm)",
|
||||
"vulnerabilityID": "CVE-2014-fake-4",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
|
|
|
@ -47,6 +47,7 @@ func ByPackageLanguage(store vulnerability.ProviderByLanguage, p pkg.Package, up
|
|||
"namespace": vuln.Namespace,
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"vulnerabilityID": vuln.ID,
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -82,6 +82,7 @@ func expectedMatch(p pkg.Package, constraint string) []match.Match {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": constraint,
|
||||
"vulnerabilityID": "CVE-2017-fake-1",
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type Provider interface {
|
||||
Get(id, namespace string) ([]Vulnerability, error)
|
||||
ProviderByDistro
|
||||
ProviderByLanguage
|
||||
ProviderByCPE
|
||||
|
|
158
grype/vulnerability_matcher.go
Normal file
158
grype/vulnerability_matcher.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
package grype
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/grype/grype/grypeerr"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
type VulnerabilityMatcher struct {
|
||||
Store store.Store
|
||||
Matchers []matcher.Matcher
|
||||
IgnoreRules []match.IgnoreRule
|
||||
FailSeverity *vulnerability.Severity
|
||||
NormalizeByCVE bool
|
||||
}
|
||||
|
||||
func DefaultVulnerabilityMatcher(store store.Store) *VulnerabilityMatcher {
|
||||
return &VulnerabilityMatcher{
|
||||
Store: store,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) FailAtOrAboveSeverity(severity *vulnerability.Severity) *VulnerabilityMatcher {
|
||||
m.FailSeverity = severity
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) WithMatchers(matchers []matcher.Matcher) *VulnerabilityMatcher {
|
||||
m.Matchers = matchers
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) WithIgnoreRules(ignoreRules []match.IgnoreRule) *VulnerabilityMatcher {
|
||||
m.IgnoreRules = ignoreRules
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Context) (*match.Matches, []match.IgnoredMatch, error) {
|
||||
var ignoredMatches []match.IgnoredMatch
|
||||
matches := matcher.FindMatches(m.Store, context.Distro, m.Matchers, pkgs)
|
||||
|
||||
matches, ignoredMatches = m.applyIgnoreRules(matches)
|
||||
|
||||
if m.NormalizeByCVE {
|
||||
normalizedMatches := match.NewMatches()
|
||||
for originalMatch := range matches.Enumerate() {
|
||||
normalizedMatches.Add(m.normalizeByCVE(originalMatch))
|
||||
}
|
||||
|
||||
// we apply the ignore rules again in case any of the transformations done during normalization
|
||||
// regresses the results (relative to the already applied ignore rules). Why do we additionally apply
|
||||
// the ignore rules before normalizing? In case the user has a rule that ignores a non-normalized
|
||||
// vulnerability ID, we wantMatches to ensure that the rule is honored.
|
||||
matches, ignoredMatches = m.applyIgnoreRules(normalizedMatches)
|
||||
}
|
||||
|
||||
var err error
|
||||
if m.FailSeverity != nil && HasSeverityAtOrAbove(m.Store, *m.FailSeverity, matches) {
|
||||
err = grypeerr.ErrAboveSeverityThreshold
|
||||
}
|
||||
|
||||
return &matches, ignoredMatches, err
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) applyIgnoreRules(matches match.Matches) (match.Matches, []match.IgnoredMatch) {
|
||||
var ignoredMatches []match.IgnoredMatch
|
||||
if len(m.IgnoreRules) == 0 {
|
||||
return matches, ignoredMatches
|
||||
}
|
||||
|
||||
matches, ignoredMatches = match.ApplyIgnoreRules(matches, m.IgnoreRules)
|
||||
|
||||
if count := len(ignoredMatches); count > 0 {
|
||||
log.Infof("ignoring %d matches due to user-provided ignore rules", count)
|
||||
}
|
||||
return matches, ignoredMatches
|
||||
}
|
||||
|
||||
func (m *VulnerabilityMatcher) normalizeByCVE(match match.Match) match.Match {
|
||||
if isCVE(match.Vulnerability.ID) {
|
||||
return match
|
||||
}
|
||||
|
||||
var effectiveCVERecordRefs []vulnerability.Reference
|
||||
for _, ref := range match.Vulnerability.RelatedVulnerabilities {
|
||||
if isCVE(ref.ID) {
|
||||
effectiveCVERecordRefs = append(effectiveCVERecordRefs, ref)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch len(effectiveCVERecordRefs) {
|
||||
case 0:
|
||||
// TODO: trace logging
|
||||
return match
|
||||
case 1:
|
||||
break
|
||||
default:
|
||||
// TODO: trace logging
|
||||
return match
|
||||
}
|
||||
|
||||
ref := effectiveCVERecordRefs[0]
|
||||
|
||||
upstreamVulnRecords, err := m.Store.Get(ref.ID, ref.Namespace)
|
||||
if err != nil {
|
||||
log.Warnf("unable to fetch effective CVE record for id=%q namespace=%q : %v", ref.ID, ref.Namespace, err)
|
||||
return match
|
||||
}
|
||||
|
||||
switch len(upstreamVulnRecords) {
|
||||
case 0:
|
||||
// TODO: trace logging
|
||||
return match
|
||||
case 1:
|
||||
break
|
||||
default:
|
||||
// TODO: trace logging
|
||||
return match
|
||||
}
|
||||
|
||||
originalRef := vulnerability.Reference{
|
||||
ID: match.Vulnerability.ID,
|
||||
Namespace: match.Vulnerability.Namespace,
|
||||
}
|
||||
match.Vulnerability = upstreamVulnRecords[0]
|
||||
match.Vulnerability.RelatedVulnerabilities = append(match.Vulnerability.RelatedVulnerabilities, originalRef)
|
||||
|
||||
return match
|
||||
}
|
||||
|
||||
func isCVE(id string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(id), "cve-")
|
||||
}
|
||||
|
||||
func HasSeverityAtOrAbove(store vulnerability.MetadataProvider, severity vulnerability.Severity, matches match.Matches) bool {
|
||||
if severity == vulnerability.UnknownSeverity {
|
||||
return false
|
||||
}
|
||||
for m := range matches.Enumerate() {
|
||||
metadata, err := store.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if vulnerability.ParseSeverity(metadata.Severity) >= severity {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
862
grype/vulnerability_matcher_test.go
Normal file
862
grype/vulnerability_matcher_test.go
Normal file
|
@ -0,0 +1,862 @@
|
|||
package grype
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/grype/grype/db"
|
||||
grypeDB "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/grypeerr"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/matcher/ruby"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/pkg/qualifier"
|
||||
"github.com/anchore/grype/grype/search"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/grype/grype/version"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type ack interface {
|
||||
grypeDB.VulnerabilityStoreReader
|
||||
grypeDB.VulnerabilityMetadataStoreReader
|
||||
grypeDB.VulnerabilityMatchExclusionStoreReader
|
||||
}
|
||||
|
||||
var _ ack = (*mockStore)(nil)
|
||||
|
||||
type mockStore struct {
|
||||
vulnerabilities map[string]map[string][]grypeDB.Vulnerability
|
||||
metadata map[string]map[string]*grypeDB.VulnerabilityMetadata
|
||||
}
|
||||
|
||||
func (d *mockStore) GetVulnerabilityMatchExclusion(id string) ([]grypeDB.VulnerabilityMatchExclusion, error) {
|
||||
//panic("implement me")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func newMockStore() *mockStore {
|
||||
d := mockStore{
|
||||
vulnerabilities: make(map[string]map[string][]grypeDB.Vulnerability),
|
||||
metadata: make(map[string]map[string]*grypeDB.VulnerabilityMetadata),
|
||||
}
|
||||
d.stub()
|
||||
return &d
|
||||
}
|
||||
|
||||
func (d *mockStore) stub() {
|
||||
// METADATA /////////////////////////////////////////////////////////////////////////////////
|
||||
d.metadata["CVE-2014-fake-1"] = map[string]*grypeDB.VulnerabilityMetadata{
|
||||
"debian:distro:debian:8": {
|
||||
Severity: "medium",
|
||||
},
|
||||
}
|
||||
|
||||
d.metadata["GHSA-2014-fake-3"] = map[string]*grypeDB.VulnerabilityMetadata{
|
||||
"github:language:ruby": {
|
||||
Severity: "medium",
|
||||
},
|
||||
}
|
||||
|
||||
// VULNERABILITIES ///////////////////////////////////////////////////////////////////////////
|
||||
d.vulnerabilities["debian:distro:debian:8"] = map[string][]grypeDB.Vulnerability{
|
||||
"neutron": {
|
||||
{
|
||||
PackageName: "neutron",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
VersionConstraint: "< 2014.1.3-6",
|
||||
ID: "CVE-2014-fake-1",
|
||||
VersionFormat: "deb",
|
||||
},
|
||||
{
|
||||
PackageName: "neutron",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
VersionConstraint: "< 2013.0.2-1",
|
||||
ID: "CVE-2013-fake-2",
|
||||
VersionFormat: "deb",
|
||||
},
|
||||
},
|
||||
}
|
||||
d.vulnerabilities["github:language:ruby"] = map[string][]grypeDB.Vulnerability{
|
||||
"activerecord": {
|
||||
{
|
||||
PackageName: "activerecord",
|
||||
Namespace: "github:language:ruby",
|
||||
VersionConstraint: "< 3.7.6",
|
||||
ID: "GHSA-2014-fake-3",
|
||||
VersionFormat: "unknown",
|
||||
RelatedVulnerabilities: []grypeDB.VulnerabilityReference{
|
||||
{
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
d.vulnerabilities["nvd:cpe"] = map[string][]grypeDB.Vulnerability{
|
||||
"activerecord": {
|
||||
{
|
||||
PackageName: "activerecord",
|
||||
Namespace: "nvd:cpe",
|
||||
VersionConstraint: "< 3.7.6",
|
||||
ID: "CVE-2014-fake-3",
|
||||
VersionFormat: "unknown",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageName: "activerecord",
|
||||
Namespace: "nvd:cpe",
|
||||
VersionConstraint: "< 3.7.4",
|
||||
ID: "CVE-2014-fake-4",
|
||||
VersionFormat: "unknown",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:something:*:*:ruby:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageName: "activerecord",
|
||||
Namespace: "nvd:cpe",
|
||||
VersionConstraint: "= 4.0.1",
|
||||
ID: "CVE-2014-fake-5",
|
||||
VersionFormat: "unknown",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageName: "activerecord",
|
||||
Namespace: "nvd:cpe",
|
||||
VersionConstraint: "< 98SP3",
|
||||
ID: "CVE-2014-fake-6",
|
||||
VersionFormat: "unknown",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *mockStore) GetVulnerabilityMetadata(id, namespace string) (*grypeDB.VulnerabilityMetadata, error) {
|
||||
return d.metadata[id][namespace], nil
|
||||
}
|
||||
|
||||
func (d *mockStore) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *mockStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
var results []grypeDB.Vulnerability
|
||||
for _, vulns := range d.vulnerabilities[namespace] {
|
||||
for _, vuln := range vulns {
|
||||
if vuln.ID == id {
|
||||
results = append(results, vuln)
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *mockStore) SearchForVulnerabilities(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
return d.vulnerabilities[namespace][name], nil
|
||||
}
|
||||
|
||||
func (d *mockStore) GetAllVulnerabilities() (*[]grypeDB.Vulnerability, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *mockStore) GetVulnerabilityNamespaces() ([]string, error) {
|
||||
keys := make([]string, 0, len(d.vulnerabilities))
|
||||
for k := range d.vulnerabilities {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func Test_HasSeverityAtOrAbove(t *testing.T) {
|
||||
thePkg := pkg.Package{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
Name: "the-package",
|
||||
Version: "v0.1",
|
||||
Type: syftPkg.RpmPkg,
|
||||
}
|
||||
|
||||
matches := match.NewMatches()
|
||||
matches.Add(match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2014-fake-1",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
},
|
||||
Package: thePkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
failOnSeverity string
|
||||
matches match.Matches
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "no-severity-set",
|
||||
failOnSeverity: "",
|
||||
matches: matches,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "below-threshold",
|
||||
failOnSeverity: "high",
|
||||
matches: matches,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "at-threshold",
|
||||
failOnSeverity: "medium",
|
||||
matches: matches,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "above-threshold",
|
||||
failOnSeverity: "low",
|
||||
matches: matches,
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
metadataProvider := db.NewVulnerabilityMetadataProvider(newMockStore())
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var failOnSeverity vulnerability.Severity
|
||||
if test.failOnSeverity != "" {
|
||||
sev := vulnerability.ParseSeverity(test.failOnSeverity)
|
||||
if sev == vulnerability.UnknownSeverity {
|
||||
t.Fatalf("could not parse severity")
|
||||
}
|
||||
failOnSeverity = sev
|
||||
}
|
||||
|
||||
actual := HasSeverityAtOrAbove(metadataProvider, failOnSeverity, test.matches)
|
||||
|
||||
if test.expectedResult != actual {
|
||||
t.Errorf("expected: %v got : %v", test.expectedResult, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
|
||||
mkStr := newMockStore()
|
||||
vp, err := db.NewVulnerabilityProvider(mkStr)
|
||||
require.NoError(t, err)
|
||||
str := store.Store{
|
||||
Provider: vp,
|
||||
MetadataProvider: db.NewVulnerabilityMetadataProvider(mkStr),
|
||||
ExclusionProvider: db.NewMatchExclusionProvider(mkStr),
|
||||
}
|
||||
|
||||
neutron2013Pkg := pkg.Package{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
Name: "neutron",
|
||||
Version: "2013.1.1-1",
|
||||
Type: syftPkg.DebPkg,
|
||||
}
|
||||
|
||||
mustCPE := func(c string) syftPkg.CPE {
|
||||
cp, err := syftPkg.NewCPE(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
activerecordPkg := pkg.Package{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
Name: "activerecord",
|
||||
Version: "3.7.5",
|
||||
CPEs: []syftPkg.CPE{
|
||||
mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"),
|
||||
},
|
||||
Type: syftPkg.GemPkg,
|
||||
Language: syftPkg.Ruby,
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
Store store.Store
|
||||
Matchers []matcher.Matcher
|
||||
IgnoreRules []match.IgnoreRule
|
||||
FailSeverity *vulnerability.Severity
|
||||
NormalizeByCVE bool
|
||||
}
|
||||
type args struct {
|
||||
pkgs []pkg.Package
|
||||
context pkg.Context
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantMatches match.Matches
|
||||
wantIgnoredMatches []match.IgnoredMatch
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "no matches",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
{
|
||||
ID: pkg.ID(uuid.NewString()),
|
||||
Name: "neutrino",
|
||||
Version: "2099.1.1-1",
|
||||
Type: syftPkg.DebPkg,
|
||||
},
|
||||
},
|
||||
context: pkg.Context{
|
||||
Distro: &linux.Release{
|
||||
ID: "debian",
|
||||
VersionID: "8",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matches by exact-direct match (OS)",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
neutron2013Pkg,
|
||||
},
|
||||
context: pkg.Context{
|
||||
Distro: &linux.Release{
|
||||
ID: "debian",
|
||||
VersionID: "8",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
|
||||
ID: "CVE-2014-fake-1",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
CPEs: []syftPkg.CPE{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: neutron2013Pkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"distro": map[string]string{"type": "debian", "version": "8"},
|
||||
"namespace": "debian:distro:debian:8",
|
||||
"package": map[string]string{"name": "neutron", "version": "2013.1.1-1"},
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 2014.1.3-6 (deb)",
|
||||
"vulnerabilityID": "CVE-2014-fake-1",
|
||||
},
|
||||
Matcher: "dpkg-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantIgnoredMatches: nil,
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "fail on severity threshold",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
|
||||
FailSeverity: func() *vulnerability.Severity {
|
||||
x := vulnerability.LowSeverity
|
||||
return &x
|
||||
}(),
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
neutron2013Pkg,
|
||||
},
|
||||
context: pkg.Context{
|
||||
Distro: &linux.Release{
|
||||
ID: "debian",
|
||||
VersionID: "8",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
|
||||
ID: "CVE-2014-fake-1",
|
||||
Namespace: "debian:distro:debian:8",
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
CPEs: []syftPkg.CPE{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: neutron2013Pkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"distro": map[string]string{"type": "debian", "version": "8"},
|
||||
"namespace": "debian:distro:debian:8",
|
||||
"package": map[string]string{"name": "neutron", "version": "2013.1.1-1"},
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 2014.1.3-6 (deb)",
|
||||
"vulnerabilityID": "CVE-2014-fake-1",
|
||||
},
|
||||
Matcher: "dpkg-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantIgnoredMatches: nil,
|
||||
wantErr: grypeerr.ErrAboveSeverityThreshold,
|
||||
},
|
||||
{
|
||||
name: "matches by exact-direct match (language)",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{
|
||||
Ruby: ruby.MatcherConfig{
|
||||
UseCPEs: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
activerecordPkg,
|
||||
},
|
||||
context: pkg.Context{},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []syftPkg.CPE{
|
||||
mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"),
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.CPEMatch,
|
||||
SearchedBy: search.CPEParameters{
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Found: search.CPEResult{
|
||||
VulnerabilityID: "CVE-2014-fake-3",
|
||||
VersionConstraint: "< 3.7.6 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 0.9,
|
||||
},
|
||||
},
|
||||
},
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "GHSA-2014-fake-3",
|
||||
Namespace: "github:language:ruby",
|
||||
RelatedVulnerabilities: []vulnerability.Reference{
|
||||
{
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
},
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
CPEs: []syftPkg.CPE{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"language": "ruby",
|
||||
"namespace": "github:language:ruby",
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 3.7.6 (unknown)",
|
||||
"vulnerabilityID": "GHSA-2014-fake-3",
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantIgnoredMatches: nil,
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "normalize by cve",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Ruby: ruby.MatcherConfig{
|
||||
UseCPEs: true,
|
||||
},
|
||||
},
|
||||
),
|
||||
NormalizeByCVE: true, // IMPORTANT!
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
activerecordPkg,
|
||||
},
|
||||
context: pkg.Context{},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []syftPkg.CPE{
|
||||
mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"),
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.CPEMatch,
|
||||
SearchedBy: search.CPEParameters{
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Found: search.CPEResult{
|
||||
VulnerabilityID: "CVE-2014-fake-3",
|
||||
VersionConstraint: "< 3.7.6 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 0.9,
|
||||
},
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"language": "ruby",
|
||||
"namespace": "github:language:ruby",
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 3.7.6 (unknown)",
|
||||
"vulnerabilityID": "GHSA-2014-fake-3",
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantIgnoredMatches: nil,
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "normalize by cve -- ignore GHSA",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Ruby: ruby.MatcherConfig{
|
||||
UseCPEs: true,
|
||||
},
|
||||
},
|
||||
),
|
||||
IgnoreRules: []match.IgnoreRule{
|
||||
{
|
||||
Vulnerability: "GHSA-2014-fake-3",
|
||||
},
|
||||
},
|
||||
NormalizeByCVE: true, // IMPORTANT!
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
activerecordPkg,
|
||||
},
|
||||
context: pkg.Context{},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []syftPkg.CPE{
|
||||
mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"),
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.CPEMatch,
|
||||
SearchedBy: search.CPEParameters{
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Found: search.CPEResult{
|
||||
VulnerabilityID: "CVE-2014-fake-3",
|
||||
VersionConstraint: "< 3.7.6 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 0.9,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "normalize by cve -- ignore CVE",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(
|
||||
matcher.Config{
|
||||
Ruby: ruby.MatcherConfig{
|
||||
UseCPEs: true,
|
||||
},
|
||||
},
|
||||
),
|
||||
IgnoreRules: []match.IgnoreRule{
|
||||
{
|
||||
Vulnerability: "CVE-2014-fake-3",
|
||||
},
|
||||
},
|
||||
NormalizeByCVE: true, // IMPORTANT!
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
activerecordPkg,
|
||||
},
|
||||
context: pkg.Context{},
|
||||
},
|
||||
wantMatches: match.NewMatches(),
|
||||
wantIgnoredMatches: []match.IgnoredMatch{
|
||||
{
|
||||
AppliedIgnoreRules: []match.IgnoreRule{
|
||||
{
|
||||
Vulnerability: "CVE-2014-fake-3",
|
||||
},
|
||||
},
|
||||
Match: match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []syftPkg.CPE{},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
RelatedVulnerabilities: []vulnerability.Reference{
|
||||
{
|
||||
ID: "GHSA-2014-fake-3",
|
||||
Namespace: "github:language:ruby",
|
||||
},
|
||||
},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"language": "ruby",
|
||||
"namespace": "github:language:ruby",
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 3.7.6 (unknown)",
|
||||
"vulnerabilityID": "GHSA-2014-fake-3",
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "ignore CVE (not normalized by CVE)",
|
||||
fields: fields{
|
||||
Store: str,
|
||||
Matchers: matcher.NewDefaultMatchers(matcher.Config{
|
||||
Ruby: ruby.MatcherConfig{
|
||||
UseCPEs: true,
|
||||
},
|
||||
}),
|
||||
IgnoreRules: []match.IgnoreRule{
|
||||
{
|
||||
Vulnerability: "CVE-2014-fake-3",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
pkgs: []pkg.Package{
|
||||
activerecordPkg,
|
||||
},
|
||||
},
|
||||
wantMatches: match.NewMatches(
|
||||
match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "GHSA-2014-fake-3",
|
||||
Namespace: "github:language:ruby",
|
||||
RelatedVulnerabilities: []vulnerability.Reference{
|
||||
{
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
},
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
CPEs: []syftPkg.CPE{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
SearchedBy: map[string]any{
|
||||
"language": "ruby",
|
||||
"namespace": "github:language:ruby",
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 3.7.6 (unknown)",
|
||||
"vulnerabilityID": "GHSA-2014-fake-3",
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
wantIgnoredMatches: []match.IgnoredMatch{
|
||||
{
|
||||
AppliedIgnoreRules: []match.IgnoreRule{
|
||||
{
|
||||
Vulnerability: "CVE-2014-fake-3",
|
||||
},
|
||||
},
|
||||
Match: match.Match{
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat),
|
||||
ID: "CVE-2014-fake-3",
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []syftPkg.CPE{
|
||||
mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"),
|
||||
},
|
||||
PackageQualifiers: []qualifier.Qualifier{},
|
||||
Advisories: []vulnerability.Advisory{},
|
||||
},
|
||||
Package: activerecordPkg,
|
||||
Details: match.Details{
|
||||
{
|
||||
Type: match.CPEMatch,
|
||||
SearchedBy: search.CPEParameters{
|
||||
Namespace: "nvd:cpe",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Found: search.CPEResult{
|
||||
VulnerabilityID: "CVE-2014-fake-3",
|
||||
VersionConstraint: "< 3.7.6 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
},
|
||||
Matcher: "ruby-gem-matcher",
|
||||
Confidence: 0.9,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &VulnerabilityMatcher{
|
||||
Store: tt.fields.Store,
|
||||
Matchers: tt.fields.Matchers,
|
||||
IgnoreRules: tt.fields.IgnoreRules,
|
||||
FailSeverity: tt.fields.FailSeverity,
|
||||
NormalizeByCVE: tt.fields.NormalizeByCVE,
|
||||
}
|
||||
actualMatches, actualIgnoreMatches, err := m.FindMatches(tt.args.pkgs, tt.args.context)
|
||||
if tt.wantErr != nil {
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Errorf("FindMatches() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
var opts = []cmp.Option{
|
||||
cmpopts.IgnoreUnexported(match.Match{}),
|
||||
cmpopts.IgnoreFields(vulnerability.Vulnerability{}, "Constraint"),
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "Locations"),
|
||||
cmpopts.IgnoreUnexported(match.IgnoredMatch{}),
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tt.wantMatches.Sorted(), actualMatches.Sorted(), opts...); d != "" {
|
||||
t.Errorf("FindMatches() matches mismatch [ha!] (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tt.wantIgnoredMatches, actualIgnoreMatches, opts...); d != "" {
|
||||
t.Errorf("FindMatches() ignored matches mismatch [ha!] (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ type Application struct {
|
|||
Log logging `yaml:"log" json:"log" mapstructure:"log"`
|
||||
Attestation Attestation `yaml:"attestation" json:"attestation" mapstructure:"attestation"`
|
||||
ShowSuppressed bool `yaml:"show-suppressed" json:"show-suppressed" mapstructure:"show-suppressed"`
|
||||
ByCVE bool `yaml:"by-cve" json:"by-cve" mapstructure:"by-cve"` // --by-cve, indicates if the original match vulnerability IDs should be preserved or the CVE should be used instead
|
||||
}
|
||||
|
||||
func newApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) *Application {
|
||||
|
@ -90,8 +91,6 @@ func LoadApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) (*Application
|
|||
func (cfg Application) loadDefaultValues(v *viper.Viper) {
|
||||
// set the default values for primitive fields in this struct
|
||||
v.SetDefault("check-for-app-update", true)
|
||||
v.SetDefault("only-fixed", false)
|
||||
v.SetDefault("only-notfixed", false)
|
||||
|
||||
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
|
||||
value := reflect.ValueOf(cfg)
|
||||
|
|
|
@ -26,15 +26,14 @@ func (cfg externalSources) loadDefaultValues(v *viper.Viper) {
|
|||
v.SetDefault("external-sources.maven.base-url", defaultMavenBaseURL)
|
||||
}
|
||||
|
||||
func (cfg externalSources) ToJavaMatcherConfig(matchCfg matcherConfig) java.MatcherConfig {
|
||||
func (cfg externalSources) ToJavaMatcherConfig() java.ExternalSearchConfig {
|
||||
// always respect if global config is disabled
|
||||
smu := cfg.Maven.SearchUpstreamBySha1
|
||||
if !cfg.Enable {
|
||||
smu = cfg.Enable
|
||||
}
|
||||
return java.MatcherConfig{
|
||||
return java.ExternalSearchConfig{
|
||||
SearchMavenUpstream: smu,
|
||||
MavenBaseURL: cfg.Maven.BaseURL,
|
||||
UseCPEs: matchCfg.UseCPEs,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@ type mockStore struct {
|
|||
backend map[string]map[string][]grypeDB.Vulnerability
|
||||
}
|
||||
|
||||
func (s *mockStore) GetVulnerability(namespace, id string) ([]grypeDB.Vulnerability, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (s *mockStore) GetVulnerabilityNamespaces() ([]string, error) {
|
||||
var results []string
|
||||
for k := range s.backend {
|
||||
|
@ -148,7 +153,7 @@ func newMockDbStore() *mockStore {
|
|||
},
|
||||
},
|
||||
"github:language:haskell": {
|
||||
"ShellCheck": []grypeDB.Vulnerability{
|
||||
"shellcheck": []grypeDB.Vulnerability{
|
||||
{
|
||||
ID: "CVE-haskell-sample",
|
||||
VersionConstraint: "< 0.9.0",
|
||||
|
@ -196,7 +201,7 @@ func newMockDbStore() *mockStore {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *mockStore) GetVulnerability(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
func (s *mockStore) SearchForVulnerabilities(namespace, name string) ([]grypeDB.Vulnerability, error) {
|
||||
namespaceMap := s.backend[namespace]
|
||||
if namespaceMap == nil {
|
||||
return nil, nil
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/grype/grype"
|
||||
|
@ -30,9 +33,8 @@ func addAlpineMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["alpine:distro:alpine:3.12"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
// note: we are matching on the secdb record, not NVD primarily
|
||||
|
||||
|
@ -43,14 +45,43 @@ func addAlpineMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*",
|
||||
"distro": map[string]string{
|
||||
"type": "alpine",
|
||||
"version": "3.12.0",
|
||||
},
|
||||
"namespace": "alpine:distro:alpine:3.12",
|
||||
"package": map[string]string{
|
||||
"name": "libvncserver",
|
||||
"version": "0.9.9",
|
||||
},
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"cpes": []string{"cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*"},
|
||||
"constraint": "< 0.9.10 (unknown)",
|
||||
"versionConstraint": "< 0.9.10 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
{
|
||||
// note: the input pURL has an upstream reference (redundant)
|
||||
Type: "exact-indirect-match",
|
||||
SearchedBy: map[string]any{
|
||||
"distro": map[string]string{
|
||||
"type": "alpine",
|
||||
"version": "3.12.0",
|
||||
},
|
||||
"namespace": "alpine:distro:alpine:3.12",
|
||||
"package": map[string]string{
|
||||
"name": "libvncserver",
|
||||
"version": "0.9.9",
|
||||
},
|
||||
},
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 0.9.10 (unknown)",
|
||||
"vulnerabilityID": "CVE-alpine-libvncserver",
|
||||
},
|
||||
Matcher: "apk-matcher",
|
||||
Confidence: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -64,9 +95,8 @@ func addJavascriptMatches(t *testing.T, theSource source.Source, catalog *syftPk
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["github:language:javascript"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
|
@ -75,10 +105,12 @@ func addJavascriptMatches(t *testing.T, theSource source.Source, catalog *syftPk
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "javascript",
|
||||
"language": "javascript",
|
||||
"namespace": "github:language:javascript",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "< 3.2.1 (unknown)",
|
||||
"versionConstraint": "> 5, < 7.2.1 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.JavascriptMatcher,
|
||||
},
|
||||
|
@ -99,9 +131,8 @@ func addPythonMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
normalizedName := theStore.normalizedPackageNames["github:language:python"][thePkg.Name]
|
||||
theVuln := theStore.backend["github:language:python"][normalizedName][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
|
||||
Vulnerability: *vulnObj,
|
||||
|
@ -111,10 +142,12 @@ func addPythonMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "python",
|
||||
"language": "python",
|
||||
"namespace": "github:language:python",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "< 2.6.2 (python)",
|
||||
"versionConstraint": "< 2.6.2 (python)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
|
@ -135,9 +168,8 @@ func addDotnetMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
normalizedName := theStore.normalizedPackageNames["github:language:dotnet"][thePkg.Name]
|
||||
theVuln := theStore.backend["github:language:dotnet"][normalizedName][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
|
||||
Vulnerability: *vulnObj,
|
||||
|
@ -147,10 +179,12 @@ func addDotnetMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "dotnet",
|
||||
"language": "dotnet",
|
||||
"namespace": "github:language:dotnet",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": ">= 3.7.0.0, < 3.7.12.0 (dotnet)",
|
||||
"versionConstraint": ">= 3.7.0.0, < 3.7.12.0 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.DotnetMatcher,
|
||||
},
|
||||
|
@ -167,9 +201,8 @@ func addRubyMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["github:language:ruby"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
|
||||
Vulnerability: *vulnObj,
|
||||
|
@ -179,10 +212,12 @@ func addRubyMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "ruby",
|
||||
"language": "ruby",
|
||||
"namespace": "github:language:ruby",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "> 4.0.0, <= 4.1.1 (gemfile)",
|
||||
"versionConstraint": "> 2.0.0, <= 2.1.4 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
},
|
||||
|
@ -191,40 +226,53 @@ func addRubyMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
}
|
||||
|
||||
func addGolangMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Catalog, theStore *mockStore, theResult *match.Matches) {
|
||||
packages := catalog.PackagesByPath("/go-app")
|
||||
if len(packages) != 2 {
|
||||
t.Logf("Golang Packages: %+v", packages)
|
||||
modPackages := catalog.PackagesByPath("/golang/go.mod")
|
||||
if len(modPackages) != 1 {
|
||||
t.Logf("Golang Mod Packages: %+v", modPackages)
|
||||
t.Fatalf("problem with upstream syft cataloger (golang)")
|
||||
}
|
||||
|
||||
binPackages := catalog.PackagesByPath("/go-app")
|
||||
if len(binPackages) != 2 {
|
||||
t.Logf("Golang Bin Packages: %+v", binPackages)
|
||||
t.Fatalf("problem with upstream syft cataloger (golang)")
|
||||
}
|
||||
|
||||
var packages []syftPkg.Package
|
||||
packages = append(packages, modPackages...)
|
||||
packages = append(packages, binPackages...)
|
||||
|
||||
for _, p := range packages {
|
||||
thePkg := pkg.New(p)
|
||||
theVuln := theStore.backend["github:language:go"][p.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
// no vuln match supported for main module
|
||||
if p.Name == "github.com/anchore/coverage" {
|
||||
continue
|
||||
}
|
||||
|
||||
// no vuln match supported for main module
|
||||
if p.Name != "github.com/anchore/coverage" {
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
Details: []match.Detail{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"langauge": "go",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": " < 1.4.0 (golang)",
|
||||
},
|
||||
Matcher: match.GoModuleMatcher,
|
||||
thePkg := pkg.New(p)
|
||||
theVuln := theStore.backend["github:language:go"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
Details: []match.Detail{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "go",
|
||||
"namespace": "github:language:go",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": "< 1.4.0 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.GoModuleMatcher,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,9 +294,8 @@ func addJavaMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
|
||||
theVuln := theStore.backend["github:language:java"][lookup][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
|
@ -257,10 +304,12 @@ func addJavaMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
"language": "java",
|
||||
"namespace": "github:language:java",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": ">= 0.0.1, < 1.2.0 (unknown)",
|
||||
"versionConstraint": ">= 0.0.1, < 1.2.0 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.JavaMatcher,
|
||||
},
|
||||
|
@ -278,9 +327,8 @@ func addDpkgMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
// NOTE: this is an indirect match, in typical debian style
|
||||
theVuln := theStore.backend["debian:distro:debian:8"][thePkg.Name+"-dev"][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
|
||||
Vulnerability: *vulnObj,
|
||||
|
@ -294,9 +342,15 @@ func addDpkgMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
"type": "debian",
|
||||
"version": "8",
|
||||
},
|
||||
"namespace": "debian:distro:debian:8",
|
||||
"package": map[string]string{
|
||||
"name": "apt-dev",
|
||||
"version": "1.8.2",
|
||||
},
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "<= 1.8.2 (deb)",
|
||||
"versionConstraint": "<= 1.8.2 (deb)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.DpkgMatcher,
|
||||
},
|
||||
|
@ -313,9 +367,8 @@ func addPortageMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["gentoo:distro:gentoo:2.8"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
|
@ -326,11 +379,17 @@ func addPortageMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
|
|||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "gentoo",
|
||||
"version": "portage",
|
||||
"version": "2.8",
|
||||
},
|
||||
"namespace": "gentoo:distro:gentoo:2.8",
|
||||
"package": map[string]string{
|
||||
"name": "app-containers/skopeo",
|
||||
"version": "1.5.1",
|
||||
},
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "<= 1.6.0 (gentoo)",
|
||||
"versionConstraint": "< 1.6.0 (unknown)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.PortageMatcher,
|
||||
},
|
||||
|
@ -347,9 +406,8 @@ func addRhelMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["redhat:distro:redhat:8"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
|
||||
Vulnerability: *vulnObj,
|
||||
|
@ -363,9 +421,15 @@ func addRhelMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
"type": "centos",
|
||||
"version": "8",
|
||||
},
|
||||
"namespace": "redhat:distro:redhat:8",
|
||||
"package": map[string]string{
|
||||
"name": "dive",
|
||||
"version": "0:0.9.2-1",
|
||||
},
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "<= 1.0.42 (rpm)",
|
||||
"versionConstraint": "<= 1.0.42 (rpm)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.RpmMatcher,
|
||||
},
|
||||
|
@ -382,9 +446,9 @@ func addSlesMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["redhat:distro:redhat:8"][thePkg.Name][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
vulnObj.Namespace = "sles:distro:sles:12.5"
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
|
@ -397,9 +461,15 @@ func addSlesMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
"type": "sles",
|
||||
"version": "12.5",
|
||||
},
|
||||
"namespace": "sles:distro:sles:12.5",
|
||||
"package": map[string]string{
|
||||
"name": "dive",
|
||||
"version": "0:0.9.2-1",
|
||||
},
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "<= 1.0.42 (rpm)",
|
||||
"versionConstraint": "<= 1.0.42 (rpm)",
|
||||
"vulnerabilityID": vulnObj.ID,
|
||||
},
|
||||
Matcher: match.RpmMatcher,
|
||||
},
|
||||
|
@ -414,11 +484,10 @@ func addHaskellMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
|
|||
t.Fatalf("problem with upstream syft cataloger (haskell)")
|
||||
}
|
||||
thePkg := pkg.New(packages[0])
|
||||
theVuln := theStore.backend["github:language:haskell"][thePkg.Name][0]
|
||||
theVuln := theStore.backend["github:language:haskell"][strings.ToLower(thePkg.Name)][0]
|
||||
vulnObj, err := vulnerability.NewVulnerability(theVuln)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create vuln obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
|
@ -426,16 +495,15 @@ func addHaskellMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
|
|||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": map[string]string{
|
||||
"type": "haskell",
|
||||
"version": "",
|
||||
},
|
||||
SearchedBy: map[string]any{
|
||||
"language": "haskell",
|
||||
"namespace": "github:language:haskell",
|
||||
},
|
||||
Found: map[string]interface{}{
|
||||
"constraint": "< 0.9.0 (haskell)",
|
||||
Found: map[string]any{
|
||||
"versionConstraint": "< 0.9.0 (unknown)",
|
||||
"vulnerabilityID": "CVE-haskell-sample",
|
||||
},
|
||||
Matcher: match.UnknownMatcherType,
|
||||
Matcher: match.StockMatcher,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -511,15 +579,11 @@ func TestMatchByImage(t *testing.T) {
|
|||
userImage := "docker-archive:" + tarPath
|
||||
|
||||
sourceInput, err := source.ParseInput(userImage, "", true)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse user input %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// this is purely done to help setup mocks
|
||||
theSource, cleanup, err := source.New(*sourceInput, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to determine image source: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
|
||||
// TODO: relationships are not verified at this time
|
||||
|
@ -530,73 +594,62 @@ func TestMatchByImage(t *testing.T) {
|
|||
config.Catalogers = []string{"all"}
|
||||
|
||||
theCatalog, _, theDistro, err := syft.CatalogPackages(theSource, config)
|
||||
if err != nil {
|
||||
t.Fatalf("could not get the source obj: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
matchers := matcher.NewDefaultMatchers(matcher.Config{})
|
||||
|
||||
vp, err := db.NewVulnerabilityProvider(theStore)
|
||||
require.NoError(t, err)
|
||||
ep := db.NewMatchExclusionProvider(theStore)
|
||||
store := store.Store{
|
||||
str := store.Store{
|
||||
Provider: vp,
|
||||
MetadataProvider: nil,
|
||||
ExclusionProvider: ep,
|
||||
}
|
||||
|
||||
actualResults := grype.FindVulnerabilitiesForPackage(store, theDistro, matchers, pkg.FromCatalog(theCatalog, pkg.ProviderConfig{}))
|
||||
actualResults := grype.FindVulnerabilitiesForPackage(str, theDistro, matchers, pkg.FromCatalog(theCatalog, pkg.SynthesisConfig{}))
|
||||
|
||||
for _, m := range actualResults.Sorted() {
|
||||
for _, d := range m.Details {
|
||||
observedMatchers.Add(string(d.Matcher))
|
||||
}
|
||||
}
|
||||
|
||||
// build expected matches from what's discovered from the catalog
|
||||
expectedMatches := test.expectedFn(*theSource, theCatalog, theStore)
|
||||
|
||||
// build expected match set...
|
||||
expectedMatchSet := map[string]string{}
|
||||
for eMatch := range expectedMatches.Enumerate() {
|
||||
// NOTE: this does not include all fields...
|
||||
expectedMatchSet[eMatch.Package.Name] = eMatch.String()
|
||||
}
|
||||
|
||||
expectedCount := len(expectedMatchSet)
|
||||
|
||||
// ensure that all matches are covered
|
||||
actualCount := 0
|
||||
for aMatch := range actualResults.Enumerate() {
|
||||
actualCount++
|
||||
for _, details := range aMatch.Details {
|
||||
observedMatchers.Add(string(details.Matcher))
|
||||
}
|
||||
value, ok := expectedMatchSet[aMatch.Package.Name]
|
||||
if !ok {
|
||||
t.Errorf("Package: %s was expected but not found", aMatch.Package.Name)
|
||||
}
|
||||
|
||||
if value != aMatch.String() {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(value, aMatch.String(), true)
|
||||
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
}
|
||||
|
||||
if expectedCount != actualCount {
|
||||
t.Errorf("expected %d matches but got %d matches", expectedCount, actualCount)
|
||||
}
|
||||
assertMatches(t, expectedMatches.Sorted(), actualResults.Sorted())
|
||||
})
|
||||
}
|
||||
|
||||
// ensure that integration test cases stay in sync with the implemented matchers
|
||||
observedMatchers.Remove(string(match.UnknownMatcherType))
|
||||
definedMatchers.Remove(string(match.UnknownMatcherType))
|
||||
observedMatchers.Remove(string(match.StockMatcher))
|
||||
definedMatchers.Remove(string(match.StockMatcher))
|
||||
definedMatchers.Remove(string(match.MsrcMatcher))
|
||||
|
||||
if len(observedMatchers) != len(definedMatchers) {
|
||||
t.Errorf("matcher coverage incomplete (matchers=%d, coverage=%d)", len(definedMatchers), len(observedMatchers))
|
||||
for _, m := range definedMatchers.ToSlice() {
|
||||
t.Logf(" defined: %+v\n", m)
|
||||
}
|
||||
for _, m := range observedMatchers.ToSlice() {
|
||||
t.Logf(" found: %+v\n", m)
|
||||
}
|
||||
defs := definedMatchers.ToSlice()
|
||||
sort.Strings(defs)
|
||||
obs := observedMatchers.ToSlice()
|
||||
sort.Strings(obs)
|
||||
|
||||
t.Log(cmp.Diff(defs, obs))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func assertMatches(t *testing.T, expected, actual []match.Match) {
|
||||
t.Helper()
|
||||
var opts = []cmp.Option{
|
||||
cmpopts.IgnoreFields(vulnerability.Vulnerability{}, "Constraint"),
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "Locations"),
|
||||
cmpopts.SortSlices(func(a, b match.Match) bool {
|
||||
return a.Package.ID < b.Package.ID
|
||||
}),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expected, actual, opts...); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"github.com/anchore/grype/grype"
|
||||
"github.com/anchore/grype/grype/db"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/store"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
@ -43,6 +45,7 @@ func TestMatchBySBOMDocument(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": "3200970 || 878787 || base (kb)",
|
||||
"vulnerabilityID": "CVE-2016-3333",
|
||||
},
|
||||
Matcher: match.MsrcMatcher,
|
||||
Confidence: 1,
|
||||
|
@ -62,6 +65,7 @@ func TestMatchBySBOMDocument(t *testing.T) {
|
|||
},
|
||||
Found: map[string]interface{}{
|
||||
"versionConstraint": "< 2.0 (python)",
|
||||
"vulnerabilityID": "CVE-bogus-my-package-2-python",
|
||||
},
|
||||
Matcher: match.StockMatcher,
|
||||
Confidence: 1,
|
||||
|
@ -72,16 +76,16 @@ func TestMatchBySBOMDocument(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
mockStore := newMockDbStore()
|
||||
vp, err := db.NewVulnerabilityProvider(mockStore)
|
||||
mkStr := newMockDbStore()
|
||||
vp, err := db.NewVulnerabilityProvider(mkStr)
|
||||
require.NoError(t, err)
|
||||
ep := db.NewMatchExclusionProvider(mockStore)
|
||||
store := store.Store{
|
||||
ep := db.NewMatchExclusionProvider(mkStr)
|
||||
str := store.Store{
|
||||
Provider: vp,
|
||||
MetadataProvider: nil,
|
||||
ExclusionProvider: ep,
|
||||
}
|
||||
matches, _, _, err := grype.FindVulnerabilities(store, fmt.Sprintf("sbom:%s", test.fixture), source.SquashedScope, nil)
|
||||
matches, _, _, err := grype.FindVulnerabilities(str, fmt.Sprintf("sbom:%s", test.fixture), source.SquashedScope, nil)
|
||||
assert.NoError(t, err)
|
||||
details := make([]match.Detail, 0)
|
||||
ids := strset.New()
|
||||
|
@ -91,9 +95,14 @@ func TestMatchBySBOMDocument(t *testing.T) {
|
|||
}
|
||||
|
||||
require.Len(t, details, len(test.expectedDetails))
|
||||
|
||||
cmpOpts := []cmp.Option{
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "Locations"),
|
||||
}
|
||||
|
||||
for i := range test.expectedDetails {
|
||||
for _, d := range deep.Equal(test.expectedDetails[i], details[i]) {
|
||||
t.Error(d)
|
||||
if d := cmp.Diff(test.expectedDetails[i], details[i], cmpOpts...); d != "" {
|
||||
t.Errorf("unexpected match details (-want +got):\n%s", d)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d01404841050b2215a78ba4bbc9d996abb290a9a
|
||||
Subproject commit 6ca252c622bc67e7670fe5333464400ceafbe64d
|
Loading…
Reference in a new issue