From 2295508e3ce8fab76180343f3583b7bd83613a79 Mon Sep 17 00:00:00 2001 From: James Neate Date: Sat, 10 Jun 2023 00:49:11 +0100 Subject: [PATCH 1/2] feat: add ability to ignore packages on pom properties Signed-off-by: James Neate --- README.md | 7 ++ grype/match/ignore.go | 49 ++++++++++++ grype/match/ignore_test.go | 154 +++++++++++++++++++++++++++++++++---- grype/pkg/java_metadata.go | 1 + grype/pkg/package.go | 4 +- 5 files changed, 197 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b3512974317..b2507d2f8be 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,9 @@ Each rule can specify any combination of the following criteria: - package language (e.g. `"python"`; these values are defined [here](https://github.com/anchore/syft/blob/main/syft/pkg/language.go#L14-L23)) - package type (e.g. `"npm"`; these values are defined [here](https://github.com/anchore/syft/blob/main/syft/pkg/type.go#L10-L24)) - package location (e.g. `"/usr/local/lib/node_modules/**"`; supports glob patterns) +- pom scope (e.g. `"test"`) +- pom group id (e.g. `"org.springframework.boot"`) +- pom artifact id (e.g. `"spring-boot-starter-webflux"`) Here's an example `~/.grype.yaml` that demonstrates the expected format for ignore rules: @@ -336,6 +339,10 @@ ignore: # ...or just by a single package field: - package: type: gem + + # ...or just by a single pom field: + - pom: + scope: test ``` Vulnerability matches will be ignored if **any** rules apply to the match. A rule is considered to apply to a given vulnerability match only if **all** fields specified in the rule apply to the vulnerability match. diff --git a/grype/match/ignore.go b/grype/match/ignore.go index cd719a70392..67504dc8787 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -2,6 +2,8 @@ package match import ( "github.com/bmatcuk/doublestar/v2" + + "github.com/anchore/grype/grype/pkg" ) // An IgnoredMatch is a vulnerability Match that has been ignored because one or more IgnoreRules applied to the match. @@ -21,6 +23,7 @@ type IgnoreRule struct { Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` + Pom IgnoreRulePom `yaml:"pom" json:"pom" mapstructure:"pom"` } // IgnoreRulePackage describes the Package-specific fields that comprise the IgnoreRule. @@ -32,6 +35,13 @@ type IgnoreRulePackage struct { Location string `yaml:"location" json:"location" mapstructure:"location"` } +// IgnoreRulePom describes the Pom-specific fields that comprise the IgnoreRule. +type IgnoreRulePom struct { + Scope string `yaml:"scope" json:"scope" mapstructure:"scope"` + GroupID string `yaml:"group-id" json:"group-id" mapstructure:"group-id"` + ArtifactID string `yaml:"artifact-id" json:"artifact-id" mapstructure:"artifact-id"` +} + // ApplyIgnoreRules iterates through the provided matches and, for each match, // determines if the match should be ignored, by evaluating if any of the // provided IgnoreRules apply to the match. If any rules apply to the match, all @@ -123,6 +133,18 @@ func getIgnoreConditionsForRule(rule IgnoreRule) []ignoreCondition { ignoreConditions = append(ignoreConditions, ifFixStateApplies(fs)) } + if s := rule.Pom.Scope; s != "" { + ignoreConditions = append(ignoreConditions, ifPomScopeApplies(s)) + } + + if gi := rule.Pom.GroupID; gi != "" { + ignoreConditions = append(ignoreConditions, ifPomGroupIDApplies(gi)) + } + + if ai := rule.Pom.ArtifactID; ai != "" { + ignoreConditions = append(ignoreConditions, ifPomArtifactIDApplies(ai)) + } + return ignoreConditions } @@ -174,6 +196,33 @@ func ifPackageLocationApplies(location string) ignoreCondition { } } +func ifPomScopeApplies(scope string) ignoreCondition { + return func(match Match) bool { + if metadata, ok := match.Package.Metadata.(pkg.JavaMetadata); ok { + return scope == metadata.PomScope + } + return false + } +} + +func ifPomGroupIDApplies(groupID string) ignoreCondition { + return func(match Match) bool { + if metadata, ok := match.Package.Metadata.(pkg.JavaMetadata); ok { + return groupID == metadata.PomGroupID + } + return false + } +} + +func ifPomArtifactIDApplies(artifactID string) ignoreCondition { + return func(match Match) bool { + if metadata, ok := match.Package.Metadata.(pkg.JavaMetadata); ok { + return artifactID == metadata.PomArtifactID + } + return false + } +} + func ruleLocationAppliesToMatch(location string, match Match) bool { for _, packageLocation := range match.Package.Locations.ToSlice() { if ruleLocationAppliesToPath(location, packageLocation.RealPath) { diff --git a/grype/match/ignore_test.go b/grype/match/ignore_test.go index 4490bf8a736..ebcc917808c 100644 --- a/grype/match/ignore_test.go +++ b/grype/match/ignore_test.go @@ -70,19 +70,22 @@ var ( { Vulnerability: vulnerability.Vulnerability{ ID: "CVE-458", - Namespace: "ruby-vulns", + Namespace: "java-vulns", Fix: vulnerability.Fix{ State: grypeDb.UnknownFixState, }, }, Package: pkg.Package{ ID: pkg.ID(uuid.NewString()), - Name: "speach", - Version: "100.0.52", - Language: syftPkg.Ruby, - Type: syftPkg.GemPkg, - Locations: source.NewLocationSet(source.NewVirtualLocation("/real/path/with/speach", - "/virtual/path/that/has/speach")), + Name: "log4j", + Version: "1.1.1", + Language: syftPkg.Java, + Type: syftPkg.JavaPkg, + Metadata: pkg.JavaMetadata{ + PomGroupID: "log4j", + PomArtifactID: "log4j-core", + PomScope: "test", + }, }, }, } @@ -235,6 +238,7 @@ func TestApplyIgnoreRules(t *testing.T) { }, expectedRemainingMatches: []Match{ allMatches[0], + allMatches[3], }, expectedIgnoredMatches: []IgnoredMatch{ { @@ -253,14 +257,6 @@ func TestApplyIgnoreRules(t *testing.T) { }, }, }, - { - Match: allMatches[3], - AppliedIgnoreRules: []IgnoreRule{ - { - Namespace: "ruby-vulns", - }, - }, - }, }, }, { @@ -275,6 +271,7 @@ func TestApplyIgnoreRules(t *testing.T) { }, expectedRemainingMatches: []Match{ allMatches[0], + allMatches[3], }, expectedIgnoredMatches: []IgnoredMatch{ { @@ -297,12 +294,86 @@ func TestApplyIgnoreRules(t *testing.T) { }, }, }, + }, + }, + { + name: "ignore matches on pom scope", + allMatches: allMatches, + ignoreRules: []IgnoreRule{ + { + Pom: IgnoreRulePom{ + Scope: "test", + }, + }, + }, + expectedRemainingMatches: []Match{ + allMatches[0], + allMatches[1], + allMatches[2], + }, + expectedIgnoredMatches: []IgnoredMatch{ { Match: allMatches[3], AppliedIgnoreRules: []IgnoreRule{ { - Package: IgnoreRulePackage{ - Language: string(syftPkg.Ruby), + Pom: IgnoreRulePom{ + Scope: "test", + }, + }, + }, + }, + }, + }, + { + name: "ignore matches on pom group id", + allMatches: allMatches, + ignoreRules: []IgnoreRule{ + { + Pom: IgnoreRulePom{ + GroupID: "log4j", + }, + }, + }, + expectedRemainingMatches: []Match{ + allMatches[0], + allMatches[1], + allMatches[2], + }, + expectedIgnoredMatches: []IgnoredMatch{ + { + Match: allMatches[3], + AppliedIgnoreRules: []IgnoreRule{ + { + Pom: IgnoreRulePom{ + GroupID: "log4j", + }, + }, + }, + }, + }, + }, + { + name: "ignore matches on pom artifact id", + allMatches: allMatches, + ignoreRules: []IgnoreRule{ + { + Pom: IgnoreRulePom{ + ArtifactID: "log4j-core", + }, + }, + }, + expectedRemainingMatches: []Match{ + allMatches[0], + allMatches[1], + allMatches[2], + }, + expectedIgnoredMatches: []IgnoredMatch{ + { + Match: allMatches[3], + AppliedIgnoreRules: []IgnoreRule{ + { + Pom: IgnoreRulePom{ + ArtifactID: "log4j-core", }, }, }, @@ -346,6 +417,25 @@ var ( } ) +var ( + exampleJavaMatch = Match{ + Vulnerability: vulnerability.Vulnerability{ + ID: "CVE-2000-1234", + }, + Package: pkg.Package{ + ID: pkg.ID(uuid.NewString()), + Name: "a-pkg", + Version: "1.0", + Type: "java-archive", + Metadata: pkg.JavaMetadata{ + PomGroupID: "example-group", + PomArtifactID: "example-artifact", + PomScope: "test", + }, + }, + } +) + func TestShouldIgnore(t *testing.T) { cases := []struct { name string @@ -450,6 +540,36 @@ func TestShouldIgnore(t *testing.T) { }, expected: false, }, + { + name: "rule applies via pom scope", + match: exampleJavaMatch, + rule: IgnoreRule{ + Pom: IgnoreRulePom{ + Scope: exampleJavaMatch.Package.Metadata.(pkg.JavaMetadata).PomScope, + }, + }, + expected: true, + }, + { + name: "rule applies via pom group id", + match: exampleJavaMatch, + rule: IgnoreRule{ + Pom: IgnoreRulePom{ + GroupID: exampleJavaMatch.Package.Metadata.(pkg.JavaMetadata).PomGroupID, + }, + }, + expected: true, + }, + { + name: "rule applies via pom artifact id", + match: exampleJavaMatch, + rule: IgnoreRule{ + Pom: IgnoreRulePom{ + ArtifactID: exampleJavaMatch.Package.Metadata.(pkg.JavaMetadata).PomArtifactID, + }, + }, + expected: true, + }, } for _, testCase := range cases { diff --git a/grype/pkg/java_metadata.go b/grype/pkg/java_metadata.go index 24ba9371787..0064337cb1e 100644 --- a/grype/pkg/java_metadata.go +++ b/grype/pkg/java_metadata.go @@ -4,6 +4,7 @@ type JavaMetadata struct { VirtualPath string `json:"virtualPath"` PomArtifactID string `json:"pomArtifactID"` PomGroupID string `json:"pomGroupID"` + PomScope string `json:"pomScope"` ManifestName string `json:"manifestName"` ArchiveDigests []Digest `json:"archiveDigests"` } diff --git a/grype/pkg/package.go b/grype/pkg/package.go index d8b3675d370..58116f24561 100644 --- a/grype/pkg/package.go +++ b/grype/pkg/package.go @@ -238,10 +238,11 @@ func getNameAndELVersion(sourceRpm string) (string, string) { func javaDataFromPkg(p pkg.Package) (metadata *JavaMetadata) { if value, ok := p.Metadata.(pkg.JavaMetadata); ok { - var artifact, group, name string + var artifact, group, name, scope string if value.PomProperties != nil { artifact = value.PomProperties.ArtifactID group = value.PomProperties.GroupID + scope = value.PomProperties.Scope } if value.Manifest != nil { if n, ok := value.Manifest.Main["Name"]; ok { @@ -263,6 +264,7 @@ func javaDataFromPkg(p pkg.Package) (metadata *JavaMetadata) { VirtualPath: value.VirtualPath, PomArtifactID: artifact, PomGroupID: group, + PomScope: scope, ManifestName: name, ArchiveDigests: archiveDigests, } From 0f31e606434300b2c9d6baefea11ce58f0989190 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Wed, 14 Feb 2024 11:19:16 -0500 Subject: [PATCH 2/2] fix: update SA tooling Signed-off-by: Christopher Phillips --- grype/match/ignore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grype/match/ignore.go b/grype/match/ignore.go index 23fe5e742c8..c012ebb8374 100644 --- a/grype/match/ignore.go +++ b/grype/match/ignore.go @@ -24,7 +24,7 @@ type IgnoreRule struct { Namespace string `yaml:"namespace" json:"namespace" mapstructure:"namespace"` FixState string `yaml:"fix-state" json:"fix-state" mapstructure:"fix-state"` Package IgnoreRulePackage `yaml:"package" json:"package" mapstructure:"package"` - Pom IgnoreRulePom `yaml:"pom" json:"pom" mapstructure:"pom"` + Pom IgnoreRulePom `yaml:"pom" json:"pom" mapstructure:"pom"` VexStatus string `yaml:"vex-status" json:"vex-status" mapstructure:"vex-status"` VexJustification string `yaml:"vex-justification" json:"vex-justification" mapstructure:"vex-justification"` }