feat: disable CPE-based matching for GHSA ecosystems by default (#1412)

* feat: disable CPE-based matching for GHSA ecosystems by default

Disables CPE-based matching for ecosystems which are covered by GitHub
Security Advisories.  Also adds a separate rust matcher and related
configuration to allow configuring CPE-based matching off for it while
still leaving it on for the stock matcher.

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* chore: use --by-cve with quality gate comparison

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

* chore: add rust auditable binary match integration test

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>

---------

Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
This commit is contained in:
Weston Steimel 2023-10-12 14:07:33 +01:00 committed by GitHub
parent bcbc7e4bdc
commit 25762b7e3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 7 deletions

View file

@ -53,6 +53,7 @@ jobs:
run: make quality
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GRYPE_BY_CVE: "true"
Integration-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline

View file

@ -8,6 +8,7 @@ type matchConfig struct {
Javascript matcherConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"` // settings for the javascript matcher
Python matcherConfig `yaml:"python" json:"python" mapstructure:"python"` // settings for the python matcher
Ruby matcherConfig `yaml:"ruby" json:"ruby" mapstructure:"ruby"` // settings for the ruby matcher
Rust matcherConfig `yaml:"rust" json:"rust" mapstructure:"rust"` // settings for the rust matcher
Stock matcherConfig `yaml:"stock" json:"stock" mapstructure:"stock"` // settings for the default/stock matcher
}
@ -17,13 +18,15 @@ type matcherConfig struct {
func defaultMatchConfig() matchConfig {
useCpe := matcherConfig{UseCPEs: true}
dontUseCpe := matcherConfig{UseCPEs: false}
return matchConfig{
Java: useCpe,
Dotnet: useCpe,
Golang: useCpe,
Javascript: useCpe,
Python: useCpe,
Ruby: useCpe,
Java: dontUseCpe,
Dotnet: dontUseCpe,
Golang: dontUseCpe,
Javascript: dontUseCpe,
Python: dontUseCpe,
Ruby: dontUseCpe,
Rust: dontUseCpe,
Stock: useCpe,
}
}

View file

@ -30,6 +30,8 @@ func TestFromStringSlice(t *testing.T) {
"nvd:cpe",
"github:language:ruby",
"abc.xyz:language:ruby",
"github:language:rust",
"something:language:rust",
"1234.4567:language:unknown",
"---:cpe",
"another-provider:distro:alpine:3.15",
@ -44,6 +46,10 @@ func TestFromStringSlice(t *testing.T) {
language.NewNamespace("github", syftPkg.Ruby, ""),
language.NewNamespace("abc.xyz", syftPkg.Ruby, ""),
},
syftPkg.Rust: {
language.NewNamespace("github", syftPkg.Rust, ""),
language.NewNamespace("something", syftPkg.Rust, ""),
},
syftPkg.Language("unknown"): {
language.NewNamespace("1234.4567", syftPkg.Language("unknown"), ""),
},

View file

@ -25,6 +25,10 @@ func TestFromString(t *testing.T) {
namespaceString: "github:language:java",
result: NewNamespace("github", syftPkg.Java, ""),
},
{
namespaceString: "github:language:rust",
result: NewNamespace("github", syftPkg.Rust, ""),
},
{
namespaceString: "abc.xyz:language:something",
result: NewNamespace("abc.xyz", syftPkg.Language("something"), ""),

View file

@ -15,6 +15,7 @@ const (
PortageMatcher MatcherType = "portage-matcher"
GoModuleMatcher MatcherType = "go-module-matcher"
OpenVexMatcher MatcherType = "openvex-matcher"
RustMatcher MatcherType = "rust-matcher"
)
var AllMatcherTypes = []MatcherType{
@ -30,6 +31,7 @@ var AllMatcherTypes = []MatcherType{
PortageMatcher,
GoModuleMatcher,
OpenVexMatcher,
RustMatcher,
}
type MatcherType string

View file

@ -12,6 +12,7 @@ import (
"github.com/anchore/grype/grype/matcher/python"
"github.com/anchore/grype/grype/matcher/rpm"
"github.com/anchore/grype/grype/matcher/ruby"
"github.com/anchore/grype/grype/matcher/rust"
"github.com/anchore/grype/grype/matcher/stock"
)
@ -23,6 +24,7 @@ type Config struct {
Dotnet dotnet.MatcherConfig
Javascript javascript.MatcherConfig
Golang golang.MatcherConfig
Rust rust.MatcherConfig
Stock stock.MatcherConfig
}
@ -39,6 +41,7 @@ func NewDefaultMatchers(mc Config) []Matcher {
golang.NewGolangMatcher(mc.Golang),
&msrc.Matcher{},
&portage.Matcher{},
rust.NewRustMatcher(mc.Rust),
stock.NewStockMatcher(mc.Stock),
}
}

View file

@ -0,0 +1,40 @@
package rust
import (
"github.com/anchore/grype/grype/distro"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/search"
"github.com/anchore/grype/grype/vulnerability"
syftPkg "github.com/anchore/syft/syft/pkg"
)
type Matcher struct {
cfg MatcherConfig
}
type MatcherConfig struct {
UseCPEs bool
}
func NewRustMatcher(cfg MatcherConfig) *Matcher {
return &Matcher{
cfg: cfg,
}
}
func (m *Matcher) PackageTypes() []syftPkg.Type {
return []syftPkg.Type{syftPkg.RustPkg}
}
func (m *Matcher) Type() match.MatcherType {
return match.RustMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) {
criteria := search.CommonCriteria
if m.cfg.UseCPEs {
criteria = append(criteria, search.ByCPE)
}
return search.ByCriteria(store, d, p, m.Type(), criteria...)
}

View file

@ -1 +1,2 @@
check-for-app-update: false

View file

@ -163,6 +163,22 @@ func newMockDbStore() *mockStore {
},
},
},
"github:language:rust": {
"hello-auditable": []grypeDB.Vulnerability{
{
ID: "CVE-rust-sample-1",
VersionConstraint: "< 0.2.0",
VersionFormat: "unknown",
},
},
"auditable": []grypeDB.Vulnerability{
{
ID: "CVE-rust-sample-2",
VersionConstraint: "< 0.2.0",
VersionFormat: "unknown",
},
},
},
"debian:distro:debian:8": {
"apt-dev": []grypeDB.Vulnerability{
{

View file

@ -545,6 +545,45 @@ func addHaskellMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
})
}
func addRustMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Collection, theStore *mockStore, theResult *match.Matches) {
packages := catalog.PackagesByPath("/hello-auditable")
if len(packages) < 1 {
t.Logf("Rust Packages: %+v", packages)
t.Fatalf("problem with upstream syft cataloger (cargo-auditable-binary-cataloger)")
}
for _, p := range packages {
thePkg := pkg.New(p)
theVuln := theStore.backend["github:language:rust"][strings.ToLower(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]any{
"language": "rust",
"namespace": "github:language:rust",
"package": map[string]string{
"name": thePkg.Name,
"version": thePkg.Version,
},
},
Found: map[string]any{
"versionConstraint": vulnObj.Constraint.String(),
"vulnerabilityID": vulnObj.ID,
},
Matcher: match.RustMatcher,
},
},
})
}
}
func TestMatchByImage(t *testing.T) {
observedMatchers := stringutil.NewStringSet()
definedMatchers := stringutil.NewStringSet()
@ -603,6 +642,14 @@ func TestMatchByImage(t *testing.T) {
return expectedMatches
},
},
{
fixtureImage: "image-rust-auditable-match-coverage",
expectedFn: func(theSource source.Source, catalog *syftPkg.Collection, theStore *mockStore) match.Matches {
expectedMatches := match.NewMatches()
addRustMatches(t, theSource, catalog, theStore, &expectedMatches)
return expectedMatches
},
},
}
for _, test := range tests {
@ -647,7 +694,6 @@ func TestMatchByImage(t *testing.T) {
}
actualResults := grype.FindVulnerabilitiesForPackage(str, theDistro, matchers, pkg.FromCollection(collection, pkg.SynthesisConfig{}))
for _, m := range actualResults.Sorted() {
for _, d := range m.Details {
observedMatchers.Add(string(d.Matcher))

View file

@ -0,0 +1,2 @@
# An image containing the example hello-auditable binary from https://github.com/Shnatsel/rust-audit/tree/master/hello-auditable
FROM docker.io/tofay/hello-rust-auditable:latest