mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
Add --ignore-states flag for ignoring findings with specific fix states (#1473)
* Add --ignore-states flag for ignoring findings with by fix state Signed-off-by: James Hebden <jhebden@gitlab.com> * ignore options checked before scan, fail on invalid ignore states, ignore states comma-separated Signed-off-by: James Hebden <jhebden@gitlab.com> * Add CLI tests for new --ignore-states flag Signed-off-by: Will Murphy <will.murphy@anchore.com> --------- Signed-off-by: James Hebden <jhebden@gitlab.com> Signed-off-by: Will Murphy <will.murphy@anchore.com> Co-authored-by: Will Murphy <will.murphy@anchore.com>
This commit is contained in:
parent
72390f87e9
commit
30f05c3759
8 changed files with 135 additions and 9 deletions
|
@ -401,7 +401,7 @@ NAME INSTALLED FIXED-IN VULNERABILITY SEVERITY
|
|||
apk-tools 2.10.6-r0 2.10.7-r0 CVE-2021-36159 Critical
|
||||
```
|
||||
|
||||
If you want Grype to only report vulnerabilities **that do not have a confirmed fix**, you can use the `--only-notfixed` flag. (This automatically adds [ignore rules](#specifying-matches-to-ignore) into Grype's configuration, such that vulnerabilities that are fixed will be ignored.)
|
||||
If you want Grype to only report vulnerabilities **that do not have a confirmed fix**, you can use the `--only-notfixed` flag. Alternatively, you can use the `--ignore-states` flag to filter results for vulnerabilities with specific states such as `wont-fix` (see `--help` for a list of valid fix states). These flags automatically add [ignore rules](#specifying-matches-to-ignore) into Grype's configuration, such that vulnerabilities which are fixed, or will not be fixed, will be ignored.
|
||||
|
||||
## VEX Support
|
||||
|
||||
|
|
|
@ -115,6 +115,23 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
|
|||
var s *sbom.SBOM
|
||||
var pkgContext pkg.Context
|
||||
|
||||
if opts.OnlyFixed {
|
||||
opts.Ignore = append(opts.Ignore, ignoreNonFixedMatches...)
|
||||
}
|
||||
|
||||
if opts.OnlyNotFixed {
|
||||
opts.Ignore = append(opts.Ignore, ignoreFixedMatches...)
|
||||
}
|
||||
|
||||
for _, ignoreState := range stringutil.SplitCommaSeparatedString(opts.IgnoreStates) {
|
||||
switch grypeDb.FixState(ignoreState) {
|
||||
case grypeDb.UnknownFixState, grypeDb.FixedState, grypeDb.NotFixedState, grypeDb.WontFixState:
|
||||
opts.Ignore = append(opts.Ignore, match.IgnoreRule{FixState: ignoreState})
|
||||
default:
|
||||
return fmt.Errorf("unknown fix state %s was supplied for --ignore-states", ignoreState)
|
||||
}
|
||||
}
|
||||
|
||||
err = parallel(
|
||||
func() error {
|
||||
checkForAppUpdate(app.ID(), opts)
|
||||
|
@ -147,14 +164,6 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
|
|||
defer dbCloser.Close()
|
||||
}
|
||||
|
||||
if opts.OnlyFixed {
|
||||
opts.Ignore = append(opts.Ignore, ignoreNonFixedMatches...)
|
||||
}
|
||||
|
||||
if opts.OnlyNotFixed {
|
||||
opts.Ignore = append(opts.Ignore, ignoreFixedMatches...)
|
||||
}
|
||||
|
||||
if err = applyVexRules(opts); err != nil {
|
||||
return fmt.Errorf("applying vex rules: %w", err)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ type Grype struct {
|
|||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
OnlyFixed bool `yaml:"only-fixed" json:"only-fixed" mapstructure:"only-fixed"` // only fail if detected vulns have a fix
|
||||
OnlyNotFixed bool `yaml:"only-notfixed" json:"only-notfixed" mapstructure:"only-notfixed"` // only fail if detected vulns don't have a fix
|
||||
IgnoreStates string `yaml:"ignore-states" json:"ignore-wontfix" mapstructure:"ignore-wontfix"` // ignore detections for vulnerabilities matching these comma-separated fix states
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` // --platform, override the target platform for a container image
|
||||
Search search `yaml:"search" json:"search" mapstructure:"search"`
|
||||
Ignore []match.IgnoreRule `yaml:"ignore" json:"ignore" mapstructure:"ignore"`
|
||||
|
@ -103,6 +104,11 @@ func (o *Grype) AddFlags(flags clio.FlagSet) {
|
|||
"ignore matches for vulnerabilities that are fixed",
|
||||
)
|
||||
|
||||
flags.StringVarP(&o.IgnoreStates,
|
||||
"ignore-states", "",
|
||||
fmt.Sprintf("ignore matches for vulnerabilities with specified comma separated fix states, options=%v", vulnerability.AllFixStates()),
|
||||
)
|
||||
|
||||
flags.BoolVarP(&o.ByCVE,
|
||||
"by-cve", "",
|
||||
"orient results by CVE instead of the original vulnerability ID when possible",
|
||||
|
|
|
@ -4,6 +4,15 @@ import (
|
|||
grypeDb "github.com/anchore/grype/grype/db/v5"
|
||||
)
|
||||
|
||||
func AllFixStates() []grypeDb.FixState {
|
||||
return []grypeDb.FixState{
|
||||
grypeDb.FixedState,
|
||||
grypeDb.NotFixedState,
|
||||
grypeDb.UnknownFixState,
|
||||
grypeDb.WontFixState,
|
||||
}
|
||||
}
|
||||
|
||||
type Fix struct {
|
||||
Versions []string
|
||||
State grypeDb.FixState
|
||||
|
|
|
@ -23,3 +23,14 @@ func HasAnyOfPrefixes(input string, prefixes ...string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// SplitCommaSeparatedString returns a slice of strings separated from the input string by commas
|
||||
func SplitCommaSeparatedString(input string) []string {
|
||||
output := make([]string, 0)
|
||||
for _, inputItem := range strings.Split(input, ",") {
|
||||
if len(inputItem) > 0 {
|
||||
output = append(output, inputItem)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
|
|
@ -120,3 +120,37 @@ func TestHasAnyOfPrefixes(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitCommaSeparatedString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
input: "testing",
|
||||
expected: []string{"testing"},
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
input: "testing1,testing2",
|
||||
expected: []string{"testing1", "testing2"},
|
||||
},
|
||||
{
|
||||
input: "testing1,,testing2,testing3",
|
||||
expected: []string{"testing1", "testing2", "testing3"},
|
||||
},
|
||||
{
|
||||
input: "testing1,testing2,,",
|
||||
expected: []string{"testing1", "testing2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, SplitCommaSeparatedString(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,32 @@ func TestCmd(t *testing.T) {
|
|||
assertFailingReturnCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore-states wired up",
|
||||
args: []string{"./test-fixtures/sbom-grype-source.json", "--ignore-states", "unknown"},
|
||||
assertions: []traitAssertion{
|
||||
assertSucceedingReturnCode,
|
||||
assertRowInStdOut([]string{"Pygments", "2.6.1", "2.7.4", "python", "GHSA-pq64-v7f5-gqh8", "High"}),
|
||||
assertNotInOutput("CVE-2014-6052"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore-states wired up - ignore fixed",
|
||||
args: []string{"./test-fixtures/sbom-grype-source.json", "--ignore-states", "fixed"},
|
||||
assertions: []traitAssertion{
|
||||
assertSucceedingReturnCode,
|
||||
assertRowInStdOut([]string{"libvncserver", "0.9.9", "apk", "CVE-2014-6052", "High"}),
|
||||
assertNotInOutput("GHSA-pq64-v7f5-gqh8"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore-states wired up - ignore fixed, show suppressed",
|
||||
args: []string{"./test-fixtures/sbom-grype-source.json", "--ignore-states", "fixed", "--show-suppressed"},
|
||||
assertions: []traitAssertion{
|
||||
assertSucceedingReturnCode,
|
||||
assertRowInStdOut([]string{"Pygments", "2.6.1", "2.7.4", "python", "GHSA-pq64-v7f5-gqh8", "High", "(suppressed)"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -32,3 +32,34 @@ func assertSucceedingReturnCode(tb testing.TB, _, _ string, rc int) {
|
|||
tb.Errorf("expected to succeed but got rc=%d", rc)
|
||||
}
|
||||
}
|
||||
|
||||
func assertRowInStdOut(row []string) traitAssertion {
|
||||
return func(tb testing.TB, stdout, stderr string, _ int) {
|
||||
tb.Helper()
|
||||
|
||||
for _, line := range strings.Split(stdout, "\n") {
|
||||
lineMatched := false
|
||||
for _, column := range row {
|
||||
if !strings.Contains(line, column) {
|
||||
// it wasn't this line
|
||||
lineMatched = false
|
||||
break
|
||||
}
|
||||
lineMatched = true
|
||||
}
|
||||
if lineMatched {
|
||||
return
|
||||
}
|
||||
}
|
||||
// none of the lines matched
|
||||
tb.Errorf("expected stdout to contain %s, but it did not", strings.Join(row, " "))
|
||||
}
|
||||
}
|
||||
|
||||
func assertNotInOutput(notWanted string) traitAssertion {
|
||||
return func(tb testing.TB, stdout, stderr string, _ int) {
|
||||
if strings.Contains(stdout, notWanted) {
|
||||
tb.Errorf("got unwanted %s in stdout %s", notWanted, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue