diff --git a/go.mod b/go.mod index 79eb4687e0..833f2d4e2a 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 885bf624e2..3d28b1e32b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/detector/ospkg/alpine/alpine.go b/pkg/detector/ospkg/alpine/alpine.go index 1cf0c549ad..ae22c44c39 100644 --- a/pkg/detector/ospkg/alpine/alpine.go +++ b/pkg/detector/ospkg/alpine/alpine.go @@ -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(), + 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) } diff --git a/pkg/detector/ospkg/alpine/alpine_test.go b/pkg/detector/ospkg/alpine/alpine_test.go index 2f77c9036c..e2d5eadcc2 100644 --- a/pkg/detector/ospkg/alpine/alpine_test.go +++ b/pkg/detector/ospkg/alpine/alpine_test.go @@ -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 - want []types.DetectedVulnerability - wantErr string + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string }{ { - name: "happy path", + 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", @@ -109,9 +63,10 @@ func TestScanner_Detect(t *testing.T) { }, }, { - name: "contain rc", + 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{ - { - 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{{}}}, - }, + want: []types.DetectedVulnerability{ + { + PkgName: "jq", + VulnerabilityID: "CVE-2020-1234", + InstalledVersion: "1.6-r0", + FixedVersion: "1.6-r1", }, }, }, { - name: "contain pre", + 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", @@ -202,9 +115,10 @@ func TestScanner_Detect(t *testing.T) { }, }, { - name: "Get returns an error", + 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 - osFamily string - osVersion string - expected bool + type args struct { + osFamily string + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool }{ - "alpine3.6": { - now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC), - osFamily: "alpine", - osVersion: "3.6", - expected: true, + { + name: "alpine 3.6", + now: time.Date(2019, 3, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.6", + }, + want: true, }, - "alpine3.6 with EOL": { - now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), - osFamily: "alpine", - osVersion: "3.6.5", - expected: false, + { + name: "alpine 3.6 with EOL", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.6.5", + }, + want: false, }, - "alpine3.9": { - now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), - osFamily: "alpine", - osVersion: "3.9.0", - expected: true, + { + name: "alpine 3.9", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "alpine", + osVer: "3.9.0", + }, + want: true, }, - "alpine3.10": { - now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), - osFamily: "alpine", - osVersion: "3.10", - expected: 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, }, - "unknown": { - now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), - osFamily: "alpine", - osVersion: "unknown", - expected: false, + { + 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) }) } } diff --git a/pkg/detector/ospkg/alpine/testdata/fixtures/alpine310.yaml b/pkg/detector/ospkg/alpine/testdata/fixtures/alpine310.yaml new file mode 100644 index 0000000000..1d56e4c066 --- /dev/null +++ b/pkg/detector/ospkg/alpine/testdata/fixtures/alpine310.yaml @@ -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" diff --git a/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..0250feaf95 --- /dev/null +++ b/pkg/detector/ospkg/alpine/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: alpine 3.10 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/amazon/amazon.go b/pkg/detector/ospkg/amazon/amazon.go index 6554fdd65a..bb199ef7ce 100644 --- a/pkg/detector/ospkg/amazon/amazon.go +++ b/pkg/detector/ospkg/amazon/amazon.go @@ -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 { +func NewScanner(opts ...option) *Scanner { + o := &options{ + l: log.Logger, + clock: clock.RealClock{}, + } + + for _, opt := range opts { + opt(o) + } return &Scanner{ - l: log.Logger, - ac: amazon.NewVulnSrc(), + 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) } diff --git a/pkg/detector/ospkg/amazon/amazon_test.go b/pkg/detector/ospkg/amazon/amazon_test.go index aa33b334a5..58354200f8 100644 --- a/pkg/detector/ospkg/amazon/amazon_test.go +++ b/pkg/detector/ospkg/amazon/amazon_test.go @@ -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 - }, - }, - } - - 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: "123", - PkgName: "testpkg", - InstalledVersion: "2.1.0-hotfix", - FixedVersion: "3.0.0", - 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") - }, - }, - } - 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) - }) - - 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 - }, - }, - } - - 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") - }) - - 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) + type args struct { + osVer string + pkgs []ftypes.Package + } + tests := []struct { + name string + args args + fixtures []string + want []types.DetectedVulnerability + wantErr string + }{ + { + 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", + }, + }, + }, + }, + want: []types.DetectedVulnerability{ + { + 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", + }, + }, + }, + }, + { + 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", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + + 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 + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) } - return loggedMessages } func TestScanner_IsSupportedVersion(t *testing.T) { - vectors := map[string]struct { - now time.Time - osFamily string - osVersion string - expected bool + type args struct { + osFamily string + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool }{ - "1": { - now: time.Date(2022, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "amazon", - osVersion: "1", - expected: true, + { + name: "amazon linux 1", + now: time.Date(2022, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "1", + }, + want: true, }, - "1 (eol ends)": { - now: time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "amazon", - osVersion: "1", - expected: false, + { + name: "amazon linux 1 EOL", + now: time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "amazon", + osVer: "1", + }, + want: false, }, - "2": { - now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), - osFamily: "amazon", - osVersion: "2", - expected: true, + { + name: "amazon linux 2", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "amazon", + 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) }) } - } diff --git a/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml b/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml new file mode 100644 index 0000000000..8c791b0281 --- /dev/null +++ b/pkg/detector/ospkg/amazon/testdata/fixtures/amazon.yaml @@ -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" diff --git a/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..073f4391cf --- /dev/null +++ b/pkg/detector/ospkg/amazon/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: amazon linux 1 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/debian/debian.go b/pkg/detector/ospkg/debian/debian.go index 03a792f620..9268788512 100644 --- a/pkg/detector/ospkg/debian/debian.go +++ b/pkg/detector/ospkg/debian/debian.go @@ -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(), + 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) } diff --git a/pkg/detector/ospkg/debian/debian_test.go b/pkg/detector/ospkg/debian/debian_test.go index 87b00a23e4..cd7bf78a3d 100644 --- a/pkg/detector/ospkg/debian/debian_test.go +++ b/pkg/detector/ospkg/debian/debian_test.go @@ -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 (mdc MockOvalConfig) Update(a string) error { - if mdc.update != nil { - return mdc.update(a) +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package } - return nil -} - -func (mdc MockOvalConfig) Get(a string, b string) ([]dbTypes.Advisory, error) { - if mdc.get != nil { - return mdc.get(a, b) + 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", + }, } - return []dbTypes.Advisory{}, nil -} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() -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) + 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 + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) } - 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 - osFamily string - osVersion string - expected bool + type args struct { + osFamily string + 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), - osFamily: "debian", - osVersion: "7", - expected: false, + { + name: "debian 7", + now: time.Date(2018, 3, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + osVer: "7", + }, + want: true, }, - "debian8": { - now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC), - osFamily: "debian", - osVersion: "8.11", - expected: true, + { + name: "debian 8 EOL", + now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + osVer: "8.2", + }, + want: false, }, - "debian8 eol ends": { - now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), - osFamily: "debian", - osVersion: "8.0", - expected: false, - }, - "debian9": { - now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), - osFamily: "debian", - osVersion: "9", - expected: true, - }, - "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, + { + name: "unknown", + now: time.Date(2020, 7, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "debian", + 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 := 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 -} diff --git a/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml b/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml new file mode 100644 index 0000000000..3875a34f13 --- /dev/null +++ b/pkg/detector/ospkg/debian/testdata/fixtures/debian.yaml @@ -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" diff --git a/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..0f69929273 --- /dev/null +++ b/pkg/detector/ospkg/debian/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: debian oval 9 + pairs: + - bucket: apache2 + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/oracle/oracle.go b/pkg/detector/ospkg/oracle/oracle.go index ee560d4b0e..23f42f7847 100644 --- a/pkg/detector/ospkg/oracle/oracle.go +++ b/pkg/detector/ospkg/oracle/oracle.go @@ -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 } diff --git a/pkg/detector/ospkg/photon/photon.go b/pkg/detector/ospkg/photon/photon.go index 33c42192a5..853baa5387 100644 --- a/pkg/detector/ospkg/photon/photon.go +++ b/pkg/detector/ospkg/photon/photon.go @@ -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{}, + vs: photon.NewVulnSrc(), } } @@ -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 } diff --git a/pkg/detector/ospkg/photon/photon_test.go b/pkg/detector/ospkg/photon/photon_test.go index 6b586aaaa0..fa30bf222e 100644 --- a/pkg/detector/ospkg/photon/photon_test.go +++ b/pkg/detector/ospkg/photon/photon_test.go @@ -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", + 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/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", }, - }, nil + }, }, }, - } + 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() - 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", - }, - }, + 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, tt.want, got) }) - 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) - }) - - // TODO: Add unhappy paths + } } diff --git a/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..43af3dde0b --- /dev/null +++ b/pkg/detector/ospkg/photon/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: Photon OS 1.0 + pairs: + - bucket: PyYAML + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar \ No newline at end of file diff --git a/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml b/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml new file mode 100644 index 0000000000..665dd83319 --- /dev/null +++ b/pkg/detector/ospkg/photon/testdata/fixtures/photon.yaml @@ -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" diff --git a/pkg/detector/ospkg/redhat/redhat.go b/pkg/detector/ospkg/redhat/redhat.go index 19662c01fe..f02b2af184 100644 --- a/pkg/detector/ospkg/redhat/redhat.go +++ b/pkg/detector/ospkg/redhat/redhat.go @@ -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(), + 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 { diff --git a/pkg/detector/ospkg/redhat/redhat_test.go b/pkg/detector/ospkg/redhat/redhat_test.go index 5f859b1616..a01fb17ca9 100644 --- a/pkg/detector/ospkg/redhat/redhat_test.go +++ b/pkg/detector/ospkg/redhat/redhat_test.go @@ -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) { @@ -20,14 +23,15 @@ func TestScanner_Detect(t *testing.T) { pkgs []ftypes.Package } tests := []struct { - name string - args args - get []dbTypes.GetExpectation - want []types.DetectedVulnerability - wantErr bool + name string + fixtures []string + args args + want []types.DetectedVulnerability + wantErr string }{ { - name: "happy path: src pkg name is different from bin pkg name", + 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", @@ -110,9 +80,10 @@ func TestScanner_Detect(t *testing.T) { }, }, { - name: "happy path: src pkg name is the same as bin pkg name", + 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", @@ -171,7 +118,8 @@ func TestScanner_Detect(t *testing.T) { }, }, { - name: "happy path: modular packages", + 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", @@ -221,7 +153,8 @@ func TestScanner_Detect(t *testing.T) { }, }, { - name: "happy path: packages from remi repository are skipped", + 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 - osFamily string - osVersion string - expected bool + type args struct { + osFamily string + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool }{ - "centos5": { - now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "centos", - osVersion: "5.0", - expected: false, + { + name: "centos 6", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "6.8", + }, + want: true, }, - "centos6": { - now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "centos", - osVersion: "6.7", - expected: true, + { + name: "centos 6 EOL", + now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "6.7", + }, + want: false, }, - "centos6 (eol ends)": { - now: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), - osFamily: "centos", - osVersion: "6.7", - expected: false, + { + name: "two dots", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "centos", + osVer: "8.0.1", + }, + want: true, }, - "centos7": { - now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "centos", - osVersion: "7.5", - expected: true, + { + name: "rhel 8", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "redhat", + osVer: "8.0", + }, + want: true, }, - "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": { - now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "redhat", - osVersion: "5.0", - expected: true, - }, - "redhat6": { - now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), - osFamily: "redhat", - osVersion: "6.7", - expected: true, - }, - "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, + { + name: "unknown", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "unknown", + osVer: "8.0", + }, + 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) }) } - } diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..095230aa7d --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: Red Hat Enterprise Linux 6 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml b/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml new file mode 100644 index 0000000000..65a5473265 --- /dev/null +++ b/pkg/detector/ospkg/redhat/testdata/fixtures/redhat.yaml @@ -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" \ No newline at end of file diff --git a/pkg/detector/ospkg/suse/suse.go b/pkg/detector/ospkg/suse/suse.go index e75a7c6415..6f40135ae1 100644 --- a/pkg/detector/ospkg/suse/suse.go +++ b/pkg/detector/ospkg/suse/suse.go @@ -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{}, + vs: susecvrf.NewVulnSrc(susecvrf.SUSEEnterpriseLinux), + options: o, } case OpenSUSE: return &Scanner{ - vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE), - clock: clock.RealClock{}, + vs: susecvrf.NewVulnSrc(susecvrf.OpenSUSE), + options: o, } } return nil diff --git a/pkg/detector/ospkg/suse/suse_test.go b/pkg/detector/ospkg/suse/suse_test.go index 0abfad8532..a8e825965d 100644 --- a/pkg/detector/ospkg/suse/suse_test.go +++ b/pkg/detector/ospkg/suse/suse_test.go @@ -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 (msc MockSuseConfig) Update(a string) error { - if msc.update != nil { - return msc.update(a) +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package } - return nil -} - -func (msc MockSuseConfig) Get(a string, b string) ([]dbTypes.Advisory, error) { - if msc.get != nil { - return msc.get(a, b) + 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() + + 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 + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) } - return []dbTypes.Advisory{}, nil } func TestScanner_IsSupportedVersion(t *testing.T) { - vectors := map[string]struct { - clock clock.Clock - osFamily string - osVersion string - distribution susecvrf.Distribution - expected bool + type args struct { + osFamily string + 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)), - osFamily: "opensuse.leap", - osVersion: "42.3", - distribution: susecvrf.OpenSUSE, - expected: true, + { + name: "opensuse.leap42.3", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "opensuse.leap", + osVer: "42.3", + }, + distribution: suse.OpenSUSE, + want: true, }, - "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, + { + name: "sles12.3", + now: time.Date(2019, 5, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "suse linux enterprise server", + osVer: "12.3", + }, + distribution: suse.SUSEEnterpriseLinux, + want: false, }, - "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)), - osFamily: "suse linux enterprise server", - osVersion: "12.3", - distribution: susecvrf.SUSEEnterpriseLinux, - expected: false, - }, - "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, - }, - "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 } diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..9a2e176c16 --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: SUSE Linux Enterprise 15.3 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml new file mode 100644 index 0000000000..6a17594af6 --- /dev/null +++ b/pkg/detector/ospkg/suse/testdata/fixtures/suse.yaml @@ -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: "" diff --git a/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml b/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml new file mode 100644 index 0000000000..29fcd05cbb --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/testdata/fixtures/invalid.yaml @@ -0,0 +1,9 @@ +- bucket: ubuntu 21.04 + pairs: + - bucket: jq + pairs: + - key: CVE-2020-8177 + value: + FixedVersion: + - foo + - bar diff --git a/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml b/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml new file mode 100644 index 0000000000..7da30a16b2 --- /dev/null +++ b/pkg/detector/ospkg/ubuntu/testdata/fixtures/ubuntu.yaml @@ -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" diff --git a/pkg/detector/ospkg/ubuntu/ubuntu.go b/pkg/detector/ospkg/ubuntu/ubuntu.go index 71cccce020..a6743f51ef 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu.go @@ -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(), + 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) } diff --git a/pkg/detector/ospkg/ubuntu/ubuntu_test.go b/pkg/detector/ospkg/ubuntu/ubuntu_test.go index fdacd03e6f..7454e18422 100644 --- a/pkg/detector/ospkg/ubuntu/ubuntu_test.go +++ b/pkg/detector/ospkg/ubuntu/ubuntu_test.go @@ -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 (muc MockUbuntuConfig) Update(a string) error { - if muc.update != nil { - return muc.update(a) +func TestScanner_Detect(t *testing.T) { + type args struct { + osVer string + pkgs []ftypes.Package } - return nil -} - -func (muc MockUbuntuConfig) Get(a string, b string) ([]dbTypes.Advisory, error) { - if muc.get != nil { - return muc.get(a, b) + 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() + + 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 + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) } - return []dbTypes.Advisory{}, nil } func TestScanner_IsSupportedVersion(t *testing.T) { - vectors := map[string]struct { - now time.Time - osFamily string - osVersion string - expected bool + type args struct { + osFamily string + osVer string + } + tests := []struct { + name string + now time.Time + args args + want bool }{ - "ubuntu12.04 eol ends": { - now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "12.04", - expected: true, + { + name: "ubuntu 12.04 eol ends", + now: time.Date(2019, 3, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "12.04", + }, + want: true, }, - "ubuntu12.04": { - now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "12.04", - expected: false, + { + name: "ubuntu12.04", + now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + osVer: "12.04", + }, + want: false, }, - "ubuntu12.10": { - now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "12.10", - expected: false, - }, - "ubuntu18.04": { - now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "18.04", - expected: true, - }, - "ubuntu19.04": { - now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "19.04", - expected: true, - }, - "unknown": { - now: time.Date(2019, 4, 31, 23, 59, 59, 0, time.UTC), - osFamily: "ubuntu", - osVersion: "unknown", - expected: false, + { + name: "unknown", + now: time.Date(2019, 5, 2, 23, 59, 59, 0, time.UTC), + args: args{ + osFamily: "ubuntu", + 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 := 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 -} diff --git a/pkg/result/result.go b/pkg/result/result.go index c82c5894ec..f346f21db8 100644 --- a/pkg/result/result.go +++ b/pkg/result/result.go @@ -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 == "" { diff --git a/pkg/result/result_test.go b/pkg/result/result_test.go index d52132c2bf..9d3d82b5fc 100644 --- a/pkg/result/result_test.go +++ b/pkg/result/result_test.go @@ -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", diff --git a/pkg/rpc/convert_test.go b/pkg/rpc/convert_test.go index 01c531bc04..c0affc6bb4 100644 --- a/pkg/rpc/convert_test.go +++ b/pkg/rpc/convert_test.go @@ -40,7 +40,7 @@ func TestConvertToRpcPkgs(t *testing.T) { SrcVersion: "1.2.3", SrcRelease: "1", SrcEpoch: 2, - License: "MIT", + License: "MIT", }, }, }, @@ -55,7 +55,7 @@ func TestConvertToRpcPkgs(t *testing.T) { SrcVersion: "1.2.3", SrcRelease: "1", SrcEpoch: 2, - License: "MIT", + License: "MIT", }, }, }, @@ -90,7 +90,7 @@ func TestConvertFromRpcPkgs(t *testing.T) { SrcVersion: "1.2.3", SrcRelease: "1", SrcEpoch: 2, - License: "MIT", + License: "MIT", }, }, }, @@ -105,7 +105,7 @@ func TestConvertFromRpcPkgs(t *testing.T) { SrcVersion: "1.2.3", SrcRelease: "1", SrcEpoch: 2, - License: "MIT", + License: "MIT", }, }, }, @@ -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",