Skip to content

Commit

Permalink
config: add config opt in golang pseudo version main module comparison (
Browse files Browse the repository at this point in the history
#1816)

config: add config opt in golang pseudo version main module comparison
---------

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
  • Loading branch information
spiffcs committed Apr 18, 2024
1 parent 28df30c commit b7ffbee
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -838,6 +838,7 @@ match:
using-cpes: false
# even if CPE matching is disabled, make an exception when scanning for "stdlib".
always-use-cpe-for-stdlib: true
allow-main-module-pseudo-version-comparison: true
stock:
using-cpes: true
```
Expand Down
5 changes: 3 additions & 2 deletions cmd/grype/cli/commands/root.go
Expand Up @@ -291,8 +291,9 @@ func getMatchers(opts *options.Grype) []matcher.Matcher {
Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet),
Javascript: javascript.MatcherConfig(opts.Match.Javascript),
Golang: golang.MatcherConfig{
UseCPEs: opts.Match.Golang.UseCPEs,
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
UseCPEs: opts.Match.Golang.UseCPEs,
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
AllowMainModulePseudoVersionComparison: opts.Match.Golang.AllowMainModulePseudoVersionComparison,
},
Stock: stock.MatcherConfig(opts.Match.Stock),
},
Expand Down
8 changes: 5 additions & 3 deletions cmd/grype/cli/options/match.go
Expand Up @@ -17,16 +17,18 @@ type matcherConfig struct {
}

type golangConfig struct {
matcherConfig `yaml:",inline" mapstructure:",squash"`
AlwaysUseCPEForStdlib bool `yaml:"always-use-cpe-for-stdlib" json:"always-use-cpe-for-stdlib" mapstructure:"always-use-cpe-for-stdlib"` // if CPEs should be used during matching
matcherConfig `yaml:",inline" mapstructure:",squash"`
AlwaysUseCPEForStdlib bool `yaml:"always-use-cpe-for-stdlib" json:"always-use-cpe-for-stdlib" mapstructure:"always-use-cpe-for-stdlib"` // if CPEs should be used during matching
AllowMainModulePseudoVersionComparison bool `yaml:"allow-main-module-pseudo-version-comparison" json:"allow-main-module-pseudo-version-comparison" mapstructure:"allow-main-module-pseudo-version-comparison"` // if pseudo versions should be compared
}

func defaultGolangConfig() golangConfig {
return golangConfig{
matcherConfig: matcherConfig{
UseCPEs: false,
},
AlwaysUseCPEForStdlib: true,
AlwaysUseCPEForStdlib: true,
AllowMainModulePseudoVersionComparison: true,
}
}

Expand Down
13 changes: 10 additions & 3 deletions grype/matcher/golang/matcher.go
Expand Up @@ -16,8 +16,9 @@ type Matcher struct {
}

type MatcherConfig struct {
UseCPEs bool
AlwaysUseCPEForStdlib bool
UseCPEs bool
AlwaysUseCPEForStdlib bool
AllowMainModulePseudoVersionComparison bool
}

func NewGolangMatcher(cfg MatcherConfig) *Matcher {
Expand Down Expand Up @@ -49,7 +50,13 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa
// Syft has some fallback mechanisms to come up with a more sane version value
// depending on the scenario. But if none of these apply, the Go-set value of
// "(devel)" is used, which is altogether unhelpful for vulnerability matching.
isNotCorrected := strings.HasPrefix(p.Version, "(devel)")
var isNotCorrected bool
if m.cfg.AllowMainModulePseudoVersionComparison {
isNotCorrected = strings.HasPrefix(p.Version, "(devel)")
} else {
// when AllowPseudoVersionComparison is false
isNotCorrected = strings.HasPrefix(p.Version, "v0.0.0-") || strings.HasPrefix(p.Version, "(devel)")
}
if p.Name == mainModule && isNotCorrected {
return matches, nil
}
Expand Down
84 changes: 60 additions & 24 deletions grype/matcher/golang/matcher_test.go
Expand Up @@ -15,38 +15,74 @@ import (
syftPkg "github.com/anchore/syft/syft/pkg"
)

func TestMatcher_DropMainPackageIfNoVersion(t *testing.T) {

mainModuleMetadata := pkg.GolangBinMetadata{
MainModule: "istio.io/istio",
func TestMatcher_DropMainPackageGivenVersionInfo(t *testing.T) {
tests := []struct {
name string
subjectWithoutMainModule pkg.Package
mainModuleData pkg.GolangBinMetadata
allowPsuedoVersionComparison bool
expectedMatchCount int
}{
{
name: "main module with version is matched when pseudo version comparison is allowed",
subjectWithoutMainModule: pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "istio.io/istio",
Version: "v0.0.0-20220606222826-f59ce19ec6b6",
Type: syftPkg.GoModulePkg,
Language: syftPkg.Go,
Metadata: pkg.GolangBinMetadata{},
},
mainModuleData: pkg.GolangBinMetadata{
MainModule: "istio.io/istio",
},
allowPsuedoVersionComparison: true,
expectedMatchCount: 1,
},
{
name: "main module with version is NOT matched when pseudo version comparison is disabled",
subjectWithoutMainModule: pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "istio.io/istio",
Version: "v0.0.0-20220606222826-f59ce19ec6b6",
Type: syftPkg.GoModulePkg,
Language: syftPkg.Go,
Metadata: pkg.GolangBinMetadata{},
},
mainModuleData: pkg.GolangBinMetadata{
MainModule: "istio.io/istio",
},
allowPsuedoVersionComparison: false,
expectedMatchCount: 0,
},
}

subjectWithoutMainModule := pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "istio.io/istio",
Version: "v0.0.0-20220606222826-f59ce19ec6b6",
Type: syftPkg.GoModulePkg,
Language: syftPkg.Go,
Metadata: pkg.GolangBinMetadata{},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mainModuleMetadata := test.mainModuleData
subjectWithoutMainModule := test.subjectWithoutMainModule

subjectWithMainModule := subjectWithoutMainModule
subjectWithMainModule.Metadata = mainModuleMetadata
subjectWithMainModule := subjectWithoutMainModule
subjectWithMainModule.Metadata = mainModuleMetadata

subjectWithMainModuleAsDevel := subjectWithMainModule
subjectWithMainModuleAsDevel.Version = "(devel)"
subjectWithMainModuleAsDevel := subjectWithMainModule
subjectWithMainModuleAsDevel.Version = "(devel)"

matcher := NewGolangMatcher(MatcherConfig{})
store := newMockProvider()
matcher := NewGolangMatcher(MatcherConfig{
AllowMainModulePseudoVersionComparison: test.allowPsuedoVersionComparison,
})
store := newMockProvider()

preTest, _ := matcher.Match(store, nil, subjectWithoutMainModule)
assert.Len(t, preTest, 1, "should have matched the package when there is not a main module")
preTest, _ := matcher.Match(store, nil, subjectWithoutMainModule)
assert.Len(t, preTest, 1, "should have matched the package when there is not a main module")

actual, _ := matcher.Match(store, nil, subjectWithMainModule)
assert.Len(t, actual, 1, "should match the main module (i.e. 1 match)")
actual, _ := matcher.Match(store, nil, subjectWithMainModule)
assert.Len(t, actual, test.expectedMatchCount, "should match the main module depending on config (i.e. 1 match)")

actual, _ = matcher.Match(store, nil, subjectWithMainModuleAsDevel)
assert.Len(t, actual, 0, "unexpected match count; should not match main module (devel)")
actual, _ = matcher.Match(store, nil, subjectWithMainModuleAsDevel)
assert.Len(t, actual, 0, "unexpected match count; should never match main module (devel)")
})
}
}

func TestMatcher_SearchForStdlib(t *testing.T) {
Expand Down

0 comments on commit b7ffbee

Please sign in to comment.