mirror of
https://github.com/anchore/grype
synced 2024-11-10 14:44:12 +00:00
incorporate multiple match details to accomodate more accurate reported CPE matching info
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
06dcd811d9
commit
ed054f2038
39 changed files with 1302 additions and 527 deletions
3
go.sum
3
go.sum
|
@ -114,7 +114,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
|||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alicebob/sqlittle v1.4.0 h1:vgYt0nAjhdf/hg52MjKJ84g/uTzBPfrvI+VUBrIghxA=
|
||||
github.com/alicebob/sqlittle v1.4.0/go.mod h1:Co1L1qxHqCwf41puWhk2HOodojR0mcsAV4BIt8byZh8=
|
||||
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf h1:DYssiUV1pBmKqzKsm4mqXx8artqC0Q8HgZsVI3lMsAg=
|
||||
github.com/anchore/client-go v0.0.0-20210222170800-9c70f9b80bcf/go.mod h1:FaODhIA06mxO1E6R32JE0TL1JWZZkmjRIAd4ULvHUKk=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20210415132930-2460011e83c6 h1:wEN3HXc3VuC4wo7Cz27YCpeQ4gaB5ASKwMwM5GdFsew=
|
||||
github.com/anchore/go-rpmdb v0.0.0-20210415132930-2460011e83c6/go.mod h1:8jNYOxCJC5kyD/Ct4MbzsDN2hOhRoCAzQcb/7KdYYGw=
|
||||
|
@ -132,7 +131,6 @@ github.com/anchore/syft v0.16.2-0.20210526131825-2754c889eb0e/go.mod h1:9jf8z5ub
|
|||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
|
||||
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
|
||||
|
@ -651,7 +649,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
|
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
|
||||
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
|
@ -2,6 +2,7 @@ package match
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
|
@ -10,20 +11,41 @@ import (
|
|||
// Match represents a finding in the vulnerability matching process, pairing a single package and a single vulnerability object.
|
||||
type Match struct {
|
||||
Type Type // The kind of match made (an exact match, fuzzy match, indirect vs direct, etc).
|
||||
Confidence float64 // The certainty of the match as a ratio (currently unused, reserved for future use).
|
||||
Vulnerability vulnerability.Vulnerability // The vulnerability details of the match.
|
||||
Package pkg.Package // The package used to search for a match.
|
||||
SearchedBy map[string]interface{} // The specific attributes that were used to search (other than package name and version) --this indicates "how" the match was made.
|
||||
SearchMatches map[string]interface{} // The specific attributes on the vulnerability object that were matched with --this indicates "what" was matched on / within.
|
||||
Matcher MatcherType // The matcher object that discovered the match.
|
||||
MatchDetails []Details // all ways in which how this particular match was made.
|
||||
}
|
||||
|
||||
type Details struct {
|
||||
SearchedBy interface{} // The specific attributes that were used to search (other than package name and version) --this indicates "how" the match was made.
|
||||
MatchedOn interface{} // The specific attributes on the vulnerability object that were matched with --this indicates "what" was matched on / within.
|
||||
Matcher MatcherType // The matcher object that discovered the match.
|
||||
Confidence float64 // The certainty of the match as a ratio (currently unused, reserved for future use).
|
||||
}
|
||||
|
||||
type Fingerprint struct {
|
||||
VulnerabilityID string
|
||||
VulnerabilityNamespace string
|
||||
VulnerabilityFixes string
|
||||
PackageID pkg.ID // this encodes package name, version, type, location
|
||||
MatchType Type
|
||||
}
|
||||
|
||||
// String is the string representation of select match fields.
|
||||
func (m Match) String() string {
|
||||
return fmt.Sprintf("Match(pkg=%s vuln=%s type='%s' foundBy='%s')", m.Package, m.Vulnerability.String(), m.Type, m.Matcher)
|
||||
return fmt.Sprintf("Match(pkg=%s vuln=%q type=%q)", m.Package, m.Vulnerability.String(), m.Type)
|
||||
}
|
||||
|
||||
// Summary is a short string representation of the match object.
|
||||
func (m Match) Summary() string {
|
||||
return fmt.Sprintf("vuln='%s' type='%s' key='%s' foundBy='%s'", m.Vulnerability.ID, m.Type, m.SearchedBy, m.Matcher)
|
||||
return fmt.Sprintf("vuln=%q type=%q searchedBy=%q foundBy=%q", m.Vulnerability.ID, m.Type, m.MatchDetails[0].SearchedBy, m.MatchDetails[0].Matcher)
|
||||
}
|
||||
|
||||
func (m Match) Fingerprint() Fingerprint {
|
||||
return Fingerprint{
|
||||
VulnerabilityID: m.Vulnerability.ID,
|
||||
VulnerabilityNamespace: m.Vulnerability.Namespace,
|
||||
VulnerabilityFixes: strings.Join(m.Vulnerability.Fix.Versions, ","),
|
||||
PackageID: m.Package.ID,
|
||||
MatchType: m.Type,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ func (r *Matches) add(id pkg.ID, matches ...Match) {
|
|||
}
|
||||
|
||||
func (r *Matches) Add(p pkg.Package, matches ...Match) {
|
||||
r.add(p.ID(), matches...)
|
||||
r.add(p.ID, matches...)
|
||||
}
|
||||
|
||||
func (r *Matches) Enumerate() <-chan Match {
|
||||
|
@ -66,18 +66,7 @@ func (r *Matches) Sorted() []Match {
|
|||
matches = append(matches, m)
|
||||
}
|
||||
|
||||
sort.SliceStable(matches, func(i, j int) bool {
|
||||
if matches[i].Vulnerability.ID == matches[j].Vulnerability.ID {
|
||||
if matches[i].Package.Name == matches[j].Package.Name {
|
||||
if matches[i].Package.Version == matches[j].Package.Version {
|
||||
return matches[i].Package.Type < matches[j].Package.Type
|
||||
}
|
||||
return matches[i].Package.Version < matches[j].Package.Version
|
||||
}
|
||||
return matches[i].Package.Name < matches[j].Package.Name
|
||||
}
|
||||
return matches[i].Vulnerability.ID < matches[j].Vulnerability.ID
|
||||
})
|
||||
sort.Sort(ByElements(matches))
|
||||
|
||||
return matches
|
||||
}
|
||||
|
|
31
grype/match/sort.go
Normal file
31
grype/match/sort.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package match
|
||||
|
||||
import "sort"
|
||||
|
||||
var _ sort.Interface = (*ByElements)(nil)
|
||||
|
||||
type ByElements []Match
|
||||
|
||||
// Len is the number of elements in the collection.
|
||||
func (m ByElements) Len() int {
|
||||
return len(m)
|
||||
}
|
||||
|
||||
// Less reports whether the element with index i should sort before the element with index j.
|
||||
func (m ByElements) Less(i, j int) bool {
|
||||
if m[i].Vulnerability.ID == m[j].Vulnerability.ID {
|
||||
if m[i].Package.Name == m[j].Package.Name {
|
||||
if m[i].Package.Version == m[j].Package.Version {
|
||||
return m[i].Package.Type < m[j].Package.Type
|
||||
}
|
||||
return m[i].Package.Version < m[j].Package.Version
|
||||
}
|
||||
return m[i].Package.Name < m[j].Package.Name
|
||||
}
|
||||
return m[i].Vulnerability.ID < m[j].Vulnerability.ID
|
||||
}
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (m ByElements) Swap(i, j int) {
|
||||
m[i], m[j] = m[j], m[i]
|
||||
}
|
|
@ -127,12 +127,9 @@ func matchesByID(matches []match.Match) map[string][]match.Match {
|
|||
return results
|
||||
}
|
||||
|
||||
func vulnerabilitiesByID(vulns []*vulnerability.Vulnerability) map[string][]*vulnerability.Vulnerability {
|
||||
var results = make(map[string][]*vulnerability.Vulnerability)
|
||||
func vulnerabilitiesByID(vulns []vulnerability.Vulnerability) map[string][]vulnerability.Vulnerability {
|
||||
var results = make(map[string][]vulnerability.Vulnerability)
|
||||
for _, vuln := range vulns {
|
||||
if vuln == nil {
|
||||
continue
|
||||
}
|
||||
results[vuln.ID] = append(results[vuln.ID], vuln)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package apk
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/grype/grype/matcher/common"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
|
@ -72,20 +74,24 @@ func TestSecDBOnlyMatch(t *testing.T) {
|
|||
expected := []match.Match{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnFound,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
},
|
||||
"namespace": "secdb",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"namespace": "secdb",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -148,20 +154,24 @@ func TestBothSecdbAndNvdMatches(t *testing.T) {
|
|||
expected := []match.Match{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnFound,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
},
|
||||
"namespace": "secdb",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"namespace": "secdb",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -225,20 +235,24 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) {
|
|||
expected := []match.Match{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnFound,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
},
|
||||
"namespace": "secdb",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"namespace": "secdb",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -288,18 +302,22 @@ func TestNvdOnlyMatches(t *testing.T) {
|
|||
expected := []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Vulnerability: *vulnFound,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: common.CPESearchInput{
|
||||
CPEs: []string{"cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: common.CPESearchHit{
|
||||
CPEs: []string{vulnFound.CPEs[0].BindToFmtString()},
|
||||
VersionConstraint: vulnFound.Constraint.String(),
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"cpes": []string{vulnFound.CPEs[0].BindToFmtString()},
|
||||
"versionConstraint": vulnFound.Constraint.String(),
|
||||
"namespace": "nvd",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -2,79 +2,206 @@ package common
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/version"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/grype/internal"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
"github.com/facebookincubator/nvdtools/wfn"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
type CPESearchInput struct {
|
||||
Namespace string `json:"namespace"`
|
||||
CPEs []string `json:"cpes"`
|
||||
}
|
||||
|
||||
func (i *CPESearchInput) Merge(other CPESearchInput) error {
|
||||
if i.Namespace != other.Namespace {
|
||||
return fmt.Errorf("namespaces do not match")
|
||||
}
|
||||
|
||||
existingCPEs := strset.New(i.CPEs...)
|
||||
newCPEs := strset.New(other.CPEs...)
|
||||
mergedCPEs := strset.Union(existingCPEs, newCPEs).List()
|
||||
sort.Strings(mergedCPEs)
|
||||
i.CPEs = mergedCPEs
|
||||
return nil
|
||||
}
|
||||
|
||||
type CPESearchHit struct {
|
||||
VersionConstraint string `json:"versionConstraint"`
|
||||
CPEs []string `json:"cpes"`
|
||||
}
|
||||
|
||||
func (h CPESearchHit) Equals(other CPESearchHit) bool {
|
||||
if h.VersionConstraint != other.VersionConstraint {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(h.CPEs) != len(other.CPEs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range h.CPEs {
|
||||
if h.CPEs[i] != other.CPEs[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FindMatchesByPackageCPE retrieves all vulnerabilities that match the generated CPE
|
||||
func FindMatchesByPackageCPE(store vulnerability.ProviderByCPE, p pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
|
||||
verObj, err := version.NewVersionFromPkg(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
|
||||
}
|
||||
matchesByFingerprint := make(map[match.Fingerprint]match.Match)
|
||||
for _, cpe := range p.CPEs {
|
||||
// prefer the CPE version, but if npt specified use the package version
|
||||
pkgVersion := cpe.Version
|
||||
if pkgVersion == wfn.NA || pkgVersion == wfn.Any {
|
||||
pkgVersion = p.Version
|
||||
}
|
||||
verObj, err := version.NewVersion(pkgVersion, version.FormatFromPkgType(p.Type))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
|
||||
}
|
||||
|
||||
matches := make([]match.Match, 0)
|
||||
vulnSet := vulnerability.NewSet()
|
||||
vulnerableKeys := internal.NewStringSet()
|
||||
|
||||
for _, cpe := range verObj.CPEs() {
|
||||
// find all vulnerability records in the DB for the given CPE (not including version comparisons)
|
||||
allPkgVulns, err := store.GetByCPE(cpe)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("matcher failed to fetch by CPE pkg='%s': %w", p.Name, err)
|
||||
}
|
||||
|
||||
// for each vulnerability record found, check the version constraint. If the constraint is satisfied
|
||||
// relative to the current version information from the CPE (or the package) then the given package
|
||||
// is vulnerable.
|
||||
for _, vuln := range allPkgVulns {
|
||||
if vulnSet.Contains(vuln) {
|
||||
continue
|
||||
}
|
||||
vulnSet.Add(vuln)
|
||||
|
||||
// if the constraint it met, then the given package has the vulnerability
|
||||
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cpe matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
|
||||
}
|
||||
|
||||
if isPackageVulnerable {
|
||||
// create a string key to ensure we aren't adding previously added matches
|
||||
vulnerableKey := fmt.Sprintf("%s%s%s", vuln.ID, cpe.BindToFmtString(), vuln.Constraint.String())
|
||||
if vulnerableKeys.Contains(vulnerableKey) {
|
||||
continue
|
||||
}
|
||||
vulnerableKeys.Add(vulnerableKey)
|
||||
|
||||
matches = append(matches, match.Match{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9, // TODO: this is hard coded for now
|
||||
Vulnerability: *vuln,
|
||||
Package: p,
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": cpe.BindToFmtString(),
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": vuln.Namespace,
|
||||
"cpes": cpesToString(vuln.CPEs),
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
})
|
||||
if !isPackageVulnerable {
|
||||
continue
|
||||
}
|
||||
|
||||
addNewMatch(matchesByFingerprint, vuln, p, verObj, upstreamMatcher, cpe)
|
||||
}
|
||||
}
|
||||
return matches, err
|
||||
|
||||
return toMatches(matchesByFingerprint), nil
|
||||
}
|
||||
|
||||
func addNewMatch(matchesByFingerprint map[match.Fingerprint]match.Match, vuln vulnerability.Vulnerability, p pkg.Package, pkgVersion *version.Version, upstreamMatcher match.MatcherType, searchedByCPE syftPkg.CPE) {
|
||||
candidateMatch := match.Match{
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vuln,
|
||||
Package: p,
|
||||
}
|
||||
|
||||
if existingMatch, exists := matchesByFingerprint[candidateMatch.Fingerprint()]; exists {
|
||||
candidateMatch = existingMatch
|
||||
}
|
||||
|
||||
candidateMatch.MatchDetails = addMatchDetails(candidateMatch.MatchDetails,
|
||||
match.Details{
|
||||
Confidence: 0.9, // TODO: this is hard coded for now
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: vuln.Namespace,
|
||||
CPEs: []string{
|
||||
searchedByCPE.BindToFmtString(),
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: vuln.Constraint.String(),
|
||||
CPEs: cpesToString(keepMatchingCPEs(pkgVersion, vuln.CPEs)),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
matchesByFingerprint[candidateMatch.Fingerprint()] = candidateMatch
|
||||
}
|
||||
|
||||
func addMatchDetails(existingDetails []match.Details, newDetails match.Details) []match.Details {
|
||||
newMatchedOn, ok := newDetails.MatchedOn.(CPESearchHit)
|
||||
if !ok {
|
||||
return existingDetails
|
||||
}
|
||||
|
||||
newSearchedBy, ok := newDetails.SearchedBy.(CPESearchInput)
|
||||
if !ok {
|
||||
return existingDetails
|
||||
}
|
||||
for idx, detail := range existingDetails {
|
||||
matchedOn, ok := detail.MatchedOn.(CPESearchHit)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
searchedBy, ok := detail.SearchedBy.(CPESearchInput)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !matchedOn.Equals(newMatchedOn) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := searchedBy.Merge(newSearchedBy)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
existingDetails[idx].SearchedBy = searchedBy
|
||||
return existingDetails
|
||||
}
|
||||
|
||||
// could not merge with another entry, append to the end
|
||||
existingDetails = append(existingDetails, newDetails)
|
||||
return existingDetails
|
||||
}
|
||||
|
||||
func keepMatchingCPEs(pkgVersion *version.Version, allCPEs []syftPkg.CPE) (matchedCPEs []syftPkg.CPE) {
|
||||
for _, c := range allCPEs {
|
||||
if c.Version == wfn.Any || c.Version == wfn.NA {
|
||||
matchedCPEs = append(matchedCPEs, c)
|
||||
continue
|
||||
}
|
||||
|
||||
constraint, err := version.GetConstraint(c.Version, version.UnknownFormat)
|
||||
if err != nil {
|
||||
// if we can't get a version constraint, don't filter out the CPE
|
||||
matchedCPEs = append(matchedCPEs, c)
|
||||
continue
|
||||
}
|
||||
|
||||
satisfied, err := constraint.Satisfied(pkgVersion)
|
||||
if err != nil || satisfied {
|
||||
// if we can't check for version satisfaction, don't filter out the CPE
|
||||
matchedCPEs = append(matchedCPEs, c)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return matchedCPEs
|
||||
}
|
||||
|
||||
func toMatches(matchesByFingerprint map[match.Fingerprint]match.Match) (matches []match.Match) {
|
||||
for _, m := range matchesByFingerprint {
|
||||
matches = append(matches, m)
|
||||
}
|
||||
sort.Sort(match.ByElements(matches))
|
||||
return matches
|
||||
}
|
||||
|
||||
// cpesToString receives one or more CPEs and stringifies them
|
||||
func cpesToString(cpes []wfn.Attributes) []string {
|
||||
var stringers = make([]string, 0)
|
||||
for _, cpe := range cpes {
|
||||
stringers = append(stringers, cpe.BindToFmtString())
|
||||
func cpesToString(cpes []syftPkg.CPE) []string {
|
||||
var strs = make([]string, len(cpes))
|
||||
for idx, cpe := range cpes {
|
||||
strs[idx] = cpe.BindToFmtString()
|
||||
}
|
||||
|
||||
return stringers
|
||||
sort.Strings(strs)
|
||||
return strs
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package common
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/grype-db/pkg/db"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/version"
|
||||
|
@ -18,60 +20,77 @@ func must(c syftPkg.CPE, e error) syftPkg.CPE {
|
|||
return c
|
||||
}
|
||||
|
||||
type mockCPEProvider struct {
|
||||
data map[string]map[string][]*vulnerability.Vulnerability
|
||||
var _ db.VulnerabilityStoreReader = (*mockVulnStore)(nil)
|
||||
|
||||
type mockVulnStore struct {
|
||||
data map[string]map[string][]db.Vulnerability
|
||||
}
|
||||
|
||||
func newMockProviderByCPE() *mockCPEProvider {
|
||||
pr := mockCPEProvider{
|
||||
data: make(map[string]map[string][]*vulnerability.Vulnerability),
|
||||
func newMockStore() *mockVulnStore {
|
||||
pr := mockVulnStore{
|
||||
data: make(map[string]map[string][]db.Vulnerability),
|
||||
}
|
||||
pr.stub()
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *mockCPEProvider) stub() {
|
||||
pr.data["nvd"] = map[string][]*vulnerability.Vulnerability{
|
||||
func (pr *mockVulnStore) stub() {
|
||||
pr.data["nvd"] = map[string][]db.Vulnerability{
|
||||
"activerecord": {
|
||||
{
|
||||
Constraint: version.MustGetConstraint("< 3.7.6", version.SemanticFormat),
|
||||
ID: "CVE-2017-fake-1",
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*")),
|
||||
PackageName: "activerecord",
|
||||
VersionConstraint: "< 3.7.6",
|
||||
VersionFormat: version.SemanticFormat.String(),
|
||||
ID: "CVE-2017-fake-1",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
{
|
||||
Constraint: version.MustGetConstraint("< 3.7.4", version.SemanticFormat),
|
||||
ID: "CVE-2017-fake-2",
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*")),
|
||||
PackageName: "activerecord",
|
||||
VersionConstraint: "< 3.7.4",
|
||||
VersionFormat: version.SemanticFormat.String(),
|
||||
ID: "CVE-2017-fake-2",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
{
|
||||
Constraint: version.MustGetConstraint("= 4.0.1", version.SemanticFormat),
|
||||
ID: "CVE-2017-fake-3",
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
{
|
||||
Constraint: version.MustGetConstraint("= 4.0.1", version.SemanticFormat),
|
||||
ID: "CVE-2017-fake-3",
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*")),
|
||||
PackageName: "activerecord",
|
||||
VersionConstraint: "= 4.0.1",
|
||||
VersionFormat: version.SemanticFormat.String(),
|
||||
ID: "CVE-2017-fake-3",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:4.0.1:*:*:*:*:*:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
},
|
||||
"awesome": {
|
||||
{
|
||||
Constraint: version.MustGetConstraint("< 98SP3", version.UnknownFormat),
|
||||
ID: "CVE-2017-fake-4",
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*")),
|
||||
PackageName: "awesome",
|
||||
VersionConstraint: "< 98SP3",
|
||||
VersionFormat: version.UnknownFormat.String(),
|
||||
ID: "CVE-2017-fake-4",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
},
|
||||
"multiple": {
|
||||
{
|
||||
PackageName: "multiple",
|
||||
VersionConstraint: "< 4.0",
|
||||
VersionFormat: version.UnknownFormat.String(),
|
||||
ID: "CVE-2017-fake-5",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:2.0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:3.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
|
@ -79,8 +98,8 @@ func (pr *mockCPEProvider) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockCPEProvider) GetByCPE(c syftPkg.CPE) ([]*vulnerability.Vulnerability, error) {
|
||||
return pr.data["nvd"][c.Product], nil
|
||||
func (pr *mockVulnStore) GetVulnerability(namespace, pkg string) ([]db.Vulnerability, error) {
|
||||
return pr.data[namespace][pkg], nil
|
||||
}
|
||||
|
||||
func TestFindMatchesByPackageCPE(t *testing.T) {
|
||||
|
@ -104,8 +123,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
},
|
||||
expected: []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-1",
|
||||
},
|
||||
|
@ -119,15 +137,20 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:activerecord:activerecord:3.7.5:rando1:*:rando2:*:ruby:*:*",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:3.7.5:rando4:*:rando3:*:rails:*:*"},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
VersionConstraint: "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "nvd",
|
||||
"cpes": []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
"versionConstraint": "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -145,8 +168,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
},
|
||||
expected: []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-1",
|
||||
},
|
||||
|
@ -160,19 +182,26 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:activerecord:activerecord:3.7.3:rando1:*:rando2:*:ruby:*:*",
|
||||
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:activerecord:activerecord:3.7.3:rando4:*:rando3:*:rails:*:*",
|
||||
},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
VersionConstraint: "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "nvd",
|
||||
"cpes": []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
|
||||
"versionConstraint": "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-2",
|
||||
},
|
||||
|
@ -186,15 +215,21 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:activerecord:activerecord:3.7.3:rando1:*:rando2:*:ruby:*:*",
|
||||
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:3.7.3:rando1:*:rando2:*:ruby:*:*"},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*"},
|
||||
VersionConstraint: "< 3.7.4 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "nvd",
|
||||
"cpes": []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:ruby:*:*"},
|
||||
"versionConstraint": "< 3.7.4 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -202,8 +237,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
name: "exact match",
|
||||
p: pkg.Package{
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:4.0.1:rando1:*:rando2:*:ruby:*:*")),
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:4.0.1:rando4:*:rando3:*:rails:*:*")),
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:*:activerecord:4.0.1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
Name: "activerecord",
|
||||
Version: "4.0.1",
|
||||
|
@ -212,30 +246,33 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
},
|
||||
expected: []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-3",
|
||||
},
|
||||
Package: pkg.Package{
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:4.0.1:rando1:*:rando2:*:ruby:*:*")),
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:activerecord:activerecord:4.0.1:rando4:*:rando3:*:rails:*:*")),
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:*:activerecord:4.0.1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
Name: "activerecord",
|
||||
Version: "4.0.1",
|
||||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:activerecord:activerecord:4.0.1:rando1:*:rando2:*:ruby:*:*",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
CPEs: []string{"cpe:2.3:*:*:activerecord:4.0.1:*:*:*:*:*:*:*"},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{"cpe:2.3:*:activerecord:activerecord:4.0.1:*:*:*:*:*:*:*"},
|
||||
VersionConstraint: "= 4.0.1 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "nvd",
|
||||
"cpes": []string{"cpe:2.3:*:couldntgetthisrightcouldyou:activerecord:4.0.1:*:*:*:*:*:*:*"},
|
||||
"versionConstraint": "= 4.0.1 (semver)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -260,8 +297,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
},
|
||||
expected: []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Confidence: 0.9,
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-4",
|
||||
},
|
||||
|
@ -272,15 +308,68 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
Name: "awesome",
|
||||
Version: "98SE1",
|
||||
},
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:awesome:awesome:98SE1:rando1:*:rando2:*:dunno:*:*",
|
||||
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
CPEs: []string{"cpe:2.3:*:awesome:awesome:98SE1:rando1:*:rando2:*:dunno:*:*"},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*"},
|
||||
VersionConstraint: "< 98SP3 (unknown)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "nvd",
|
||||
"cpes": []string{"cpe:2.3:*:awesome:awesome:*:*:*:*:*:*:*:*"},
|
||||
"versionConstraint": "< 98SP3 (unknown)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple matched CPEs",
|
||||
p: pkg.Package{
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*")),
|
||||
},
|
||||
Name: "multiple",
|
||||
Version: "1.0",
|
||||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
expected: []match.Match{
|
||||
{
|
||||
Type: match.FuzzyMatch,
|
||||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-5",
|
||||
},
|
||||
Package: pkg.Package{
|
||||
CPEs: []syftPkg.CPE{
|
||||
must(syftPkg.NewCPE("cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*")),
|
||||
},
|
||||
Name: "multiple",
|
||||
Version: "1.0",
|
||||
Language: syftPkg.Ruby,
|
||||
Type: syftPkg.GemPkg,
|
||||
},
|
||||
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 0.9,
|
||||
SearchedBy: CPESearchInput{
|
||||
CPEs: []string{"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*"},
|
||||
Namespace: "nvd",
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
VersionConstraint: "< 4.0 (unknown)",
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
Matcher: matcher,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -288,10 +377,422 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
store := newMockProviderByCPE()
|
||||
actual, err := FindMatchesByPackageCPE(store, test.p, matcher)
|
||||
actual, err := FindMatchesByPackageCPE(vulnerability.NewProviderFromStore(newMockStore()), test.p, matcher)
|
||||
assert.NoError(t, err)
|
||||
assertMatchesUsingIDsForVulnerabilities(t, test.expected, actual)
|
||||
for idx, e := range test.expected {
|
||||
assert.Equal(t, e.MatchDetails, actual[idx].MatchDetails)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeepMatchingCPEs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
vulnerabilityCPEs []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "filter out by simple version",
|
||||
version: "1.0",
|
||||
vulnerabilityCPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:2.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// format strings to CPE objects...
|
||||
vulnerabilityCPEs := make([]syftPkg.CPE, len(test.vulnerabilityCPEs))
|
||||
for idx, c := range test.vulnerabilityCPEs {
|
||||
vulnerabilityCPEs[idx] = must(syftPkg.NewCPE(c))
|
||||
}
|
||||
|
||||
versionObj, err := version.NewVersion(test.version, version.UnknownFormat)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get version: %+v", err)
|
||||
}
|
||||
|
||||
// run the test subject...
|
||||
actual := keepMatchingCPEs(versionObj, vulnerabilityCPEs)
|
||||
|
||||
// format CPE objects to string...
|
||||
actualStrs := make([]string, len(actual))
|
||||
for idx, a := range actual {
|
||||
actualStrs[idx] = a.BindToFmtString()
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, test.expected, actualStrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMatchDetails(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existing []match.Details
|
||||
new match.Details
|
||||
expected []match.Details
|
||||
}{
|
||||
{
|
||||
name: "append new entry -- matchedOn not equal",
|
||||
existing: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: match.Details{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"totally-different-search",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"totally-different-match",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"totally-different-search",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"totally-different-match",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "append new entry -- searchedBy merge fails",
|
||||
existing: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: match.Details{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "totally-different",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "totally-different",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "merge with exiting entry",
|
||||
existing: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: match.Details{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"totally-different-search",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
"totally-different-search",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no addition - bad new searchedBy type",
|
||||
existing: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: match.Details{
|
||||
SearchedBy: "something else!",
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no addition - bad new matchedOn type",
|
||||
existing: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: match.Details{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: "something-else!",
|
||||
},
|
||||
expected: []match.Details{
|
||||
{
|
||||
SearchedBy: CPESearchInput{
|
||||
Namespace: "nvd",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:1.0:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
MatchedOn: CPESearchHit{
|
||||
VersionConstraint: "< 2.0 (unknown)",
|
||||
CPEs: []string{
|
||||
"cpe:2.3:*:multiple:multiple:*:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, addMatchDetails(test.existing, test.new))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCPESearchHit_Equals(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
current CPESearchHit
|
||||
other CPESearchHit
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "different version constraint",
|
||||
current: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
other: CPESearchHit{
|
||||
VersionConstraint: "different-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "different number of CPEs",
|
||||
current: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
other: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
"b-cpe",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "different CPE value",
|
||||
current: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
other: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"b-cpe",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "matches",
|
||||
current: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
other: CPESearchHit{
|
||||
VersionConstraint: "current-constraint",
|
||||
CPEs: []string{
|
||||
"a-cpe",
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, test.current.Equals(test.other))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d *distro.
|
|||
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
|
||||
}
|
||||
|
||||
var allPkgVulns []*vulnerability.Vulnerability
|
||||
var allPkgVulns []vulnerability.Vulnerability
|
||||
|
||||
allPkgVulns, err = store.GetByDistro(*d, p)
|
||||
if err != nil {
|
||||
|
@ -35,25 +35,31 @@ func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d *distro.
|
|||
return nil, fmt.Errorf("distro matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
|
||||
}
|
||||
|
||||
if isPackageVulnerable {
|
||||
matches = append(matches, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0, // TODO: this is hard coded for now
|
||||
Vulnerability: *vuln,
|
||||
Package: p,
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": vuln.Namespace,
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
})
|
||||
if !isPackageVulnerable {
|
||||
continue
|
||||
}
|
||||
|
||||
matches = append(matches, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Vulnerability: vuln,
|
||||
Package: p,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": d.Type.String(),
|
||||
"version": d.RawVersion,
|
||||
},
|
||||
"namespace": vuln.Namespace,
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
Confidence: 1.0, // TODO: this is hard coded for now
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return matches, err
|
||||
|
|
|
@ -14,19 +14,19 @@ import (
|
|||
)
|
||||
|
||||
type mockDistroProvider struct {
|
||||
data map[string]map[string][]*vulnerability.Vulnerability
|
||||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func newMockProviderByDistro() *mockDistroProvider {
|
||||
pr := mockDistroProvider{
|
||||
data: make(map[string]map[string][]*vulnerability.Vulnerability),
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
}
|
||||
pr.stub()
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *mockDistroProvider) stub() {
|
||||
pr.data["debian:8"] = map[string][]*vulnerability.Vulnerability{
|
||||
pr.data["debian:8"] = map[string][]vulnerability.Vulnerability{
|
||||
// direct...
|
||||
"neutron": {
|
||||
{
|
||||
|
@ -55,7 +55,7 @@ func (pr *mockDistroProvider) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockDistroProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]*vulnerability.Vulnerability, error) {
|
||||
func (pr *mockDistroProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
|
||||
return pr.data[strings.ToLower(d.Type.String())+":"+d.FullVersion()][p.Name], nil
|
||||
}
|
||||
|
||||
|
@ -80,19 +80,23 @@ func TestFindMatchesByPackageDistro(t *testing.T) {
|
|||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2014-fake-1",
|
||||
},
|
||||
Confidence: 1,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "debian",
|
||||
"version": "8",
|
||||
Package: p,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "debian",
|
||||
"version": "8",
|
||||
},
|
||||
"namespace": "debian:8",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": "< 2014.1.5-6 (deb)",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "debian:8",
|
||||
"versionConstraint": "< 2014.1.5-6 (deb)",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -29,22 +29,28 @@ func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l syft
|
|||
return nil, fmt.Errorf("language matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
|
||||
}
|
||||
|
||||
if isPackageVulnerable {
|
||||
matches = append(matches, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0, // TODO: this is hard coded for now
|
||||
Vulnerability: *vuln,
|
||||
Package: p,
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": l.String(),
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": vuln.Namespace,
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
})
|
||||
if !isPackageVulnerable {
|
||||
continue
|
||||
}
|
||||
|
||||
matches = append(matches, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Vulnerability: vuln,
|
||||
Package: p,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0, // TODO: this is hard coded for now
|
||||
Matcher: upstreamMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": l.String(),
|
||||
"namespace": vuln.Namespace,
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": vuln.Constraint.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return matches, err
|
||||
|
|
|
@ -14,19 +14,19 @@ import (
|
|||
)
|
||||
|
||||
type mockLanguageProvider struct {
|
||||
data map[string]map[string][]*vulnerability.Vulnerability
|
||||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func newMockProviderByLanguage() *mockLanguageProvider {
|
||||
pr := mockLanguageProvider{
|
||||
data: make(map[string]map[string][]*vulnerability.Vulnerability),
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
}
|
||||
pr.stub()
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *mockLanguageProvider) stub() {
|
||||
pr.data["github:gem"] = map[string][]*vulnerability.Vulnerability{
|
||||
pr.data["github:gem"] = map[string][]vulnerability.Vulnerability{
|
||||
// direct...
|
||||
"activerecord": {
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ func (pr *mockLanguageProvider) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockLanguageProvider) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]*vulnerability.Vulnerability, error) {
|
||||
func (pr *mockLanguageProvider) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]vulnerability.Vulnerability, error) {
|
||||
if l != syftPkg.Ruby {
|
||||
panic(fmt.Errorf("test mock only supports ruby"))
|
||||
}
|
||||
|
@ -64,16 +64,20 @@ func TestFindMatchesByPackageLanguage(t *testing.T) {
|
|||
Vulnerability: vulnerability.Vulnerability{
|
||||
ID: "CVE-2017-fake-1",
|
||||
},
|
||||
Confidence: 1,
|
||||
Package: p,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "ruby",
|
||||
Package: p,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "ruby",
|
||||
"namespace": "github:ruby",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"versionConstraint": "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"namespace": "github:ruby",
|
||||
"versionConstraint": "< 3.7.6 (semver)",
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,11 @@ import (
|
|||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertMatchesUsingIDsForVulnerabilities(t testing.TB, expected, actual []match.Match) {
|
||||
assert.Len(t, actual, len(expected))
|
||||
for idx, a := range actual {
|
||||
// only compare the vulnerability ID, nothing else
|
||||
a.Vulnerability = vulnerability.Vulnerability{ID: a.Vulnerability.ID}
|
||||
|
|
|
@ -74,7 +74,6 @@ func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro,
|
|||
for idx := range matches {
|
||||
matches[idx].Type = match.ExactIndirectMatch
|
||||
matches[idx].Package = p
|
||||
matches[idx].Matcher = m.Type()
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
|
|
|
@ -10,19 +10,19 @@ import (
|
|||
)
|
||||
|
||||
type mockProvider struct {
|
||||
data map[string]map[string][]*vulnerability.Vulnerability
|
||||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func newMockProvider() *mockProvider {
|
||||
pr := mockProvider{
|
||||
data: make(map[string]map[string][]*vulnerability.Vulnerability),
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
}
|
||||
pr.stub()
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *mockProvider) stub() {
|
||||
pr.data["debian:8"] = map[string][]*vulnerability.Vulnerability{
|
||||
pr.data["debian:8"] = map[string][]vulnerability.Vulnerability{
|
||||
// direct...
|
||||
"neutron": {
|
||||
{
|
||||
|
@ -50,6 +50,6 @@ func (pr *mockProvider) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]*vulnerability.Vulnerability, error) {
|
||||
func (pr *mockProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
|
||||
return pr.data[strings.ToLower(d.Type.String())+":"+d.FullVersion()][p.Name], nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package dpkg
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/internal"
|
||||
|
@ -29,27 +31,17 @@ func TestMatcherDpkg_matchBySourceIndirection(t *testing.T) {
|
|||
store := newMockProvider()
|
||||
actual, err := matcher.matchBySourceIndirection(store, &d, p)
|
||||
|
||||
if len(actual) != 2 {
|
||||
t.Fatalf("unexpected indirect matches count: %d", len(actual))
|
||||
}
|
||||
assert.Len(t, actual, 2, "unexpected indirect matches count")
|
||||
|
||||
foundCVEs := internal.NewStringSet()
|
||||
|
||||
for _, a := range actual {
|
||||
foundCVEs.Add(a.Vulnerability.ID)
|
||||
|
||||
if a.Type != match.ExactIndirectMatch {
|
||||
t.Error("indirect match not indicated")
|
||||
assert.Equal(t, match.ExactIndirectMatch, a.Type, "indirect match not indicated")
|
||||
assert.Equal(t, p.Name, a.Package.Name, "failed to capture original package name")
|
||||
for _, detail := range a.MatchDetails {
|
||||
assert.Equal(t, matcher.Type(), detail.Matcher, "failed to capture matcher type")
|
||||
}
|
||||
|
||||
if a.Package.Name != p.Name {
|
||||
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
|
||||
}
|
||||
|
||||
if a.Matcher != matcher.Type() {
|
||||
t.Errorf("failed to capture matcher type: %s", a.Matcher)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, id := range []string{"CVE-2014-fake-2", "CVE-2013-fake-3"} {
|
||||
|
@ -60,5 +52,4 @@ func TestMatcherDpkg_matchBySourceIndirection(t *testing.T) {
|
|||
if t.Failed() {
|
||||
t.Logf("discovered CVES: %+v", foundCVEs)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -95,7 +95,6 @@ func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro,
|
|||
for idx := range matches {
|
||||
matches[idx].Type = match.ExactIndirectMatch
|
||||
matches[idx].Package = p
|
||||
matches[idx].Matcher = m.Type()
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
|
|
|
@ -10,19 +10,19 @@ import (
|
|||
)
|
||||
|
||||
type mockProvider struct {
|
||||
data map[string]map[string][]*vulnerability.Vulnerability
|
||||
data map[string]map[string][]vulnerability.Vulnerability
|
||||
}
|
||||
|
||||
func newMockProvider() *mockProvider {
|
||||
pr := mockProvider{
|
||||
data: make(map[string]map[string][]*vulnerability.Vulnerability),
|
||||
data: make(map[string]map[string][]vulnerability.Vulnerability),
|
||||
}
|
||||
pr.stub()
|
||||
return &pr
|
||||
}
|
||||
|
||||
func (pr *mockProvider) stub() {
|
||||
pr.data["rhel:8"] = map[string][]*vulnerability.Vulnerability{
|
||||
pr.data["rhel:8"] = map[string][]vulnerability.Vulnerability{
|
||||
// direct...
|
||||
"neutron-libs": {
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ func (pr *mockProvider) stub() {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *mockProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]*vulnerability.Vulnerability, error) {
|
||||
func (pr *mockProvider) GetByDistro(d distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
|
||||
var ty = strings.ToLower(d.Type.String())
|
||||
if d.Type == distro.CentOS || d.Type == distro.RedHat {
|
||||
ty = "rhel"
|
||||
|
|
|
@ -3,6 +3,8 @@ package rpmdb
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/internal"
|
||||
|
@ -29,25 +31,17 @@ func TestMatcherDpkg_matchBySourceIndirection(t *testing.T) {
|
|||
store := newMockProvider()
|
||||
actual, err := matcher.matchBySourceIndirection(store, &d, p)
|
||||
|
||||
if len(actual) != 2 {
|
||||
t.Fatalf("unexpected indirect matches count: %d", len(actual))
|
||||
}
|
||||
assert.Len(t, actual, 2, "unexpected indirect matches count")
|
||||
|
||||
foundCVEs := internal.NewStringSet()
|
||||
|
||||
for _, a := range actual {
|
||||
foundCVEs.Add(a.Vulnerability.ID)
|
||||
|
||||
if a.Type != match.ExactIndirectMatch {
|
||||
t.Error("indirect match not indicated")
|
||||
}
|
||||
|
||||
if a.Package.Name != p.Name {
|
||||
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
|
||||
}
|
||||
|
||||
if a.Matcher != matcher.Type() {
|
||||
t.Errorf("failed to capture matcher type: %s", a.Matcher)
|
||||
assert.Equal(t, match.ExactIndirectMatch, a.Type, "indirect match not indicated")
|
||||
assert.Equal(t, p.Name, a.Package.Name, "failed to capture original package name")
|
||||
for _, detail := range a.MatchDetails {
|
||||
assert.Equal(t, matcher.Type(), detail.Matcher, "failed to capture matcher type")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,8 +76,5 @@ func TestMatcherDpkg_matchBySourceIndirection_ignoreSource(t *testing.T) {
|
|||
store := newMockProvider()
|
||||
actual, err := matcher.matchBySourceIndirection(store, &d, p)
|
||||
|
||||
if len(actual) != 0 {
|
||||
t.Fatalf("unexpected indirect matches count: %d", len(actual))
|
||||
}
|
||||
|
||||
assert.Len(t, actual, 0, "unexpected indirect matches count")
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ type ID string
|
|||
|
||||
// Package represents an application or library that has been bundled into a distributable format.
|
||||
type Package struct {
|
||||
id ID
|
||||
ID ID
|
||||
Name string // the package name
|
||||
Version string // the version of the package
|
||||
Locations []source.Location // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package)
|
||||
|
@ -66,7 +66,7 @@ func New(p *pkg.Package) Package {
|
|||
}
|
||||
|
||||
return Package{
|
||||
id: ID(p.ID),
|
||||
ID: ID(p.ID),
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Locations: p.Locations,
|
||||
|
@ -87,11 +87,6 @@ func FromCatalog(catalog *pkg.Catalog) []Package {
|
|||
return result
|
||||
}
|
||||
|
||||
// ID returns the package ID, which is unique relative to a package catalog.
|
||||
func (p Package) ID() ID {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// Stringer to represent a package.
|
||||
func (p Package) String() string {
|
||||
return fmt.Sprintf("Pkg(type=%s, name=%s, version=%s)", p.Type, p.Name, p.Version)
|
||||
|
@ -99,7 +94,7 @@ func (p Package) String() string {
|
|||
|
||||
func ByID(id ID, pkgs []Package) *Package {
|
||||
for _, p := range pkgs {
|
||||
if p.ID() == id {
|
||||
if p.ID == id {
|
||||
return &p
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,8 +97,8 @@ func TestNew_MetadataExtraction(t *testing.T) {
|
|||
PomProperties: &syftPkg.PomProperties{
|
||||
Path: "pom-path-info",
|
||||
Name: "pom-name-info",
|
||||
GroupID: "pom-group-id-info",
|
||||
ArtifactID: "pom-artifact-id-info",
|
||||
GroupID: "pom-group-ID-info",
|
||||
ArtifactID: "pom-artifact-ID-info",
|
||||
Version: "pom-version-info",
|
||||
Extra: map[string]string{
|
||||
"extra-key": "extra-value",
|
||||
|
@ -108,8 +108,8 @@ func TestNew_MetadataExtraction(t *testing.T) {
|
|||
},
|
||||
metadata: JavaMetadata{
|
||||
VirtualPath: "virtual-path-info",
|
||||
PomArtifactID: "pom-artifact-id-info",
|
||||
PomGroupID: "pom-group-id-info",
|
||||
PomArtifactID: "pom-artifact-ID-info",
|
||||
PomGroupID: "pom-group-ID-info",
|
||||
ManifestName: "main-section-name-info",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -83,7 +83,7 @@ type partialSyftPackage struct {
|
|||
|
||||
// packageBasicMetadata contains non-ambiguous values (type-wise) from pkg.Package.
|
||||
type packageBasicMetadata struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
|
@ -202,7 +202,7 @@ func parseSyftJSON(reader io.Reader) ([]Package, Context, error) {
|
|||
}
|
||||
|
||||
packages[i] = Package{
|
||||
id: ID(a.ID),
|
||||
ID: ID(a.ID),
|
||||
Name: a.Name,
|
||||
Version: a.Version,
|
||||
Locations: a.Locations,
|
||||
|
|
|
@ -63,7 +63,7 @@ func NewDocument(packages []pkg.Package, matches match.Matches, srcMetadata *sou
|
|||
|
||||
// mutate the Component
|
||||
|
||||
pkgMatches := matches.GetByPkgID(p.ID())
|
||||
pkgMatches := matches.GetByPkgID(p.ID)
|
||||
|
||||
if len(pkgMatches) > 0 {
|
||||
var vulnerabilities []Vulnerability
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Presenter writes a CycloneDX report from the given Catalog and Scope contents
|
||||
// Presenter writes a CycloneDX report from the given Matches and Scope contents
|
||||
type Presenter struct {
|
||||
results match.Matches
|
||||
packages []pkg.Package
|
||||
|
|
|
@ -50,7 +50,11 @@ func createResults() (match.Matches, []pkg.Package) {
|
|||
Namespace: "source-1",
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var match2 = match.Match{
|
||||
|
@ -60,9 +64,13 @@ func createResults() (match.Matches, []pkg.Package) {
|
|||
Namespace: "source-2",
|
||||
},
|
||||
Package: pkg2,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"some": "key",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"some": "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -76,16 +76,20 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
},
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
}
|
||||
|
||||
var match2 = match.Match{
|
||||
|
@ -95,12 +99,16 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
Namespace: "source-2",
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -116,12 +124,16 @@ func TestJsonImgsPresenter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "< 2.0.0",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "< 2.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -210,16 +222,20 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
},
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
}
|
||||
|
||||
var match2 = match.Match{
|
||||
|
@ -229,12 +245,16 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
Namespace: "source-2",
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -250,12 +270,16 @@ func TestJsonDirsPresenter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "< 2.0.0",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "< 2.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -26,18 +26,20 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"distro": {
|
||||
"type": "ubuntu",
|
||||
"version": "20.04"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"distro": {
|
||||
"type": "ubuntu",
|
||||
"version": "20.04"
|
||||
}
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": ">= 20"
|
||||
}
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": ">= 20"
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
|
@ -85,15 +87,17 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"cpe": "somecpe"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "somecpe"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"cpe": "somecpe"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "somecpe"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
|
@ -129,15 +133,17 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"language": "java"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "< 2.0.0"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"language": "java"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "< 2.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
|
|
|
@ -26,18 +26,20 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"distro": {
|
||||
"type": "ubuntu",
|
||||
"version": "20.04"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"distro": {
|
||||
"type": "ubuntu",
|
||||
"version": "20.04"
|
||||
}
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": ">= 20"
|
||||
}
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": ">= 20"
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.1.1",
|
||||
|
@ -88,15 +90,17 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"cpe": "somecpe"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "somecpe"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"cpe": "somecpe"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "somecpe"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.1.1",
|
||||
|
@ -135,15 +139,17 @@
|
|||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [],
|
||||
"matchDetails": {
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"language": "java"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "< 2.0.0"
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "dpkg-matcher",
|
||||
"searchedBy": {
|
||||
"language": "java"
|
||||
},
|
||||
"matchedOn": {
|
||||
"constraint": "< 2.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"artifact": {
|
||||
"name": "package-1",
|
||||
"version": "1.1.1",
|
||||
|
|
|
@ -24,7 +24,7 @@ func NewDocument(packages []pkg.Package, context pkg.Context, matches match.Matc
|
|||
// we must preallocate the findings to ensure the JSON document does not show "null" when no matches are found
|
||||
var findings = make([]Match, 0)
|
||||
for _, m := range matches.Sorted() {
|
||||
p := pkg.ByID(m.Package.ID(), packages)
|
||||
p := pkg.ByID(m.Package.ID, packages)
|
||||
if p == nil {
|
||||
return Document{}, fmt.Errorf("unable to find package in collection: %+v", p)
|
||||
}
|
||||
|
|
|
@ -12,15 +12,15 @@ import (
|
|||
type Match struct {
|
||||
Vulnerability Vulnerability `json:"vulnerability"`
|
||||
RelatedVulnerabilities []VulnerabilityMetadata `json:"relatedVulnerabilities"`
|
||||
MatchDetails MatchDetails `json:"matchDetails"`
|
||||
MatchDetails []MatchDetails `json:"matchDetails"`
|
||||
Artifact Package `json:"artifact"`
|
||||
}
|
||||
|
||||
// MatchDetails contains all data that indicates how the result match was found
|
||||
type MatchDetails struct {
|
||||
Matcher string `json:"matcher"`
|
||||
SearchedBy map[string]interface{} `json:"searchedBy"`
|
||||
MatchedOn map[string]interface{} `json:"matchedOn"`
|
||||
Matcher string `json:"matcher"`
|
||||
SearchedBy interface{} `json:"searchedBy"`
|
||||
MatchedOn interface{} `json:"matchedOn"`
|
||||
}
|
||||
|
||||
func newMatch(m match.Match, p pkg.Package, metadataProvider vulnerability.MetadataProvider) (*Match, error) {
|
||||
|
@ -40,14 +40,19 @@ func newMatch(m match.Match, p pkg.Package, metadataProvider vulnerability.Metad
|
|||
return nil, fmt.Errorf("unable to fetch vuln=%q metadata: %+v", m.Vulnerability.ID, err)
|
||||
}
|
||||
|
||||
details := make([]MatchDetails, len(m.MatchDetails))
|
||||
for idx, d := range m.MatchDetails {
|
||||
details[idx] = MatchDetails{
|
||||
Matcher: d.Matcher.String(),
|
||||
SearchedBy: d.SearchedBy,
|
||||
MatchedOn: d.MatchedOn,
|
||||
}
|
||||
}
|
||||
|
||||
return &Match{
|
||||
Vulnerability: NewVulnerability(m.Vulnerability, metadata),
|
||||
Artifact: newPackage(p),
|
||||
RelatedVulnerabilities: relatedVulnerabilities,
|
||||
MatchDetails: MatchDetails{
|
||||
Matcher: m.Matcher.String(),
|
||||
SearchedBy: m.SearchedBy,
|
||||
MatchedOn: m.SearchMatches,
|
||||
},
|
||||
MatchDetails: details,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -39,16 +39,20 @@ func generateMatches(t *testing.T, p pkg.Package) match.Matches {
|
|||
},
|
||||
},
|
||||
Package: p,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "ubuntu",
|
||||
"version": "20.04",
|
||||
},
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": ">= 20",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: match.ExactIndirectMatch,
|
||||
|
@ -57,12 +61,16 @@ func generateMatches(t *testing.T, p pkg.Package) match.Matches {
|
|||
Namespace: "source-2",
|
||||
},
|
||||
Package: p,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "somecpe",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "somecpe",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -41,7 +41,11 @@ func TestTablePresenter(t *testing.T) {
|
|||
Namespace: "source-1",
|
||||
},
|
||||
Package: pkg1,
|
||||
Matcher: match.DpkgMatcher,
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var match2 = match.Match{
|
||||
|
@ -56,9 +60,13 @@ func TestTablePresenter(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Package: pkg2,
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"some": "key",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Matcher: match.DpkgMatcher,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"some": "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,18 @@ package template
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/grype/grype/presenter/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update the *.golden files for template presenters")
|
||||
|
||||
func TestPresenter_Present(t *testing.T) {
|
||||
matches, packages, context, metadataProvider, appConfig, dbStatus := models.GenerateAnalysis(t)
|
||||
|
||||
|
@ -30,6 +31,9 @@ func TestPresenter_Present(t *testing.T) {
|
|||
}
|
||||
|
||||
actual := buffer.Bytes()
|
||||
if *update {
|
||||
testutils.UpdateGoldenFileContents(t, actual)
|
||||
}
|
||||
expected := testutils.GetGoldenFileContents(t)
|
||||
|
||||
assert.Equal(t, string(expected), string(actual))
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
Identified distro as centos version 8.0.
|
||||
|
||||
Vulnerability: CVE-1999-0001
|
||||
Severity: Low
|
||||
Matched by: dpkg-matcher
|
||||
Package: package-1 version 1.1.1 (deb)
|
||||
|
||||
Matched by: dpkg-matcher
|
||||
Vulnerability: CVE-1999-0002
|
||||
Severity: Critical
|
||||
Matched by: dpkg-matcher
|
||||
Package: package-1 version 1.1.1 (deb)
|
||||
Matched by: dpkg-matcher
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
Identified distro as {{.Distro.Name}} version {{.Distro.Version}}.
|
||||
{{- range .Matches}}
|
||||
|
||||
Vulnerability: {{.Vulnerability.ID}}
|
||||
Severity: {{.Vulnerability.Severity}}
|
||||
Matched by: {{.MatchDetails.Matcher}}
|
||||
Package: {{.Artifact.Name}} version {{.Artifact.Version}} ({{.Artifact.Type}})
|
||||
{{- range .MatchDetails}}
|
||||
Matched by: {{.Matcher}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@ type Provider interface {
|
|||
}
|
||||
|
||||
type ProviderByDistro interface {
|
||||
GetByDistro(distro.Distro, pkg.Package) ([]*Vulnerability, error)
|
||||
GetByDistro(distro.Distro, pkg.Package) ([]Vulnerability, error)
|
||||
}
|
||||
|
||||
type ProviderByLanguage interface {
|
||||
GetByLanguage(syftPkg.Language, pkg.Package) ([]*Vulnerability, error)
|
||||
GetByLanguage(syftPkg.Language, pkg.Package) ([]Vulnerability, error)
|
||||
}
|
||||
|
||||
type ProviderByCPE interface {
|
||||
GetByCPE(syftPkg.CPE) ([]*Vulnerability, error)
|
||||
GetByCPE(syftPkg.CPE) ([]Vulnerability, error)
|
||||
}
|
||||
|
||||
type MetadataProvider interface {
|
||||
|
|
|
@ -21,8 +21,8 @@ func NewProviderFromStore(store db.VulnerabilityStoreReader) *StoreAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
func (pr *StoreAdapter) GetByDistro(d distro.Distro, p pkg.Package) ([]*Vulnerability, error) {
|
||||
vulns := make([]*Vulnerability, 0)
|
||||
func (pr *StoreAdapter) GetByDistro(d distro.Distro, p pkg.Package) ([]Vulnerability, error) {
|
||||
vulns := make([]Vulnerability, 0)
|
||||
|
||||
namespace := distroNamespace(d)
|
||||
allPkgVulns, err := pr.store.GetVulnerability(namespace, p.Name)
|
||||
|
@ -36,14 +36,14 @@ func (pr *StoreAdapter) GetByDistro(d distro.Distro, p pkg.Package) ([]*Vulnerab
|
|||
return nil, fmt.Errorf("provider failed to parse distro='%s': %w", d, err)
|
||||
}
|
||||
|
||||
vulns = append(vulns, vulnObj)
|
||||
vulns = append(vulns, *vulnObj)
|
||||
}
|
||||
|
||||
return vulns, nil
|
||||
}
|
||||
|
||||
func (pr *StoreAdapter) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]*Vulnerability, error) {
|
||||
vulns := make([]*Vulnerability, 0)
|
||||
func (pr *StoreAdapter) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]Vulnerability, error) {
|
||||
vulns := make([]Vulnerability, 0)
|
||||
|
||||
namespaces := languageNamespaces(l)
|
||||
if namespaces == nil {
|
||||
|
@ -63,7 +63,7 @@ func (pr *StoreAdapter) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]*Vul
|
|||
return nil, fmt.Errorf("provider failed to parse language='%s': %w", l, err)
|
||||
}
|
||||
|
||||
vulns = append(vulns, vulnObj)
|
||||
vulns = append(vulns, *vulnObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ func (pr *StoreAdapter) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]*Vul
|
|||
return vulns, nil
|
||||
}
|
||||
|
||||
func (pr *StoreAdapter) GetByCPE(requestCPE syftPkg.CPE) ([]*Vulnerability, error) {
|
||||
vulns := make([]*Vulnerability, 0)
|
||||
func (pr *StoreAdapter) GetByCPE(requestCPE syftPkg.CPE) ([]Vulnerability, error) {
|
||||
vulns := make([]Vulnerability, 0)
|
||||
|
||||
namespaces := cpeNamespaces()
|
||||
if namespaces == nil {
|
||||
|
@ -106,7 +106,7 @@ func (pr *StoreAdapter) GetByCPE(requestCPE syftPkg.CPE) ([]*Vulnerability, erro
|
|||
|
||||
vulnObj.CPEs = candidateMatchCpes
|
||||
|
||||
vulns = append(vulns, vulnObj)
|
||||
vulns = append(vulns, *vulnObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestGetByDistro(t *testing.T) {
|
|||
assert.Len(t, actual, len(expected))
|
||||
|
||||
for idx, vuln := range actual {
|
||||
for _, d := range deep.Equal(&expected[idx], vuln) {
|
||||
for _, d := range deep.Equal(expected[idx], vuln) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ func TestGetByCPE(t *testing.T) {
|
|||
assert.Len(t, actual, len(test.expected))
|
||||
|
||||
for idx, vuln := range actual {
|
||||
for _, d := range deep.Equal(&test.expected[idx], vuln) {
|
||||
for _, d := range deep.Equal(test.expected[idx], vuln) {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,17 +30,21 @@ func addAlpineMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
theResult.Add(thePkg, match.Match{
|
||||
// note: we are matching on the secdb record, not NVD primarily
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"cpe": "cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"cpes": []string{"cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*"},
|
||||
"constraint": "< 0.9.10 (unknown)",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"cpes": []string{"cpe:2.3:*:*:libvncserver:0.9.9:*:*:*:*:*:*:*"},
|
||||
"constraint": "< 0.9.10 (unknown)",
|
||||
},
|
||||
Matcher: match.ApkMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -58,16 +62,20 @@ func addJavascriptMatches(t *testing.T, theSource source.Source, catalog *syftPk
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "javascript",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "javascript",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "< 3.2.1 (unknown)",
|
||||
},
|
||||
Matcher: match.JavascriptMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "< 3.2.1 (unknown)",
|
||||
},
|
||||
Matcher: match.JavascriptMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -88,16 +96,20 @@ func addPythonMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Ca
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "python",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "python",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "< 2.6.2 (python)",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "< 2.6.2 (python)",
|
||||
},
|
||||
Matcher: match.PythonMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -115,16 +127,20 @@ func addRubyMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "ruby",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "ruby",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "> 4.0.0, <= 4.1.1 (semver)",
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "> 4.0.0, <= 4.1.1 (semver)",
|
||||
},
|
||||
Matcher: match.RubyGemMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -151,16 +167,20 @@ func addJavaMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"language": "java",
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": ">= 0.0.1, < 1.2.0 (unknown)",
|
||||
},
|
||||
Matcher: match.JavaMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": ">= 0.0.1, < 1.2.0 (unknown)",
|
||||
},
|
||||
Matcher: match.JavaMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -179,19 +199,23 @@ func addDpkgMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactIndirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "debian",
|
||||
"version": "8",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "debian",
|
||||
"version": "8",
|
||||
},
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "<= 1.8.2 (deb)",
|
||||
},
|
||||
Matcher: match.DpkgMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "<= 1.8.2 (deb)",
|
||||
},
|
||||
Matcher: match.DpkgMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -209,19 +233,23 @@ func addRhelMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Cata
|
|||
}
|
||||
theResult.Add(thePkg, match.Match{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
Vulnerability: *vulnObj,
|
||||
Package: thePkg,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "centos",
|
||||
"version": "8",
|
||||
MatchDetails: []match.Details{
|
||||
{
|
||||
Confidence: 1.0,
|
||||
SearchedBy: map[string]interface{}{
|
||||
"distro": map[string]string{
|
||||
"type": "centos",
|
||||
"version": "8",
|
||||
},
|
||||
},
|
||||
MatchedOn: map[string]interface{}{
|
||||
"constraint": "<= 1.0.42 (rpm)",
|
||||
},
|
||||
Matcher: match.RpmDBMatcher,
|
||||
},
|
||||
},
|
||||
SearchMatches: map[string]interface{}{
|
||||
"constraint": "<= 1.0.42 (rpm)",
|
||||
},
|
||||
Matcher: match.RpmDBMatcher,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -310,7 +338,9 @@ func TestPkgCoverageImage(t *testing.T) {
|
|||
actualCount := 0
|
||||
for aMatch := range actualResults.Enumerate() {
|
||||
actualCount++
|
||||
observedMatchers.Add(aMatch.Matcher.String())
|
||||
for _, details := range aMatch.MatchDetails {
|
||||
observedMatchers.Add(details.Matcher.String())
|
||||
}
|
||||
value, ok := expectedMatchSet[aMatch.Package.Name]
|
||||
if !ok {
|
||||
t.Errorf("Package: %s was expected but not found", aMatch.Package.Name)
|
||||
|
@ -327,12 +357,10 @@ func TestPkgCoverageImage(t *testing.T) {
|
|||
if expectedCount != actualCount {
|
||||
t.Errorf("expected %d matches but got %d matches", expectedCount, actualCount)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// ensure that integration test cases stay in sync with the implemented matchers
|
||||
|
||||
observedMatchers.Remove(match.UnknownMatcherType.String())
|
||||
definedMatchers.Remove(match.UnknownMatcherType.String())
|
||||
definedMatchers.Remove(match.MsrcMatcher.String())
|
||||
|
|
Loading…
Reference in a new issue