mirror of
https://github.com/anchore/grype.git
synced 2026-02-11 09:42:33 +02:00
feat: add hex matcher for Erlang/Elixir ecosystem (#3194)
* feat: add hex matcher for Erlang/Elixir ecosystem Add a separate matcher for Hex packages so that the GHSA data for the Erlang ecosystem can be used by Grype. Add a config to allow continued us of CPEs for this ecosystem, but turn off CPE matching by default because they cause a lot of false positives. Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> * ci: integ test for mix.lock Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com> --------- Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/matcher/dpkg"
|
||||
"github.com/anchore/grype/grype/matcher/golang"
|
||||
"github.com/anchore/grype/grype/matcher/hex"
|
||||
"github.com/anchore/grype/grype/matcher/java"
|
||||
"github.com/anchore/grype/grype/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/matcher/python"
|
||||
@@ -371,6 +372,7 @@ func getMatcherConfig(opts *options.Grype) matcher.Config {
|
||||
AlwaysUseCPEForStdlib: opts.Match.Golang.AlwaysUseCPEForStdlib,
|
||||
AllowMainModulePseudoVersionComparison: opts.Match.Golang.AllowMainModulePseudoVersionComparison,
|
||||
},
|
||||
Hex: hex.MatcherConfig(opts.Match.Hex),
|
||||
Stock: stock.MatcherConfig(opts.Match.Stock),
|
||||
Dpkg: dpkg.MatcherConfig{
|
||||
MissingEpochStrategy: opts.Match.Dpkg.MissingEpochStrategy,
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/matcher/dpkg"
|
||||
"github.com/anchore/grype/grype/matcher/golang"
|
||||
"github.com/anchore/grype/grype/matcher/hex"
|
||||
"github.com/anchore/grype/grype/matcher/java"
|
||||
"github.com/anchore/grype/grype/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/matcher/python"
|
||||
@@ -114,6 +115,7 @@ func Test_getMatcherConfig(t *testing.T) {
|
||||
AlwaysUseCPEForStdlib: true,
|
||||
AllowMainModulePseudoVersionComparison: false,
|
||||
},
|
||||
Hex: hex.MatcherConfig{},
|
||||
Stock: stock.MatcherConfig{UseCPEs: true},
|
||||
Rpm: rpm.MatcherConfig{
|
||||
MissingEpochStrategy: "auto",
|
||||
@@ -148,6 +150,7 @@ func Test_getMatcherConfig(t *testing.T) {
|
||||
AlwaysUseCPEForStdlib: true,
|
||||
AllowMainModulePseudoVersionComparison: false,
|
||||
},
|
||||
Hex: hex.MatcherConfig{},
|
||||
Stock: stock.MatcherConfig{UseCPEs: true},
|
||||
Rpm: rpm.MatcherConfig{
|
||||
MissingEpochStrategy: "zero",
|
||||
@@ -182,6 +185,7 @@ func Test_getMatcherConfig(t *testing.T) {
|
||||
AlwaysUseCPEForStdlib: true,
|
||||
AllowMainModulePseudoVersionComparison: false,
|
||||
},
|
||||
Hex: hex.MatcherConfig{},
|
||||
Stock: stock.MatcherConfig{UseCPEs: true},
|
||||
Rpm: rpm.MatcherConfig{
|
||||
MissingEpochStrategy: "auto",
|
||||
|
||||
@@ -17,6 +17,7 @@ type matchConfig struct {
|
||||
Python matcherConfig `yaml:"python" json:"python" mapstructure:"python"` // settings for the python matcher
|
||||
Ruby matcherConfig `yaml:"ruby" json:"ruby" mapstructure:"ruby"` // settings for the ruby matcher
|
||||
Rust matcherConfig `yaml:"rust" json:"rust" mapstructure:"rust"` // settings for the rust matcher
|
||||
Hex matcherConfig `yaml:"hex" json:"hex" mapstructure:"hex"` // settings for the hex matcher (Elixir/Erlang)
|
||||
Stock matcherConfig `yaml:"stock" json:"stock" mapstructure:"stock"` // settings for the default/stock matcher
|
||||
Dpkg dpkgConfig `yaml:"dpkg" json:"dpkg" mapstructure:"dpkg"` // settings for the dpkg matcher
|
||||
Rpm rpmConfig `yaml:"rpm" json:"rpm" mapstructure:"rpm"` // settings for the rpm matcher
|
||||
@@ -125,6 +126,7 @@ func defaultMatchConfig() matchConfig {
|
||||
Python: dontUseCpe,
|
||||
Ruby: dontUseCpe,
|
||||
Rust: dontUseCpe,
|
||||
Hex: dontUseCpe,
|
||||
Stock: useCpe,
|
||||
Dpkg: defaultDpkgConfig(),
|
||||
Rpm: defaultRpmConfig(),
|
||||
@@ -170,6 +172,7 @@ func (cfg *matchConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&cfg.Python.UseCPEs, usingCpeDescription)
|
||||
descriptions.Add(&cfg.Ruby.UseCPEs, usingCpeDescription)
|
||||
descriptions.Add(&cfg.Rust.UseCPEs, usingCpeDescription)
|
||||
descriptions.Add(&cfg.Hex.UseCPEs, usingCpeDescription)
|
||||
descriptions.Add(&cfg.Stock.UseCPEs, usingCpeDescription)
|
||||
descriptions.Add(&cfg.Dpkg.MissingEpochStrategy,
|
||||
`strategy for handling missing epochs in dpkg package versions during matching (options: zero, auto)`)
|
||||
|
||||
@@ -79,7 +79,8 @@ func KnownPackageSpecifierOverrides() []PackageSpecifierOverride {
|
||||
{Ecosystem: pkg.Dart.String(), ReplacementEcosystem: ptr(string(pkg.DartPubPkg))},
|
||||
{Ecosystem: pkg.Dotnet.String(), ReplacementEcosystem: ptr(string(pkg.DotnetPkg))},
|
||||
{Ecosystem: pkg.Elixir.String(), ReplacementEcosystem: ptr(string(pkg.HexPkg))},
|
||||
{Ecosystem: pkg.Erlang.String(), ReplacementEcosystem: ptr(string(pkg.ErlangOTPPkg))},
|
||||
{Ecosystem: pkg.Erlang.String(), ReplacementEcosystem: ptr(string(pkg.HexPkg))}, // Erlang packages use hex.pm, same as Elixir
|
||||
{Ecosystem: string(pkg.ErlangOTPPkg), ReplacementEcosystem: ptr(string(pkg.HexPkg))}, // remap erlang-otp to hex for GHSA matching
|
||||
{Ecosystem: pkg.Go.String(), ReplacementEcosystem: ptr(string(pkg.GoModulePkg))},
|
||||
{Ecosystem: pkg.Haskell.String(), ReplacementEcosystem: ptr(string(pkg.HackagePkg))},
|
||||
{Ecosystem: pkg.Java.String(), ReplacementEcosystem: ptr(string(pkg.JavaPkg))},
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
RustMatcher MatcherType = "rust-matcher"
|
||||
BitnamiMatcher MatcherType = "bitnami-matcher"
|
||||
PacmanMatcher MatcherType = "pacman-matcher"
|
||||
HexMatcher MatcherType = "hex-matcher"
|
||||
)
|
||||
|
||||
var AllMatcherTypes = []MatcherType{
|
||||
@@ -38,6 +39,7 @@ var AllMatcherTypes = []MatcherType{
|
||||
RustMatcher,
|
||||
BitnamiMatcher,
|
||||
PacmanMatcher,
|
||||
HexMatcher,
|
||||
}
|
||||
|
||||
type MatcherType string
|
||||
|
||||
35
grype/matcher/hex/matcher.go
Normal file
35
grype/matcher/hex/matcher.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package hex
|
||||
|
||||
import (
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher/internal"
|
||||
"github.com/anchore/grype/grype/pkg"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type Matcher struct {
|
||||
cfg MatcherConfig
|
||||
}
|
||||
|
||||
type MatcherConfig struct {
|
||||
UseCPEs bool
|
||||
}
|
||||
|
||||
func NewHexMatcher(cfg MatcherConfig) *Matcher {
|
||||
return &Matcher{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Matcher) PackageTypes() []syftPkg.Type {
|
||||
return []syftPkg.Type{syftPkg.HexPkg, syftPkg.ErlangOTPPkg}
|
||||
}
|
||||
|
||||
func (m *Matcher) Type() match.MatcherType {
|
||||
return match.HexMatcher
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Match, []match.IgnoreFilter, error) {
|
||||
return internal.MatchPackageByEcosystemAndCPEs(store, p, m.Type(), m.cfg.UseCPEs)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
"github.com/anchore/grype/grype/matcher/dpkg"
|
||||
"github.com/anchore/grype/grype/matcher/golang"
|
||||
"github.com/anchore/grype/grype/matcher/hex"
|
||||
"github.com/anchore/grype/grype/matcher/java"
|
||||
"github.com/anchore/grype/grype/matcher/javascript"
|
||||
"github.com/anchore/grype/grype/matcher/msrc"
|
||||
@@ -28,6 +29,7 @@ type Config struct {
|
||||
Javascript javascript.MatcherConfig
|
||||
Golang golang.MatcherConfig
|
||||
Rust rust.MatcherConfig
|
||||
Hex hex.MatcherConfig
|
||||
Stock stock.MatcherConfig
|
||||
Dpkg dpkg.MatcherConfig
|
||||
Rpm rpm.MatcherConfig
|
||||
@@ -47,6 +49,7 @@ func NewDefaultMatchers(mc Config) []match.Matcher {
|
||||
&msrc.Matcher{},
|
||||
&portage.Matcher{},
|
||||
rust.NewRustMatcher(mc.Rust),
|
||||
hex.NewHexMatcher(mc.Hex),
|
||||
stock.NewStockMatcher(mc.Stock),
|
||||
&bitnami.Matcher{},
|
||||
&pacman.Matcher{},
|
||||
|
||||
@@ -172,6 +172,14 @@ func newMockDbProvider() vulnerability.Provider {
|
||||
PackageName: "shellcheck",
|
||||
Constraint: version.MustGetConstraint("< 0.9.0", version.UnknownFormat), // was: "haskell"
|
||||
},
|
||||
{
|
||||
Reference: vulnerability.Reference{
|
||||
ID: "CVE-hex-plug",
|
||||
Namespace: "github:language:elixir",
|
||||
},
|
||||
PackageName: "plug",
|
||||
Constraint: version.MustGetConstraint("< 1.12.0", version.UnknownFormat),
|
||||
},
|
||||
{
|
||||
Reference: vulnerability.Reference{
|
||||
ID: "CVE-rust-sample-1",
|
||||
|
||||
@@ -613,6 +613,43 @@ func addHaskellMatches(t *testing.T, theSource source.Source, catalog *syftPkg.C
|
||||
})
|
||||
}
|
||||
|
||||
func addHexMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Collection, provider vulnerability.Provider, theResult *match.Matches) {
|
||||
packages := catalog.PackagesByPath("/hex/mix.lock")
|
||||
if len(packages) < 1 {
|
||||
t.Logf("Hex Packages: %+v", packages)
|
||||
t.Fatalf("problem with upstream syft cataloger (elixir-mix-lock)")
|
||||
}
|
||||
thePkg := pkg.New(packages[0])
|
||||
vulns, err := provider.FindVulnerabilities(byNamespace("github:language:elixir"), search.ByPackageName(thePkg.Name))
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, vulns)
|
||||
vulnObj := vulns[0]
|
||||
|
||||
theResult.Add(match.Match{
|
||||
Vulnerability: vulnObj,
|
||||
Package: thePkg,
|
||||
Details: []match.Detail{
|
||||
{
|
||||
Type: match.ExactDirectMatch,
|
||||
Confidence: 1.0,
|
||||
SearchedBy: match.EcosystemParameters{
|
||||
Language: "elixir",
|
||||
Namespace: "github:language:elixir",
|
||||
Package: match.PackageParameter{
|
||||
Name: thePkg.Name,
|
||||
Version: thePkg.Version,
|
||||
},
|
||||
},
|
||||
Found: match.EcosystemResult{
|
||||
VersionConstraint: "< 1.12.0 (unknown)",
|
||||
VulnerabilityID: "CVE-hex-plug",
|
||||
},
|
||||
Matcher: match.HexMatcher,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func addJvmMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Collection, provider vulnerability.Provider, theResult *match.Matches) {
|
||||
packages := catalog.PackagesByPath("/opt/java/openjdk/release")
|
||||
if len(packages) < 1 {
|
||||
@@ -723,6 +760,7 @@ func TestMatchByImage(t *testing.T) {
|
||||
addDotnetMatches(t, theSource, catalog, provider, &expectedMatches)
|
||||
addGolangMatches(t, theSource, catalog, provider, &expectedMatches)
|
||||
addHaskellMatches(t, theSource, catalog, provider, &expectedMatches)
|
||||
addHexMatches(t, theSource, catalog, provider, &expectedMatches)
|
||||
return expectedMatches
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%{
|
||||
"plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
|
||||
}
|
||||
Reference in New Issue
Block a user