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

feat: --enrich flag to enable data enrichment #2110

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
Next Next commit
feat: --enrich flag to enable data enrichment
Signed-off-by: Keith Zantow <kzantow@gmail.com>
kzantow committed Sep 12, 2024
commit 14b1a8337eb1d7d6f6829e10d60e3b589aca0732
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
@@ -12,10 +12,10 @@ linters:
enable:
- asciicheck
- bodyclose
- copyloopvar
- dogsled
- dupl
- errcheck
- exportloopref
- funlen
- gocognit
- goconst
25 changes: 2 additions & 23 deletions cmd/grype/cli/commands/root.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,6 @@ import (
"github.com/anchore/grype/internal/format"
"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/stringutil"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/linux"
syftPkg "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
@@ -159,7 +158,7 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
// the SBOM is returned for downstream formatting concerns
// grype uses the SBOM in combination with syft formatters to produce cycloneDX
// with vulnerability information appended
packages, pkgContext, s, err = pkg.Provide(userInput, getProviderConfig(opts))
packages, pkgContext, s, err = pkg.Provide(userInput, opts.ToProviderConfig())
if err != nil {
return fmt.Errorf("failed to catalog: %w", err)
}
@@ -282,7 +281,7 @@ func getMatchers(opts *options.Grype) []matcher.Matcher {
return matcher.NewDefaultMatchers(
matcher.Config{
Java: java.MatcherConfig{
ExternalSearchConfig: opts.ExternalSources.ToJavaMatcherConfig(),
ExternalSearchConfig: opts.ToJavaExternalSearchConfig(),
UseCPEs: opts.Match.Java.UseCPEs,
},
Ruby: ruby.MatcherConfig(opts.Match.Ruby),
@@ -299,26 +298,6 @@ func getMatchers(opts *options.Grype) []matcher.Matcher {
)
}

func getProviderConfig(opts *options.Grype) pkg.ProviderConfig {
cfg := syft.DefaultCreateSBOMConfig()
cfg.Packages.JavaArchive.IncludeIndexedArchives = opts.Search.IncludeIndexedArchives
cfg.Packages.JavaArchive.IncludeUnindexedArchives = opts.Search.IncludeUnindexedArchives

return pkg.ProviderConfig{
SyftProviderConfig: pkg.SyftProviderConfig{
RegistryOptions: opts.Registry.ToOptions(),
Exclusions: opts.Exclusions,
SBOMOptions: cfg,
Platform: opts.Platform,
Name: opts.Name,
DefaultImagePullSource: opts.DefaultImagePullSource,
},
SynthesisConfig: pkg.SynthesisConfig{
GenerateMissingCPEs: opts.GenerateMissingCPEs,
},
}
}

func validateDBLoad(loadErr error, status *db.Status) error {
if loadErr != nil {
return fmt.Errorf("failed to load vulnerability db: %w", loadErr)
19 changes: 3 additions & 16 deletions cmd/grype/cli/options/datasources.go
Original file line number Diff line number Diff line change
@@ -2,15 +2,14 @@ package options

import (
"github.com/anchore/clio"
"github.com/anchore/grype/grype/matcher/java"
)

const (
defaultMavenBaseURL = "https://search.maven.org/solrsearch/select"
)

type externalSources struct {
Enable bool `yaml:"enable" json:"enable" mapstructure:"enable"`
Enable *bool `yaml:"enable" json:"enable" mapstructure:"enable"`
Maven maven `yaml:"maven" json:"maven" mapstructure:"maven"`
}

@@ -19,31 +18,19 @@ var _ interface {
} = (*externalSources)(nil)

type maven struct {
SearchUpstreamBySha1 bool `yaml:"search-upstream" json:"searchUpstreamBySha1" mapstructure:"search-maven-upstream"`
SearchUpstreamBySha1 *bool `yaml:"search-upstream" json:"searchUpstreamBySha1" mapstructure:"search-maven-upstream"`
BaseURL string `yaml:"base-url" json:"baseUrl" mapstructure:"base-url"`
}

func defaultExternalSources() externalSources {
return externalSources{
Maven: maven{
SearchUpstreamBySha1: true,
SearchUpstreamBySha1: nil,
BaseURL: defaultMavenBaseURL,
},
}
}

func (cfg externalSources) ToJavaMatcherConfig() java.ExternalSearchConfig {
// always respect if global config is disabled
smu := cfg.Maven.SearchUpstreamBySha1
if !cfg.Enable {
smu = cfg.Enable
}
return java.ExternalSearchConfig{
SearchMavenUpstream: smu,
MavenBaseURL: cfg.Maven.BaseURL,
}
}

func (cfg *externalSources) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&cfg.Enable, `enable Grype searching network source for additional information`)
descriptions.Add(&cfg.Maven.SearchUpstreamBySha1, `search for Maven artifacts by SHA1`)
106 changes: 106 additions & 0 deletions cmd/grype/cli/options/grype.go
Original file line number Diff line number Diff line change
@@ -2,11 +2,16 @@ package options

import (
"fmt"
"strings"

"github.com/anchore/clio"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/matcher/java"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/format"
"github.com/anchore/grype/internal/log"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/source"
)

@@ -25,6 +30,7 @@ type Grype struct {
Ignore []match.IgnoreRule `yaml:"ignore" json:"ignore" mapstructure:"ignore"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
DB Database `yaml:"db" json:"db" mapstructure:"db"`
Enrich []string `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
ExternalSources externalSources `yaml:"external-sources" json:"externalSources" mapstructure:"external-sources"`
Match matchConfig `yaml:"match" json:"match" mapstructure:"match"`
FailOn string `yaml:"fail-on-severity" json:"fail-on-severity" mapstructure:"fail-on-severity"`
@@ -136,6 +142,9 @@ func (o *Grype) AddFlags(flags clio.FlagSet) {
"vex", "",
"a list of VEX documents to consider when producing scanning results",
)

flags.StringArrayVarP(&o.Enrich, "enrich", "",
fmt.Sprintf("enable package data enrichment from local and online sources (options: %s)", strings.Join(publicisedEnrichmentOptions, ", ")))
}

func (o *Grype) PostLoad() error {
@@ -183,9 +192,106 @@ VEX fields apply when Grype reads vex data:
`)
descriptions.Add(&o.VexAdd, `VEX statuses to consider as ignored rules`)
descriptions.Add(&o.MatchUpstreamKernelHeaders, `match kernel-header packages with upstream kernel as kernel vulnerabilities`)

descriptions.Add(&o.Enrich, fmt.Sprintf(`Enable data enrichment operations, which can utilize services such as Maven Central and NPM.
Use: all to enable everything. Available options are: %s`, strings.Join(publicisedEnrichmentOptions, ", ")))
}

func (o Grype) FailOnSeverity() *vulnerability.Severity {
severity := vulnerability.ParseSeverity(o.FailOn)
return &severity
}

func (o *Grype) ToProviderConfig() pkg.ProviderConfig {
cfg := syft.DefaultCreateSBOMConfig()
cfg.Packages.JavaArchive.IncludeIndexedArchives = o.Search.IncludeIndexedArchives
cfg.Packages.JavaArchive.IncludeUnindexedArchives = o.Search.IncludeUnindexedArchives
cfg = cfg.WithPackagesConfig(cfg.Packages.
WithJavaArchiveConfig(cfg.Packages.JavaArchive.
WithUseNetwork(*multiLevelOption(false, enrichmentEnabled(o.Enrich, "java", "maven"))),
))

return pkg.ProviderConfig{
SyftProviderConfig: pkg.SyftProviderConfig{
RegistryOptions: o.Registry.ToOptions(),
Exclusions: o.Exclusions,
SBOMOptions: cfg,
Platform: o.Platform,
Name: o.Name,
DefaultImagePullSource: o.DefaultImagePullSource,
},
SynthesisConfig: pkg.SynthesisConfig{
GenerateMissingCPEs: o.GenerateMissingCPEs,
},
}
}

func (o Grype) ToJavaExternalSearchConfig() java.ExternalSearchConfig {
// always respect if global config is disabled
return java.ExternalSearchConfig{
SearchMavenUpstream: *multiLevelOption(false, enrichmentEnabled(o.Enrich, "java", "maven"), o.ExternalSources.Enable, o.ExternalSources.Maven.SearchUpstreamBySha1),
MavenBaseURL: o.ExternalSources.Maven.BaseURL,
}
}

func multiLevelOption[T any](defaultValue T, option ...*T) *T {
result := defaultValue
for _, opt := range option {
if opt != nil {
result = *opt
}
}
return &result
}

var publicisedEnrichmentOptions = []string{
"all",
"java",
}

func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
if len(enrichDirectives) == 0 {
return nil
}

enabled := func(features ...string) *bool {
for _, directive := range enrichDirectives {
enable := true
directive = strings.TrimPrefix(directive, "+") // +java and java are equivalent
if strings.HasPrefix(directive, "-") {
directive = directive[1:]
enable = false
}
for _, feature := range features {
if directive == feature {
return &enable
}
}
}
return nil
}

enableAll := enabled("all")
disableAll := enabled("none")

if disableAll != nil && *disableAll {
if enableAll != nil {
log.Warn("you have specified to both enable and disable all enrichment functionality, defaulting to disabled")
}
enableAll = ptr(false)
}

// check for explicit enable/disable of feature names
for _, feat := range features {
enableFeature := enabled(feat)
if enableFeature != nil {
return enableFeature
}
}

return enableAll
}

func ptr[T any](val T) *T {
return &val
}

Unchanged files with check annotations Beta

cmpopts.IgnoreFields(binary.Classifier{}, "EvidenceMatcher"),
cmpopts.IgnoreUnexported(syft.CreateSBOMConfig{}),
}
if d := cmp.Diff(tt.want, getProviderConfig(tt.opts), opts...); d != "" {

Check failure on line 78 in cmd/grype/cli/commands/root_test.go

GitHub Actions / Unit tests

undefined: getProviderConfig
t.Errorf("getProviderConfig() mismatch (-want +got):\n%s", d)
}
})