mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
Merge pull request #15 from anchore/add-gem-matcher
Add gem/bundler matcher
This commit is contained in:
commit
c0d20adc72
24 changed files with 663 additions and 229 deletions
10
go.mod
10
go.mod
|
@ -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
24
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ const (
|
|||
type Type int
|
||||
|
||||
var typeStr = []string{
|
||||
"UnknownMatchType",
|
||||
"Exact-Direct Match",
|
||||
"Exact-Indirect Match",
|
||||
}
|
||||
|
|
24
vulnscan/matcher/bundler/matcher.go
Normal file
24
vulnscan/matcher/bundler/matcher.go
Normal 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())
|
||||
}
|
24
vulnscan/matcher/common/distro_matchers.go
Normal file
24
vulnscan/matcher/common/distro_matchers.go
Normal 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
|
||||
}
|
113
vulnscan/matcher/common/distro_matchers_test.go
Normal file
113
vulnscan/matcher/common/distro_matchers_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
23
vulnscan/matcher/common/language_matchers.go
Normal file
23
vulnscan/matcher/common/language_matchers.go
Normal 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
|
||||
}
|
95
vulnscan/matcher/common/language_matchers_test.go
Normal file
95
vulnscan/matcher/common/language_matchers_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
73
vulnscan/version/format_test.go
Normal file
73
vulnscan/version/format_test.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
7
vulnscan/version/version_test.go
Normal file
7
vulnscan/version/version_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package version
|
||||
|
||||
// func TestNewVersionFromPkg(t *testing.T) {
|
||||
// tests := []struct{
|
||||
|
||||
// }
|
||||
// }
|
38
vulnscan/vulnerability/namespace.go
Normal file
38
vulnscan/vulnerability/namespace.go
Normal 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]
|
||||
}
|
84
vulnscan/vulnerability/namespace_test.go
Normal file
84
vulnscan/vulnerability/namespace_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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), ", ")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue