Add integration tests (#54)

* add integration tests + add matcher types

* tweak db auto update var; rm dead cache cmd

* Update cmd/root.go

Co-authored-by: Alfredo Deza <adeza@anchore.com>

Co-authored-by: Alfredo Deza <adeza@anchore.com>
This commit is contained in:
Alex Goodman 2020-07-21 12:34:39 -04:00 committed by GitHub
parent 66453e65f2
commit c8bca755ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 736 additions and 297 deletions

View file

@ -9,6 +9,9 @@ jobs:
- image: circleci/golang:<< parameters.version >>
environment:
GO111MODULE: "on"
# work around for recent circle CI breaking change
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
DOCKER_API_VERSION: "1.38"
# 1CPU / 2GB RAM
resource_class: small
steps:
@ -39,6 +42,9 @@ jobs:
- image: circleci/golang:<< parameters.version >>
environment:
GO111MODULE: "on"
# work around for recent circle CI breaking change
# Error: "Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38"
DOCKER_API_VERSION: "1.38"
# 1CPU / 2GB RAM
resource_class: small
steps:
@ -77,25 +83,21 @@ jobs:
name: run unit tests
command: make unit
# TODO: uncomment me when there are integration tests
- run:
name: build hash key for integration test-fixtures blobs
command: make integration-fingerprint
# - run:
# name: build hash key for tar cache
# command: find integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee integration/test-fixtures/tar-cache.key
- restore_cache:
keys:
- integration-test-tar-cache-{{ checksum "integration/test-fixtures/tar-cache.fingerprint" }}
- run:
name: run integration tests
command: make integration
# - restore_cache:
# keys:
# - integration-test-tar-cache-{{ checksum "integration/test-fixtures/tar-cache.key" }}
# - run:
# name: run integration tests
# command: |
# docker version
# make integration
# - save_cache:
# key: integration-test-tar-cache-{{ checksum "integration/test-fixtures/tar-cache.key" }}
# paths:
# - "integration/test-fixtures/tar-cache"
- save_cache:
key: integration-test-tar-cache-{{ checksum "integration/test-fixtures/tar-cache.fingerprint" }}
paths:
- "integration/test-fixtures/tar-cache"
workflows:
"Static Analysis & All Tests":

View file

@ -31,8 +31,7 @@ all: lint test ## Run all checks (linting, unit tests, and integration tests)
compare:
@cd comparison && make
# TODO: add me back in when integration tests are implemented
test: unit #integration ## Run all tests (currently only unit)
test: unit integration ## Run all tests (unit & integration tests)
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'
@ -75,10 +74,12 @@ unit: ## Run unit tests (with coverage)
@echo "Coverage: $$(cat $(COVER_TOTAL))"
@if [ $$(echo "$$(cat $(COVER_TOTAL)) >= $(COVERAGE_THRESHOLD)" | bc -l) -ne 1 ]; then echo "$(RED)$(BOLD)Failed coverage quality gate (> $(COVERAGE_THRESHOLD)%)$(RESET)" && false; fi
# TODO: add me back in when integration tests are implemented
#integration: ## Run integration tests
# $(call title,Running integration tests)
# go test -tags=integration ./integration
integration: ## Run integration tests
$(call title,Running integration tests)
go test -v -tags=integration ./integration
integration/test-fixtures/tar-cache.key, integration-fingerprint:
find integration/test-fixtures/image-* -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | tee integration/test-fixtures/tar-cache.fingerprint
clear-test-cache: ## Delete all test cache (built docker image tars)
find . -type f -wholename "**/test-fixtures/tar-cache/*.tar" -delete

View file

@ -1,14 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
)
var cacheCmd = &cobra.Command{
Use: "cache",
Short: "operate on the result cache",
}
func init() {
rootCmd.AddCommand(cacheCmd)
}

View file

@ -1,24 +0,0 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var cacheClearCmd = &cobra.Command{
Use: "clear",
Short: "delete the results cache",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runCacheClearCmd(cmd, args))
},
}
func init() {
cacheCmd.AddCommand(cacheClearCmd)
}
func runCacheClearCmd(cmd *cobra.Command, args []string) int {
log.Error("cache CLEAR command...")
return 0
}

View file

@ -1,24 +0,0 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var cacheShowCmd = &cobra.Command{
Use: "show",
Short: "show images that have been scanned",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runCacheShowCmd(cmd, args))
},
}
func init() {
cacheCmd.AddCommand(cacheShowCmd)
}
func runCacheShowCmd(cmd *cobra.Command, args []string) int {
log.Error("cache SHOW command...")
return 0
}

View file

@ -20,6 +20,17 @@ var log *zap.SugaredLogger
var cliOnlyOpts config.CliOnlyOptions
func init() {
setGlobalCliOptions()
// read in config and setup logger
cobra.OnInitialize(
initAppConfig,
initLogging,
logAppConfig,
)
}
func setGlobalCliOptions() {
// setup global CLI options (available on all CLI commands)
rootCmd.PersistentFlags().StringVarP(&cliOnlyOpts.ConfigPath, "config", "c", "", "application config file")
@ -34,13 +45,6 @@ func init() {
}
rootCmd.PersistentFlags().CountVarP(&cliOnlyOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
// read in config and setup logger
cobra.OnInitialize(
initAppConfig,
initLogging,
logAppConfig,
)
}
func Execute() {

View file

@ -5,16 +5,12 @@ import (
"os"
"runtime/pprof"
"github.com/anchore/imgbom/imgbom"
_distro "github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/internal/format"
"github.com/anchore/vulnscan/internal/version"
"github.com/anchore/vulnscan/vulnscan"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/anchore/vulnscan/vulnscan/presenter"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -43,13 +39,16 @@ var rootCmd = &cobra.Command{
}
}
exitCode := runDefaultCmd(cmd, args)
err := runDefaultCmd(cmd, args)
if appConfig.Dev.ProfileCPU {
pprof.StopCPUProfile()
}
os.Exit(exitCode)
if err != nil {
log.Errorf(err.Error())
os.Exit(1)
}
},
}
@ -60,7 +59,8 @@ func init() {
flag := "scope"
rootCmd.Flags().StringP(
"scope", "s", scope.AllLayersScope.String(),
fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options))
fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options),
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
@ -69,7 +69,7 @@ func init() {
// output & formatting options
flag = "output"
rootCmd.Flags().StringP(
flag, "o", "json",
flag, "o", presenter.JSONPresenter.String(),
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
@ -78,8 +78,7 @@ func init() {
}
}
// nolint:funlen
func runDefaultCmd(_ *cobra.Command, args []string) int {
func runDefaultCmd(_ *cobra.Command, args []string) error {
if appConfig.CheckForAppUpdate {
isAvailable, newVersion, err := version.IsUpdateAvailable()
if err != nil {
@ -93,66 +92,20 @@ func runDefaultCmd(_ *cobra.Command, args []string) int {
}
userImageStr := args[0]
scope, cleanup, err := imgbom.NewScope(userImageStr, appConfig.ScopeOpt)
provider, err := vulnscan.LoadVulnerabilityDb(appConfig.Db.ToCuratorConfig(), appConfig.Db.AutoUpdate)
if err != nil {
log.Errorf("could not produce catalog: %w", err)
return 1
return fmt.Errorf("failed to load vulnerability db: %w", err)
}
defer cleanup()
log.Info("creating catalog")
catalog, err := imgbom.Catalog(scope)
results, catalog, _, err := vulnscan.FindVulnerabilities(provider, userImageStr, appConfig.ScopeOpt)
if err != nil {
log.Errorf("could not produce catalog: %w", err)
return fmt.Errorf("failed to find vulnerabilities: %w", err)
}
osObj := _distro.Identify(scope)
dbCurator, err := db.NewCurator(appConfig.Db.ToCuratorConfig())
if err != nil {
log.Errorf("could not curate database: %+v", err)
return 1
if err = presenter.GetPresenter(appConfig.PresenterOpt).Present(os.Stdout, catalog, results); err != nil {
return fmt.Errorf("could not format catalog results: %w", err)
}
if appConfig.Db.UpdateOnStartup {
updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
log.Errorf("unable to check for vulnerability database update: %+v", err)
return 1
}
if updateAvailable {
err = dbCurator.UpdateTo(updateEntry)
if err != nil {
log.Errorf("unable to update vulnerability database: %+v", err)
return 1
}
}
}
store, err := dbCurator.GetStore()
if err != nil {
log.Errorf("failed to load vulnerability database: %+v", err)
return 1
}
provider := vulnerability.NewProviderFromStore(store)
results := vulnscan.FindAllVulnerabilities(provider, *osObj, catalog)
outputOption := viper.GetString("output")
presenterType := presenter.ParseOption(outputOption)
if presenterType == presenter.UnknownPresenter {
log.Errorf("cannot find an output presenter for option: %s", outputOption)
return 1
}
err = presenter.GetPresenter(presenterType).Present(os.Stdout, catalog, results)
if err != nil {
log.Errorf("could not format catalog results: %+v", err)
return 1
}
return 0
return nil
}

2
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/adrg/xdg v0.2.1
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/imgbom v0.0.0-20200717175414-b5a353349f55
github.com/anchore/imgbom v0.0.0-20200717191633-9e285fd0e27a
github.com/anchore/siren-db v0.0.0-20200716152335-9bc4580f72a1
github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639
github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6

6
go.sum
View file

@ -113,12 +113,10 @@ github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db h1:LWKezJnFTF
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
github.com/anchore/imgbom v0.0.0-20200713170720-e8d11eec6992 h1:ERVRoY8sKpccEbuV53NyG/frJzIZ4n4NyOhbSGGOMSs=
github.com/anchore/imgbom v0.0.0-20200713170720-e8d11eec6992/go.mod h1:b7euhNKBz5ReqVtal47okqWXg4YPT2/aitoWyQsDFns=
github.com/anchore/imgbom v0.0.0-20200717123122-f03f7b32e93d h1:P8S+0tRUst2BZoTHrrsZ9QmPXtCdx9tyW7fksrZqYik=
github.com/anchore/imgbom v0.0.0-20200717123122-f03f7b32e93d/go.mod h1:b7euhNKBz5ReqVtal47okqWXg4YPT2/aitoWyQsDFns=
github.com/anchore/imgbom v0.0.0-20200717175414-b5a353349f55 h1:5PeLVtjW8yGRGC3MkrLwV1LqOdhawGKiWpG1Sk/PJhA=
github.com/anchore/imgbom v0.0.0-20200717175414-b5a353349f55/go.mod h1:b7euhNKBz5ReqVtal47okqWXg4YPT2/aitoWyQsDFns=
github.com/anchore/imgbom v0.0.0-20200717191633-9e285fd0e27a h1:JW8PTvx051Vc1LoNNxZgOYckRWox4RHNUOOw18uZYLs=
github.com/anchore/imgbom v0.0.0-20200717191633-9e285fd0e27a/go.mod h1:b7euhNKBz5ReqVtal47okqWXg4YPT2/aitoWyQsDFns=
github.com/anchore/siren-db v0.0.0-20200716152335-9bc4580f72a1 h1:0EorIdCoVGD/Nv6zNfXduCubAixzZ/0VH6PGrK8xKug=
github.com/anchore/siren-db v0.0.0-20200716152335-9bc4580f72a1/go.mod h1:kw/8/5C2Shyk5TzyaLZvwABulWJNtJbFo6FaQzeQEs0=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e h1:QBwtrM0MXi0z+GcHk3RoSyzaQ+CLgas0bC/uOd1P+PQ=

View file

@ -0,0 +1,72 @@
package integration
import (
"github.com/anchore/siren-db/pkg/db"
)
// integrity check
var _ db.VulnerabilityStoreReader = &mockStore{}
type mockStore struct {
backend map[string]map[string][]db.Vulnerability
}
func NewMockDbStore() *mockStore {
return &mockStore{
backend: map[string]map[string][]db.Vulnerability{
"github:python": {
"Pygments": []db.Vulnerability{
{
ID: "CVE-python-pygments",
VersionConstraint: "< 2.6.2",
VersionFormat: "semver",
},
},
},
"github:gem": {
"rails": []db.Vulnerability{
{
ID: "CVE-ruby-activerecord",
VersionConstraint: "> 4.0.0, <= 4.1.1",
VersionFormat: "semver",
},
},
},
"github:java": {
"org.anchore:example-java-app-maven": []db.Vulnerability{
{
ID: "CVE-java-example-java-app",
VersionConstraint: ">= 0.0.1, < 1.2.0",
VersionFormat: "unknown",
},
},
},
"debian:8": {
"apt-dev": []db.Vulnerability{
{
ID: "CVE-dpkg-apt",
VersionConstraint: "<= 1.8.2",
VersionFormat: "dpkg",
},
},
},
"rhel:8": {
"dive": []db.Vulnerability{
{
ID: "CVE-rpmdb-dive",
VersionConstraint: "<= 1.0.42",
VersionFormat: "rpm",
},
},
},
},
}
}
func (s *mockStore) GetVulnerability(namespace, name string) ([]db.Vulnerability, error) {
namespaceMap := s.backend[namespace]
if namespaceMap == nil {
return nil, nil
}
return namespaceMap[name], nil
}

View file

@ -0,0 +1,240 @@
package integration
import (
"github.com/anchore/go-testutils"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/result"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
"testing"
)
func getPackagesByPath(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, thePath string) []*pkg.Package {
t.Helper()
refs, err := theScope.FilesByGlob(thePath)
if err != nil {
t.Fatalf("could not get ref by path %q: %+v", thePath, err)
}
if len(refs) != 1 {
t.Fatalf("unexpected paths for %q: %+v", thePath, refs)
}
return catalog.PackagesByFile(refs[0])
}
func addPythonMatches(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore, theResult *result.Result) {
packages := getPackagesByPath(t, theScope, catalog, "/python/dist-info/METADATA")
if len(packages) != 1 {
t.Logf("Python Packages: %+v", packages)
t.Fatalf("problem with upstream imgbom cataloger (python)")
}
thePkg := packages[0]
theVuln := theStore.backend["github:python"][thePkg.Name][0]
vulnObj, err := vulnerability.NewVulnerability(theVuln)
if err != nil {
t.Fatalf("failed to create vuln obj: %+v", err)
}
theResult.Add(thePkg, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0,
Vulnerability: *vulnObj,
Package: thePkg,
SearchKey: "language[python] constraint[< 2.6.2 (semver)]",
IndirectPackage: nil,
Matcher: match.PythonMatcher,
})
}
func addRubyMatches(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore, theResult *result.Result) {
packages := getPackagesByPath(t, theScope, catalog, "/ruby/Gemfile.lock")
if len(packages) != 1 {
t.Logf("Ruby Packages: %+v", packages)
t.Fatalf("problem with upstream imgbom cataloger (ruby)")
}
thePkg := packages[0]
theVuln := theStore.backend["github:gem"][thePkg.Name][0]
vulnObj, err := vulnerability.NewVulnerability(theVuln)
if err != nil {
t.Fatalf("failed to create vuln obj: %+v", err)
}
theResult.Add(thePkg, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0,
Vulnerability: *vulnObj,
Package: thePkg,
SearchKey: "language[ruby] constraint[> 4.0.0, <= 4.1.1 (semver)]",
IndirectPackage: nil,
Matcher: match.RubyBundleMatcher,
})
}
func addJavaMatches(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore, theResult *result.Result) {
packages := make([]*pkg.Package, 0)
for p := range catalog.Enumerate(pkg.JavaPkg) {
packages = append(packages, p)
}
if len(packages) != 1 {
t.Logf("Java Packages: %+v", packages)
t.Fatalf("problem with upstream imgbom cataloger (java)")
}
thePkg := packages[0]
groupId := thePkg.Metadata.(pkg.JavaMetadata).PomProperties.GroupID
lookup := groupId + ":" + thePkg.Name
theVuln := theStore.backend["github:java"][lookup][0]
vulnObj, err := vulnerability.NewVulnerability(theVuln)
if err != nil {
t.Fatalf("failed to create vuln obj: %+v", err)
}
theResult.Add(thePkg, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0,
Vulnerability: *vulnObj,
Package: thePkg,
SearchKey: "language[java] constraint[>= 0.0.1, < 1.2.0 (unknown)]",
IndirectPackage: nil,
Matcher: match.JavaMatcher,
})
}
func addDpkgMatches(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore, theResult *result.Result) {
packages := getPackagesByPath(t, theScope, catalog, "/var/lib/dpkg/status")
if len(packages) != 1 {
t.Logf("Dpkg Packages: %+v", packages)
t.Fatalf("problem with upstream imgbom cataloger (dpkg)")
}
thePkg := packages[0]
// NOTE: this is an indirect match, in typical debian style
theVuln := theStore.backend["debian:8"][thePkg.Name+"-dev"][0]
vulnObj, err := vulnerability.NewVulnerability(theVuln)
if err != nil {
t.Fatalf("failed to create vuln obj: %+v", err)
}
theResult.Add(thePkg, match.Match{
Type: match.ExactIndirectMatch,
Confidence: 1.0,
Vulnerability: *vulnObj,
Package: thePkg,
SearchKey: "distro[debian 8] constraint[<= 1.8.2 (deb)]",
IndirectPackage: nil,
Matcher: match.DpkgMatcher,
})
}
func addRhelMatches(t *testing.T, theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore, theResult *result.Result) {
packages := getPackagesByPath(t, theScope, catalog, "/var/lib/rpm/Packages")
if len(packages) != 1 {
t.Logf("RPMDB Packages: %+v", packages)
t.Fatalf("problem with upstream imgbom cataloger (RPMDB)")
}
thePkg := packages[0]
theVuln := theStore.backend["rhel:8"][thePkg.Name][0]
vulnObj, err := vulnerability.NewVulnerability(theVuln)
if err != nil {
t.Fatalf("failed to create vuln obj: %+v", err)
}
theResult.Add(thePkg, match.Match{
Type: match.ExactDirectMatch,
Confidence: 1.0,
Vulnerability: *vulnObj,
Package: thePkg,
SearchKey: "distro[centos 8] constraint[<= 1.0.42 (rpm)]",
IndirectPackage: nil,
Matcher: match.RpmDBMatcher,
})
}
func TestPkgCoverageImage(t *testing.T) {
observedMatchers := internal.NewStringSet()
definedMatchers := internal.NewStringSet()
for _, l := range match.AllMatcherTypes {
definedMatchers.Add(l.String())
}
tests := []struct {
fixtureImage string
expectedFn func(scope.Scope, *pkg.Catalog, *mockStore) result.Result
}{
{
fixtureImage: "image-debian-match-coverage",
expectedFn: func(theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore) result.Result {
expectedResults := result.NewResult()
addPythonMatches(t, theScope, catalog, theStore, &expectedResults)
addRubyMatches(t, theScope, catalog, theStore, &expectedResults)
addJavaMatches(t, theScope, catalog, theStore, &expectedResults)
addDpkgMatches(t, theScope, catalog, theStore, &expectedResults)
return expectedResults
},
},
{
fixtureImage: "image-centos-match-coverage",
expectedFn: func(theScope scope.Scope, catalog *pkg.Catalog, theStore *mockStore) result.Result {
expectedResults := result.NewResult()
addRhelMatches(t, theScope, catalog, theStore, &expectedResults)
return expectedResults
},
},
}
for _, test := range tests {
t.Run(test.fixtureImage, func(t *testing.T) {
theStore := NewMockDbStore()
_, cleanup := testutils.GetFixtureImage(t, "docker-archive", test.fixtureImage)
tarPath := testutils.GetFixtureImageTarPath(t, test.fixtureImage)
defer cleanup()
actualResults, catalog, theScope, err := vulnscan.FindVulnerabilities(
vulnerability.NewProviderFromStore(theStore),
"docker-archive://"+tarPath,
scope.AllLayersScope,
)
if err != nil {
t.Fatalf("failed to find vulnerabilities: %+v", err)
}
// build expected matches from what's discovered from the catalog
expectedResults := test.expectedFn(*theScope, catalog, theStore)
// build expected match set...
expectedMatchSet := internal.NewStringSet()
expectedCount := 0
for eMatch := range expectedResults.Enumerate() {
expectedCount++
// NOTE: this does not include all fields...
expectedMatchSet.Add(eMatch.String())
}
// ensure that all matches are covered
actualCount := 0
for aMatch := range actualResults.Enumerate() {
actualCount++
observedMatchers.Add(aMatch.Matcher.String())
if !expectedMatchSet.Contains(aMatch.String()) {
// NOTE: this is not the same as comparing an expected sequence which would allow for detailed diffing
t.Errorf("Disjoint Match: %+v", aMatch)
}
}
if expectedCount != actualCount {
t.Errorf("expected %d matches but got %d matches", expectedCount, actualCount)
}
})
}
// ensure that integration test cases stay in sync with the implemented matchers
observedMatchers.Remove(match.UnknownMatcherType.String())
definedMatchers.Remove(match.UnknownMatcherType.String())
if len(observedMatchers) != len(definedMatchers) {
t.Errorf("matcher coverage incomplete (matchers=%d, coverage=%d)", len(definedMatchers), len(observedMatchers))
}
}

1
integration/test-fixtures/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
!**/image-*/Dockerfile

View file

@ -0,0 +1,2 @@
FROM scratch
COPY . .

View file

@ -0,0 +1,11 @@
NAME="CentOS Linux"
VERSION="8 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Linux 8 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

View file

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -eux
docker create --name generate-rpmdb-fixture centos:latest sh -c 'tail -f /dev/null'
function cleanup {
docker kill generate-rpmdb-fixture
docker rm generate-rpmdb-fixture
}
trap cleanup EXIT
docker start generate-rpmdb-fixture
docker exec -i --tty=false generate-rpmdb-fixture bash <<-EOF
mkdir -p /scratch
cd /scratch
rpm --initdb --dbpath /scratch
curl -sSLO https://github.com/wagoodman/dive/releases/download/v0.9.2/dive_0.9.2_linux_amd64.rpm
rpm --dbpath /scratch -ivh dive_0.9.2_linux_amd64.rpm
rm dive_0.9.2_linux_amd64.rpm
rpm --dbpath /scratch -qa
EOF
docker cp generate-rpmdb-fixture:/scratch/Packages .

View file

@ -0,0 +1,2 @@
FROM scratch
COPY . .

View file

@ -0,0 +1 @@
See the imgbom/cataloger/java/test-fixtures/java-builds dir to generate test fixtures and copy to here manually.

View file

@ -0,0 +1,47 @@
Metadata-Version: 2.1
Name: Pygments
Version: 2.6.1
Summary: Pygments is a syntax highlighting package written in Python.
Home-page: https://pygments.org/
Author: Georg Brandl
Author-email: georg@python.org
License: BSD License
Keywords: syntax highlighting
Platform: any
Classifier: License :: OSI Approved :: BSD License
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: System Administrators
Classifier: Development Status :: 6 - Mature
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Operating System :: OS Independent
Classifier: Topic :: Text Processing :: Filters
Classifier: Topic :: Utilities
Requires-Python: >=3.5
Pygments
~~~~~~~~
Pygments is a syntax highlighting package written in Python.
It is a generic syntax highlighter suitable for use in code hosting, forums,
wikis or other applications that need to prettify source code. Highlights
are:
* a wide range of over 500 languages and other text formats is supported
* special attention is paid to details, increasing quality by a fair amount
* support for new languages and formats are added easily
* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image formats that PIL supports and ANSI sequences
* it is usable as a command-line tool and as a library
:copyright: Copyright 2006-2019 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.

View file

@ -0,0 +1,11 @@
GEM
remote: https://rubygems.org/
specs:
rails (4.1.1)
activerecord (= 4.1.1)
PLATFORMS
ruby
DEPENDENCIES
rails (= 4.1.1)

View file

@ -0,0 +1,8 @@
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"
NAME="Debian GNU/Linux"
VERSION_ID="8"
VERSION="8 (jessie)"
ID=debian
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

View file

@ -0,0 +1,35 @@
Package: apt
Status: install ok installed
Priority: required
Section: admin
Installed-Size: 4064
Maintainer: APT Development Team <deity@lists.debian.org>
Architecture: amd64
Version: 1.8.2
Source: apt-dev
Replaces: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~)
Provides: apt-transport-https (= 1.8.2)
Depends: adduser, gpgv | gpgv2 | gpgv1, debian-archive-keyring, libapt-pkg5.0 (>= 1.7.0~alpha3~), libc6 (>= 2.15), libgcc1 (>= 1:3.0), libgnutls30 (>= 3.6.6), libseccomp2 (>= 1.0.1), libstdc++6 (>= 5.2)
Recommends: ca-certificates
Suggests: apt-doc, aptitude | synaptic | wajig, dpkg-dev (>= 1.17.2), gnupg | gnupg2 | gnupg1, powermgmt-base
Breaks: apt-transport-https (<< 1.5~alpha4~), apt-utils (<< 1.3~exp2~), aptitude (<< 0.8.10)
Conffiles:
/etc/apt/apt.conf.d/01autoremove 76120d358bc9037bb6358e737b3050b5
/etc/cron.daily/apt-compat 49e9b2cfa17849700d4db735d04244f3
/etc/kernel/postinst.d/apt-auto-removal 4ad976a68f045517cf4696cec7b8aa3a
/etc/logrotate.d/apt 179f2ed4f85cbaca12fa3d69c2a4a1c3
Description: commandline package manager
This package provides commandline tools for searching and
managing as well as querying information about packages
as a low-level access to all features of the libapt-pkg library.
.
These include:
* apt-get for retrieval of packages and information about them
from authenticated sources and for installation, upgrade and
removal of packages together with their dependencies
* apt-cache for querying available information about installed
as well as installable packages
* apt-cdrom to use removable media as a source for packages
* apt-config as an interface to the configuration settings
* apt-key as an interface to manage authentication keys

View file

@ -5,6 +5,8 @@ import (
"path"
"strings"
"github.com/anchore/vulnscan/vulnscan/presenter"
"github.com/adrg/xdg"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/vulnscan/internal"
@ -21,6 +23,8 @@ type CliOnlyOptions struct {
type Application struct {
ConfigPath string
PresenterOpt presenter.Option
Output string `mapstructure:"output"`
ScopeOpt scope.Option
Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"`
@ -39,9 +43,9 @@ type Logging struct {
}
type Database struct {
Dir string `mapstructure:"cache-dir"`
UpdateURL string `mapstructure:"update-url"`
UpdateOnStartup bool `mapstructure:"update-on-startup"`
Dir string `mapstructure:"cache-dir"`
UpdateURL string `mapstructure:"update-url"`
AutoUpdate bool `mapstructure:"auto-update"`
}
type Development struct {
@ -64,7 +68,7 @@ func setNonCliDefaultValues(v *viper.Viper) {
// TODO: change me to the production URL before release
v.SetDefault("db.update-url", "http://localhost:5000/listing.json")
// TODO: set this to true before release
v.SetDefault("db.update-on-startup", false)
v.SetDefault("db.auto-update", false)
v.SetDefault("dev.profile-cpu", false)
v.SetDefault("check-for-app-update", true)
}
@ -96,6 +100,13 @@ func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application,
}
func (cfg *Application) Build() error {
// set the presenter
presenterOption := presenter.ParseOption(cfg.Output)
if presenterOption == presenter.UnknownPresenter {
return fmt.Errorf("bad --output value '%s'", cfg.Output)
}
cfg.PresenterOpt = presenterOption
// set the scope
scopeOption := scope.ParseOption(cfg.Scope)
if scopeOption == scope.UnknownScope {

View file

@ -1,5 +0,0 @@
package vulnscan
// note: must be a single word, all lowercase
const LibraryName = "vulnscan"
const DbSchemaConstraint = ">= 1.0.0, < 2.0.0"

View file

@ -12,7 +12,6 @@ import (
"github.com/anchore/siren-db/pkg/store"
"github.com/anchore/vulnscan/internal/file"
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan"
"github.com/spf13/afero"
)
@ -33,9 +32,9 @@ type Curator struct {
}
func NewCurator(cfg Config) (Curator, error) {
constraint, err := version.NewConstraint(vulnscan.DbSchemaConstraint)
constraint, err := version.NewConstraint(DbSchemaConstraint)
if err != nil {
return Curator{}, fmt.Errorf("unable to set DB curator version constraint (%s): %w", vulnscan.DbSchemaConstraint, err)
return Curator{}, fmt.Errorf("unable to set DB curator version constraint (%s): %w", DbSchemaConstraint, err)
}
return Curator{
@ -74,7 +73,7 @@ func (c *Curator) Status() Status {
return Status{
Age: metadata.Built,
SchemaVersion: metadata.Version.String(),
SchemaConstraint: vulnscan.DbSchemaConstraint,
SchemaConstraint: DbSchemaConstraint,
Location: c.config.DbDir,
Err: err,
}

View file

@ -0,0 +1,3 @@
package db
const DbSchemaConstraint = ">= 1.0.0, < 2.0.0"

View file

@ -1,25 +0,0 @@
package vulnscan
import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/matcher"
"github.com/anchore/vulnscan/vulnscan/result"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindAllVulnerabilities(store vulnerability.Provider, o distro.Distro, catalog *pkg.Catalog) result.Result {
res := result.NewResult()
for p := range catalog.Enumerate() {
res.Merge(FindVulnerabilities(store, o, p))
}
return res
}
func FindVulnerabilities(store vulnerability.Provider, o distro.Distro, packages ...*pkg.Package) result.Result {
res := result.NewResult()
for _, p := range packages {
res.Merge(matcher.FindMatches(store, o, p))
}
return res
}

79
vulnscan/lib.go Normal file
View file

@ -0,0 +1,79 @@
package vulnscan
import (
"fmt"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/anchore/vulnscan/vulnscan/logger"
"github.com/anchore/imgbom/imgbom"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan/matcher"
"github.com/anchore/vulnscan/vulnscan/result"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
// note: lib name must be a single word, all lowercase
const LibraryName = "vulnscan"
func FindVulnerabilities(provider vulnerability.Provider, userImageStr string, scopeOpt scope.Option) (result.Result, *pkg.Catalog, *scope.Scope, error) {
log.Info("Cataloging image")
catalog, theScope, theDistro, err := imgbom.Catalog(userImageStr, scopeOpt)
if err != nil {
return result.Result{}, nil, nil, err
}
return FindVulnerabilitiesForCatalog(provider, *theDistro, catalog), catalog, theScope, nil
}
func FindVulnerabilitiesForCatalog(provider vulnerability.Provider, d distro.Distro, catalog *pkg.Catalog) result.Result {
res := result.NewResult()
for p := range catalog.Enumerate() {
res.Merge(FindVulnerabilitiesForPackage(provider, d, p))
}
return res
}
func FindVulnerabilitiesForPackage(provider vulnerability.Provider, d distro.Distro, packages ...*pkg.Package) result.Result {
res := result.NewResult()
for _, p := range packages {
res.Merge(matcher.FindMatches(provider, d, p))
}
return res
}
func LoadVulnerabilityDb(cfg db.Config, update bool) (vulnerability.Provider, error) {
dbCurator, err := db.NewCurator(cfg)
if err != nil {
return nil, fmt.Errorf("could not curate database: %w", err)
}
if update {
updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
return nil, fmt.Errorf("unable to check for vulnerability database update: %w", err)
}
if updateAvailable {
err = dbCurator.UpdateTo(updateEntry)
if err != nil {
return nil, fmt.Errorf("unable to update vulnerability database: %w", err)
}
}
}
store, err := dbCurator.GetStore()
if err != nil {
return nil, err
}
return vulnerability.NewProviderFromStore(store), nil
}
func SetLogger(logger logger.Logger) {
log.Log = logger
}

View file

@ -1,10 +0,0 @@
package vulnscan
import (
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan/logger"
)
func SetLogger(logger logger.Logger) {
log.Log = logger
}

View file

@ -16,13 +16,13 @@ type Match struct {
// TODO: is this a good name for what it represents? (which is an audit trail of HOW we got this match from the store)
SearchKey string
IndirectPackage *pkg.Package
Matcher string
Matcher MatcherType
}
func (m Match) String() string {
return fmt.Sprintf("Match(pkg=%s vuln=%s confidence=%f type='%s' key='%s' foundBy='%s')", m.Package, m.Vulnerability.String(), m.Confidence, m.Type, m.SearchKey, m.Matcher)
return fmt.Sprintf("Match(pkg=%s vuln=%s type='%s' key='%s' foundBy='%s')", m.Package, m.Vulnerability.String(), m.Type, m.SearchKey, m.Matcher)
}
func (m Match) Summary() string {
return fmt.Sprintf("vuln='%s' confidence=%0.2f type='%s' key='%s' foundBy='%s')", m.Vulnerability.ID, m.Confidence, m.Type, m.SearchKey, m.Matcher)
return fmt.Sprintf("vuln='%s' type='%s' key='%s' foundBy='%s')", m.Vulnerability.ID, m.Type, m.SearchKey, m.Matcher)
}

View file

@ -0,0 +1,37 @@
package match
const (
UnknownMatcherType MatcherType = iota
RubyBundleMatcher
DpkgMatcher
RpmDBMatcher
JavaMatcher
PythonMatcher
)
var matcherTypeStr = []string{
"UnknownMatcherType",
"ruby-bundle-matcher",
"dpkg-matcher",
"rpmdb-matcher",
"java-matcher",
"python-matcher",
}
var AllMatcherTypes = []MatcherType{
RubyBundleMatcher,
DpkgMatcher,
RpmDBMatcher,
JavaMatcher,
PythonMatcher,
}
type MatcherType int
func (f MatcherType) String() string {
if int(f) >= len(matcherTypeStr) || f < 0 {
return matcherTypeStr[0]
}
return matcherTypeStr[f]
}

View file

@ -9,8 +9,6 @@ const (
FuzzyMatch
)
type Type int
var typeStr = []string{
"UnknownMatchType",
"Exact-Direct Match",
@ -18,6 +16,8 @@ var typeStr = []string{
"Fuzzy Match",
}
type Type int
func ParseType(userStr string) Type {
switch strings.ToLower(userStr) {
case strings.ToLower(ExactDirectMatch.String()):

View file

@ -11,23 +11,23 @@ import (
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
func (m *Matcher) PackageTypes() []pkg.Type {
return []pkg.Type{pkg.BundlerPkg}
}
func (m *Matcher) Name() string {
return "bundler-matcher"
func (m *Matcher) Type() match.MatcherType {
return match.RubyBundleMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, _ distro.Distro, p *pkg.Package) ([]match.Match, error) {
var matches = make([]match.Match, 0)
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Type())
if err != nil {
return nil, err
}
matches = append(matches, langMatches...)
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Name())
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Type())
if err != nil {
return nil, err
}

View file

@ -9,7 +9,7 @@ import (
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageCPE(store vulnerability.ProviderByCPE, p *pkg.Package, matcherName string) ([]match.Match, error) {
func FindMatchesByPackageCPE(store vulnerability.ProviderByCPE, p *pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
@ -42,7 +42,7 @@ func FindMatchesByPackageCPE(store vulnerability.ProviderByCPE, p *pkg.Package,
Confidence: 0.9, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
Matcher: upstreamMatcher,
SearchKey: fmt.Sprintf("cpe[%s] constraint[%s]", cpe.BindToFmtString(), vuln.Constraint.String()),
})
}

View file

@ -139,7 +139,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
store := newMockProviderByCPE()
actual, err := FindMatchesByPackageCPE(store, &test.p, "SOME_OTHER_MATCHER")
actual, err := FindMatchesByPackageCPE(store, &test.p, match.PythonMatcher)
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
@ -164,7 +164,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
if a.Matcher != match.PythonMatcher {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}

View file

@ -12,7 +12,7 @@ import (
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d distro.Distro, p *pkg.Package, matcherName string) ([]match.Match, error) {
func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d distro.Distro, p *pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
@ -37,7 +37,7 @@ func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d distro.D
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
Matcher: upstreamMatcher,
SearchKey: fmt.Sprintf("distro[%s] constraint[%s]", d, vuln.Constraint.String()),
})
}

View file

@ -73,7 +73,7 @@ func TestFindMatchesByPackageDistro(t *testing.T) {
}
store := newMockProviderByDistro()
actual, err := FindMatchesByPackageDistro(store, d, &p, "SOME_OTHER_MATCHER")
actual, err := FindMatchesByPackageDistro(store, d, &p, match.PythonMatcher)
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
@ -95,7 +95,7 @@ func TestFindMatchesByPackageDistro(t *testing.T) {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
if a.Matcher != match.PythonMatcher {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}

View file

@ -11,7 +11,7 @@ import (
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l pkg.Language, p *pkg.Package, matcherName string) ([]match.Match, error) {
func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l pkg.Language, p *pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
verObj, err := version.NewVersionFromPkg(p)
if err != nil {
return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err)
@ -36,7 +36,7 @@ func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l pkg.
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
Matcher: matcherName,
Matcher: upstreamMatcher,
SearchKey: fmt.Sprintf("language[%s] constraint[%s]", l, vuln.Constraint.String()),
})
}

View file

@ -55,7 +55,7 @@ func TestFindMatchesByPackageLanguage(t *testing.T) {
}
store := newMockProviderByLanguage()
actual, err := FindMatchesByPackageLanguage(store, p.Language, &p, "SOME_OTHER_MATCHER")
actual, err := FindMatchesByPackageLanguage(store, p.Language, &p, match.PythonMatcher)
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
@ -77,7 +77,7 @@ func TestFindMatchesByPackageLanguage(t *testing.T) {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
if a.Matcher != match.PythonMatcher {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}

View file

@ -38,7 +38,7 @@ func newController() controller {
func (c *controller) add(matchers ...Matcher) {
for _, m := range matchers {
for _, t := range m.Types() {
for _, t := range m.PackageTypes() {
if _, ok := c.matchers[t]; ok {
c.matchers[t] = make([]Matcher, 0)
}
@ -49,7 +49,7 @@ func (c *controller) add(matchers ...Matcher) {
}
}
func (c *controller) findMatches(s vulnerability.Provider, o distro.Distro, packages ...*pkg.Package) result.Result {
func (c *controller) findMatches(provider vulnerability.Provider, d distro.Distro, packages ...*pkg.Package) result.Result {
res := result.NewResult()
for _, p := range packages {
log.Debugf("searching for vulnerability matches for pkg=%s", p)
@ -59,7 +59,7 @@ func (c *controller) findMatches(s vulnerability.Provider, o distro.Distro, pack
log.Errorf("no matchers available for package pkg=%s", p)
}
for _, m := range matchers {
matches, err := m.Match(s, o, p)
matches, err := m.Match(provider, d, p)
if err != nil {
log.Errorf("matcher failed for pkg=%s: %+v", p, err)
} else {
@ -71,8 +71,8 @@ func (c *controller) findMatches(s vulnerability.Provider, o distro.Distro, pack
return res
}
func FindMatches(s vulnerability.Provider, o distro.Distro, packages ...*pkg.Package) result.Result {
return controllerInstance.findMatches(s, o, packages...)
func FindMatches(provider vulnerability.Provider, d distro.Distro, packages ...*pkg.Package) result.Result {
return controllerInstance.findMatches(provider, d, packages...)
}
func logMatches(p *pkg.Package, matches []match.Match) {

View file

@ -14,12 +14,12 @@ import (
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
func (m *Matcher) PackageTypes() []pkg.Type {
return []pkg.Type{pkg.DebPkg}
}
func (m *Matcher) Name() string {
return "dpkg-matcher"
func (m *Matcher) Type() match.MatcherType {
return match.DpkgMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
@ -31,7 +31,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Pa
}
matches = append(matches, sourceMatches...)
exactMatches, err := common.FindMatchesByPackageDistro(store, d, p, m.Name())
exactMatches, err := common.FindMatchesByPackageDistro(store, d, p, m.Type())
if err != nil {
return nil, fmt.Errorf("failed to match by exact package name: %w", err)
}
@ -65,7 +65,7 @@ func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro,
// use the source package name
indirectPackage.Name = sourcePkgName
matches, err := common.FindMatchesByPackageDistro(store, d, &indirectPackage, m.Name())
matches, err := common.FindMatchesByPackageDistro(store, d, &indirectPackage, m.Type())
if err != nil {
return nil, fmt.Errorf("failed to find vulnerabilities by dkpg source indirection: %w", err)
}
@ -76,7 +76,7 @@ func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro,
matches[idx].Type = match.ExactIndirectMatch
matches[idx].Package = p
matches[idx].IndirectPackage = &indirectPackage
matches[idx].Matcher = m.Name()
matches[idx].Matcher = m.Type()
}
return matches, nil

View file

@ -45,8 +45,8 @@ func TestMatcherDpkg_matchBySourceIndirection(t *testing.T) {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != matcher.Name() {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
if a.Matcher != matcher.Type() {
t.Errorf("failed to capture matcher type: %s", a.Matcher)
}
if a.IndirectPackage == nil {

View file

@ -11,23 +11,23 @@ import (
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
func (m *Matcher) PackageTypes() []pkg.Type {
return []pkg.Type{pkg.JavaPkg, pkg.JenkinsPluginPkg}
}
func (m *Matcher) Name() string {
return "java-matcher"
func (m *Matcher) Type() match.MatcherType {
return match.JavaMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, _ distro.Distro, p *pkg.Package) ([]match.Match, error) {
var matches = make([]match.Match, 0)
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Type())
if err != nil {
return nil, err
}
matches = append(matches, langMatches...)
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Name())
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Type())
if err != nil {
return nil, err
}

View file

@ -8,6 +8,7 @@ import (
)
type Matcher interface {
Types() []pkg.Type
PackageTypes() []pkg.Type
Type() match.MatcherType
Match(vulnerability.Provider, distro.Distro, *pkg.Package) ([]match.Match, error)
}

View file

@ -11,23 +11,23 @@ import (
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
func (m *Matcher) PackageTypes() []pkg.Type {
return []pkg.Type{pkg.EggPkg, pkg.WheelPkg}
}
func (m *Matcher) Name() string {
return "python-matcher"
func (m *Matcher) Type() match.MatcherType {
return match.PythonMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, _ distro.Distro, p *pkg.Package) ([]match.Match, error) {
var matches = make([]match.Match, 0)
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
langMatches, err := common.FindMatchesByPackageLanguage(store, p.Language, p, m.Type())
if err != nil {
return nil, err
}
matches = append(matches, langMatches...)
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Name())
cpeMatches, err := common.FindMatchesByPackageCPE(store, p, m.Type())
if err != nil {
return nil, err
}

View file

@ -11,14 +11,14 @@ import (
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
func (m *Matcher) PackageTypes() []pkg.Type {
return []pkg.Type{pkg.RpmPkg}
}
func (m *Matcher) Name() string {
return "rpmdb-matcher"
func (m *Matcher) Type() match.MatcherType {
return match.RpmDBMatcher
}
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
return common.FindMatchesByPackageDistro(store, d, p, m.Name())
return common.FindMatchesByPackageDistro(store, d, p, m.Type())
}

View file

@ -47,7 +47,7 @@ func (pres *Presenter) Present(output io.Writer, catalog *pkg.Catalog, results r
ResultObj{
Cve: match.Vulnerability.ID,
FoundBy: FoundBy{
Matcher: match.Matcher,
Matcher: match.Matcher.String(),
SearchKey: match.SearchKey,
},
Package: Package{Name: p.Name, Version: p.Version, Type: p.Type.String()},

View file

@ -36,12 +36,14 @@ func TestJsonPresenter(t *testing.T) {
Type: match.ExactDirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0001"},
Package: &pkg1,
Matcher: match.DpkgMatcher,
}
var match2 = match.Match{
Type: match.ExactIndirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0002"},
Package: &pkg1,
Matcher: match.DpkgMatcher,
}
results := result.NewResult()

View file

@ -2,7 +2,7 @@
{
"cve": "CVE-1999-0001",
"found-by": {
"matcher": "",
"matcher": "dpkg-matcher",
"search-key": ""
},
"package": {
@ -14,7 +14,7 @@
{
"cve": "CVE-1999-0002",
"found-by": {
"matcher": "",
"matcher": "dpkg-matcher",
"search-key": ""
},
"package": {

View file

@ -46,8 +46,8 @@ func (r *Result) Enumerate() <-chan match.Match {
go func() {
defer close(channel)
for _, matches := range r.byPackage {
for _, match := range matches {
channel <- match
for _, m := range matches {
channel <- m
}
}
}()

View file

@ -7,14 +7,11 @@ import (
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/siren-db/pkg/db"
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/facebookincubator/nvdtools/wfn"
)
type StoreProvider struct {
store db.VulnerabilityStoreReader
// TODO: allows the ability to have a db cache for keeping rich objects around
// or to have a sync.Pool of warm objects
}
func NewProviderFromStore(store db.VulnerabilityStoreReader) *StoreProvider {
@ -33,17 +30,12 @@ func (pr *StoreProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*Vulner
}
for _, vuln := range allPkgVulns {
format := version.ParseFormat(vuln.VersionFormat)
constraint, err := version.GetConstraint(vuln.VersionConstraint, format)
vulnObj, err := NewVulnerability(vuln)
if err != nil {
return nil, fmt.Errorf("provider failed to parse distro='%s' constraint='%s' format='%s': %w", d, vuln.VersionConstraint, format, err)
return nil, fmt.Errorf("provider failed to parse distro='%s': %w", d, err)
}
vulns = append(vulns, &Vulnerability{
Constraint: constraint,
ID: vuln.ID,
})
vulns = append(vulns, vulnObj)
}
return vulns, nil
@ -65,17 +57,12 @@ func (pr *StoreProvider) GetByLanguage(l pkg.Language, p *pkg.Package) ([]*Vulne
}
for _, vuln := range allPkgVulns {
format := version.ParseFormat(vuln.VersionFormat)
constraint, err := version.GetConstraint(vuln.VersionConstraint, format)
vulnObj, err := NewVulnerability(vuln)
if err != nil {
return nil, fmt.Errorf("provider failed to parse language='%s' constraint='%s' format='%s': %w", l, vuln.VersionConstraint, format, err)
return nil, fmt.Errorf("provider failed to parse language='%s': %w", l, err)
}
vulns = append(vulns, &Vulnerability{
Constraint: constraint,
ID: vuln.ID,
})
vulns = append(vulns, vulnObj)
}
}
}
@ -96,21 +83,12 @@ func (pr *StoreProvider) GetByCPE(requestCPE cpe.CPE) ([]*Vulnerability, error)
}
for _, namespace := range namespaces {
// TODO: should we encode vendor + name? or leave it as just package name (product)?
allPkgVulns, err := pr.store.GetVulnerability(namespace, requestCPE.Product)
if err != nil {
return nil, fmt.Errorf("provider failed to fetch namespace='%s' product='%s': %w", namespace, requestCPE.Product, err)
}
for _, vuln := range allPkgVulns {
format := version.ParseFormat(vuln.VersionFormat)
constraint, err := version.GetConstraint(vuln.VersionConstraint, format)
if err != nil {
return nil, fmt.Errorf("provider failed to parse cpe='%s' constraint='%s' format='%s': %w", requestCPE.BindToFmtString(), vuln.VersionConstraint, format, err)
}
vulnCPEs, err := cpe.NewSlice(vuln.CPEs...)
if err != nil {
return nil, err
@ -120,11 +98,14 @@ func (pr *StoreProvider) GetByCPE(requestCPE cpe.CPE) ([]*Vulnerability, error)
candidateMatchCpes := cpe.MatchWithoutVersion(requestCPE, vulnCPEs)
if len(candidateMatchCpes) > 0 {
vulns = append(vulns, &Vulnerability{
Constraint: constraint,
CPEs: candidateMatchCpes,
ID: vuln.ID,
})
vulnObj, err := NewVulnerability(vuln)
if err != nil {
return nil, fmt.Errorf("provider failed to parse cpe='%s': %w", requestCPE.BindToFmtString(), err)
}
vulnObj.CPEs = candidateMatchCpes
vulns = append(vulns, vulnObj)
}
}
}

View file

@ -3,6 +3,8 @@ package vulnerability
import (
"fmt"
"github.com/anchore/siren-db/pkg/db"
"github.com/anchore/vulnscan/vulnscan/cpe"
"github.com/anchore/vulnscan/vulnscan/version"
@ -14,6 +16,21 @@ type Vulnerability struct {
ID string
}
func NewVulnerability(vuln db.Vulnerability) (*Vulnerability, error) {
format := version.ParseFormat(vuln.VersionFormat)
constraint, err := version.GetConstraint(vuln.VersionConstraint, format)
if err != nil {
return nil, fmt.Errorf("failed to parse constraint='%s' format='%s': %w", vuln.VersionConstraint, format, err)
}
return &Vulnerability{
Constraint: constraint,
ID: vuln.ID,
CPEs: make([]cpe.CPE, 0),
}, nil
}
func (v Vulnerability) String() string {
return fmt.Sprintf("Vuln(id=%s constraint='%s')", v.ID, v.Constraint)
}