fix: take VEX docs into account when --fail-on is set (#1657)

* Take VEX docs into account when --fail-on is set

Previously, VEX documents provided to Grype when --fail-on was set were not
taken into account. That led to inconsistent behaviour where a vulnerability
would be ignored when only `--vex` was specified, but would be included in
Grype output when both `--vex` and `--fail-on` were specified.

This change fixes that by moving the failure severity check to after the VEX
documents provided are tested.

I have also added a unit test to check that the combination of VEX docs and
failure severity checks works as expected.

Signed-off-by: Feroz Salam <feroz.salam@isovalent.com>

* Fix typos

Signed-off-by: Feroz Salam <feroz.salam@isovalent.com>

---------

Signed-off-by: Feroz Salam <feroz.salam@isovalent.com>
This commit is contained in:
Feroz Salam 2024-01-23 20:38:25 +05:30 committed by GitHub
parent 5e1ba46fb8
commit a3ade4242b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 119 additions and 14 deletions

View file

@ -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,

View file

@ -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
}

View file

@ -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,

View file

@ -0,0 +1,20 @@
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78",
"author": "The OpenVEX Project <openvex@openssf.org>",
"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"
}
]
}

View file

@ -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
}

View file

@ -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 {