config: add config opt in golang pseudo version main module comparison (#1816)

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

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Christopher Angelo Phillips 2024-04-18 14:40:52 -04:00 committed by GitHub
parent 28df30c0ed
commit b7ffbeee53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 37 deletions

View file

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

View file

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

View file

@ -17,8 +17,9 @@ 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 {
@ -26,7 +27,8 @@ func defaultGolangConfig() golangConfig {
matcherConfig: matcherConfig{
UseCPEs: false,
},
AlwaysUseCPEForStdlib: true,
AlwaysUseCPEForStdlib: true,
AllowMainModulePseudoVersionComparison: true,
}
}

View file

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

View file

@ -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
subjectWithMainModuleAsDevel := subjectWithMainModule
subjectWithMainModuleAsDevel.Version = "(devel)"
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")
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 never match main module (devel)")
})
}
subjectWithMainModule := subjectWithoutMainModule
subjectWithMainModule.Metadata = mainModuleMetadata
subjectWithMainModuleAsDevel := subjectWithMainModule
subjectWithMainModuleAsDevel.Version = "(devel)"
matcher := NewGolangMatcher(MatcherConfig{})
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")
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, subjectWithMainModuleAsDevel)
assert.Len(t, actual, 0, "unexpected match count; should not match main module (devel)")
}
func TestMatcher_SearchForStdlib(t *testing.T) {