175 patch - allow ignore not fixed to work independently of configured rules (#454)

* add ignore rules that allow different states of fixes to be ignored

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Christopher Angelo Phillips 2021-10-18 09:46:12 -04:00 committed by GitHub
parent e544dff368
commit 30340dbdf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 22 deletions

View file

@ -27,10 +27,18 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/wagoodman/go-partybus"
grypeDb "github.com/anchore/grype-db/pkg/db/v3"
)
var persistentOpts = config.CliOnlyOptions{}
var ignoreNonFixedMatches = []match.IgnoreRule{
{FixState: string(grypeDb.NotFixedState)},
{FixState: string(grypeDb.WontFixState)},
{FixState: string(grypeDb.UnknownFixState)},
}
var (
rootCmd = &cobra.Command{
Use: fmt.Sprintf("%s [IMAGE]", internal.ApplicationName),
@ -117,7 +125,7 @@ func setRootFlags(flags *pflag.FlagSet) {
flags.BoolP(
"only-fixed", "", false,
"only set the return code to 1 if vulnerabilities are found that have fixes",
"ignore matches for vulnerabilities that are not fixed",
)
}
@ -240,8 +248,12 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
return
}
if appConfig.OnlyFixed {
appConfig.Ignore = append(appConfig.Ignore, ignoreNonFixedMatches...)
}
allMatches := grype.FindVulnerabilitiesForPackage(provider, context.Distro, packages...)
remainingMatches, ignoredMatches := match.ApplyIgnoreRules(allMatches, appConfig.Ignore, appConfig.OnlyFixed)
remainingMatches, ignoredMatches := match.ApplyIgnoreRules(allMatches, appConfig.Ignore)
if count := len(ignoredMatches); count > 0 {
log.Infof("Ignoring %d matches due to user-provided ignore rules", count)

View file

@ -2,8 +2,6 @@ package match
import (
"github.com/bmatcuk/doublestar/v2"
grypeDb "github.com/anchore/grype-db/pkg/db/v3"
)
// An IgnoredMatch is a vulnerability Match that has been ignored because one or more IgnoreRules applied to the match.
@ -20,6 +18,7 @@ type IgnoredMatch struct {
// rule to apply.
type IgnoreRule struct {
Vulnerability string `yaml:"vulnerability" json:"vulnerability" mapstructure:"vulnerability"`
FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"`
Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"`
}
@ -37,11 +36,7 @@ type IgnoreRulePackage struct {
// applicable rules are attached to the Match to form an IgnoredMatch.
// ApplyIgnoreRules returns two collections: the matches that are not being
// ignored, and the matches that are being ignored.
func ApplyIgnoreRules(matches Matches, rules []IgnoreRule, failOnlyFixed bool) (Matches, []IgnoredMatch) {
if len(rules) == 0 {
return matches, nil
}
func ApplyIgnoreRules(matches Matches, rules []IgnoreRule) (Matches, []IgnoredMatch) {
var ignoredMatches []IgnoredMatch
remainingMatches := NewMatches()
@ -54,12 +49,7 @@ func ApplyIgnoreRules(matches Matches, rules []IgnoreRule, failOnlyFixed bool) (
}
}
var ignoreMatch bool
if failOnlyFixed {
ignoreMatch = isNotFixed(match)
}
if len(applicableRules) > 0 || ignoreMatch {
if len(applicableRules) > 0 {
ignoredMatches = append(ignoredMatches, IgnoredMatch{
Match: match,
AppliedIgnoreRules: applicableRules,
@ -74,10 +64,6 @@ func ApplyIgnoreRules(matches Matches, rules []IgnoreRule, failOnlyFixed bool) (
return remainingMatches, ignoredMatches
}
func isNotFixed(m Match) bool {
return m.Vulnerability.Fix.State == grypeDb.NotFixedState
}
func shouldIgnore(match Match, rule IgnoreRule) bool {
ignoreConditions := getIgnoreConditionsForRule(rule)
if len(ignoreConditions) == 0 {
@ -123,9 +109,19 @@ func getIgnoreConditionsForRule(rule IgnoreRule) []ignoreCondition {
ignoreConditions = append(ignoreConditions, ifPackageLocationApplies(l))
}
if fs := rule.FixState; fs != "" {
ignoreConditions = append(ignoreConditions, ifFixStateApplies(fs))
}
return ignoreConditions
}
func ifFixStateApplies(fs string) ignoreCondition {
return func(match Match) bool {
return fs == string(match.Vulnerability.Fix.State)
}
}
func ifVulnerabilityApplies(vulnerability string) ignoreCondition {
return func(match Match) bool {
return vulnerability == match.Vulnerability.ID

View file

@ -11,6 +11,8 @@ import (
"github.com/anchore/grype/grype/vulnerability"
"github.com/stretchr/testify/assert"
grypeDb "github.com/anchore/grype-db/pkg/db/v3"
)
var (
@ -18,6 +20,9 @@ var (
{
Vulnerability: vulnerability.Vulnerability{
ID: "CVE-123",
Fix: vulnerability.Fix{
State: grypeDb.FixedState,
},
},
Package: pkg.Package{
Name: "dive",
@ -33,6 +38,9 @@ var (
{
Vulnerability: vulnerability.Vulnerability{
ID: "CVE-456",
Fix: vulnerability.Fix{
State: grypeDb.NotFixedState,
},
},
Package: pkg.Package{
Name: "reach",
@ -46,6 +54,44 @@ var (
},
},
},
{
Vulnerability: vulnerability.Vulnerability{
ID: "CVE-457",
Fix: vulnerability.Fix{
State: grypeDb.WontFixState,
},
},
Package: pkg.Package{
Name: "beach",
Version: "100.0.51",
Type: "gem",
Locations: []source.Location{
{
RealPath: "/real/path/with/beach",
VirtualPath: "/virtual/path/that/has/beach",
},
},
},
},
{
Vulnerability: vulnerability.Vulnerability{
ID: "CVE-458",
Fix: vulnerability.Fix{
State: grypeDb.UnknownFixState,
},
},
Package: pkg.Package{
Name: "speach",
Version: "100.0.52",
Type: "gem",
Locations: []source.Location{
{
RealPath: "/real/path/with/speach",
VirtualPath: "/virtual/path/that/has/speach",
},
},
},
},
}
)
@ -101,7 +147,10 @@ func TestApplyIgnoreRules(t *testing.T) {
},
},
},
expectedRemainingMatches: nil,
expectedRemainingMatches: []Match{
allMatches[2],
allMatches[3],
},
expectedIgnoredMatches: []IgnoredMatch{
{
Match: allMatches[0],
@ -133,6 +182,8 @@ func TestApplyIgnoreRules(t *testing.T) {
},
expectedRemainingMatches: []Match{
allMatches[0],
allMatches[2],
allMatches[3],
},
expectedIgnoredMatches: []IgnoredMatch{
{
@ -145,16 +196,53 @@ func TestApplyIgnoreRules(t *testing.T) {
},
},
},
{
name: "ignore matches without fix",
allMatches: allMatches,
ignoreRules: []IgnoreRule{
{FixState: string(grypeDb.NotFixedState)},
{FixState: string(grypeDb.WontFixState)},
{FixState: string(grypeDb.UnknownFixState)},
},
expectedRemainingMatches: []Match{
allMatches[0],
},
expectedIgnoredMatches: []IgnoredMatch{
{
Match: allMatches[1],
AppliedIgnoreRules: []IgnoreRule{
{
FixState: "not-fixed",
},
},
},
{
Match: allMatches[2],
AppliedIgnoreRules: []IgnoreRule{
{
FixState: "wont-fix",
},
},
},
{
Match: allMatches[3],
AppliedIgnoreRules: []IgnoreRule{
{
FixState: "unknown",
},
},
},
},
},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
failOnlyFixed := false
locationComparerOption := cmp.Comparer(func(x, y source.Location) bool {
return x.RealPath == y.RealPath && x.VirtualPath == y.VirtualPath
})
actualRemainingMatches, actualIgnoredMatches := ApplyIgnoreRules(sliceToMatches(testCase.allMatches), testCase.ignoreRules, failOnlyFixed)
actualRemainingMatches, actualIgnoredMatches := ApplyIgnoreRules(sliceToMatches(testCase.allMatches), testCase.ignoreRules)
if diff := cmp.Diff(testCase.expectedRemainingMatches, matchesToSlice(actualRemainingMatches), locationComparerOption); diff != "" {
t.Errorf("unexpected diff in remaining matches (-expected +actual):\n%s", diff)