Merge pull request #15 from anchore/add-gem-matcher

Add gem/bundler matcher
This commit is contained in:
Alex Goodman 2020-06-04 15:44:23 -04:00 committed by GitHub
commit c0d20adc72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 663 additions and 229 deletions

10
go.mod
View file

@ -4,21 +4,19 @@ go 1.14
require (
github.com/adrg/xdg v0.2.1
github.com/anchore/imgbom v0.0.0-20200603004815-b6122a413ba8
github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5
github.com/anchore/vulnscan-db v0.0.0-20200528193934-4a3f5d48b4c8
github.com/anchore/imgbom v0.0.0-20200604184352-e88669c536ce
github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf
github.com/hashicorp/go-version v1.2.0
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-version v1.1.1
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0
go.uber.org/zap v1.15.0
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
google.golang.org/genproto v0.0.0-20200602104108-2bb8d6132df6 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
gopkg.in/yaml.v2 v2.3.0
)
replace github.com/anchore/vulnscan-db => ../vulnscan-db

24
go.sum
View file

@ -108,17 +108,28 @@ github.com/anchore/imgbom v0.0.0-20200601214218-f0b8aaacdae2 h1:hGKC1CpgK1x8HIUJ
github.com/anchore/imgbom v0.0.0-20200601214218-f0b8aaacdae2/go.mod h1:Ttau0/FsMvXMTlPQvSpyQPi0VZQDosjwuHUv1zUwl7k=
github.com/anchore/imgbom v0.0.0-20200603004815-b6122a413ba8 h1:k8e5yQI3mnkoxeEI+s7ujBEtjLuicxyOn7IfKsQsotY=
github.com/anchore/imgbom v0.0.0-20200603004815-b6122a413ba8/go.mod h1:Ttau0/FsMvXMTlPQvSpyQPi0VZQDosjwuHUv1zUwl7k=
github.com/anchore/imgbom v0.0.0-20200604184352-e88669c536ce h1:t/2K7VPuKX7DrYnPeclLNrH0gsRbW8ZBig7o50pqq50=
github.com/anchore/imgbom v0.0.0-20200604184352-e88669c536ce/go.mod h1:oVcJ4sEuqz/7XTPaJYIZRc4NYVl3zPP96g7RtWG31SE=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e h1:QBwtrM0MXi0z+GcHk3RoSyzaQ+CLgas0bC/uOd1P+PQ=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200523232006-be5f3c18958f h1:aPJQyXi8Y7PhnzhUszZfS/23TA5o29UCc3XGreflaqo=
github.com/anchore/stereoscope v0.0.0-20200523232006-be5f3c18958f/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5 h1:eViCIr4O1e4M93nbbMZdrRW0JjqDjPYdtMXEOC3jQQQ=
github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5/go.mod h1:OeCrFeSu8+p02qC7u9/u8wBOh50VQa8eHJjXVuANvLo=
github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6 h1:Fu779yw004jyFH1UkQD8lTf0GmGRfrOQIK5QiqmIwU8=
github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ=
github.com/anchore/vulnscan-db v0.0.0-20200528193934-4a3f5d48b4c8 h1:pSGrFVpRZe2P9Ov+6GQSz0vh1yrUEmkMTEpjn+HT2Sw=
github.com/anchore/vulnscan-db v0.0.0-20200528193934-4a3f5d48b4c8/go.mod h1:VX8yNH+ERoyXQ0Qsd+Ct8FxRvMSyOPcno/zwq/pjg0s=
github.com/anchore/vulnscan-db v0.0.0-20200603183000-b64fbcd7044b h1:cFBPQpPv/Nk8G1bVsMU8xxwqXLXCe3GdyGn9AEsVuNE=
github.com/anchore/vulnscan-db v0.0.0-20200603183000-b64fbcd7044b/go.mod h1:OVROq5+BT+g+ES+heRewy7NU2f147o2QyMortckSXek=
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf h1:U1KgI8Lk6acUjjmtBLWOCoL9U7AV8tFmHnqAtFikJ7E=
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf/go.mod h1:OVROq5+BT+g+ES+heRewy7NU2f147o2QyMortckSXek=
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/aquasecurity/go-dep-parser v0.0.0-20200123140603-4dc0125084da/go.mod h1:X42mTIRhgPalSm81Om2kD+3ydeunbC8TZtZj1bvgRo8=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@ -342,6 +353,8 @@ github.com/google/go-containerregistry v0.0.0-20200521151920-a873a21aff23 h1:42q
github.com/google/go-containerregistry v0.0.0-20200521151920-a873a21aff23/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
github.com/google/go-containerregistry v0.0.0-20200601195303-96cf69f03a3c h1:skQHUoy/S0WT+HFlN4MuDQCHsc6vTgKTMaKA3QKGABI=
github.com/google/go-containerregistry v0.0.0-20200601195303-96cf69f03a3c/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g=
github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
@ -461,6 +474,10 @@ github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM52
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c=
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
github.com/knqyf263/go-version v1.1.1 h1:+MpcBC9b7rk5ihag8Y/FLG8get1H2GjniwKQ+9DxI2o=
github.com/knqyf263/go-version v1.1.1/go.mod h1:0tBvHvOBSf5TqGNcY+/ih9o8qo3R16iZCpB9rP0D3VM=
github.com/knqyf263/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/knqyf263/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
@ -474,6 +491,7 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -506,6 +524,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
@ -1060,9 +1079,14 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200602104108-2bb8d6132df6 h1:fsxmG3uIxSjgTNy6zSkdHSyElfRV0Tq+yzS+Ukjthx0=
google.golang.org/genproto v0.0.0-20200602104108-2bb8d6132df6/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736 h1:+IE3xTD+6Eb7QWG5JFp+dQr/XjKpjmrNkh4pdjTdHEs=
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -1,8 +1,17 @@
package db
import "github.com/anchore/vulnscan-db/pkg/db"
import (
"github.com/anchore/vulnscan-db/pkg/db"
"github.com/anchore/vulnscan-db/pkg/sqlite"
)
func GetStore() db.VulnStore {
// TODO: add connection options and info
return db.NewSqliteStore(nil)
// TODO: we are ignoreing cleanup/close function (not good)
store, _, err := sqlite.NewStore(nil)
if err != nil {
// TODO: replace me
panic(err)
}
return store
}

View file

@ -11,6 +11,7 @@ const (
type Type int
var typeStr = []string{
"UnknownMatchType",
"Exact-Direct Match",
"Exact-Indirect Match",
}

View file

@ -0,0 +1,24 @@
package bundler
import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/matcher/common"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
type Matcher struct {
}
func (m *Matcher) Types() []pkg.Type {
return []pkg.Type{pkg.BundlerPkg}
}
func (m *Matcher) Name() string {
return "bundler-matcher"
}
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
return common.FindMatchesByPackageLanguage(store, p.Language, p, m.Name())
}

View file

@ -0,0 +1,24 @@
package common
import (
"fmt"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageDistro(store vulnerability.ProviderByDistro, d distro.Distro, p *pkg.Package, matcherName string) ([]match.Match, error) {
allPkgVulns, err := store.GetByDistro(d, p)
if err != nil {
return nil, fmt.Errorf("distro matcher failed to fetch distro='%s' pkg='%s': %w", d, p.Name, err)
}
matches, err := FindMatchesForPackage(allPkgVulns, p, matcherName)
for idx := range matches {
// explicitly set the search key to indicate a distro match
matches[idx].SearchKey = fmt.Sprintf("distro=[%s] pkg=[%s:%s]", d, p.Name, p.Version)
}
return matches, err
}

View file

@ -0,0 +1,113 @@
package common
import (
"strings"
"testing"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
type mockDistroProvider struct {
data map[string]map[string][]*vulnerability.Vulnerability
}
func newMockProviderByDistro() *mockDistroProvider {
pr := mockDistroProvider{
data: make(map[string]map[string][]*vulnerability.Vulnerability),
}
pr.stub()
return &pr
}
func (pr *mockDistroProvider) stub() {
pr.data["debian:8"] = map[string][]*vulnerability.Vulnerability{
// direct...
"neutron": {
{
Constraint: version.MustGetConstraint("< 2014.1.5-6", version.DpkgFormat),
ID: "CVE-2014-fake-1",
},
},
// indirect...
"neutron-devel": {
// expected...
{
Constraint: version.MustGetConstraint("< 2014.1.4-5", version.DpkgFormat),
ID: "CVE-2014-fake-2",
},
{
Constraint: version.MustGetConstraint("< 2015.0.0-1", version.DpkgFormat),
ID: "CVE-2013-fake-3",
},
// unexpected...
{
Constraint: version.MustGetConstraint("< 2014.0.4-1", version.DpkgFormat),
ID: "CVE-2013-fake-BAD",
},
},
}
}
func (pr *mockDistroProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*vulnerability.Vulnerability, error) {
return pr.data[strings.ToLower(d.Type.String())+":"+d.FullVersion()][p.Name], nil
}
func TestFindMatchesByPackageDistro(t *testing.T) {
p := pkg.Package{
Name: "neutron",
Version: "2014.1.3-6",
Type: pkg.DebPkg,
Metadata: pkg.DpkgMetadata{
Source: "neutron-devel",
},
}
d, err := distro.NewDistro(distro.Debian, "8")
if err != nil {
t.Fatal("could not create distro: ", err)
}
store := newMockProviderByDistro()
actual, err := FindMatchesByPackageDistro(store, d, &p, "SOME_OTHER_MATCHER")
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
if len(actual) != 1 {
t.Fatalf("unexpected direct matches count: %d", len(actual))
}
foundCVEs := internal.NewStringSet()
for _, a := range actual {
foundCVEs.Add(a.Vulnerability.ID)
if a.Type != match.ExactDirectMatch {
t.Error("direct match not indicated")
}
if a.Package.Name != p.Name {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}
if a.IndirectPackage != nil {
t.Fatalf("should not have captured indirect package")
}
}
for _, id := range []string{"CVE-2014-fake-1"} {
if !foundCVEs.Contains(id) {
t.Errorf("missing discovered CVE: %s", id)
}
}
}

View file

@ -1,23 +1,16 @@
package distro
package common
import (
"fmt"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func ExactPackageNameMatch(store vulnerability.Provider, o distro.Distro, p *pkg.Package, matcherName string) ([]match.Match, error) {
func FindMatchesForPackage(allPkgVulns []*vulnerability.Vulnerability, p *pkg.Package, matcherName string) ([]match.Match, error) {
matches := make([]match.Match, 0)
allPkgVulns, err := store.GetByDistro(o, p)
if err != nil {
return nil, fmt.Errorf("matcher failed to fetch distro='%s' pkg='%s': %w", o, p.Name, err)
}
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)
@ -28,7 +21,7 @@ func ExactPackageNameMatch(store vulnerability.Provider, o distro.Distro, p *pkg
isPackageVulnerable, err := vuln.Constraint.Satisfied(verObj)
if err != nil {
// TODO: not enough information (cannot back track constraint object)
return nil, fmt.Errorf("matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
return nil, fmt.Errorf("language matcher failed to check constraint='%s' version='%s': %w", vuln.Constraint, verObj, err)
}
if isPackageVulnerable {
@ -37,9 +30,7 @@ func ExactPackageNameMatch(store vulnerability.Provider, o distro.Distro, p *pkg
Confidence: 1.0, // TODO: this is hard coded for now
Vulnerability: *vuln,
Package: p,
// signifies that we have a match from a search by exact package name and version
SearchKey: fmt.Sprintf("%s:%s", p.Name, p.Version),
Matcher: matcherName,
Matcher: matcherName,
})
}
}

View file

@ -0,0 +1,23 @@
package common
import (
"fmt"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
func FindMatchesByPackageLanguage(store vulnerability.ProviderByLanguage, l pkg.Language, p *pkg.Package, matcherName string) ([]match.Match, error) {
allPkgVulns, err := store.GetByLanguage(l, p)
if err != nil {
return nil, fmt.Errorf("language matcher failed to fetch language='%s' pkg='%s': %w", l, p.Name, err)
}
matches, err := FindMatchesForPackage(allPkgVulns, p, matcherName)
for idx := range matches {
// explicitly set the search key to indicate a language match
matches[idx].SearchKey = fmt.Sprintf("language=[%s] pkg=[%s:%s]", l, p.Name, p.Version)
}
return matches, err
}

View file

@ -0,0 +1,95 @@
package common
import (
"fmt"
"testing"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
type mockLanguageProvider struct {
data map[string]map[string][]*vulnerability.Vulnerability
}
func newMockProviderByLanguage() *mockLanguageProvider {
pr := mockLanguageProvider{
data: make(map[string]map[string][]*vulnerability.Vulnerability),
}
pr.stub()
return &pr
}
func (pr *mockLanguageProvider) stub() {
pr.data["github:gem"] = map[string][]*vulnerability.Vulnerability{
// direct...
"activerecord": {
{
Constraint: version.MustGetConstraint("< 3.7.6", version.SemanticFormat),
ID: "CVE-2017-fake-1",
},
{
Constraint: version.MustGetConstraint("< 3.7.4", version.SemanticFormat),
ID: "CVE-2017-fake-2",
},
},
}
}
func (pr *mockLanguageProvider) GetByLanguage(l pkg.Language, p *pkg.Package) ([]*vulnerability.Vulnerability, error) {
if l != pkg.Ruby {
panic(fmt.Errorf("test mock only supports ruby"))
}
return pr.data["github:gem"][p.Name], nil
}
func TestFindMatchesByPackageLanguage(t *testing.T) {
p := pkg.Package{
Name: "activerecord",
Version: "3.7.5",
Language: pkg.Ruby,
Type: pkg.BundlerPkg,
}
store := newMockProviderByLanguage()
actual, err := FindMatchesByPackageLanguage(store, p.Language, &p, "SOME_OTHER_MATCHER")
if err != nil {
t.Fatalf("error while finding matches: %+v", err)
}
if len(actual) != 1 {
t.Fatalf("unexpected direct matches count: %d", len(actual))
}
foundCVEs := internal.NewStringSet()
for _, a := range actual {
foundCVEs.Add(a.Vulnerability.ID)
if a.Type != match.ExactDirectMatch {
t.Error("direct match not indicated")
}
if a.Package.Name != p.Name {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}
if a.IndirectPackage != nil {
t.Fatalf("should not have captured indirect package")
}
}
for _, id := range []string{"CVE-2017-fake-1"} {
if !foundCVEs.Contains(id) {
t.Errorf("missing discovered CVE: %s", id)
}
}
}

View file

@ -4,6 +4,7 @@ import (
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal/log"
"github.com/anchore/vulnscan/vulnscan/matcher/bundler"
"github.com/anchore/vulnscan/vulnscan/matcher/dpkg"
"github.com/anchore/vulnscan/vulnscan/result"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
@ -12,16 +13,22 @@ import (
var controllerInstance controller
func init() {
controllerInstance = controller{
matchers: make(map[pkg.Type][]Matcher),
}
controllerInstance.add(&dpkg.Matcher{})
controllerInstance = newController()
}
type controller struct {
matchers map[pkg.Type][]Matcher
}
func newController() controller {
ctrlr := controller{
matchers: make(map[pkg.Type][]Matcher),
}
ctrlr.add(&dpkg.Matcher{})
ctrlr.add(&bundler.Matcher{})
return ctrlr
}
func (c *controller) add(matchers ...Matcher) {
for _, m := range matchers {
for _, t := range m.Types() {

View file

@ -1,55 +0,0 @@
package distro
import (
"strings"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/version"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
)
type mockProvider struct {
data map[string]map[string][]*vulnerability.Vulnerability
}
func newMockProvider() *mockProvider {
pr := mockProvider{
data: make(map[string]map[string][]*vulnerability.Vulnerability),
}
pr.stub()
return &pr
}
func (pr *mockProvider) stub() {
pr.data["debian:8"] = map[string][]*vulnerability.Vulnerability{
// direct...
"neutron": {
{
Constraint: version.MustGetConstraint("< 2014.1.5-6", version.DpkgFormat),
ID: "CVE-2014-fake-1",
},
},
// indirect...
"neutron-devel": {
// expected...
{
Constraint: version.MustGetConstraint("< 2014.1.4-5", version.DpkgFormat),
ID: "CVE-2014-fake-2",
},
{
Constraint: version.MustGetConstraint("< 2015.0.0-1", version.DpkgFormat),
ID: "CVE-2013-fake-3",
},
// unexpected...
{
Constraint: version.MustGetConstraint("< 2014.0.4-1", version.DpkgFormat),
ID: "CVE-2013-fake-BAD",
},
},
}
}
func (pr *mockProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*vulnerability.Vulnerability, error) {
return pr.data[strings.ToLower(d.Type.String())+":"+d.FullVersion()][p.Name], nil
}

View file

@ -1,63 +0,0 @@
package distro
import (
"testing"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan/match"
)
func TestMatcher_ExactPackageNameMatch(t *testing.T) {
p := pkg.Package{
Name: "neutron",
Version: "2014.1.3-6",
Type: pkg.DebPkg,
Metadata: pkg.DpkgMetadata{
Source: "neutron-devel",
},
}
d, err := distro.NewDistro(distro.Debian, "8")
if err != nil {
t.Fatal("could not create distro: ", err)
}
store := newMockProvider()
actual, err := ExactPackageNameMatch(store, d, &p, "SOME_OTHER_MATCHER")
if len(actual) != 1 {
t.Fatalf("unexpected direct matches count: %d", len(actual))
}
foundCVEs := internal.NewStringSet()
for _, a := range actual {
foundCVEs.Add(a.Vulnerability.ID)
if a.Type != match.ExactDirectMatch {
t.Error("direct match not indicated")
}
if a.Package.Name != p.Name {
t.Errorf("failed to capture correct original package: %s", a.Package.Name)
}
if a.Matcher != "SOME_OTHER_MATCHER" {
t.Errorf("failed to capture matcher name: %s", a.Matcher)
}
if a.IndirectPackage != nil {
t.Fatalf("should not have captured indirect package")
}
}
for _, id := range []string{"CVE-2014-fake-1"} {
if !foundCVEs.Contains(id) {
t.Errorf("missing discovered CVE: %s", id)
}
}
}

View file

@ -3,10 +3,10 @@ package dpkg
import (
"fmt"
_distro "github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/anchore/vulnscan/vulnscan/match"
"github.com/anchore/vulnscan/vulnscan/matcher/distro"
"github.com/anchore/vulnscan/vulnscan/matcher/common"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
"github.com/jinzhu/copier"
)
@ -22,7 +22,7 @@ func (m *Matcher) Name() string {
return "dpkg-matcher"
}
func (m *Matcher) Match(store vulnerability.Provider, d _distro.Distro, p *pkg.Package) ([]match.Match, error) {
func (m *Matcher) Match(store vulnerability.Provider, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
matches := make([]match.Match, 0)
sourceMatches, err := m.matchBySourceIndirection(store, d, p)
@ -31,7 +31,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d _distro.Distro, p *pkg.P
}
matches = append(matches, sourceMatches...)
exactMatches, err := distro.ExactPackageNameMatch(store, d, p, m.Name())
exactMatches, err := common.FindMatchesByPackageDistro(store, d, p, m.Name())
if err != nil {
return nil, fmt.Errorf("failed to match by exact package name: %w", err)
}
@ -40,7 +40,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d _distro.Distro, p *pkg.P
return matches, nil
}
func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro, d _distro.Distro, p *pkg.Package) ([]match.Match, error) {
func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro, d distro.Distro, p *pkg.Package) ([]match.Match, error) {
value, ok := p.Metadata.(pkg.DpkgMetadata)
if !ok {
return nil, fmt.Errorf("bad dpkg metadata type='%T'", value)
@ -65,7 +65,7 @@ func (m *Matcher) matchBySourceIndirection(store vulnerability.ProviderByDistro,
// use the source package name
indirectPackage.Name = sourcePkgName
matches, err := distro.ExactPackageNameMatch(store, d, &indirectPackage, m.Name())
matches, err := common.FindMatchesByPackageDistro(store, d, &indirectPackage, m.Name())
if err != nil {
return nil, fmt.Errorf("failed to find vulnerabilities by dkpg source indirection: %w", err)
}

View file

@ -1,6 +1,10 @@
package version
import "strings"
import (
"strings"
"github.com/anchore/imgbom/imgbom/pkg"
)
const (
UnknownFormat Format = iota
@ -23,14 +27,27 @@ var Formats = []Format{
func ParseFormat(userStr string) Format {
switch strings.ToLower(userStr) {
case strings.ToLower(SemanticFormat.String()):
case strings.ToLower(SemanticFormat.String()), "semver":
return SemanticFormat
case strings.ToLower(DpkgFormat.String()):
case strings.ToLower(DpkgFormat.String()), "deb":
return DpkgFormat
}
return UnknownFormat
}
func FormatFromPkgType(t pkg.Type) Format {
var format Format
switch t {
case pkg.DebPkg:
format = DpkgFormat
case pkg.BundlerPkg:
format = SemanticFormat
default:
format = UnknownFormat
}
return format
}
func (f Format) String() string {
if int(f) >= len(formatStr) || f < 0 {
return formatStr[0]

View file

@ -0,0 +1,73 @@
package version
import (
"fmt"
"testing"
"github.com/anchore/imgbom/imgbom/pkg"
)
func TestParseFormat(t *testing.T) {
tests := []struct {
input string
format Format
}{
{
input: "dpkg",
format: DpkgFormat,
},
{
input: "deb",
format: DpkgFormat,
},
{
input: "semantic",
format: SemanticFormat,
},
{
input: "semver",
format: SemanticFormat,
},
}
for _, test := range tests {
name := fmt.Sprintf("'%s'->format[%s]", test.input, test.format)
t.Run(name, func(t *testing.T) {
actual := ParseFormat(test.input)
if actual != test.format {
t.Errorf("mismatched user string -> format mapping, pkgType='%s': '%s'!='%s'", test.input, test.format, actual)
}
})
}
}
func TestFormatFromPkgType(t *testing.T) {
tests := []struct {
pkgType pkg.Type
format Format
}{
{
pkgType: pkg.DebPkg,
format: DpkgFormat,
},
{
pkgType: pkg.BundlerPkg,
format: SemanticFormat,
},
}
for _, test := range tests {
name := fmt.Sprintf("pkgType[%s]->format[%s]", test.pkgType, test.format)
t.Run(name, func(t *testing.T) {
actual := FormatFromPkgType(test.pkgType)
if actual != test.format {
t.Errorf("mismatched pkgType->format mapping, pkgType='%s': '%s'!='%s'", test.pkgType, test.format, actual)
}
})
}
// TODO: once all pkgs are added, make this fail
if len(tests) != len(pkg.AllPkgs) {
t.Log("may have missed testing a pkgType -> version.Format test case")
}
}

View file

@ -2,12 +2,17 @@ package version
import (
"fmt"
"strings"
hashiVer "github.com/hashicorp/go-version"
hashiVer "github.com/knqyf263/go-version"
)
// ruby packages such as activerecord and sprockets don't strictly follow semver
// note: this may result in missed matches for versioned betas
var normalizer = strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc")
func newSemanticVersion(raw string) (*hashiVer.Version, error) {
return hashiVer.NewVersion(raw)
return hashiVer.NewVersion(normalizer.Replace(raw))
}
type semanticConstraint struct {
@ -16,12 +21,13 @@ type semanticConstraint struct {
}
func newSemanticConstraint(constStr string) (semanticConstraint, error) {
constraints, err := hashiVer.NewConstraint(constStr)
normalized := normalizer.Replace(constStr)
constraints, err := hashiVer.NewConstraint(normalized)
if err != nil {
return semanticConstraint{}, err
}
return semanticConstraint{
raw: constStr,
raw: normalized,
constraint: constraints,
}, nil
}

View file

@ -4,8 +4,8 @@ import (
"fmt"
"github.com/anchore/imgbom/imgbom/pkg"
hashiVer "github.com/hashicorp/go-version"
deb "github.com/knqyf263/go-deb-version"
hashiVer "github.com/knqyf263/go-version"
)
type Version struct {
@ -34,14 +34,7 @@ func NewVersion(raw string, format Format) (*Version, error) {
}
func NewVersionFromPkg(p *pkg.Package) (*Version, error) {
var format Format
switch p.Type {
case pkg.DebPkg:
format = DpkgFormat
default:
format = UnknownFormat
}
return NewVersion(p.Version, format)
return NewVersion(p.Version, FormatFromPkgType(p.Type))
}
func (v *Version) populate() error {

View file

@ -0,0 +1,7 @@
package version
// func TestNewVersionFromPkg(t *testing.T) {
// tests := []struct{
// }
// }

View file

@ -0,0 +1,38 @@
package vulnerability
import (
"fmt"
"strings"
"github.com/anchore/imgbom/imgbom/distro"
"github.com/anchore/imgbom/imgbom/pkg"
)
var langLookup map[pkg.Language][]string
func init() {
langLookup = make(map[pkg.Language][]string)
for _, lang := range pkg.AllLanguages {
// TODO: can we drive this from information from vulnscan-db? that would be ideal...
switch lang {
case pkg.Ruby:
langLookup[pkg.Ruby] = []string{"github:gem"}
case pkg.Java:
langLookup[pkg.Java] = []string{"github:java"}
case pkg.JavaScript:
langLookup[pkg.JavaScript] = []string{"github:npm"}
case pkg.Python:
langLookup[pkg.Python] = []string{"github:python"}
default:
panic(fmt.Errorf("unsupported language: %+v", lang))
}
}
}
func distroNamespace(d distro.Distro) string {
return fmt.Sprintf("%s:%s", strings.ToLower(d.Type.String()), d.FullVersion())
}
func languageNamespaces(l pkg.Language) []string {
return langLookup[l]
}

View file

@ -0,0 +1,84 @@
package vulnerability
import (
"fmt"
"testing"
"github.com/anchore/imgbom/imgbom/distro"
)
func TestDistroNamespace_AllDistros(t *testing.T) {
tests := []struct {
dist distro.Type
version string
expected string
}{
{
dist: distro.Debian,
version: "8",
expected: "debian:8",
},
}
for _, test := range tests {
name := fmt.Sprintf("%s:%s", test.dist, test.version)
t.Run(name, func(t *testing.T) {
d, err := distro.NewDistro(test.dist, test.version)
if err != nil {
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
}
actual := distroNamespace(d)
if actual != test.expected {
t.Errorf("mismatched distro namespace: '%s'!='%s'", actual, test.expected)
}
})
}
if len(tests) != len(distro.All) {
t.Errorf("probably excluded a distro in namespace name testing")
}
}
func TestDistroNamespace_VersionHandeling(t *testing.T) {
tests := []struct {
dist distro.Type
version string
expected string
}{
{
dist: distro.Debian,
version: "8",
expected: "debian:8",
},
{
dist: distro.Debian,
version: "18.04",
expected: "debian:18.04",
},
{
dist: distro.Debian,
version: "0",
expected: "debian:0",
},
}
for _, test := range tests {
name := fmt.Sprintf("%s:%s", test.dist, test.version)
t.Run(name, func(t *testing.T) {
d, err := distro.NewDistro(test.dist, test.version)
if err != nil {
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
}
actual := distroNamespace(d)
if actual != test.expected {
t.Errorf("mismatched distro namespace: '%s'!='%s'", actual, test.expected)
}
})
}
}

View file

@ -7,8 +7,13 @@ import (
type Provider interface {
ProviderByDistro
ProviderByLanguage
}
type ProviderByDistro interface {
GetByDistro(distro.Distro, *pkg.Package) ([]*Vulnerability, error)
}
type ProviderByLanguage interface {
GetByLanguage(pkg.Language, *pkg.Package) ([]*Vulnerability, error)
}

View file

@ -2,6 +2,7 @@ package vulnerability
import (
"fmt"
"regexp"
"strings"
"github.com/anchore/imgbom/imgbom/distro"
@ -36,6 +37,9 @@ func (pr *StoreProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*Vulner
// TODO: delete me, this should be implemented in the vulnscan-db repo
var prefix string
if vuln.Version == "None" {
vuln.Version = ""
}
if format == version.DpkgFormat && vuln.Version != "" {
prefix = "< "
}
@ -43,7 +47,7 @@ func (pr *StoreProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*Vulner
constraint, err := version.GetConstraint(prefix+vuln.Version, format)
if err != nil {
return nil, fmt.Errorf("provider failed to parse constraint='%s' format='%s': %w", vuln.Version, format, err)
return nil, fmt.Errorf("provider failed to parse distro='%s' constraint='%s' format='%s': %w", d, vuln.Version, format, err)
}
vulns = append(vulns, &Vulnerability{
@ -55,6 +59,55 @@ func (pr *StoreProvider) GetByDistro(d distro.Distro, p *pkg.Package) ([]*Vulner
return vulns, nil
}
func distroNamespace(d distro.Distro) string {
return fmt.Sprintf("%s:%s", strings.ToLower(d.Type.String()), d.FullVersion())
func (pr *StoreProvider) GetByLanguage(l pkg.Language, p *pkg.Package) ([]*Vulnerability, error) {
vulns := make([]*Vulnerability, 0)
namespaces := languageNamespaces(l)
if namespaces == nil {
return nil, fmt.Errorf("no store namespaces found for language '%s'", l)
}
for _, namespace := range namespaces {
allPkgVulns, err := pr.store.Get(namespace, p.Name)
if err != nil {
return nil, fmt.Errorf("provider failed to fetch namespace='%s' pkg='%s': %w", namespace, p.Name, err)
}
for _, vuln := range allPkgVulns {
format := version.ParseFormat(vuln.VersionFormat)
// TODO: delete me, this should be implemented in the vulnscan-db repo
if format == version.SemanticFormat && vuln.Version != "" {
vuln.Version = forceSemVerConstraint(vuln.Version)
}
// </TODO>
constraint, err := version.GetConstraint(vuln.Version, format)
if err != nil {
return nil, fmt.Errorf("provider failed to parse language='%s' constraint='%s' format='%s': %w", l, vuln.Version, format, err)
}
vulns = append(vulns, &Vulnerability{
Constraint: constraint,
ID: vuln.VulnerabilityID,
})
}
}
return vulns, nil
}
// match examples:
// >= 5.0.0
// <= 6.1.2.beta
// >= 5.0.0
// < 6.1
// > 5.0.0
// >=5
// <6
var forceSemVerPattern = regexp.MustCompile(`[><=]+\s*[^<>=]+`)
// TODO: delete me once implemented in vulnscan-db
func forceSemVerConstraint(ver string) string {
return strings.Join(forceSemVerPattern.FindAllString(ver, -1), ", ")
}

View file

@ -52,78 +52,45 @@ func TestGetByDistro(t *testing.T) {
}
func TestDistroNamespace_AllDistros(t *testing.T) {
// TODO: delete me once implemented in vulnscan-db
func TestForceSemVerConstraint(t *testing.T) {
tests := []struct {
dist distro.Type
version string
input string
expected string
}{
{
dist: distro.Debian,
version: "8",
expected: "debian:8",
input: "> 5.0.0",
expected: "> 5.0.0",
},
{
input: ">= 5.0.0",
expected: ">= 5.0.0",
},
{
input: ">= 5.0.0 < 6.1",
expected: ">= 5.0.0 , < 6.1",
},
{
input: ">= 5.0.0 < 6.1.2",
expected: ">= 5.0.0 , < 6.1.2",
},
{
input: ">= 5 <= 6",
expected: ">= 5 , <= 6",
},
{
input: ">=5<6",
expected: ">=5, <6",
},
}
for _, test := range tests {
name := fmt.Sprintf("%s:%s", test.dist, test.version)
name := fmt.Sprintf("'%s'->'%s'", test.input, test.expected)
t.Run(name, func(t *testing.T) {
d, err := distro.NewDistro(test.dist, test.version)
if err != nil {
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
}
actual := distroNamespace(d)
actual := forceSemVerConstraint(test.input)
if actual != test.expected {
t.Errorf("mismatched distro namespace: '%s'!='%s'", actual, test.expected)
t.Errorf("failed to force semver constraint: input='%s': '%s'!='%s'", test.input, test.expected, actual)
}
})
}
if len(tests) != len(distro.All) {
t.Errorf("probably excluded a distro in namespace name testing")
}
}
func TestDistroNamespace_VersionHandeling(t *testing.T) {
tests := []struct {
dist distro.Type
version string
expected string
}{
{
dist: distro.Debian,
version: "8",
expected: "debian:8",
},
{
dist: distro.Debian,
version: "18.04",
expected: "debian:18.04",
},
{
dist: distro.Debian,
version: "0",
expected: "debian:0",
},
}
for _, test := range tests {
name := fmt.Sprintf("%s:%s", test.dist, test.version)
t.Run(name, func(t *testing.T) {
d, err := distro.NewDistro(test.dist, test.version)
if err != nil {
t.Errorf("could not create distro='%+v:%+v': %+v", test.dist, test.version, err)
}
actual := distroNamespace(d)
if actual != test.expected {
t.Errorf("mismatched distro namespace: '%s'!='%s'", actual, test.expected)
}
})
}
}