diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml index 0552836fd4a..f7825cd4b3f 100644 --- a/.github/workflows/validations.yaml +++ b/.github/workflows/validations.yaml @@ -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 diff --git a/cmd/grype/cli/options/match.go b/cmd/grype/cli/options/match.go index f3eae474f10..47dc715dff5 100644 --- a/cmd/grype/cli/options/match.go +++ b/cmd/grype/cli/options/match.go @@ -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, } } diff --git a/grype/db/v5/namespace/index_test.go b/grype/db/v5/namespace/index_test.go index 64ce00c4a79..cac7b68aa87 100644 --- a/grype/db/v5/namespace/index_test.go +++ b/grype/db/v5/namespace/index_test.go @@ -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"), ""), }, diff --git a/grype/db/v5/namespace/language/namespace_test.go b/grype/db/v5/namespace/language/namespace_test.go index 35cd74241b7..faad7bd5d12 100644 --- a/grype/db/v5/namespace/language/namespace_test.go +++ b/grype/db/v5/namespace/language/namespace_test.go @@ -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"), ""), diff --git a/grype/match/matcher_type.go b/grype/match/matcher_type.go index e8e8858f9cf..ad547c6d94c 100644 --- a/grype/match/matcher_type.go +++ b/grype/match/matcher_type.go @@ -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 diff --git a/grype/matcher/matchers.go b/grype/matcher/matchers.go index a27faa442f0..72778eb6292 100644 --- a/grype/matcher/matchers.go +++ b/grype/matcher/matchers.go @@ -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), } } diff --git a/grype/matcher/rust/matcher.go b/grype/matcher/rust/matcher.go new file mode 100644 index 00000000000..6ce17449302 --- /dev/null +++ b/grype/matcher/rust/matcher.go @@ -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...) +} diff --git a/test/grype-test-config.yaml b/test/grype-test-config.yaml index 4b4d63bf0f0..21b7d303846 100644 --- a/test/grype-test-config.yaml +++ b/test/grype-test-config.yaml @@ -1 +1,2 @@ check-for-app-update: false + diff --git a/test/integration/db_mock_test.go b/test/integration/db_mock_test.go index 33069e1da51..6466488872e 100644 --- a/test/integration/db_mock_test.go +++ b/test/integration/db_mock_test.go @@ -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{ { diff --git a/test/integration/match_by_image_test.go b/test/integration/match_by_image_test.go index 49499ef435c..60ef75800de 100644 --- a/test/integration/match_by_image_test.go +++ b/test/integration/match_by_image_test.go @@ -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)) diff --git a/test/integration/test-fixtures/image-rust-auditable-match-coverage/Dockerfile b/test/integration/test-fixtures/image-rust-auditable-match-coverage/Dockerfile new file mode 100644 index 00000000000..ac01990fce0 --- /dev/null +++ b/test/integration/test-fixtures/image-rust-auditable-match-coverage/Dockerfile @@ -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 \ No newline at end of file