Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading