diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go index 64fc7edce3a..dc1664dc1e0 100644 --- a/cmd/grype/cli/commands/root.go +++ b/cmd/grype/cli/commands/root.go @@ -175,7 +175,7 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs Store: *str, IgnoreRules: opts.Ignore, NormalizeByCVE: opts.ByCVE, - FailSeverity: opts.FailOnServerity(), + FailSeverity: opts.FailOnSeverity(), Matchers: getMatchers(opts), VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ Documents: opts.VexDocuments, diff --git a/cmd/grype/cli/options/grype.go b/cmd/grype/cli/options/grype.go index 8219d77c369..2b408c22a08 100644 --- a/cmd/grype/cli/options/grype.go +++ b/cmd/grype/cli/options/grype.go @@ -137,7 +137,7 @@ func (o *Grype) AddFlags(flags clio.FlagSet) { func (o *Grype) PostLoad() error { if o.FailOn != "" { - failOnSeverity := *o.FailOnServerity() + failOnSeverity := *o.FailOnSeverity() if failOnSeverity == vulnerability.UnknownSeverity { return fmt.Errorf("bad --fail-on severity value '%s'", o.FailOn) } @@ -145,7 +145,7 @@ func (o *Grype) PostLoad() error { return nil } -func (o Grype) FailOnServerity() *vulnerability.Severity { +func (o Grype) FailOnSeverity() *vulnerability.Severity { severity := vulnerability.ParseSeverity(o.FailOn) return &severity } diff --git a/grype/vex/processor_test.go b/grype/vex/processor_test.go index 85168f2b1ab..4a4d0a218b0 100644 --- a/grype/vex/processor_test.go +++ b/grype/vex/processor_test.go @@ -92,7 +92,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { return &s } - metchesRef := func(ms ...match.Match) *match.Matches { + matchesRef := func(ms ...match.Match) *match.Matches { m := match.NewMatches(ms...) return &m } @@ -127,7 +127,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { pkgContext: pkgContext, matches: getSubject(), }, - wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantMatches: matchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), wantIgnoredMatches: []match.IgnoredMatch{ { Match: libCryptoCVE_2023_1255, @@ -157,7 +157,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { pkgContext: pkgContext, matches: getSubject(), }, - wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantMatches: matchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), wantIgnoredMatches: []match.IgnoredMatch{ { Match: libCryptoCVE_2023_1255, @@ -187,7 +187,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { pkgContext: pkgContext, matches: getSubject(), }, - wantMatches: metchesRef(libCryptoCVE_2023_3817), + wantMatches: matchesRef(libCryptoCVE_2023_3817), wantIgnoredMatches: []match.IgnoredMatch{ { Match: libCryptoCVE_2023_1255, @@ -226,7 +226,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { pkgContext: pkgContext, matches: getSubject(), }, - wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), + wantMatches: matchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975), wantIgnoredMatches: []match.IgnoredMatch{ { Match: libCryptoCVE_2023_1255, @@ -258,7 +258,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { matches: getSubject(), }, // nothing gets ignored! - wantMatches: metchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), + wantMatches: matchesRef(libCryptoCVE_2023_3817, libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), wantIgnoredMatches: []match.IgnoredMatch{}, }, { @@ -278,7 +278,7 @@ func TestProcessor_ApplyVEX(t *testing.T) { pkgContext: pkgContext, matches: getSubject(), }, - wantMatches: metchesRef(libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), + wantMatches: matchesRef(libCryptoCVE_2023_2975, libCryptoCVE_2023_1255), wantIgnoredMatches: []match.IgnoredMatch{ { Match: libCryptoCVE_2023_3817, diff --git a/grype/vex/testdata/vex-docs/openvex-debian.json b/grype/vex/testdata/vex-docs/openvex-debian.json new file mode 100644 index 00000000000..4a32e1351a9 --- /dev/null +++ b/grype/vex/testdata/vex-docs/openvex-debian.json @@ -0,0 +1,20 @@ +{ + "@context": "https://openvex.dev/ns/v0.2.0", + "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78", + "author": "The OpenVEX Project ", + "timestamp": "2023-07-17T18:28:47.696004345-06:00", + "version": 1, + "statements": [ + { + "vulnerability": { + "name": "CVE-2014-fake-1" + }, + "products": [ + { + "@id": "pkg:oci/debian@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126?repository_url=index.docker.io/library" + } + ], + "status": "fixed" + } + ] + } \ No newline at end of file diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index 14f5299e604..7ae9dbf4c8b 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -83,6 +83,11 @@ func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Conte return remainingMatches, ignoredMatches, err } + if m.FailSeverity != nil && HasSeverityAtOrAbove(m.Store, *m.FailSeverity, *remainingMatches) { + err = grypeerr.ErrAboveSeverityThreshold + return remainingMatches, ignoredMatches, err + } + logListSummary(progressMonitor) logIgnoredMatches(ignoredMatches) @@ -114,10 +119,6 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con matches, ignoredMatches = m.applyIgnoreRules(normalizedMatches) } - if m.FailSeverity != nil && HasSeverityAtOrAbove(m.Store, *m.FailSeverity, matches) { - err = grypeerr.ErrAboveSeverityThreshold - } - return &matches, ignoredMatches, err } diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go index e72df760ab7..b201ade43b2 100644 --- a/grype/vulnerability_matcher_test.go +++ b/grype/vulnerability_matcher_test.go @@ -21,11 +21,13 @@ import ( "github.com/anchore/grype/grype/search" "github.com/anchore/grype/grype/store" "github.com/anchore/grype/grype/version" + "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" syftPkg "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/source" ) type ack interface { @@ -327,6 +329,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { IgnoreRules []match.IgnoreRule FailSeverity *vulnerability.Severity NormalizeByCVE bool + VexProcessor *vex.Processor } type args struct { pkgs []pkg.Package @@ -466,6 +469,86 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { wantIgnoredMatches: nil, wantErr: grypeerr.ErrAboveSeverityThreshold, }, + { + name: "pass on severity threshold with VEX", + fields: fields{ + Store: str, + Matchers: matcher.NewDefaultMatchers(matcher.Config{}), + FailSeverity: func() *vulnerability.Severity { + x := vulnerability.LowSeverity + return &x + }(), + VexProcessor: vex.NewProcessor(vex.ProcessorOptions{ + Documents: []string{ + "vex/testdata/vex-docs/openvex-debian.json", + }, + IgnoreRules: []match.IgnoreRule{ + { + VexStatus: "fixed", + }, + }, + }), + }, + args: args{ + pkgs: []pkg.Package{ + neutron2013Pkg, + }, + context: pkg.Context{ + Source: &source.Description{ + Name: "debian", + Version: "2013.1.1-1", + Metadata: source.StereoscopeImageSourceMetadata{ + RepoDigests: []string{ + "debian@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126", + }, + }, + }, + Distro: &linux.Release{ + ID: "debian", + VersionID: "8", + }, + }, + }, + wantMatches: match.NewMatches(), + wantIgnoredMatches: []match.IgnoredMatch{ + { + AppliedIgnoreRules: []match.IgnoreRule{ + { + Namespace: "vex", + VexStatus: "fixed", + }, + }, + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat), + ID: "CVE-2014-fake-1", + Namespace: "debian:distro:debian:8", + PackageQualifiers: []qualifier.Qualifier{}, + CPEs: []cpe.CPE{}, + Advisories: []vulnerability.Advisory{}, + }, + Package: neutron2013Pkg, + Details: match.Details{ + { + Type: match.ExactDirectMatch, + SearchedBy: map[string]any{ + "distro": map[string]string{"type": "debian", "version": "8"}, + "namespace": "debian:distro:debian:8", + "package": map[string]string{"name": "neutron", "version": "2013.1.1-1"}, + }, + Found: map[string]any{ + "versionConstraint": "< 2014.1.3-6 (deb)", + "vulnerabilityID": "CVE-2014-fake-1", + }, + Matcher: "dpkg-matcher", + Confidence: 1, + }, + }, + }, + }, + }, + wantErr: nil, + }, { name: "matches by exact-direct match (language)", fields: fields{ @@ -884,6 +967,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { IgnoreRules: tt.fields.IgnoreRules, FailSeverity: tt.fields.FailSeverity, NormalizeByCVE: tt.fields.NormalizeByCVE, + VexProcessor: tt.fields.VexProcessor, } actualMatches, actualIgnoreMatches, err := m.FindMatches(tt.args.pkgs, tt.args.context) if tt.wantErr != nil {