refactor: use testing DB instead of mock (#1234)

This commit is contained in:
Teppei Fukuda
2021-09-15 10:06:01 +03:00
committed by GitHub
parent d8cc8b550b
commit 31c45ffc52
34 changed files with 1186 additions and 1159 deletions

5
go.mod
View File

@@ -13,13 +13,12 @@ require (
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492
github.com/aquasecurity/trivy-db v0.0.0-20210809142931-da8e09204404
github.com/aquasecurity/trivy-db v0.0.0-20210907100132-2ec74c43526d
github.com/caarlos0/env/v6 v6.0.0
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cheggaaa/pb/v3 v3.0.3
github.com/docker/docker v20.10.8+incompatible
github.com/docker/go-connections v0.4.0
github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d // indirect
github.com/fatih/color v1.10.0
github.com/go-redis/redis/v8 v8.11.3
github.com/goccy/go-yaml v1.8.2 // indirect
@@ -27,7 +26,6 @@ require (
github.com/google/go-containerregistry v0.6.0
github.com/google/go-github/v33 v33.0.0
github.com/google/wire v0.4.0
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/hashicorp/go-getter v1.5.2
github.com/huandu/xstrings v1.3.2 // indirect
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
@@ -38,7 +36,6 @@ require (
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/open-policy-agent/opa v0.32.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/spf13/afero v1.6.0
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0

17
go.sum
View File

@@ -218,9 +218,8 @@ github.com/aquasecurity/testdocker v0.0.0-20210911155206-e1e85f5a1516 h1:moQmzbp
github.com/aquasecurity/testdocker v0.0.0-20210911155206-e1e85f5a1516/go.mod h1:gTd97VdQ0rg8Mkiic3rPgNOQdprZ7feTAhiD5mGQjgM=
github.com/aquasecurity/tfsec v0.46.0 h1:R9djHTpk+YrFuFv2GRdfU4rRz6uk5wLrgfx1fp9K1es=
github.com/aquasecurity/tfsec v0.46.0/go.mod h1:Dafx5dX/1QV1d5en62shpzEXfq5F31IG6oNNxhleV5Y=
github.com/aquasecurity/trivy-db v0.0.0-20210809142931-da8e09204404 h1:6nJle4kjovrm3gK+xl1iuYkv1vbbMRRviHkR7fj3Tjc=
github.com/aquasecurity/trivy-db v0.0.0-20210809142931-da8e09204404/go.mod h1:N7CWA/vjVw78GWAdCJGhFQVqNGEA4e47a6eIWm+C/Bc=
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA=
github.com/aquasecurity/trivy-db v0.0.0-20210907100132-2ec74c43526d h1:AMuCVa54YX5wVmvqeUZY/PSfMbHtiX1PukVZHocCLr0=
github.com/aquasecurity/trivy-db v0.0.0-20210907100132-2ec74c43526d/go.mod h1:lcUx+KZjKYLu7gCe8Gwe3ZUBUsxeRUg3mwkvzikP6kQ=
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ=
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -496,10 +495,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d h1:rtM8HsT3NG37YPjz8sYSbUSdElP9lUsQENYzJDZDUBE=
github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
@@ -724,6 +720,7 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-github/v38 v38.1.0/go.mod h1:cStvrz/7nFr0FoENgG6GLbp53WaelXucT+BBz/3VKx4=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
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=
@@ -776,8 +773,6 @@ github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3i
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904=
github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w=
github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40=
@@ -912,7 +907,6 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@@ -1156,7 +1150,6 @@ github.com/owenrumney/go-sarif v1.0.11/go.mod h1:hTBFbxU7GuVRUvwMx+eStp9M/Oun4xH
github.com/owenrumney/squealer v0.2.26/go.mod h1:wwVPzhjiUBILIdDtnzGSEcapXczIj/tONP+ZJ49IhPY=
github.com/owenrumney/squealer v0.2.28 h1:LYsqUHal+5QlANjbZ+h44SN5kIZSfHCWKUzBAS1KwB0=
github.com/owenrumney/squealer v0.2.28/go.mod h1:wwVPzhjiUBILIdDtnzGSEcapXczIj/tONP+ZJ49IhPY=
github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ=
github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
@@ -1277,11 +1270,8 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@@ -2137,7 +2127,6 @@ modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=

View File

@@ -6,9 +6,9 @@ import (
version "github.com/knqyf263/go-apk-version"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/alpine"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -43,15 +43,36 @@ var (
}
)
type options struct {
clock clock.Clock
}
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Scanner implements the Alpine scanner
type Scanner struct {
vs dbTypes.VulnSrc
vs alpine.VulnSrc
*options
}
// NewScanner is the factory method for Scanner
func NewScanner() *Scanner {
func NewScanner(opts ...option) *Scanner {
o := &options{
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
return &Scanner{
vs: alpine.NewVulnSrc(),
options: o,
}
}
@@ -101,11 +122,6 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
// IsSupportedVersion checks the OSFamily can be scanned using Alpine scanner
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
now := time.Now()
return s.isSupportedVersion(now, osFamily, osVer)
}
func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
if strings.Count(osVer, ".") > 1 {
osVer = osVer[:strings.LastIndex(osVer, ".")]
}
@@ -115,5 +131,6 @@ func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool
log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
return false
}
return now.Before(eol)
return s.clock.Now().Before(eol)
}

View File

@@ -1,15 +1,17 @@
package alpine
package alpine_test
import (
"errors"
"testing"
"time"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
fake "k8s.io/utils/clock/testing"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -18,31 +20,16 @@ func TestScanner_Detect(t *testing.T) {
osVer string
pkgs []ftypes.Package
}
type getInput struct {
osVer string
pkgName string
}
type getOutput struct {
advisories []dbTypes.Advisory
err error
}
type get struct {
input getInput
output getOutput
}
type mocks struct {
get []get
}
tests := []struct {
name string
args args
mocks mocks
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "happy path",
fixtures: []string{"testdata/fixtures/alpine310.yaml"},
args: args{
osVer: "3.10.2",
pkgs: []ftypes.Package{
@@ -63,39 +50,6 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
mocks: mocks{
get: []get{
{
input: getInput{
osVer: "3.10",
pkgName: "ansible",
},
output: getOutput{
advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2018-10875",
FixedVersion: "2.6.3-r0",
},
{
VulnerabilityID: "CVE-2019-10217",
FixedVersion: "2.8.4-r0",
},
{
VulnerabilityID: "CVE-2019-INVALID",
FixedVersion: "invalid", // skipped
},
},
},
},
{
input: getInput{
osVer: "3.10",
pkgName: "invalid",
},
output: getOutput{advisories: []dbTypes.Advisory{{}}},
},
},
},
want: []types.DetectedVulnerability{
{
PkgName: "ansible",
@@ -110,8 +64,9 @@ func TestScanner_Detect(t *testing.T) {
},
{
name: "contain rc",
fixtures: []string{"testdata/fixtures/alpine310.yaml"},
args: args{
osVer: "3.9",
osVer: "3.10",
pkgs: []ftypes.Package{
{
Name: "jq",
@@ -121,40 +76,20 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
mocks: mocks{
get: []get{
want: []types.DetectedVulnerability{
{
input: getInput{
osVer: "3.9",
pkgName: "jq",
},
output: getOutput{
advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2016-4074",
FixedVersion: "1.6_rc1-r0",
},
{
VulnerabilityID: "CVE-2019-9999",
FixedVersion: "1.6_rc2",
},
},
},
},
{
input: getInput{
osVer: "3.10",
pkgName: "invalid",
},
output: getOutput{advisories: []dbTypes.Advisory{{}}},
},
PkgName: "jq",
VulnerabilityID: "CVE-2020-1234",
InstalledVersion: "1.6-r0",
FixedVersion: "1.6-r1",
},
},
},
{
name: "contain pre",
fixtures: []string{"testdata/fixtures/alpine310.yaml"},
args: args{
osVer: "3.12",
osVer: "3.10",
pkgs: []ftypes.Package{
{
Name: "test",
@@ -167,28 +102,6 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
mocks: mocks{
get: []get{
{
input: getInput{
osVer: "3.12",
pkgName: "test-src",
},
output: getOutput{
advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2030-0001",
FixedVersion: "0.1.0_alpha_pre2",
},
{
VulnerabilityID: "CVE-2030-0002",
FixedVersion: "0.1.0_alpha2",
},
},
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2030-0002",
@@ -203,8 +116,9 @@ func TestScanner_Detect(t *testing.T) {
},
{
name: "Get returns an error",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "3.8.1",
osVer: "3.10.2",
pkgs: []ftypes.Package{
{
Name: "jq",
@@ -214,91 +128,89 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
mocks: mocks{
get: []get{
{
input: getInput{
osVer: "3.8",
pkgName: "jq",
},
output: getOutput{err: errors.New("error")},
},
},
},
wantErr: "failed to get alpine advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockVulnSrc := new(dbTypes.MockVulnSrc)
for _, g := range tt.mocks.get {
mockVulnSrc.On("Get", g.input.osVer, g.input.pkgName).Return(
g.output.advisories, g.output.err)
}
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
s := &Scanner{
vs: mockVulnSrc,
}
s := alpine.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
switch {
case tt.wantErr != "":
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
default:
assert.NoError(t, err, tt.name)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.ElementsMatch(t, got, tt.want, tt.name)
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
now time.Time
type args struct {
osFamily string
osVersion string
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
args args
want bool
}{
"alpine3.6": {
{
name: "alpine 3.6",
now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "alpine",
osVersion: "3.6",
expected: true,
osVer: "3.6",
},
"alpine3.6 with EOL": {
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
osFamily: "alpine",
osVersion: "3.6.5",
expected: false,
want: true,
},
"alpine3.9": {
{
name: "alpine 3.6 with EOL",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "alpine",
osVersion: "3.9.0",
expected: true,
osVer: "3.6.5",
},
"alpine3.10": {
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
osFamily: "alpine",
osVersion: "3.10",
expected: true,
want: false,
},
"unknown": {
{
name: "alpine 3.9",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "alpine",
osVersion: "unknown",
expected: false,
osVer: "3.9.0",
},
want: true,
},
{
name: "alpine 3.10",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "alpine",
osVer: "3.10",
},
want: true,
},
{
name: "unknown",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "alpine",
osVer: "unknown",
},
want: false,
},
}
for testName, v := range vectors {
s := NewScanner()
t.Run(testName, func(t *testing.T) {
actual := s.isSupportedVersion(v.now, v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := alpine.NewScanner(alpine.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,32 @@
- bucket: alpine 3.10
pairs:
- bucket: ansible
pairs:
- key: CVE-2018-10875
value:
FixedVersion: "2.6.3-r0"
- key: CVE-2019-10217
value:
FixedVersion: "2.8.4-r0"
- key: CVE-2019-INVALID
value:
FixedVersion: "invalid"
- bucket: jq
pairs:
- key: CVE-2016-4074
value:
FixedVersion: "1.6_rc1-r0"
- key: CVE-2019-9999
value:
FixedVersion: "1.6_rc2"
- key: CVE-2020-1234
value:
FixedVersion: "1.6-r1"
- bucket: test-src
pairs:
- key: CVE-2030-0001
value:
FixedVersion: "0.1.0_alpha_pre2"
- key: CVE-2030-0002
value:
FixedVersion: "0.1.0_alpha2"

View File

@@ -0,0 +1,9 @@
- bucket: alpine 3.10
pairs:
- bucket: jq
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -4,12 +4,13 @@ import (
"strings"
"time"
"k8s.io/utils/clock"
version "github.com/knqyf263/go-deb-version"
"go.uber.org/zap"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/amazon"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -24,17 +25,38 @@ var (
}
)
type options struct {
clock clock.Clock
l *zap.SugaredLogger
}
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Scanner to scan amazon vulnerabilities
type Scanner struct {
l *zap.SugaredLogger
ac dbTypes.VulnSrc
ac amazon.VulnSrc
options
}
// NewScanner is the factory method to return Amazon scanner
func NewScanner() *Scanner {
return &Scanner{
func NewScanner(opts ...option) *Scanner {
o := &options{
l: log.Logger,
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
return &Scanner{
ac: amazon.NewVulnSrc(),
options: *o,
}
}
@@ -91,11 +113,6 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
// IsSupportedVersion checks if os can be scanned using amazon scanner
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
now := time.Now()
return s.isSupportedVersion(now, osFamily, osVer)
}
func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
osVer = strings.Fields(osVer)[0]
if osVer != "2" {
osVer = "1"
@@ -105,5 +122,6 @@ func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool
log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
return false
}
return now.Before(eol)
return s.clock.Now().Before(eol)
}

View File

@@ -1,213 +1,179 @@
package amazon
package amazon_test
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zaptest/observer"
"github.com/stretchr/testify/require"
fake "k8s.io/utils/clock/testing"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon"
"github.com/aquasecurity/trivy/pkg/types"
)
type MockAmazonConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
}
func (mac MockAmazonConfig) Update(a string) error {
if mac.update != nil {
return mac.update(a)
}
return nil
}
func (mac MockAmazonConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if mac.get != nil {
return mac.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
func TestScanner_Detect(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
zc, recorder := observer.New(zapcore.DebugLevel)
log.Logger = zap.New(zc).Sugar()
s := &Scanner{
l: log.Logger,
ac: MockAmazonConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, e error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "123",
FixedVersion: "3.0.0",
},
}, nil
},
},
type args struct {
osVer string
pkgs []ftypes.Package
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
Name: "testpkg",
Version: "2.1.0",
Release: "hotfix",
SrcRelease: "test-hotfix",
SrcVersion: "2.1.0",
name: "amazon linux 1",
fixtures: []string{"testdata/fixtures/amazon.yaml"},
args: args{
osVer: "1.2",
pkgs: []ftypes.Package{
{
Name: "bind",
Epoch: 32,
Version: "9.8.2",
Release: "0.68.rc1.85.amzn1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
Name: "foopkg",
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability{
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-hotfix",
FixedVersion: "3.0.0",
PkgName: "bind",
VulnerabilityID: "CVE-2020-8625",
InstalledVersion: "32:9.8.2-0.68.rc1.85.amzn1",
FixedVersion: "32:9.8.2-0.68.rc1.86.amzn1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
}, vuls)
loggedMessages := getAllLoggedLogs(recorder)
assert.Contains(t, loggedMessages, "amazon: os version: 1")
assert.Contains(t, loggedMessages, "amazon: the number of packages: 2")
})
t.Run("get vulnerabilities fails to fetch", func(t *testing.T) {
s := &Scanner{
l: log.Logger,
ac: MockAmazonConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, e error) {
return nil, errors.New("failed to fetch advisories")
},
},
{
name: "amazon linux 2",
fixtures: []string{"testdata/fixtures/amazon.yaml"},
args: args{
osVer: "2",
pkgs: []ftypes.Package{
{
Name: "bash",
Version: "4.2.45",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
PkgName: "bash",
VulnerabilityID: "CVE-2019-9924",
InstalledVersion: "4.2.45",
FixedVersion: "4.2.46-34.amzn2",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
{
name: "empty version",
fixtures: []string{"testdata/fixtures/amazon.yaml"},
args: args{
osVer: "2",
pkgs: []ftypes.Package{
{
Name: "bash",
},
},
},
},
{
name: "Get returns an error",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "1",
pkgs: []ftypes.Package{
{
Name: "jq",
Version: "1.6-r0",
SrcName: "jq",
SrcVersion: "1.6-r0",
},
},
},
wantErr: "failed to get amazon advisories",
},
}
vuls, err := s.Detect("foo", []ftypes.Package{
{
Name: "testpkg",
},
})
assert.Equal(t, "failed to get amazon advisories: failed to fetch advisories", err.Error())
assert.Empty(t, vuls)
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
t.Run("invalid installed package version", func(t *testing.T) {
zc, recorder := observer.New(zapcore.DebugLevel)
log.Logger = zap.New(zc).Sugar()
s := &Scanner{
l: log.Logger,
ac: MockAmazonConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, e error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "123",
FixedVersion: "3.0.0",
},
}, nil
},
},
s := amazon.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
{
Name: "testpkg",
Version: "badsourceversion",
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability(nil), vuls)
loggedMessages := getAllLoggedLogs(recorder)
assert.Contains(t, loggedMessages, "failed to parse Amazon Linux installed package version: upstream_version must start with digit")
assert.Equal(t, tt.want, got)
})
t.Run("invalid fixed package version", func(t *testing.T) {
zc, recorder := observer.New(zapcore.DebugLevel)
log.Logger = zap.New(zc).Sugar()
s := &Scanner{
l: log.Logger,
ac: MockAmazonConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, e error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "123",
FixedVersion: "thisisbadversioning",
},
}, nil
},
},
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
{
Name: "testpkg",
Version: "3.1.0",
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability(nil), vuls)
loggedMessages := getAllLoggedLogs(recorder)
assert.Contains(t, loggedMessages, "failed to parse Amazon Linux package version: upstream_version must start with digit")
})
}
func getAllLoggedLogs(recorder *observer.ObservedLogs) []string {
allLogs := recorder.AllUntimed()
var loggedMessages []string
for _, l := range allLogs {
loggedMessages = append(loggedMessages, l.Message)
}
return loggedMessages
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
now time.Time
type args struct {
osFamily string
osVersion string
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
args args
want bool
}{
"1": {
{
name: "amazon linux 1",
now: time.Date(2022, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "amazon",
osVersion: "1",
expected: true,
osVer: "1",
},
"1 (eol ends)": {
want: true,
},
{
name: "amazon linux 1 EOL",
now: time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "amazon",
osVersion: "1",
expected: false,
osVer: "1",
},
"2": {
want: false,
},
{
name: "amazon linux 2",
now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC),
args: args{
osFamily: "amazon",
osVersion: "2",
expected: true,
osVer: "2",
},
want: true,
},
}
for testName, v := range vectors {
s := NewScanner()
t.Run(testName, func(t *testing.T) {
actual := s.isSupportedVersion(v.now, v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := amazon.NewScanner(amazon.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,14 @@
- bucket: amazon linux 1
pairs:
- bucket: bind
pairs:
- key: CVE-2020-8625
value:
FixedVersion: "32:9.8.2-0.68.rc1.86.amzn1"
- bucket: amazon linux 2
pairs:
- bucket: bash
pairs:
- key: CVE-2019-9924
value:
FixedVersion: "4.2.46-34.amzn2"

View File

@@ -0,0 +1,9 @@
- bucket: amazon linux 1
pairs:
- bucket: jq
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -4,14 +4,13 @@ import (
"strings"
"time"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian"
debianoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian-oval"
version "github.com/knqyf263/go-deb-version"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian"
debianoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/debian-oval"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
@@ -40,17 +39,38 @@ var (
}
)
type options struct {
clock clock.Clock
}
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Scanner implements the Debian scanner
type Scanner struct {
ovalVs dbTypes.VulnSrc
vs dbTypes.VulnSrc
ovalVs debianoval.VulnSrc
vs debian.VulnSrc
*options
}
// NewScanner is the factory method to return Scanner
func NewScanner() *Scanner {
func NewScanner(opts ...option) *Scanner {
o := &options{
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
return &Scanner{
ovalVs: debianoval.NewVulnSrc(),
vs: debian.NewVulnSrc(),
options: o,
}
}
@@ -121,11 +141,6 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
// IsSupportedVersion checks is OSFamily can be scanned using Debian
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
now := time.Now()
return s.isSupportedVersion(now, osFamily, osVer)
}
func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
if strings.Count(osVer, ".") > 0 {
osVer = osVer[:strings.Index(osVer, ".")]
}
@@ -135,5 +150,5 @@ func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool
log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
return false
}
return now.Before(eol)
return s.clock.Now().Before(eol)
}

View File

@@ -1,182 +1,152 @@
package debian
package debian_test
import (
"testing"
"time"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/debian"
"github.com/aquasecurity/trivy/pkg/types"
fake "k8s.io/utils/clock/testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type MockOvalConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
func TestScanner_Detect(t *testing.T) {
type args struct {
osVer string
pkgs []ftypes.Package
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "happy path",
fixtures: []string{"testdata/fixtures/debian.yaml"},
args: args{
osVer: "9.1",
pkgs: []ftypes.Package{
{
Name: "htpasswd",
Version: "2.4.24",
SrcName: "apache2",
SrcVersion: "2.4.24",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
PkgName: "htpasswd",
VulnerabilityID: "CVE-2020-11985",
InstalledVersion: "2.4.24",
FixedVersion: "2.4.25-1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
PkgName: "htpasswd",
VulnerabilityID: "CVE-2021-31618",
InstalledVersion: "2.4.24",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
{
name: "invalid bucket",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "9.1",
pkgs: []ftypes.Package{
{
Name: "htpasswd",
Version: "2.4.24",
SrcName: "apache2",
SrcVersion: "2.4.24",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
wantErr: "failed to get Debian OVAL advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
func (mdc MockOvalConfig) Update(a string) error {
if mdc.update != nil {
return mdc.update(a)
s := debian.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
return nil
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
func (mdc MockOvalConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if mdc.get != nil {
return mdc.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
type MockDebianConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
}
func (mdc MockDebianConfig) Update(a string) error {
if mdc.update != nil {
return mdc.update(a)
}
return nil
}
func (mdc MockDebianConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if mdc.get != nil {
return mdc.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
now time.Time
type args struct {
osFamily string
osVersion string
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
args args
want bool
}{
"debian7": {
now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC),
{
name: "debian 7",
now: time.Date(2018, 3, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "debian",
osVersion: "7",
expected: false,
osVer: "7",
},
"debian8": {
now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "8.11",
expected: true,
want: true,
},
"debian8 eol ends": {
{
name: "debian 8 EOL",
now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "debian",
osVersion: "8.0",
expected: false,
osVer: "8.2",
},
"debian9": {
want: false,
},
{
name: "unknown",
now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "debian",
osVersion: "9",
expected: true,
osVer: "unknown",
},
"debian9 eol ends": {
now: time.Date(2022, 7, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "9",
expected: false,
},
"debian10": {
now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "10",
expected: true,
},
"debian10 eol ends": {
now: time.Date(2024, 7, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "10",
expected: false,
},
"unknown": {
now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "unknown",
expected: false,
want: false,
},
}
for testName, v := range vectors {
s := NewScanner()
t.Run(testName, func(t *testing.T) {
actual := s.isSupportedVersion(v.now, v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := debian.NewScanner(debian.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanner_Detect(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
s := &Scanner{
vs: MockDebianConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, err error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "debian-123",
},
}, nil
},
},
ovalVs: MockOvalConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, e error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "oval-123",
FixedVersion: "3.0.0",
},
}, nil
},
},
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
{
Name: "testpkg",
Version: "2.1.0",
Release: "hotfix",
SrcRelease: "test-hotfix",
SrcVersion: "2.1.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
Name: "foopkg",
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability{
{
VulnerabilityID: "oval-123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-test-hotfix",
FixedVersion: "3.0.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
VulnerabilityID: "debian-123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-test-hotfix",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
}, vuls)
})
// TODO: Add unhappy paths
}

View File

@@ -0,0 +1,17 @@
- bucket: debian 9
pairs:
- bucket: apache2
pairs:
- key: CVE-2021-31618
value:
FixedVersion: ""
- bucket: debian oval 9
pairs:
- bucket: apache2
pairs:
- key: CVE-2012-3499
value:
FixedVersion: "2.2.22-13"
- key: CVE-2020-11985
value:
FixedVersion: "2.4.25-1"

View File

@@ -0,0 +1,9 @@
- bucket: debian oval 9
pairs:
- bucket: apache2
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -9,7 +9,6 @@ import (
"k8s.io/utils/clock"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
oracleoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/oracle-oval"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -32,7 +31,7 @@ var (
// Scanner implements oracle vulnerability scanner
type Scanner struct {
vs dbTypes.VulnSrc
vs oracleoval.VulnSrc
clock clock.Clock
}

View File

@@ -3,10 +3,8 @@ package photon
import (
version "github.com/knqyf263/go-rpm-version"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/photon"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -18,17 +16,15 @@ import (
// eolDates = map[string]time.Time{}
//)
// Scanner implements Photon scanner
// Scanner implements the Photon scanner
type Scanner struct {
vs dbTypes.VulnSrc
clock clock.Clock
vs photon.VulnSrc
}
// NewScanner is the factory method for Scanner
func NewScanner() *Scanner {
return &Scanner{
vs: photon.NewVulnSrc(),
clock: clock.RealClock{},
}
}
@@ -65,6 +61,6 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
}
// IsSupportedVersion checks is OSFamily can be scanned
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
func (s *Scanner) IsSupportedVersion(_, _ string) bool {
return true
}

View File

@@ -1,73 +1,92 @@
package photon
package photon_test
import (
"testing"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/photon"
"github.com/aquasecurity/trivy/pkg/types"
)
type MockPhotonConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
}
func (mpc MockPhotonConfig) Update(a string) error {
if mpc.update != nil {
return mpc.update(a)
}
return nil
}
func (mpc MockPhotonConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if mpc.get != nil {
return mpc.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
func TestScanner_Detect(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
s := &Scanner{
vs: MockPhotonConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, err error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "photon-123",
FixedVersion: "3.0.0",
},
}, nil
},
},
type args struct {
osVer string
pkgs []ftypes.Package
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
Name: "testpkg",
Version: "2.1.0",
Release: "hotfix",
SrcRelease: "test-hotfix",
SrcVersion: "2.1.0",
name: "happy path",
fixtures: []string{"testdata/fixtures/photon.yaml"},
args: args{
osVer: "1.0",
pkgs: []ftypes.Package{
{
Name: "PyYAML",
Version: "3.12",
Release: "4.ph1",
SrcName: "PyYAML",
SrcVersion: "3.12",
SrcRelease: "4.ph1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
})
},
},
want: []types.DetectedVulnerability{
{
PkgName: "PyYAML",
VulnerabilityID: "CVE-2020-1747",
InstalledVersion: "3.12-4.ph1",
FixedVersion: "3.12-5.ph1",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
{
name: "invalid bucket",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "1.0",
pkgs: []ftypes.Package{
{
Name: "PyYAML",
Version: "3.12",
SrcName: "PyYAML",
SrcVersion: "3.12",
},
},
},
wantErr: "failed to get Photon advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
s := photon.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability{
{
VulnerabilityID: "photon-123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-hotfix",
FixedVersion: "3.0.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
}, vuls)
assert.Equal(t, tt.want, got)
})
// TODO: Add unhappy paths
}
}

View File

@@ -0,0 +1,9 @@
- bucket: Photon OS 1.0
pairs:
- bucket: PyYAML
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -0,0 +1,10 @@
- bucket: Photon OS 1.0
pairs:
- bucket: PyYAML
pairs:
- key: CVE-2017-18342
value:
FixedVersion: "3.12-3.ph1"
- key: CVE-2020-1747
value:
FixedVersion: "3.12-5.ph1"

View File

@@ -6,10 +6,10 @@ import (
version "github.com/knqyf263/go-rpm-version"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
"github.com/aquasecurity/fanal/analyzer/os"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -38,15 +38,36 @@ var (
}
)
// Scanner implements the Redhat scanner
type options struct {
clock clock.Clock
}
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Scanner implements the Alpine scanner
type Scanner struct {
vs dbTypes.VulnSrc
vs redhat.VulnSrc
*options
}
// NewScanner is the factory method for Scanner
func NewScanner() *Scanner {
func NewScanner(opts ...option) *Scanner {
o := &options{
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
return &Scanner{
vs: redhat.NewVulnSrc(),
options: o,
}
}
@@ -115,11 +136,6 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
// IsSupportedVersion checks is OSFamily can be scanned with Redhat scanner
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
now := time.Now()
return s.isSupportedVersion(now, osFamily, osVer)
}
func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
if strings.Count(osVer, ".") > 0 {
osVer = osVer[:strings.Index(osVer, ".")]
}
@@ -135,7 +151,8 @@ func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool
log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
return false
}
return now.Before(eolDate)
return s.clock.Now().Before(eolDate)
}
func (s *Scanner) isFromSupportedVendor(pkg ftypes.Package) bool {

View File

@@ -1,17 +1,20 @@
package redhat
package redhat_test
import (
"testing"
"time"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
fake "k8s.io/utils/clock/testing"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestScanner_Detect(t *testing.T) {
@@ -21,13 +24,14 @@ func TestScanner_Detect(t *testing.T) {
}
tests := []struct {
name string
fixtures []string
args args
get []dbTypes.GetExpectation
want []types.DetectedVulnerability
wantErr bool
wantErr string
}{
{
name: "happy path: src pkg name is different from bin pkg name",
fixtures: []string{"testdata/fixtures/redhat.yaml"},
args: args{
osVer: "7.6",
pkgs: []ftypes.Package{
@@ -47,40 +51,6 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
get: []dbTypes.GetExpectation{
{
Args: dbTypes.GetArgs{
Release: "7",
PkgName: "vim",
},
Returns: dbTypes.GetReturns{
Advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2017-5953",
FixedVersion: "",
},
{
VulnerabilityID: "CVE-2017-6350",
FixedVersion: "",
},
},
},
},
{
Args: dbTypes.GetArgs{
Release: "7",
PkgName: "vim-minimal",
},
Returns: dbTypes.GetReturns{
Advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2019-12735",
FixedVersion: "2:7.4.160-6.el7_6",
},
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2017-5953",
@@ -111,8 +81,9 @@ func TestScanner_Detect(t *testing.T) {
},
{
name: "happy path: src pkg name is the same as bin pkg name",
fixtures: []string{"testdata/fixtures/redhat.yaml"},
args: args{
osVer: "6.5",
osVer: "7.3",
pkgs: []ftypes.Package{
{
Name: "nss",
@@ -127,30 +98,6 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
get: []dbTypes.GetExpectation{
{
Args: dbTypes.GetArgs{
Release: "6",
PkgName: "nss",
},
Returns: dbTypes.GetReturns{
Advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2015-2808",
FixedVersion: "",
},
{
VulnerabilityID: "CVE-2016-2183",
FixedVersion: "",
},
{
VulnerabilityID: "CVE-2018-12404",
FixedVersion: "3.44.0-4.el7",
},
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2015-2808",
@@ -172,6 +119,7 @@ func TestScanner_Detect(t *testing.T) {
},
{
name: "happy path: modular packages",
fixtures: []string{"testdata/fixtures/redhat.yaml"},
args: args{
osVer: "8.3",
pkgs: []ftypes.Package{
@@ -192,22 +140,6 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
get: []dbTypes.GetExpectation{
{
Args: dbTypes.GetArgs{
Release: "8",
PkgName: "php:7.2::php",
},
Returns: dbTypes.GetReturns{
Advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2019-11043",
FixedVersion: "7.3.5-5.module+el8.1.0+4560+e0eee7d6",
},
},
},
},
},
want: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2019-11043",
@@ -222,6 +154,7 @@ func TestScanner_Detect(t *testing.T) {
},
{
name: "happy path: packages from remi repository are skipped",
fixtures: []string{"testdata/fixtures/redhat.yaml"},
args: args{
osVer: "7.6",
pkgs: []ftypes.Package{
@@ -241,171 +174,105 @@ func TestScanner_Detect(t *testing.T) {
},
},
},
get: []dbTypes.GetExpectation{
{
Args: dbTypes.GetArgs{
Release: "7",
PkgName: "php",
},
Returns: dbTypes.GetReturns{
Advisories: []dbTypes.Advisory{
{
VulnerabilityID: "CVE-2011-4718",
FixedVersion: "",
},
},
},
},
},
want: []types.DetectedVulnerability(nil),
},
{
name: "sad path: Get returns an error",
name: "invalid bucket",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "5",
osVer: "6",
pkgs: []ftypes.Package{
{
Name: "nss",
Name: "jq",
Version: "3.36.0",
Release: "7.1.el7_6",
Epoch: 0,
Arch: "x86_64",
SrcName: "nss",
SrcName: "jq",
SrcVersion: "3.36.0",
SrcRelease: "7.4.160",
SrcEpoch: 0,
},
},
},
get: []dbTypes.GetExpectation{
{
Args: dbTypes.GetArgs{
Release: "5",
PkgName: "nss",
},
Returns: dbTypes.GetReturns{
Err: xerrors.New("error"),
},
},
},
wantErr: true,
wantErr: "failed to get Red Hat advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockVs := new(dbTypes.MockVulnSrc)
mockVs.ApplyGetExpectations(tt.get)
s := &Scanner{
vs: mockVs,
}
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
s := redhat.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
require.Equal(t, tt.wantErr, err != nil)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
now time.Time
type args struct {
osFamily string
osVersion string
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
args args
want bool
}{
"centos5": {
{
name: "centos 6",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "centos",
osVersion: "5.0",
expected: false,
osVer: "6.8",
},
"centos6": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "centos",
osVersion: "6.7",
expected: true,
want: true,
},
"centos6 (eol ends)": {
{
name: "centos 6 EOL",
now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC),
args: args{
osFamily: "centos",
osVersion: "6.7",
expected: false,
osVer: "6.7",
},
"centos7": {
want: false,
},
{
name: "two dots",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "centos",
osVersion: "7.5",
expected: true,
osVer: "8.0.1",
},
"centos8": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "centos",
osVersion: "8.0",
expected: true,
},
"centos8 (eol ends)": {
now: time.Date(2022, 12, 1, 0, 0, 0, 0, time.UTC),
osFamily: "centos",
osVersion: "8.0",
expected: false,
},
"two dots": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "centos",
osVersion: "8.0.1",
expected: true,
},
"redhat5": {
want: true,
},
{
name: "rhel 8",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "redhat",
osVersion: "5.0",
expected: true,
osVer: "8.0",
},
"redhat6": {
want: true,
},
{
name: "unknown",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "redhat",
osVersion: "6.7",
expected: true,
args: args{
osFamily: "unknown",
osVer: "8.0",
},
"redhat6 (eol ends)": {
now: time.Date(2024, 7, 1, 0, 0, 0, 0, time.UTC),
osFamily: "redhat",
osVersion: "6.7",
expected: false,
},
"redhat7": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "redhat",
osVersion: "7.5",
expected: true,
},
"redhat8": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "redhat",
osVersion: "8.0",
expected: true,
},
"no dot": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "redhat",
osVersion: "8",
expected: true,
},
"debian": {
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
osFamily: "debian",
osVersion: "8",
expected: false,
want: false,
},
}
for testName, v := range vectors {
s := NewScanner()
t.Run(testName, func(t *testing.T) {
actual := s.isSupportedVersion(v.now, v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := redhat.NewScanner(redhat.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,9 @@
- bucket: Red Hat Enterprise Linux 6
pairs:
- bucket: jq
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -0,0 +1,38 @@
- bucket: Red Hat Enterprise Linux 7
pairs:
- bucket: php
pairs:
- key: CVE-2011-4718
value:
FixedVersion: ""
- bucket: vim
pairs:
- key: CVE-2017-5953
value:
FixedVersion: ""
- key: CVE-2017-6350
value:
FixedVersion: ""
- bucket: vim-minimal
pairs:
- key: CVE-2019-12735
value:
FixedVersion: "2:7.4.160-6.el7_6"
- bucket: nss
pairs:
- key: CVE-2015-2808
value:
FixedVersion: ""
- key: CVE-2016-2183
value:
FixedVersion: ""
- key: CVE-2018-12404
value:
FixedVersion: "3.44.0-4.el7"
- bucket: Red Hat Enterprise Linux 8
pairs:
- bucket: "php:7.2::php"
pairs:
- key: CVE-2019-11043
value:
FixedVersion: "7.3.5-5.module+el8.1.0+4560+e0eee7d6"

View File

@@ -10,7 +10,6 @@ import (
fos "github.com/aquasecurity/fanal/analyzer/os"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
susecvrf "github.com/aquasecurity/trivy-db/pkg/vulnsrc/suse-cvrf"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -57,13 +56,19 @@ var (
}
)
// Scanner implements suse scanner
type Scanner struct {
vs dbTypes.VulnSrc
type options struct {
clock clock.Clock
}
// Type to define SUSE type
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Type defines SUSE type
type Type int
const (
@@ -73,18 +78,32 @@ const (
OpenSUSE
)
// Scanner implements the Alpine scanner
type Scanner struct {
vs susecvrf.VulnSrc
*options
}
// NewScanner is the factory method for Scanner
func NewScanner(t Type) *Scanner {
func NewScanner(t Type, opts ...option) *Scanner {
o := &options{
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
switch t {
case SUSEEnterpriseLinux:
return &Scanner{
vs: susecvrf.NewVulnSrc(susecvrf.SUSEEnterpriseLinux),
clock: clock.RealClock{},
options: o,
}
case OpenSUSE:
return &Scanner{
vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE),
clock: clock.RealClock{},
options: o,
}
}
return nil

View File

@@ -1,153 +1,148 @@
package suse
package suse_test
import (
"testing"
"time"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
fake "k8s.io/utils/clock/testing"
susecvrf "github.com/aquasecurity/trivy-db/pkg/vulnsrc/suse-cvrf"
"k8s.io/utils/clock"
clocktesting "k8s.io/utils/clock/testing"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/suse"
"github.com/aquasecurity/trivy/pkg/types"
)
type MockSuseConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
func TestScanner_Detect(t *testing.T) {
type args struct {
osVer string
pkgs []ftypes.Package
}
tests := []struct {
name string
args args
fixtures []string
distribution suse.Type
want []types.DetectedVulnerability
wantErr string
}{
{
name: "happy path",
fixtures: []string{"testdata/fixtures/suse.yaml"},
distribution: suse.OpenSUSE,
args: args{
osVer: "15.3",
pkgs: []ftypes.Package{
{
Name: "postgresql",
Version: "13",
Release: "4.6.6",
SrcName: "postgresql",
SrcVersion: "13",
SrcRelease: "4.6.6",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
PkgName: "postgresql",
VulnerabilityID: "SUSE-SU-2021:0175-1",
InstalledVersion: "13-4.6.6",
FixedVersion: "13-4.6.7",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
{
name: "broken bucket",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
distribution: suse.SUSEEnterpriseLinux,
args: args{
osVer: "15.3",
pkgs: []ftypes.Package{
{
Name: "jq",
Version: "1.6-r0",
SrcName: "jq",
SrcVersion: "1.6-r0",
},
},
},
wantErr: "failed to get SUSE advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
func (msc MockSuseConfig) Update(a string) error {
if msc.update != nil {
return msc.update(a)
s := suse.NewScanner(tt.distribution)
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
return nil
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
func (msc MockSuseConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if msc.get != nil {
return msc.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
clock clock.Clock
type args struct {
osFamily string
osVersion string
distribution susecvrf.Distribution
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
distribution suse.Type
args args
want bool
}{
"opensuse.leap42.3": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
{
name: "opensuse.leap42.3",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "opensuse.leap",
osVersion: "42.3",
distribution: susecvrf.OpenSUSE,
expected: true,
osVer: "42.3",
},
"opensuse.leap15": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
osFamily: "opensuse.leap",
osVersion: "15.0",
distribution: susecvrf.OpenSUSE,
expected: true,
distribution: suse.OpenSUSE,
want: true,
},
"opensuse.leap15.1": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
osFamily: "opensuse.leap",
osVersion: "15.1",
distribution: susecvrf.OpenSUSE,
expected: true,
},
"opensuse.leap15.1-sametime": {
clock: clocktesting.NewFakeClock(time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC)),
osFamily: "opensuse.leap",
osVersion: "15.1",
distribution: susecvrf.OpenSUSE,
expected: false,
},
"sles12.3": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
{
name: "sles12.3",
now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "suse linux enterprise server",
osVersion: "12.3",
distribution: susecvrf.SUSEEnterpriseLinux,
expected: false,
osVer: "12.3",
},
"sles15": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
osFamily: "suse linux enterprise server",
osVersion: "15",
distribution: susecvrf.SUSEEnterpriseLinux,
expected: true,
distribution: suse.SUSEEnterpriseLinux,
want: false,
},
"unknown": {
clock: clocktesting.NewFakeClock(time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC)),
osFamily: "oracle",
osVersion: "unknown",
distribution: susecvrf.SUSEEnterpriseLinux,
expected: false,
{
name: "unknown",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "unknown",
osVer: "unknown",
},
want: false,
},
}
for testName, v := range vectors {
s := &Scanner{
vs: susecvrf.NewVulnSrc(v.distribution),
clock: v.clock,
}
t.Run(testName, func(t *testing.T) {
actual := s.IsSupportedVersion(v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := suse.NewScanner(tt.distribution, suse.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanner_Detect(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
s := &Scanner{
vs: MockSuseConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, err error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "suse-123",
FixedVersion: "3.0.0",
},
}, nil
},
},
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
{
Name: "testpkg",
Version: "2.1.0",
Release: "hotfix",
SrcRelease: "test-hotfix",
SrcVersion: "2.1.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability{
{
VulnerabilityID: "suse-123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-hotfix",
FixedVersion: "3.0.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
}, vuls)
})
// TODO: Add unhappy paths
}

View File

@@ -0,0 +1,9 @@
- bucket: SUSE Linux Enterprise 15.3
pairs:
- bucket: jq
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -0,0 +1,10 @@
- bucket: openSUSE Leap 15.3
pairs:
- bucket: postgresql
pairs:
- key: SUSE-SU-2021:0175-1
value:
FixedVersion: "13-4.6.7"
- key: CVE-2021-0001
value:
FixedVersion: ""

View File

@@ -0,0 +1,9 @@
- bucket: ubuntu 21.04
pairs:
- bucket: jq
pairs:
- key: CVE-2020-8177
value:
FixedVersion:
- foo
- bar

View File

@@ -0,0 +1,13 @@
- bucket: ubuntu 20.04
pairs:
- bucket: wpa
pairs:
- key: CVE-2021-27803
value:
FixedVersion: "2:2.9-1ubuntu4.3"
- key: CVE-2019-9243
value:
FixedVersion: ""
- key: CVE-2016-4476
value:
FixedVersion: "2.4-0ubuntu10"

View File

@@ -5,9 +5,9 @@ import (
version "github.com/knqyf263/go-deb-version"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/ubuntu"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
@@ -54,15 +54,36 @@ var (
}
)
// Scanner implements the Ubuntu scanner
type options struct {
clock clock.Clock
}
type option func(*options)
func WithClock(clock clock.Clock) option {
return func(opts *options) {
opts.clock = clock
}
}
// Scanner implements the Alpine scanner
type Scanner struct {
vs dbTypes.VulnSrc
vs ubuntu.VulnSrc
*options
}
// NewScanner is the factory method for Scanner
func NewScanner() *Scanner {
func NewScanner(opts ...option) *Scanner {
o := &options{
clock: clock.RealClock{},
}
for _, opt := range opts {
opt(o)
}
return &Scanner{
vs: ubuntu.NewVulnSrc(),
options: o,
}
}
@@ -116,15 +137,10 @@ func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedV
// IsSupportedVersion checks is OSFamily can be scanned using Ubuntu scanner
func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
now := time.Now()
return s.isSupportedVersion(now, osFamily, osVer)
}
func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
eol, ok := eolDates[osVer]
if !ok {
log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
return false
}
return now.Before(eol)
return s.clock.Now().Before(eol)
}

View File

@@ -1,131 +1,149 @@
package ubuntu
package ubuntu_test
import (
"testing"
"time"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
fake "k8s.io/utils/clock/testing"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu"
"github.com/aquasecurity/trivy/pkg/types"
)
type MockUbuntuConfig struct {
update func(string) error
get func(string, string) ([]dbTypes.Advisory, error)
func TestScanner_Detect(t *testing.T) {
type args struct {
osVer string
pkgs []ftypes.Package
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "happy path",
fixtures: []string{"testdata/fixtures/ubuntu.yaml"},
args: args{
osVer: "20.04",
pkgs: []ftypes.Package{
{
Name: "wpa",
Version: "2.9",
SrcName: "wpa",
SrcVersion: "2.9",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
want: []types.DetectedVulnerability{
{
PkgName: "wpa",
VulnerabilityID: "CVE-2019-9243",
InstalledVersion: "2.9",
FixedVersion: "",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
{
PkgName: "wpa",
VulnerabilityID: "CVE-2021-27803",
InstalledVersion: "2.9",
FixedVersion: "2:2.9-1ubuntu4.3",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
},
},
{
name: "broken bucket",
fixtures: []string{"testdata/fixtures/invalid.yaml"},
args: args{
osVer: "21.04",
pkgs: []ftypes.Package{
{
Name: "jq",
Version: "1.6-r0",
SrcName: "jq",
SrcVersion: "1.6-r0",
},
},
},
wantErr: "failed to get Ubuntu advisories",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_ = dbtest.InitDB(t, tt.fixtures)
defer db.Close()
func (muc MockUbuntuConfig) Update(a string) error {
if muc.update != nil {
return muc.update(a)
s := ubuntu.NewScanner()
got, err := s.Detect(tt.args.osVer, tt.args.pkgs)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
return nil
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
func (muc MockUbuntuConfig) Get(a string, b string) ([]dbTypes.Advisory, error) {
if muc.get != nil {
return muc.get(a, b)
}
return []dbTypes.Advisory{}, nil
}
func TestScanner_IsSupportedVersion(t *testing.T) {
vectors := map[string]struct {
now time.Time
type args struct {
osFamily string
osVersion string
expected bool
osVer string
}
tests := []struct {
name string
now time.Time
args args
want bool
}{
"ubuntu12.04 eol ends": {
{
name: "ubuntu 12.04 eol ends",
now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "ubuntu",
osVersion: "12.04",
expected: true,
osVer: "12.04",
},
"ubuntu12.04": {
now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC),
osFamily: "ubuntu",
osVersion: "12.04",
expected: false,
want: true,
},
"ubuntu12.10": {
{
name: "ubuntu12.04",
now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "ubuntu",
osVersion: "12.10",
expected: false,
osVer: "12.04",
},
"ubuntu18.04": {
now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC),
osFamily: "ubuntu",
osVersion: "18.04",
expected: true,
want: false,
},
"ubuntu19.04": {
now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC),
{
name: "unknown",
now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC),
args: args{
osFamily: "ubuntu",
osVersion: "19.04",
expected: true,
osVer: "unknown",
},
"unknown": {
now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC),
osFamily: "ubuntu",
osVersion: "unknown",
expected: false,
want: false,
},
}
for testName, v := range vectors {
s := NewScanner()
t.Run(testName, func(t *testing.T) {
actual := s.isSupportedVersion(v.now, v.osFamily, v.osVersion)
if actual != v.expected {
t.Errorf("[%s] got %v, want %v", testName, actual, v.expected)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := ubuntu.NewScanner(ubuntu.WithClock(fake.NewFakeClock(tt.now)))
got := s.IsSupportedVersion(tt.args.osFamily, tt.args.osVer)
assert.Equal(t, tt.want, got)
})
}
}
func TestScanner_Detect(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
s := &Scanner{
vs: MockUbuntuConfig{
get: func(s string, s2 string) (advisories []dbTypes.Advisory, err error) {
return []dbTypes.Advisory{
{
VulnerabilityID: "ubuntu-123",
FixedVersion: "3.0.0",
},
}, nil
},
},
}
vuls, err := s.Detect("3.1.0", []ftypes.Package{
{
Name: "testpkg",
Version: "2.1.0",
Release: "hotfix",
SrcRelease: "test-hotfix",
SrcVersion: "2.1.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
})
assert.NoError(t, err)
assert.Equal(t, []types.DetectedVulnerability{
{
VulnerabilityID: "ubuntu-123",
PkgName: "testpkg",
InstalledVersion: "2.1.0-test-hotfix",
FixedVersion: "3.0.0",
Layer: ftypes.Layer{
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
},
},
}, vuls)
})
// TODO: Add unhappy paths
}

View File

@@ -104,8 +104,8 @@ func (c Client) getVendorSeverity(vuln *types.DetectedVulnerability, source stri
}
// Try NVD as a fallback if it exists
if vs, ok := vuln.VendorSeverity[vulnerability.Nvd]; ok {
return vs.String(), vulnerability.Nvd
if vs, ok := vuln.VendorSeverity[vulnerability.NVD]; ok {
return vs.String(), vulnerability.NVD
}
if vuln.Severity == "" {

View File

@@ -72,7 +72,7 @@ func TestClient_FillVulnerabilityInfo(t *testing.T) {
LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"),
PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"),
},
SeveritySource: vulnerability.Nvd,
SeveritySource: vulnerability.NVD,
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0002",
},
},
@@ -118,7 +118,7 @@ func TestClient_FillVulnerabilityInfo(t *testing.T) {
CweIDs: []string{"CWE-311"},
References: []string{"http://example.com"},
CVSS: map[string]dbTypes.CVSS{
vulnerability.Nvd: {
vulnerability.NVD: {
V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P",
V2Score: 4.5,
V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H",

View File

@@ -329,7 +329,7 @@ func TestConvertFromRPCResults(t *testing.T) {
Title: "DoS",
Description: "Denial of Service",
Severity: common.Severity_MEDIUM,
SeveritySource: vulnerability.Nvd,
SeveritySource: vulnerability.NVD,
CweIds: []string{"CWE-123", "CWE-456"},
Cvss: map[string]*common.CVSS{
"redhat": {
@@ -365,7 +365,7 @@ func TestConvertFromRPCResults(t *testing.T) {
Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812",
DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079",
},
SeveritySource: vulnerability.Nvd,
SeveritySource: vulnerability.NVD,
PrimaryURL: "https://avd.aquasec.com/nvd/CVE-2019-0001",
Vulnerability: dbTypes.Vulnerability{
Title: "DoS",
@@ -405,7 +405,7 @@ func TestConvertFromRPCResults(t *testing.T) {
Title: "DoS",
Description: "Denial of Service",
Severity: common.Severity_MEDIUM,
SeveritySource: vulnerability.Nvd,
SeveritySource: vulnerability.NVD,
CweIds: []string{"CWE-123", "CWE-456"},
Cvss: map[string]*common.CVSS{
"redhat": {
@@ -441,7 +441,7 @@ func TestConvertFromRPCResults(t *testing.T) {
Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812",
DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079",
},
SeveritySource: vulnerability.Nvd,
SeveritySource: vulnerability.NVD,
PrimaryURL: "https://avd.aquasec.com/nvd/CVE-2019-0001",
Vulnerability: dbTypes.Vulnerability{
Title: "DoS",