Skip to content

Commit

Permalink
feat: disable CPE-based matching for GHSA ecosystems by default (#1412)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
westonsteimel authored Oct 12, 2023
1 parent bcbc7e4 commit 25762b7
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/validations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions cmd/grype/cli/options/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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,
}
}
6 changes: 6 additions & 0 deletions grype/db/v5/namespace/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"), ""),
},
Expand Down
4 changes: 4 additions & 0 deletions grype/db/v5/namespace/language/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"), ""),
Expand Down
2 changes: 2 additions & 0 deletions grype/match/matcher_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -30,6 +31,7 @@ var AllMatcherTypes = []MatcherType{
PortageMatcher,
GoModuleMatcher,
OpenVexMatcher,
RustMatcher,
}

type MatcherType string
3 changes: 3 additions & 0 deletions grype/matcher/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -23,6 +24,7 @@ type Config struct {
Dotnet dotnet.MatcherConfig
Javascript javascript.MatcherConfig
Golang golang.MatcherConfig
Rust rust.MatcherConfig
Stock stock.MatcherConfig
}

Expand All @@ -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),
}
}
40 changes: 40 additions & 0 deletions grype/matcher/rust/matcher.go
Original file line number Diff line number Diff line change
@@ -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...)
}
1 change: 1 addition & 0 deletions test/grype-test-config.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
check-for-app-update: false

16 changes: 16 additions & 0 deletions test/integration/db_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand Down
48 changes: 47 additions & 1 deletion test/integration/match_by_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 25762b7

Please sign in to comment.