From 7c9bbf27260e33df214b0d86055d6e6dcf3b2de4 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 13 Sep 2024 17:22:09 -0400 Subject: [PATCH] basic matcher in place Signed-off-by: Alex Goodman --- .gitignore | 4 +- cmd/grype/cli/commands/root.go | 4 ++ cmd/grype/cli/options/match.go | 2 + grype/internal/packagemetadata/names.go | 11 +++--- grype/match/matcher_type.go | 2 + grype/matcher/jvm/matcher.go | 50 +++++++++++++++++++++++++ grype/matcher/matchers.go | 3 ++ grype/matcher/stock/matcher.go | 8 ++++ grype/pkg/java_metadata.go | 30 +++++++++++++++ grype/pkg/package.go | 17 +++++++++ grype/version/jvm_version.go | 16 +++++--- grype/version/version.go | 4 ++ 12 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 grype/matcher/jvm/matcher.go diff --git a/.gitignore b/.gitignore index af1ef36b559..f1e1f81bd2c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,8 @@ bin/ /.task # changelog generation -CHANGELOG.md -VERSION +/CHANGELOG.md +/VERSION # IDE configuration .vscode/ diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go index f23382db24a..ccf01e15d5f 100644 --- a/cmd/grype/cli/commands/root.go +++ b/cmd/grype/cli/commands/root.go @@ -3,6 +3,7 @@ package commands import ( "errors" "fmt" + "github.com/anchore/grype/grype/matcher/jvm" "strings" "github.com/spf13/cobra" @@ -285,6 +286,9 @@ func getMatchers(opts *options.Grype) []matcher.Matcher { ExternalSearchConfig: opts.ExternalSources.ToJavaMatcherConfig(), UseCPEs: opts.Match.Java.UseCPEs, }, + JVM: jvm.MatcherConfig{ + UseCPEs: opts.Match.JVM.UseCPEs, + }, Ruby: ruby.MatcherConfig(opts.Match.Ruby), Python: python.MatcherConfig(opts.Match.Python), Dotnet: dotnet.MatcherConfig(opts.Match.Dotnet), diff --git a/cmd/grype/cli/options/match.go b/cmd/grype/cli/options/match.go index f789a5b4689..b24df165b29 100644 --- a/cmd/grype/cli/options/match.go +++ b/cmd/grype/cli/options/match.go @@ -5,6 +5,7 @@ import "github.com/anchore/clio" // matchConfig contains all matching-related configuration options available to the user via the application config. type matchConfig struct { Java matcherConfig `yaml:"java" json:"java" mapstructure:"java"` // settings for the java matcher + JVM matcherConfig `yaml:"jvm" json:"jvm" mapstructure:"jvm"` // settings for the jvm matcher Dotnet matcherConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"` // settings for the dotnet matcher Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"` // settings for the golang matcher Javascript matcherConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"` // settings for the javascript matcher @@ -43,6 +44,7 @@ func defaultMatchConfig() matchConfig { dontUseCpe := matcherConfig{UseCPEs: false} return matchConfig{ Java: dontUseCpe, + JVM: useCpe, Dotnet: dontUseCpe, Golang: defaultGolangConfig(), Javascript: dontUseCpe, diff --git a/grype/internal/packagemetadata/names.go b/grype/internal/packagemetadata/names.go index caf79c0465b..75e01a0da10 100644 --- a/grype/internal/packagemetadata/names.go +++ b/grype/internal/packagemetadata/names.go @@ -13,11 +13,12 @@ import ( // not the same it may be important to select different names. This design decision has been deferred, for now // the same metadata types that have been used in the past should be used here. var jsonNameFromType = map[reflect.Type][]string{ - reflect.TypeOf(pkg.ApkMetadata{}): nameList("ApkMetadata"), - reflect.TypeOf(pkg.GolangBinMetadata{}): nameList("GolangBinMetadata"), - reflect.TypeOf(pkg.GolangModMetadata{}): nameList("GolangModMetadata"), - reflect.TypeOf(pkg.JavaMetadata{}): nameList("JavaMetadata"), - reflect.TypeOf(pkg.RpmMetadata{}): nameList("RpmMetadata"), + reflect.TypeOf(pkg.ApkMetadata{}): nameList("ApkMetadata"), + reflect.TypeOf(pkg.GolangBinMetadata{}): nameList("GolangBinMetadata"), + reflect.TypeOf(pkg.GolangModMetadata{}): nameList("GolangModMetadata"), + reflect.TypeOf(pkg.JavaMetadata{}): nameList("JavaMetadata"), + reflect.TypeOf(pkg.RpmMetadata{}): nameList("RpmMetadata"), + reflect.TypeOf(pkg.JavaVMInstallationMetadata{}): nameList("JavaVMInstallationMetadata"), } //nolint:unparam diff --git a/grype/match/matcher_type.go b/grype/match/matcher_type.go index ad547c6d94c..29f6f9255de 100644 --- a/grype/match/matcher_type.go +++ b/grype/match/matcher_type.go @@ -16,6 +16,7 @@ const ( GoModuleMatcher MatcherType = "go-module-matcher" OpenVexMatcher MatcherType = "openvex-matcher" RustMatcher MatcherType = "rust-matcher" + JVMMatcher MatcherType = "jvm-matcher" ) var AllMatcherTypes = []MatcherType{ @@ -32,6 +33,7 @@ var AllMatcherTypes = []MatcherType{ GoModuleMatcher, OpenVexMatcher, RustMatcher, + JVMMatcher, } type MatcherType string diff --git a/grype/matcher/jvm/matcher.go b/grype/matcher/jvm/matcher.go new file mode 100644 index 00000000000..256caf582db --- /dev/null +++ b/grype/matcher/jvm/matcher.go @@ -0,0 +1,50 @@ +package jvm + +import ( + "fmt" + "github.com/anchore/grype/grype/distro" + "github.com/anchore/grype/grype/match" + "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/search" + "github.com/anchore/grype/grype/vulnerability" + syftPkg "github.com/anchore/syft/syft/pkg" +) + +type MatcherConfig struct { + UseCPEs bool +} + +type Matcher struct { + cfg MatcherConfig +} + +func NewJVMMatcher(cfg MatcherConfig) *Matcher { + return &Matcher{ + cfg: cfg, + } +} + +func (m *Matcher) PackageTypes() []syftPkg.Type { + return []syftPkg.Type{syftPkg.BinaryPkg} +} + +func (m *Matcher) Type() match.MatcherType { + return match.JVMMatcher +} + +func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) { + if !pkg.IsJvmPackage(p) { + return nil, nil + } + + criteria := search.CommonCriteria + if m.cfg.UseCPEs { + criteria = append(criteria, search.ByCPE) + } + matches, err := search.ByCriteria(store, d, p, m.Type(), criteria...) + if err != nil { + return nil, fmt.Errorf("failed to match by exact package: %w", err) + } + + return matches, nil +} diff --git a/grype/matcher/matchers.go b/grype/matcher/matchers.go index 72778eb6292..8c538433b5b 100644 --- a/grype/matcher/matchers.go +++ b/grype/matcher/matchers.go @@ -7,6 +7,7 @@ import ( "github.com/anchore/grype/grype/matcher/golang" "github.com/anchore/grype/grype/matcher/java" "github.com/anchore/grype/grype/matcher/javascript" + "github.com/anchore/grype/grype/matcher/jvm" "github.com/anchore/grype/grype/matcher/msrc" "github.com/anchore/grype/grype/matcher/portage" "github.com/anchore/grype/grype/matcher/python" @@ -19,6 +20,7 @@ import ( // Config contains values used by individual matcher structs for advanced configuration type Config struct { Java java.MatcherConfig + JVM jvm.MatcherConfig Ruby ruby.MatcherConfig Python python.MatcherConfig Dotnet dotnet.MatcherConfig @@ -36,6 +38,7 @@ func NewDefaultMatchers(mc Config) []Matcher { dotnet.NewDotnetMatcher(mc.Dotnet), &rpm.Matcher{}, java.NewJavaMatcher(mc.Java), + jvm.NewJVMMatcher(mc.JVM), javascript.NewJavascriptMatcher(mc.Javascript), &apk.Matcher{}, golang.NewGolangMatcher(mc.Golang), diff --git a/grype/matcher/stock/matcher.go b/grype/matcher/stock/matcher.go index 7f30a52df9a..661f4b67604 100644 --- a/grype/matcher/stock/matcher.go +++ b/grype/matcher/stock/matcher.go @@ -32,9 +32,17 @@ func (m *Matcher) Type() match.MatcherType { } func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) { + if !inboundsForMatcher(p) { + return nil, nil + } + criteria := search.CommonCriteria if m.cfg.UseCPEs { criteria = append(criteria, search.ByCPE) } return search.ByCriteria(store, d, p, m.Type(), criteria...) } + +func inboundsForMatcher(p pkg.Package) bool { + return !pkg.IsJvmPackage(p) +} diff --git a/grype/pkg/java_metadata.go b/grype/pkg/java_metadata.go index 24ba9371787..6e08eb88f95 100644 --- a/grype/pkg/java_metadata.go +++ b/grype/pkg/java_metadata.go @@ -1,5 +1,10 @@ package pkg +import ( + "github.com/anchore/syft/syft/pkg" + "strings" +) + type JavaMetadata struct { VirtualPath string `json:"virtualPath"` PomArtifactID string `json:"pomArtifactID"` @@ -12,3 +17,28 @@ type Digest struct { Algorithm string `json:"algorithm"` Value string `json:"value"` } + +type JavaVMInstallationMetadata struct { + Release JavaVMReleaseMetadata `json:"release,omitempty"` +} + +type JavaVMReleaseMetadata struct { + JavaRuntimeVersion string `json:"javaRuntimeVersion,omitempty"` + JavaVersion string `json:"javaVersion,omitempty"` + FullVersion string `json:"fullVersion,omitempty"` + SemanticVersion string `json:"semanticVersion,omitempty"` +} + +func IsJvmPackage(p Package) bool { + if p.Type == pkg.BinaryPkg { + if strings.Contains(p.Name, "jdk") || strings.Contains(p.Name, "jre") || strings.Contains(p.Name, "java") { + return true + } + } + + if _, ok := p.Metadata.(JavaVMInstallationMetadata); ok { + return true + } + + return false +} diff --git a/grype/pkg/package.go b/grype/pkg/package.go index e5b6399ac88..c807833177f 100644 --- a/grype/pkg/package.go +++ b/grype/pkg/package.go @@ -214,10 +214,27 @@ func dataFromPkg(p pkg.Package) (interface{}, []UpstreamPackage) { case pkg.ApkDBEntry: metadata = apkMetadataFromPkg(p) upstreams = apkDataFromPkg(p) + case pkg.JavaVMInstallation: + metadata = javaVmDataFromPkg(p) } return metadata, upstreams } +func javaVmDataFromPkg(p pkg.Package) any { + if value, ok := p.Metadata.(pkg.JavaVMInstallation); ok { + return JavaVMInstallationMetadata{ + Release: JavaVMReleaseMetadata{ + JavaRuntimeVersion: value.Release.JavaRuntimeVersion, + JavaVersion: value.Release.JavaVersion, + FullVersion: value.Release.FullVersion, + SemanticVersion: value.Release.SemanticVersion, + }, + } + } + + return nil +} + func apkMetadataFromPkg(p pkg.Package) interface{} { if m, ok := p.Metadata.(pkg.ApkDBEntry); ok { metadata := ApkMetadata{} diff --git a/grype/version/jvm_version.go b/grype/version/jvm_version.go index b23cc882b9a..30a83f5aa71 100644 --- a/grype/version/jvm_version.go +++ b/grype/version/jvm_version.go @@ -37,15 +37,21 @@ func newJvmVersion(raw string) (*jvmVersion, error) { } func (v *jvmVersion) Compare(other *Version) (int, error) { - if other.Format != JVMFormat { - return -1, fmt.Errorf("unable to compare JVM to given format: %s", other.Format) + if other.Format == JVMFormat { + if other.rich.jvmVersion == nil { + return -1, fmt.Errorf("given empty jvmVersion object") + } + return other.rich.jvmVersion.compare(*v), nil } - if other.rich.jvmVersion == nil { - return -1, fmt.Errorf("given empty jvmVersion object") + if other.Format == SemanticFormat { + if other.rich.semVer == nil { + return -1, fmt.Errorf("given empty semVer object") + } + return other.rich.semVer.verObj.Compare(v.semVer), nil } - return other.rich.jvmVersion.compare(*v), nil + return -1, fmt.Errorf("unable to compare JVM to given format: %s", other.Format) } func (v jvmVersion) compare(other jvmVersion) int { diff --git a/grype/version/version.go b/grype/version/version.go index 6c7fcf4b281..b65e3bb3dab 100644 --- a/grype/version/version.go +++ b/grype/version/version.go @@ -2,6 +2,8 @@ package version import ( "fmt" + syftPkg "github.com/anchore/syft/syft/pkg" + "strings" "github.com/anchore/grype/grype/pkg" "github.com/anchore/syft/syft/cpe" @@ -51,6 +53,8 @@ func NewVersionFromPkg(p pkg.Package) (*Version, error) { if format == UnknownFormat { if _, ok := p.Metadata.(pkg.JavaVMInstallationMetadata); ok { format = JVMFormat + } else if p.Type == syftPkg.BinaryPkg && (strings.Contains(p.Name, "jdk") || strings.Contains(p.Name, "jre") || strings.Contains(p.Name, "java")) { + format = JVMFormat } }