Skip to content

Commit

Permalink
Merge branch 'main' into upgrade-cyclonedx
Browse files Browse the repository at this point in the history
* main:
  chore: add GitLab Community Edition image to quality gate (#1035)
  Update Syft to v0.63.0 (#1037)
  fix: Exclude binary packages that have overlap by file ownership relationship (#1024)
  docs: update quality gate docs (#1032)
  Optionally orient results by CVE (#1020)

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
  • Loading branch information
spiffcs committed Dec 13, 2022
2 parents cb3116e + ea05be9 commit dbee164
Show file tree
Hide file tree
Showing 64 changed files with 2,453 additions and 596 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ CHANGELOG.md
*.tmp
coverage.txt

# OS files
.DS_Store

# Binaries for programs and plugins
*.exe
*.exe~
Expand Down
152 changes: 80 additions & 72 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/anchore/grype/grype/matcher"
"github.com/anchore/grype/grype/matcher/dotnet"
"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/python"
"github.com/anchore/grype/grype/matcher/ruby"
Expand Down Expand Up @@ -169,6 +171,11 @@ func setRootFlags(flags *pflag.FlagSet) {
"ignore matches for vulnerabilities that are fixed",
)

flags.BoolP(
"by-cve", "", false,
"orient results by CVE instead of the original vulnerability ID when possible",
)

flags.BoolP(
"show-suppressed", "", false,
"show suppressed/ignored vulnerabilities in the output (only supported with table output format)",
Expand Down Expand Up @@ -231,6 +238,10 @@ func bindRootConfigOptions(flags *pflag.FlagSet) error {
return err
}

if err := viper.BindPFlag("by-cve", flags.Lookup("by-cve")); err != nil {
return err
}

if err := viper.BindPFlag("show-suppressed", flags.Lookup("show-suppressed")); err != nil {
return err
}
Expand Down Expand Up @@ -300,29 +311,14 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
return
}

if appConfig.CheckForAppUpdate {
isAvailable, newVersion, err := version.IsUpdateAvailable()
if err != nil {
log.Errorf(err.Error())
}
if isAvailable {
log.Infof("new version of %s is available: %s (currently running: %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)

bus.Publish(partybus.Event{
Type: event.AppUpdateAvailable,
Value: newVersion,
})
} else {
log.Debugf("No new %s update available", internal.ApplicationName)
}
}
checkForAppUpdate()

var store *store.Store
var str *store.Store
var status *db.Status
var dbCloser *db.Closer
var packages []pkg.Package
var sbom *sbom.SBOM
var context pkg.Context
var pkgContext pkg.Context
var wg = &sync.WaitGroup{}
var loadedDB, gatheredPackages bool

Expand All @@ -331,7 +327,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
go func() {
defer wg.Done()
log.Debug("loading DB")
store, status, dbCloser, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
str, status, dbCloser, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
if err = validateDBLoad(err, status); err != nil {
errs <- err
return
Expand All @@ -344,7 +340,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
log.Debugf("gathering packages")
// packages here are grype.Pacakge, not syft.Package
// the SBOM is returned for formatting concerns (e.g. CYCLONEDX)
packages, context, sbom, err = pkg.Provide(userInput, getProviderConfig())
packages, pkgContext, sbom, err = pkg.Provide(userInput, getProviderConfig())
if err != nil {
errs <- fmt.Errorf("failed to catalog: %w", err)
return
Expand All @@ -369,38 +365,30 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
appConfig.Ignore = append(appConfig.Ignore, ignoreFixedMatches...)
}

applyDistroHint(packages, &context, appConfig)

matchers := matcher.NewDefaultMatchers(matcher.Config{
Java: appConfig.ExternalSources.ToJavaMatcherConfig(appConfig.Match.Java),
Ruby: ruby.MatcherConfig(appConfig.Match.Ruby),
Python: python.MatcherConfig(appConfig.Match.Python),
Dotnet: dotnet.MatcherConfig(appConfig.Match.Dotnet),
Javascript: javascript.MatcherConfig(appConfig.Match.Javascript),
Golang: golang.MatcherConfig(appConfig.Match.Golang),
Stock: stock.MatcherConfig(appConfig.Match.Stock),
})

allMatches := grype.FindVulnerabilitiesForPackage(*store, context.Distro, matchers, packages)
remainingMatches, ignoredMatches := match.ApplyIgnoreRules(allMatches, appConfig.Ignore)
applyDistroHint(packages, &pkgContext, appConfig)

if count := len(ignoredMatches); count > 0 {
log.Infof("ignoring %d matches due to user-provided ignore rules", count)
vulnMatcher := grype.VulnerabilityMatcher{
Store: *str,
IgnoreRules: appConfig.Ignore,
NormalizeByCVE: appConfig.ByCVE,
FailSeverity: failOnSeverity,
Matchers: getMatchers(),
}

// determine if there are any severities >= to the max allowable severity (which is optional).
// note: until the shared file lock in sqlittle is fixed the sqlite DB cannot be access concurrently,
// implying that the fail-on-severity check must be done before sending the presenter object.
if hitSeverityThreshold(failOnSeverity, remainingMatches, store) {
errs <- grypeerr.ErrAboveSeverityThreshold
remainingMatches, ignoredMatches, err := vulnMatcher.FindMatches(packages, pkgContext)
if err != nil {
errs <- err
if !errors.Is(err, grypeerr.ErrAboveSeverityThreshold) {
return
}
}

pb := models.PresenterBundle{
Matches: remainingMatches,
IgnoredMatches: ignoredMatches,
Packages: packages,
Context: context,
MetadataProvider: store,
Context: pkgContext,
MetadataProvider: str,
SBOM: sbom,
AppConfig: appConfig,
DBStatus: status,
Expand Down Expand Up @@ -450,15 +438,57 @@ func applyDistroHint(pkgs []pkg.Package, context *pkg.Context, appConfig *config
}
}

func checkForAppUpdate() {
if !appConfig.CheckForAppUpdate {
return
}

isAvailable, newVersion, err := version.IsUpdateAvailable()
if err != nil {
log.Errorf(err.Error())
}
if isAvailable {
log.Infof("new version of %s is available: %s (currently running: %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)

bus.Publish(partybus.Event{
Type: event.AppUpdateAvailable,
Value: newVersion,
})
} else {
log.Debugf("no new %s update available", internal.ApplicationName)
}
}

func getMatchers() []matcher.Matcher {
return matcher.NewDefaultMatchers(
matcher.Config{
Java: java.MatcherConfig{
ExternalSearchConfig: appConfig.ExternalSources.ToJavaMatcherConfig(),
UseCPEs: appConfig.Match.Java.UseCPEs,
},
Ruby: ruby.MatcherConfig(appConfig.Match.Ruby),
Python: python.MatcherConfig(appConfig.Match.Python),
Dotnet: dotnet.MatcherConfig(appConfig.Match.Dotnet),
Javascript: javascript.MatcherConfig(appConfig.Match.Javascript),
Golang: golang.MatcherConfig(appConfig.Match.Golang),
Stock: stock.MatcherConfig(appConfig.Match.Stock),
},
)
}

func getProviderConfig() pkg.ProviderConfig {
return pkg.ProviderConfig{
RegistryOptions: appConfig.Registry.ToOptions(),
Exclusions: appConfig.Exclusions,
CatalogingOptions: appConfig.Search.ToConfig(),
GenerateMissingCPEs: appConfig.GenerateMissingCPEs,
Platform: appConfig.Platform,
AttestationPublicKey: appConfig.Attestation.PublicKey,
AttestationIgnoreVerification: appConfig.Attestation.SkipVerification,
SyftProviderConfig: pkg.SyftProviderConfig{
RegistryOptions: appConfig.Registry.ToOptions(),
Exclusions: appConfig.Exclusions,
CatalogingOptions: appConfig.Search.ToConfig(),
Platform: appConfig.Platform,
AttestationPublicKey: appConfig.Attestation.PublicKey,
AttestationIgnoreVerification: appConfig.Attestation.SkipVerification,
},
SynthesisConfig: pkg.SynthesisConfig{
GenerateMissingCPEs: appConfig.GenerateMissingCPEs,
},
}
}

Expand Down Expand Up @@ -492,25 +522,3 @@ func validateRootArgs(cmd *cobra.Command, args []string) error {

return cobra.MaximumNArgs(1)(cmd, args)
}

// hitSeverityThreshold indicates if there are any severities >= to the max allowable severity (which is optional)
func hitSeverityThreshold(thresholdSeverity *vulnerability.Severity, matches match.Matches, metadataProvider vulnerability.MetadataProvider) bool {
if thresholdSeverity != nil {
var maxDiscoveredSeverity vulnerability.Severity
for m := range matches.Enumerate() {
metadata, err := metadataProvider.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace)
if err != nil {
continue
}
severity := vulnerability.ParseSeverity(metadata.Severity)
if severity > maxDiscoveredSeverity {
maxDiscoveredSeverity = severity
}
}

if maxDiscoveredSeverity >= *thresholdSeverity {
return true
}
}
return false
}
110 changes: 0 additions & 110 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,122 +3,12 @@ package cmd
import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"

"github.com/anchore/grype/grype/db"
grypeDB "github.com/anchore/grype/grype/db/v5"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/config"
syftPkg "github.com/anchore/syft/syft/pkg"
)

type mockMetadataStore struct {
data map[string]map[string]*grypeDB.VulnerabilityMetadata
}

func newMockStore() *mockMetadataStore {
d := mockMetadataStore{
data: make(map[string]map[string]*grypeDB.VulnerabilityMetadata),
}
d.stub()
return &d
}

func (d *mockMetadataStore) stub() {
d.data["CVE-2014-fake-1"] = map[string]*grypeDB.VulnerabilityMetadata{
"source-1": {
Severity: "medium",
},
}
}

func (d *mockMetadataStore) GetVulnerabilityMetadata(id, recordSource string) (*grypeDB.VulnerabilityMetadata, error) {
return d.data[id][recordSource], nil
}

func (d *mockMetadataStore) GetAllVulnerabilityMetadata() (*[]grypeDB.VulnerabilityMetadata, error) {
return nil, nil
}

func TestAboveAllowableSeverity(t *testing.T) {
thePkg := pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "the-package",
Version: "v0.1",
Type: syftPkg.RpmPkg,
}

matches := match.NewMatches()
matches.Add(match.Match{
Vulnerability: vulnerability.Vulnerability{
ID: "CVE-2014-fake-1",
Namespace: "source-1",
},
Package: thePkg,
Details: match.Details{
{
Type: match.ExactDirectMatch,
},
},
})

tests := []struct {
name string
failOnSeverity string
matches match.Matches
expectedResult bool
}{
{
name: "no-severity-set",
failOnSeverity: "",
matches: matches,
expectedResult: false,
},
{
name: "below-threshold",
failOnSeverity: "high",
matches: matches,
expectedResult: false,
},
{
name: "at-threshold",
failOnSeverity: "medium",
matches: matches,
expectedResult: true,
},
{
name: "above-threshold",
failOnSeverity: "low",
matches: matches,
expectedResult: true,
},
}

metadataProvider := db.NewVulnerabilityMetadataProvider(newMockStore())

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var failOnSeverity *vulnerability.Severity
if test.failOnSeverity != "" {
sev := vulnerability.ParseSeverity(test.failOnSeverity)
if sev == vulnerability.UnknownSeverity {
t.Fatalf("could not parse severity")
}
failOnSeverity = &sev
}

actual := hitSeverityThreshold(failOnSeverity, test.matches, metadataProvider)

if test.expectedResult != actual {
t.Errorf("expected: %v got : %v", test.expectedResult, actual)
}
})
}
}

func Test_applyDistroHint(t *testing.T) {
ctx := pkg.Context{}
cfg := config.Application{}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ require (
require (
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963
github.com/anchore/syft v0.62.3
github.com/anchore/syft v0.63.0
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
github.com/in-toto/in-toto-golang v0.4.1-0.20221018183522-731d0640b65f
github.com/mitchellh/mapstructure v1.5.0
Expand Down Expand Up @@ -269,7 +269,7 @@ require (
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2324,8 +2324,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
Loading

0 comments on commit dbee164

Please sign in to comment.