From c9f22f4e55a67516cc4690dd9afebbdd8cb708b9 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sun, 14 Feb 2021 18:19:42 +0200 Subject: [PATCH] feat(java): support jar/war/ear (#837) * refactor(server): remove Detect endpoint * refactor(library): do not use interface * refactor: add dbtest package * test: add bolt fixtures * feat: support jar scanning * refactor: rename node to npm * refactor: fix lint issues * test(maven): remove some tests * chore(mod): update fanal * docs: update README * chore(mod): update trivy-db * fix(library/drive): add ecosystem * fix: do not display 0 vulnerabilities * refactor(table): split method * Update README.md (#838) * fix(app): increase the default value of timeout (#842) * feat(maven): use go-mvn-version * test(maven): update tests * fix(scan): skip files and dirs before vulnerability detection * fix: display log messages only once per type * docs(README): add file suffixes * chore(mod): update go-mvn-version * feat(log): set go-dep-parser logger * chore(mod): update fanal * docs: update README * docs(README): add java source * test(maven): fix invalid case --- README.md | 8 +- go.mod | 15 +- go.sum | 34 +- internal/artifact/wire_gen.go | 17 +- pkg/cache/remote_test.go | 5 +- pkg/dbtest/db.go | 34 + pkg/detector/library/advisory_test.go | 6 +- pkg/detector/library/bundler/advisory_test.go | 8 +- pkg/detector/library/cargo/advisory_test.go | 11 +- .../library/composer/advisory_test.go | 11 +- pkg/detector/library/detect.go | 34 +- pkg/detector/library/driver.go | 73 +- pkg/detector/library/driver_test.go | 25 +- pkg/detector/library/ghsa/advisory_test.go | 8 +- pkg/detector/library/maven/compare.go | 33 + pkg/detector/library/maven/compare_test.go | 87 ++ .../library/{node => npm}/advisory.go | 6 +- .../library/{node => npm}/advisory_test.go | 14 +- pkg/detector/library/{node => npm}/compare.go | 10 +- .../library/{node => npm}/compare_test.go | 6 +- .../testdata/fixtures/invalid-type.yaml | 0 .../testdata/fixtures/no-value.yaml | 0 .../{node => npm}/testdata/fixtures/npm.yaml | 0 pkg/detector/library/python/advisory_test.go | 8 +- pkg/detector/ospkg/oracle/oracle_test.go | 18 +- pkg/log/logger.go | 8 +- pkg/report/writer.go | 73 +- pkg/rpc/server/inject.go | 12 - pkg/rpc/server/library/server.go | 47 - pkg/rpc/server/library/server_test.go | 187 --- pkg/rpc/server/listen.go | 12 +- pkg/rpc/server/ospkg/server.go | 47 - pkg/rpc/server/ospkg/server_test.go | 167 --- pkg/rpc/server/wire_gen.go | 24 +- pkg/scanner/local/mock_library_detector.go | 88 -- pkg/scanner/local/scan.go | 40 +- pkg/scanner/local/scan_test.go | 364 +---- .../local/testdata/fixtures/happy.yaml | 28 + pkg/scanner/local/testdata/fixtures/sad.yaml | 7 + pkg/utils/utils.go | 28 - rpc/detector/service.pb.go | 243 ---- rpc/detector/service.proto | 37 - rpc/detector/service.twirp.go | 1267 ----------------- 43 files changed, 455 insertions(+), 2695 deletions(-) create mode 100644 pkg/dbtest/db.go create mode 100644 pkg/detector/library/maven/compare.go create mode 100644 pkg/detector/library/maven/compare_test.go rename pkg/detector/library/{node => npm}/advisory.go (96%) rename pkg/detector/library/{node => npm}/advisory_test.go (87%) rename pkg/detector/library/{node => npm}/compare.go (74%) rename pkg/detector/library/{node => npm}/compare_test.go (96%) rename pkg/detector/library/{node => npm}/testdata/fixtures/invalid-type.yaml (100%) rename pkg/detector/library/{node => npm}/testdata/fixtures/no-value.yaml (100%) rename pkg/detector/library/{node => npm}/testdata/fixtures/npm.yaml (100%) delete mode 100644 pkg/rpc/server/library/server.go delete mode 100644 pkg/rpc/server/library/server_test.go delete mode 100644 pkg/rpc/server/ospkg/server.go delete mode 100644 pkg/rpc/server/ospkg/server_test.go delete mode 100644 pkg/scanner/local/mock_library_detector.go create mode 100644 pkg/scanner/local/testdata/fixtures/happy.yaml create mode 100644 pkg/scanner/local/testdata/fixtures/sad.yaml delete mode 100644 rpc/detector/service.pb.go delete mode 100644 rpc/detector/service.proto delete mode 100644 rpc/detector/service.twirp.go diff --git a/README.md b/README.md index 83fc9e7499..3ac1cbd01f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ See [here](#continuous-integration-ci) for details. - Detect comprehensive vulnerabilities - OS packages (Alpine, **Red Hat Universal Base Image**, Red Hat Enterprise Linux, CentOS, Oracle Linux, Debian, Ubuntu, Amazon Linux, openSUSE Leap, SUSE Enterprise Linux, Photon OS and Distroless) - - **Application dependencies** (Bundler, Composer, Pipenv, Poetry, npm, yarn, Cargo and NuGet) + - **Application dependencies** (Bundler, Composer, Pipenv, Poetry, npm, yarn, Cargo, NuGet, and Maven) - Simple - Specify only an image name or artifact name - See [Quick Start](#quick-start) and [Examples](#examples) @@ -1763,6 +1763,8 @@ Distroless: https://github.com/GoogleContainerTools/distroless - Cargo.lock - .NET - packages.lock.json +- Java + - JAR/WAR/EAR files (*.jar, *.war, and *.ear) The path of these files does not matter. @@ -1796,6 +1798,8 @@ Trivy scans a tar image with the following format. - https://github.com/RustSec/advisory-db - .NET - https://github.com/advisories?query=ecosystem%3Anuget +- Java + - https://github.com/advisories?query=ecosystem%3Amaven # Usage Trivy has several sub commands, image, fs, repo, client and server. @@ -1921,7 +1925,7 @@ See [here](docs/air-gap.md) | Scanner | OS
Packages | Application
Dependencies | Easy to use | Accuracy | Suitable
for CI | | -------------- | :-------------: | :-------------------------: | :----------: | :---------: | :-----------------: | -| Trivy | ✅ | ✅
(5 languages) | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | +| Trivy | ✅ | ✅
(7 languages) | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | ⭐ ⭐ ⭐ | | Clair | ✅ | × | ⭐ | ⭐ ⭐ | ⭐ ⭐ | | Anchore Engine | ✅ | ✅
(4 languages) | ⭐ ⭐ | ⭐ ⭐ | ⭐ ⭐ ⭐ | | Quay | ✅ | × | ⭐ ⭐ ⭐ | ⭐ ⭐ | × | diff --git a/go.mod b/go.mod index e4f1b10fad..ac231d0ed5 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.15 require ( github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 - github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd - github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0 + github.com/aquasecurity/fanal v0.0.0-20210214122859-98d76e2b3b96 + github.com/aquasecurity/go-dep-parser v0.0.0-20210214113128-b97635cfd627 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce 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-20210105160501-c5bf4e153277 + github.com/aquasecurity/trivy-db v0.0.0-20210214043256-acc144af2228 github.com/caarlos0/env/v6 v6.0.0 github.com/cenkalti/backoff v2.2.1+incompatible github.com/cheggaaa/pb/v3 v3.0.3 @@ -30,6 +30,7 @@ require ( github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 github.com/kylelemons/godebug v1.1.0 + github.com/masahiro331/go-mvn-version v0.0.0-20210214074851-415aa65db8c0 github.com/mattn/go-runewidth v0.0.9 // indirect github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a github.com/open-policy-agent/opa v0.21.1 @@ -37,13 +38,11 @@ require ( github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/afero v1.2.2 github.com/stretchr/objx v0.3.0 // indirect - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/testcontainers/testcontainers-go v0.3.1 github.com/twitchtv/twirp v5.10.1+incompatible github.com/urfave/cli/v2 v2.3.0 - go.uber.org/atomic v1.5.1 // indirect - go.uber.org/multierr v1.4.0 // indirect - go.uber.org/zap v1.13.0 + go.uber.org/zap v1.16.0 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf // indirect @@ -51,6 +50,6 @@ require ( google.golang.org/protobuf v1.25.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/go-playground/validator.v9 v9.31.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/utils v0.0.0-20201110183641-67b214c5f920 ) diff --git a/go.sum b/go.sum index 41817fefed..3057895209 100644 --- a/go.sum +++ b/go.sum @@ -91,10 +91,10 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM= github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= -github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd h1:meqa2AA+7K1r/nfNB19K2AP/v8+nemuWeQoTSqZ2R9s= -github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd/go.mod h1:kur6SaohYhsjQLzijAdtn+X8rkTtwxawE51WyVCXLKk= -github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0 h1:cLH3SebzhbJ+jU1GIad8A1N8p7m7OjHhtY6JePISiVc= -github.com/aquasecurity/go-dep-parser v0.0.0-20201028043324-889d4a92b8e0/go.mod h1:X42mTIRhgPalSm81Om2kD+3ydeunbC8TZtZj1bvgRo8= +github.com/aquasecurity/fanal v0.0.0-20210214122859-98d76e2b3b96 h1:5Uk/RmXp+Itbm3fLa0vg1OKBl6Z6BfxDVrJpwETYqWk= +github.com/aquasecurity/fanal v0.0.0-20210214122859-98d76e2b3b96/go.mod h1:8HoL/kipOrDodHh+jzGgO2/iXJoRf6roLP5qPv8Mi90= +github.com/aquasecurity/go-dep-parser v0.0.0-20210214113128-b97635cfd627 h1:ccLbhkZ54xTmI6Q+CyBL8sll+OXIEzX0aDDjmy8pVWQ= +github.com/aquasecurity/go-dep-parser v0.0.0-20210214113128-b97635cfd627/go.mod h1:KHiF3uK6FOE1v27YK+5CWEJ1jd0OvA2Gmk8VFjT3utk= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 h1:eveqE9ivrt30CJ7dOajOfBavhZ4zPqHcZe/4tKp0alc= @@ -107,8 +107,8 @@ github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU= github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674 h1:Xq/HxWFGaB4G/prC6czH/F5woB91GMCCilJxs/5DnDk= github.com/aquasecurity/testdocker v0.0.0-20210106133225-0b17fe083674/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs= -github.com/aquasecurity/trivy-db v0.0.0-20210105160501-c5bf4e153277 h1:lXN72H9uNM1exUArIsN++n7b67PIaqhZON2cVWxwmpg= -github.com/aquasecurity/trivy-db v0.0.0-20210105160501-c5bf4e153277/go.mod h1:N7CWA/vjVw78GWAdCJGhFQVqNGEA4e47a6eIWm+C/Bc= +github.com/aquasecurity/trivy-db v0.0.0-20210214043256-acc144af2228 h1:Hl1eKVUApdGdMUNiSYWekViWccauKxscorOV2bc1cJE= +github.com/aquasecurity/trivy-db v0.0.0-20210214043256-acc144af2228/go.mod h1:N7CWA/vjVw78GWAdCJGhFQVqNGEA4e47a6eIWm+C/Bc= github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA= 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= @@ -429,6 +429,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/masahiro331/go-mvn-version v0.0.0-20210214074851-415aa65db8c0 h1:GT77MM4NtCZv3oeOlY+Y7EANZ86/h49oqPytEKTm3f8= +github.com/masahiro331/go-mvn-version v0.0.0-20210214074851-415aa65db8c0/go.mod h1:M5wLEr5YKZ6OcOpiNGO0qPE4cd+xjUbBLlBvJGhZgWQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -584,6 +586,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/testcontainers/testcontainers-go v0.3.1 h1:KZkEKNfnlsipJblzGCz6fmzd+0DzJ3djulYrislG3Zw= github.com/testcontainers/testcontainers-go v0.3.1/go.mod h1:br7bkzIukhPSIjy07Ma3OuXjjFvl2jm7CDU0LQNsqLw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -624,18 +628,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ= go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= -go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= -go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -993,8 +995,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/internal/artifact/wire_gen.go b/internal/artifact/wire_gen.go index 477e405fac..c1e2287120 100644 --- a/internal/artifact/wire_gen.go +++ b/internal/artifact/wire_gen.go @@ -14,7 +14,6 @@ import ( "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/fanal/image" "github.com/aquasecurity/trivy-db/pkg/db" - "github.com/aquasecurity/trivy/pkg/detector/library" "github.com/aquasecurity/trivy/pkg/detector/ospkg" "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/scanner/local" @@ -28,9 +27,7 @@ import ( func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} - driverFactory := library.DriverFactory{} - libraryDetector := library.NewDetector(driverFactory) - localScanner := local.NewScanner(applierApplier, detector, libraryDetector) + localScanner := local.NewScanner(applierApplier, detector) dockerOption, err := types.GetDockerOption(timeout) if err != nil { return scanner.Scanner{}, nil, err @@ -49,9 +46,7 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration) (scanner.Scanner, error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} - driverFactory := library.DriverFactory{} - libraryDetector := library.NewDetector(driverFactory) - localScanner := local.NewScanner(applierApplier, detector, libraryDetector) + localScanner := local.NewScanner(applierApplier, detector) imageImage, err := image.NewArchiveImage(filePath) if err != nil { return scanner.Scanner{}, err @@ -64,9 +59,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} - driverFactory := library.DriverFactory{} - libraryDetector := library.NewDetector(driverFactory) - localScanner := local.NewScanner(applierApplier, detector, libraryDetector) + localScanner := local.NewScanner(applierApplier, detector) artifact := local2.NewArtifact(dir, artifactCache) scannerScanner := scanner.NewScanner(localScanner, artifact) return scannerScanner, func() { @@ -76,9 +69,7 @@ func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} - driverFactory := library.DriverFactory{} - libraryDetector := library.NewDetector(driverFactory) - localScanner := local.NewScanner(applierApplier, detector, libraryDetector) + localScanner := local.NewScanner(applierApplier, detector) artifact, cleanup, err := remote.NewArtifact(url, artifactCache) if err != nil { return scanner.Scanner{}, nil, err diff --git a/pkg/cache/remote_test.go b/pkg/cache/remote_test.go index 28d41f1994..12db8d1c1f 100644 --- a/pkg/cache/remote_test.go +++ b/pkg/cache/remote_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" + google_protobuf "github.com/golang/protobuf/ptypes/empty" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +20,6 @@ import ( "github.com/aquasecurity/fanal/types" "github.com/aquasecurity/trivy/pkg/cache" rpcCache "github.com/aquasecurity/trivy/rpc/cache" - "github.com/aquasecurity/trivy/rpc/detector" ) type mockCacheServer struct { @@ -53,7 +54,7 @@ func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBl func withToken(base http.Handler, token, tokenHeader string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if token != "" && token != r.Header.Get(tokenHeader) { - detector.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) + rpcScanner.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) return } base.ServeHTTP(w, r) diff --git a/pkg/dbtest/db.go b/pkg/dbtest/db.go new file mode 100644 index 0000000000..05700a22e4 --- /dev/null +++ b/pkg/dbtest/db.go @@ -0,0 +1,34 @@ +package dbtest + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + fixtures "github.com/aquasecurity/bolt-fixtures" + "github.com/aquasecurity/trivy-db/pkg/db" +) + +// InitDB initializes testing database. +func InitDB(t *testing.T, fixtureFiles []string) string { + // Create a temp dir + dir := t.TempDir() + + dbPath := db.Path(dir) + dbDir := filepath.Dir(dbPath) + err := os.MkdirAll(dbDir, 0700) + require.NoError(t, err) + + // Load testdata into BoltDB + loader, err := fixtures.New(dbPath, fixtureFiles) + require.NoError(t, err) + require.NoError(t, loader.Load()) + require.NoError(t, loader.Close()) + + // Initialize DB + require.NoError(t, db.Init(dir)) + + return dir +} diff --git a/pkg/detector/library/advisory_test.go b/pkg/detector/library/advisory_test.go index 810040faf2..7f14cffee1 100644 --- a/pkg/detector/library/advisory_test.go +++ b/pkg/detector/library/advisory_test.go @@ -1,7 +1,6 @@ package library_test import ( - "os" "testing" "github.com/stretchr/testify/assert" @@ -9,11 +8,11 @@ import ( "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library" "github.com/aquasecurity/trivy/pkg/detector/library/bundler" "github.com/aquasecurity/trivy/pkg/detector/library/comparer" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -98,8 +97,7 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Initialize DB - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) defer db.Close() adv := library.NewAdvisory(tt.ecosystem, tt.comparer) diff --git a/pkg/detector/library/bundler/advisory_test.go b/pkg/detector/library/bundler/advisory_test.go index 5d970a0f41..bd9f315316 100644 --- a/pkg/detector/library/bundler/advisory_test.go +++ b/pkg/detector/library/bundler/advisory_test.go @@ -1,16 +1,16 @@ package bundler_test import ( - "os" "testing" "github.com/aquasecurity/trivy/pkg/detector/library/bundler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -65,8 +65,8 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() a := bundler.NewAdvisory() got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/library/cargo/advisory_test.go b/pkg/detector/library/cargo/advisory_test.go index 760c132c3a..8f5dfe3582 100644 --- a/pkg/detector/library/cargo/advisory_test.go +++ b/pkg/detector/library/cargo/advisory_test.go @@ -1,17 +1,16 @@ package cargo_test import ( - "os" "testing" - "github.com/aquasecurity/trivy/pkg/detector/library/cargo" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/library/cargo" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -81,8 +80,8 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() a := cargo.NewAdvisory() got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/library/composer/advisory_test.go b/pkg/detector/library/composer/advisory_test.go index 64906d475e..af726cacbc 100644 --- a/pkg/detector/library/composer/advisory_test.go +++ b/pkg/detector/library/composer/advisory_test.go @@ -1,17 +1,16 @@ package composer_test import ( - "os" "testing" - "github.com/aquasecurity/trivy/pkg/detector/library/composer" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/library/composer" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -65,8 +64,8 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() a := composer.NewAdvisory() got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/library/detect.go b/pkg/detector/library/detect.go index fa18a2683a..dea5b85e11 100644 --- a/pkg/detector/library/detect.go +++ b/pkg/detector/library/detect.go @@ -1,44 +1,15 @@ package library import ( - "path/filepath" - "time" - - "github.com/google/wire" "golang.org/x/xerrors" ftypes "github.com/aquasecurity/fanal/types" - "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) -// SuperSet binds the dependencies for library scan -var SuperSet = wire.NewSet( - wire.Struct(new(DriverFactory)), - wire.Bind(new(Factory), new(DriverFactory)), - NewDetector, - wire.Bind(new(Operation), new(Detector)), -) - -// Operation defines library scan operations -type Operation interface { - Detect(imageName string, filePath string, created time.Time, pkgs []ftypes.LibraryInfo) (vulns []types.DetectedVulnerability, err error) -} - -// Detector implements driverFactory -type Detector struct { - driverFactory Factory -} - -// NewDetector is the factory method for detector -func NewDetector(factory Factory) Detector { - return Detector{driverFactory: factory} -} - // Detect scans and returns vulnerabilities of library -func (d Detector) Detect(_, filePath string, _ time.Time, pkgs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) { - log.Logger.Debugf("Detecting library vulnerabilities, path: %s", filePath) - driver, err := d.driverFactory.NewDriver(filepath.Base(filePath)) +func Detect(libType string, pkgs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) { + driver, err := NewDriver(libType) if err != nil { return nil, xerrors.Errorf("failed to new driver: %w", err) } @@ -52,7 +23,6 @@ func (d Detector) Detect(_, filePath string, _ time.Time, pkgs []ftypes.LibraryI } func detect(driver Driver, libs []ftypes.LibraryInfo) ([]types.DetectedVulnerability, error) { - log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type()) var vulnerabilities []types.DetectedVulnerability for _, lib := range libs { vulns, err := driver.Detect(lib.Library.Name, lib.Library.Version) diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index 07228d0db3..5f27e549a9 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -1,10 +1,9 @@ package library import ( - "fmt" - "golang.org/x/xerrors" + "github.com/aquasecurity/fanal/analyzer/library" ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/detector/library/bundler" @@ -12,41 +11,36 @@ import ( "github.com/aquasecurity/trivy/pkg/detector/library/comparer" "github.com/aquasecurity/trivy/pkg/detector/library/composer" "github.com/aquasecurity/trivy/pkg/detector/library/ghsa" - "github.com/aquasecurity/trivy/pkg/detector/library/node" + "github.com/aquasecurity/trivy/pkg/detector/library/maven" + "github.com/aquasecurity/trivy/pkg/detector/library/npm" "github.com/aquasecurity/trivy/pkg/detector/library/python" "github.com/aquasecurity/trivy/pkg/types" ) -// Factory defines library operations -type Factory interface { - NewDriver(filename string) (Driver, error) -} - type advisory interface { DetectVulnerabilities(string, string) ([]types.DetectedVulnerability, error) } -// DriverFactory implements Factory -type DriverFactory struct{} - -// NewDriver factory method for driver -func (d DriverFactory) NewDriver(filename string) (Driver, error) { +// NewDriver returns a driver according to the library type +func NewDriver(libType string) (Driver, error) { var driver Driver - switch filename { - case "Gemfile.lock": + switch libType { + case library.Bundler: driver = newRubyGemsDriver() - case "Cargo.lock": + case library.Cargo: driver = newCargoDriver() - case "composer.lock": + case library.Composer: driver = newComposerDriver() - case "package-lock.json", "yarn.lock": + case library.Npm, library.Yarn: driver = newNpmDriver() - case "Pipfile.lock", "poetry.lock": + case library.Pipenv, library.Poetry: driver = newPipDriver() - case "packages.lock.json": + case library.NuGet: driver = newNugetDriver() + case library.Jar: + driver = newMavenDriver() default: - return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename)) + return Driver{}, xerrors.Errorf("unsupported type %s", libType) } return driver, nil } @@ -57,17 +51,17 @@ type Driver struct { advisories []advisory } -// NewDriver is the factory method from drier -func NewDriver(advisories ...advisory) Driver { - return Driver{advisories: advisories} +// Aggregate aggregates drivers +func Aggregate(ecosystem string, advisories ...advisory) Driver { + return Driver{ecosystem: ecosystem, advisories: advisories} } // Detect scans and returns vulnerabilities func (d *Driver) Detect(pkgName string, pkgVer string) ([]types.DetectedVulnerability, error) { var detectedVulnerabilities []types.DetectedVulnerability uniqVulnIDMap := make(map[string]struct{}) - for _, d := range d.advisories { - vulns, err := d.DetectVulnerabilities(pkgName, pkgVer) + for _, adv := range d.advisories { + vulns, err := adv.DetectVulnerabilities(pkgName, pkgVer) if err != nil { return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err) } @@ -90,34 +84,41 @@ func (d *Driver) Type() string { func newRubyGemsDriver() Driver { c := bundler.RubyGemsComparer{} - return NewDriver(ghsa.NewAdvisory(ecosystem.Rubygems, c), bundler.NewAdvisory(), + return Aggregate(vulnerability.RubyGems, ghsa.NewAdvisory(ecosystem.Rubygems, c), bundler.NewAdvisory(), NewAdvisory(vulnerability.RubyGems, c)) } func newComposerDriver() Driver { c := comparer.GenericComparer{} - return NewDriver( - ghsa.NewAdvisory(ecosystem.Composer, c), composer.NewAdvisory(), + return Aggregate(vulnerability.Composer, ghsa.NewAdvisory(ecosystem.Composer, c), composer.NewAdvisory(), NewAdvisory(vulnerability.Composer, c)) } func newCargoDriver() Driver { - return NewDriver(cargo.NewAdvisory(), NewAdvisory(vulnerability.Cargo, comparer.GenericComparer{})) + return Aggregate(vulnerability.Cargo, cargo.NewAdvisory(), + NewAdvisory(vulnerability.Cargo, comparer.GenericComparer{})) } func newNpmDriver() Driver { - c := node.NpmComparer{} - return NewDriver(ghsa.NewAdvisory(ecosystem.Npm, c), node.NewAdvisory(), - NewAdvisory(vulnerability.Npm, c)) + c := npm.Comparer{} + return Aggregate(vulnerability.Npm, ghsa.NewAdvisory(ecosystem.Npm, c), + npm.NewAdvisory(), NewAdvisory(vulnerability.Npm, c)) } func newPipDriver() Driver { c := comparer.GenericComparer{} - return NewDriver(ghsa.NewAdvisory(ecosystem.Pip, c), python.NewAdvisory(), - NewAdvisory(vulnerability.Pip, c)) + return Aggregate(vulnerability.Pip, ghsa.NewAdvisory(ecosystem.Pip, c), + python.NewAdvisory(), NewAdvisory(vulnerability.Pip, c)) } func newNugetDriver() Driver { c := comparer.GenericComparer{} - return NewDriver(ghsa.NewAdvisory(ecosystem.Nuget, c), NewAdvisory(vulnerability.NuGet, c)) + return Aggregate(vulnerability.NuGet, ghsa.NewAdvisory(ecosystem.Nuget, c), + NewAdvisory(vulnerability.NuGet, c)) +} + +func newMavenDriver() Driver { + c := maven.Comparer{} + return Aggregate(vulnerability.Maven, ghsa.NewAdvisory(ecosystem.Maven, c), + NewAdvisory(vulnerability.Maven, c)) } diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go index bd5162476a..b1f0e3e1b4 100644 --- a/pkg/detector/library/driver_test.go +++ b/pkg/detector/library/driver_test.go @@ -1,22 +1,19 @@ package library_test import ( - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + lib "github.com/aquasecurity/fanal/analyzer/library" "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestDriver_Detect(t *testing.T) { - type fields struct { - fileName string - } type args struct { pkgName string pkgVer string @@ -24,7 +21,7 @@ func TestDriver_Detect(t *testing.T) { tests := []struct { name string fixtures []string - fields fields + libType string args args want []types.DetectedVulnerability wantErr string @@ -32,7 +29,7 @@ func TestDriver_Detect(t *testing.T) { { name: "happy path", fixtures: []string{"testdata/fixtures/php.yaml"}, - fields: fields{fileName: "composer.lock"}, + libType: lib.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.2.6", @@ -49,7 +46,7 @@ func TestDriver_Detect(t *testing.T) { { name: "non-prefix buckets", fixtures: []string{"testdata/fixtures/php-without-prefix.yaml"}, - fields: fields{fileName: "composer.lock"}, + libType: lib.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.2.6", @@ -66,7 +63,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no patched versions in the advisory", fixtures: []string{"testdata/fixtures/php.yaml"}, - fields: fields{fileName: "composer.lock"}, + libType: lib.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.4.6", @@ -83,7 +80,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no vulnerable versions in the advisory", fixtures: []string{"testdata/fixtures/ruby.yaml"}, - fields: fields{fileName: "Gemfile.lock"}, + libType: lib.Bundler, args: args{ pkgName: "activesupport", pkgVer: "4.1.1", @@ -100,7 +97,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no vulnerability", fixtures: []string{"testdata/fixtures/php.yaml"}, - fields: fields{fileName: "composer.lock"}, + libType: lib.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.4.7", @@ -110,12 +107,10 @@ func TestDriver_Detect(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Initialize DB - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) defer db.Close() - factory := library.DriverFactory{} - driver, err := factory.NewDriver(tt.fields.fileName) + driver, err := library.NewDriver(tt.libType) require.NoError(t, err) got, err := driver.Detect(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/library/ghsa/advisory_test.go b/pkg/detector/library/ghsa/advisory_test.go index 17f7cb60f5..b0113bf524 100644 --- a/pkg/detector/library/ghsa/advisory_test.go +++ b/pkg/detector/library/ghsa/advisory_test.go @@ -1,18 +1,18 @@ package ghsa_test import ( - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" ghsaSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library/comparer" "github.com/aquasecurity/trivy/pkg/detector/library/ghsa" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -103,8 +103,8 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() a := ghsa.NewAdvisory(tt.fields.ecosystem, tt.fields.comparer) got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/library/maven/compare.go b/pkg/detector/library/maven/compare.go new file mode 100644 index 0000000000..5218cb2c5e --- /dev/null +++ b/pkg/detector/library/maven/compare.go @@ -0,0 +1,33 @@ +package maven + +import ( + "golang.org/x/xerrors" + + version "github.com/masahiro331/go-mvn-version" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/detector/library/comparer" +) + +// Comparer represents a comparer for maven +type Comparer struct{} + +// IsVulnerable checks if the package version is vulnerable to the advisory. +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { + return comparer.IsVulnerable(ver, advisory, n.matchVersion) +} + +// matchVersion checks if the package version satisfies the given constraint. +func (n Comparer) matchVersion(currentVersion, constraint string) (bool, error) { + v, err := version.NewVersion(currentVersion) + if err != nil { + return false, xerrors.Errorf("maven version error (%s): %s", currentVersion, err) + } + + c, err := version.NewConstraints(constraint) + if err != nil { + return false, xerrors.Errorf("maven constraint error (%s): %s", constraint, err) + } + + return c.Check(v), nil +} diff --git a/pkg/detector/library/maven/compare_test.go b/pkg/detector/library/maven/compare_test.go new file mode 100644 index 0000000000..eafd08dadf --- /dev/null +++ b/pkg/detector/library/maven/compare_test.go @@ -0,0 +1,87 @@ +package maven_test + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/detector/library/maven" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" +) + +func TestComparer_IsVulnerable(t *testing.T) { + type args struct { + currentVersion string + advisory dbTypes.Advisory + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "happy path", + args: args{ + currentVersion: "1.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<=1.0"}, + PatchedVersions: []string{">=1.1"}, + }, + }, + want: true, + }, + { + name: "final release", + args: args{ + currentVersion: "1.2.3.final", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.2.3"}, + PatchedVersions: []string{"1.2.3"}, + }, + }, + want: false, + }, + { + name: "pre-release", + args: args{ + currentVersion: "1.2.3-a1", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{"<1.2.3"}, + PatchedVersions: []string{">=1.2.3"}, + }, + }, + want: true, + }, + { + name: "multiple constraints", + args: args{ + currentVersion: "2.0.0", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"}, + PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"}, + }, + }, + want: true, + }, + { + name: "invalid constraint", + args: args{ + currentVersion: "1.2.3", + advisory: dbTypes.Advisory{ + VulnerableVersions: []string{`<1.0\.0`}, + }, + }, + want: false, + }, + } + log.InitLogger(false, false) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := maven.Comparer{} + got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/node/advisory.go b/pkg/detector/library/npm/advisory.go similarity index 96% rename from pkg/detector/library/node/advisory.go rename to pkg/detector/library/npm/advisory.go index f0689a1359..17cb6b56e5 100644 --- a/pkg/detector/library/node/advisory.go +++ b/pkg/detector/library/npm/advisory.go @@ -1,4 +1,4 @@ -package node +package npm import ( "strings" @@ -12,7 +12,7 @@ import ( // Advisory encapsulate Node vulnerability source type Advisory struct { - comparer NpmComparer + comparer Comparer vs node.VulnSrc } @@ -20,7 +20,7 @@ type Advisory struct { func NewAdvisory() *Advisory { return &Advisory{ vs: node.NewVulnSrc(), - comparer: NpmComparer{}, + comparer: Comparer{}, } } diff --git a/pkg/detector/library/node/advisory_test.go b/pkg/detector/library/npm/advisory_test.go similarity index 87% rename from pkg/detector/library/node/advisory_test.go rename to pkg/detector/library/npm/advisory_test.go index bbc73c5cb5..3f2f308bd7 100644 --- a/pkg/detector/library/node/advisory_test.go +++ b/pkg/detector/library/npm/advisory_test.go @@ -1,16 +1,16 @@ -package node_test +package npm_test import ( - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/detector/library/node" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/detector/library/npm" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -72,10 +72,10 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() - a := node.NewAdvisory() + a := npm.NewAdvisory() got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) if tt.wantErr != "" { require.NotNil(t, err) diff --git a/pkg/detector/library/node/compare.go b/pkg/detector/library/npm/compare.go similarity index 74% rename from pkg/detector/library/node/compare.go rename to pkg/detector/library/npm/compare.go index 3533de0465..847254c9a8 100644 --- a/pkg/detector/library/node/compare.go +++ b/pkg/detector/library/npm/compare.go @@ -1,4 +1,4 @@ -package node +package npm import ( "golang.org/x/xerrors" @@ -8,16 +8,16 @@ import ( "github.com/aquasecurity/trivy/pkg/detector/library/comparer" ) -// NpmComparer represents a comparer for npm -type NpmComparer struct{} +// Comparer represents a comparer for npm +type Comparer struct{} // IsVulnerable checks if the package version is vulnerable to the advisory. -func (n NpmComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { +func (n Comparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool { return comparer.IsVulnerable(ver, advisory, n.matchVersion) } // matchVersion checks if the package version satisfies the given constraint. -func (n NpmComparer) matchVersion(currentVersion, constraint string) (bool, error) { +func (n Comparer) matchVersion(currentVersion, constraint string) (bool, error) { v, err := npm.NewVersion(currentVersion) if err != nil { return false, xerrors.Errorf("npm version error (%s): %s", currentVersion, err) diff --git a/pkg/detector/library/node/compare_test.go b/pkg/detector/library/npm/compare_test.go similarity index 96% rename from pkg/detector/library/node/compare_test.go rename to pkg/detector/library/npm/compare_test.go index 91fe7df451..57549f0b4b 100644 --- a/pkg/detector/library/node/compare_test.go +++ b/pkg/detector/library/npm/compare_test.go @@ -1,4 +1,4 @@ -package node_test +package npm_test import ( "testing" @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/detector/library/node" + "github.com/aquasecurity/trivy/pkg/detector/library/npm" "github.com/aquasecurity/trivy/pkg/log" ) @@ -132,7 +132,7 @@ func TestNpmComparer_IsVulnerable(t *testing.T) { log.InitLogger(false, false) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := node.NpmComparer{} + c := npm.Comparer{} got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory) assert.Equal(t, tt.want, got) }) diff --git a/pkg/detector/library/node/testdata/fixtures/invalid-type.yaml b/pkg/detector/library/npm/testdata/fixtures/invalid-type.yaml similarity index 100% rename from pkg/detector/library/node/testdata/fixtures/invalid-type.yaml rename to pkg/detector/library/npm/testdata/fixtures/invalid-type.yaml diff --git a/pkg/detector/library/node/testdata/fixtures/no-value.yaml b/pkg/detector/library/npm/testdata/fixtures/no-value.yaml similarity index 100% rename from pkg/detector/library/node/testdata/fixtures/no-value.yaml rename to pkg/detector/library/npm/testdata/fixtures/no-value.yaml diff --git a/pkg/detector/library/node/testdata/fixtures/npm.yaml b/pkg/detector/library/npm/testdata/fixtures/npm.yaml similarity index 100% rename from pkg/detector/library/node/testdata/fixtures/npm.yaml rename to pkg/detector/library/npm/testdata/fixtures/npm.yaml diff --git a/pkg/detector/library/python/advisory_test.go b/pkg/detector/library/python/advisory_test.go index bc078e132e..6bf04b2250 100644 --- a/pkg/detector/library/python/advisory_test.go +++ b/pkg/detector/library/python/advisory_test.go @@ -1,16 +1,16 @@ package python_test import ( - "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library/python" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) func TestAdvisory_DetectVulnerabilities(t *testing.T) { @@ -65,8 +65,8 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() a := python.NewAdvisory() got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) diff --git a/pkg/detector/ospkg/oracle/oracle_test.go b/pkg/detector/ospkg/oracle/oracle_test.go index 3c044243d8..de02321138 100644 --- a/pkg/detector/ospkg/oracle/oracle_test.go +++ b/pkg/detector/ospkg/oracle/oracle_test.go @@ -5,17 +5,17 @@ import ( "testing" "time" - ftypes "github.com/aquasecurity/fanal/types" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - oracleoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/oracle-oval" - "github.com/aquasecurity/trivy/pkg/log" - "k8s.io/utils/clock" clocktesting "k8s.io/utils/clock/testing" + + ftypes "github.com/aquasecurity/fanal/types" + "github.com/aquasecurity/trivy-db/pkg/db" + oracleoval "github.com/aquasecurity/trivy-db/pkg/vulnsrc/oracle-oval" + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/types" ) func TestMain(m *testing.M) { @@ -209,8 +209,8 @@ func TestScanner_Detect(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - dir := utils.InitTestDB(t, tt.fixtures) - defer os.RemoveAll(dir) + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() s := NewScanner() got, err := s.Detect(tt.args.osVer, tt.args.pkgs) diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 655f60c683..dccfbef259 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -6,6 +6,8 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "golang.org/x/xerrors" + + "github.com/aquasecurity/go-dep-parser/pkg/log" ) var ( @@ -19,8 +21,12 @@ func InitLogger(debug, disable bool) (err error) { debugOption = debug Logger, err = NewLogger(debug, disable) if err != nil { - return xerrors.Errorf("error in new logger: %w", err) + return xerrors.Errorf("failed to initialize a logger: %w", err) } + + // Set logger for go-dep-parser + log.SetLogger(Logger) + return nil } diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 0180ac8091..ca560d109e 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -74,13 +74,14 @@ type TableWriter struct { // Write writes the result on standard output func (tw TableWriter) Write(results Results) error { for _, result := range results { + if len(result.Vulnerabilities) == 0 { + continue + } tw.write(result) } return nil } -// nolint: gocyclo -// TODO: refactror and fix cyclometic complexity func (tw TableWriter) write(result Result) { table := tablewriter.NewWriter(tw.Output) header := []string{"Library", "Vulnerability ID", "Severity", "Installed Version", "Fixed Version"} @@ -88,38 +89,7 @@ func (tw TableWriter) write(result Result) { header = append(header, "Title") } table.SetHeader(header) - - severityCount := map[string]int{} - for _, v := range result.Vulnerabilities { - severityCount[v.Severity]++ - - title := v.Title - if title == "" { - title = v.Description - } - splittedTitle := strings.Split(title, " ") - if len(splittedTitle) >= 12 { - title = strings.Join(splittedTitle[:12], " ") + "..." - } - - if len(v.PrimaryURL) > 0 { - r := strings.NewReplacer("https://", "", "http://", "") - title = fmt.Sprintf("%s -->%s", title, r.Replace(v.PrimaryURL)) - } - - var row []string - if tw.Output == os.Stdout { - row = []string{v.PkgName, v.VulnerabilityID, dbTypes.ColorizeSeverity(v.Severity), - v.InstalledVersion, v.FixedVersion} - } else { - row = []string{v.PkgName, v.VulnerabilityID, v.Severity, v.InstalledVersion, v.FixedVersion} - } - - if !tw.Light { - row = append(row, strings.TrimSpace(title)) - } - table.Append(row) - } + severityCount := tw.setRows(table, result.Vulnerabilities) var results []string @@ -150,6 +120,41 @@ func (tw TableWriter) write(result Result) { return } +func (tw TableWriter) setRows(table *tablewriter.Table, vulns []types.DetectedVulnerability) map[string]int { + severityCount := map[string]int{} + for _, v := range vulns { + severityCount[v.Severity]++ + + title := v.Title + if title == "" { + title = v.Description + } + splitTitle := strings.Split(title, " ") + if len(splitTitle) >= 12 { + title = strings.Join(splitTitle[:12], " ") + "..." + } + + if len(v.PrimaryURL) > 0 { + r := strings.NewReplacer("https://", "", "http://", "") + title = fmt.Sprintf("%s -->%s", title, r.Replace(v.PrimaryURL)) + } + + var row []string + if tw.Output == os.Stdout { + row = []string{v.PkgName, v.VulnerabilityID, dbTypes.ColorizeSeverity(v.Severity), + v.InstalledVersion, v.FixedVersion} + } else { + row = []string{v.PkgName, v.VulnerabilityID, v.Severity, v.InstalledVersion, v.FixedVersion} + } + + if !tw.Light { + row = append(row, strings.TrimSpace(title)) + } + table.Append(row) + } + return severityCount +} + // JSONWriter implements result Writer type JSONWriter struct { Output io.Writer diff --git a/pkg/rpc/server/inject.go b/pkg/rpc/server/inject.go index b9dc81103d..06dd02ce29 100644 --- a/pkg/rpc/server/inject.go +++ b/pkg/rpc/server/inject.go @@ -4,8 +4,6 @@ package server import ( "github.com/aquasecurity/fanal/cache" - "github.com/aquasecurity/trivy/pkg/rpc/server/library" - "github.com/aquasecurity/trivy/pkg/rpc/server/ospkg" "github.com/google/wire" ) @@ -14,16 +12,6 @@ func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServ return &ScanServer{} } -func initializeOspkgServer() *ospkg.Server { - wire.Build(ospkg.SuperSet) - return &ospkg.Server{} -} - -func initializeLibServer() *library.Server { - wire.Build(library.SuperSet) - return &library.Server{} -} - func initializeDBWorker(cacheDir string, quiet bool) dbWorker { wire.Build(DBWorkerSuperSet) return dbWorker{} diff --git a/pkg/rpc/server/library/server.go b/pkg/rpc/server/library/server.go deleted file mode 100644 index f08649b340..0000000000 --- a/pkg/rpc/server/library/server.go +++ /dev/null @@ -1,47 +0,0 @@ -package library - -import ( - "context" - "time" - - "github.com/google/wire" - "golang.org/x/xerrors" - - detector "github.com/aquasecurity/trivy/pkg/detector/library" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/rpc" - "github.com/aquasecurity/trivy/pkg/vulnerability" - proto "github.com/aquasecurity/trivy/rpc/detector" -) - -// SuperSet binds the dependencies for library RPC server -var SuperSet = wire.NewSet( - detector.SuperSet, - vulnerability.SuperSet, - NewServer, -) - -// Server is for backward compatibility -type Server struct { - detector detector.Operation - vulnClient vulnerability.Operation -} - -// NewServer is the facotry method for Server -func NewServer(detector detector.Operation, vulnClient vulnerability.Operation) *Server { - return &Server{detector: detector, vulnClient: vulnClient} -} - -// Detect is for backward compatibility -func (s *Server) Detect(_ context.Context, req *proto.LibDetectRequest) (res *proto.DetectResponse, err error) { - vulns, err := s.detector.Detect("", req.FilePath, time.Time{}, rpc.ConvertFromRPCLibraries(req.Libraries)) - if err != nil { - err = xerrors.Errorf("failed to detect library vulnerabilities: %w", err) - log.Logger.Error(err) - return nil, err - } - - s.vulnClient.FillInfo(vulns, "") - - return &proto.DetectResponse{Vulnerabilities: rpc.ConvertToRPCVulns(vulns)}, nil -} diff --git a/pkg/rpc/server/library/server_test.go b/pkg/rpc/server/library/server_test.go deleted file mode 100644 index 5af70fc43e..0000000000 --- a/pkg/rpc/server/library/server_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package library - -import ( - "context" - "os" - "testing" - - "github.com/golang/protobuf/ptypes/timestamp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - ftypes "github.com/aquasecurity/fanal/types" - ptypes "github.com/aquasecurity/go-dep-parser/pkg/types" - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy-db/pkg/utils" - "github.com/aquasecurity/trivy/pkg/detector/library" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/vulnerability" - "github.com/aquasecurity/trivy/rpc/common" - proto "github.com/aquasecurity/trivy/rpc/detector" -) - -func TestMain(m *testing.M) { - log.InitLogger(false, false) - code := m.Run() - os.Exit(code) -} - -func TestServer_Detect(t *testing.T) { - type args struct { - req *proto.LibDetectRequest - } - tests := []struct { - name string - args args - detectExpectation library.OperationDetectExpectation - fillInfoExpectation vulnerability.OperationFillInfoExpectation - wantRes *proto.DetectResponse - wantErr string - }{ - { - name: "happy path", - args: args{ - req: &proto.LibDetectRequest{ - ImageName: "alpine:3.10", - FilePath: "app/Pipfile.lock", - Libraries: []*common.Library{ - {Name: "django", Version: "3.0.0"}, - }, - }, - }, - detectExpectation: library.OperationDetectExpectation{ - Args: library.OperationDetectArgs{ - FilePath: "app/Pipfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: ptypes.Library{Name: "django", Version: "3.0.0"}, - }, - }, - }, - Returns: library.OperationDetectReturns{ - Vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "test", - InstalledVersion: "1", - FixedVersion: "2", - Vulnerability: dbTypes.Vulnerability{ - Title: "title", - Description: "description", - Severity: "MEDIUM", - References: []string{"http://example.com"}, - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - CweIDs: []string{"CWE-78"}, - }, - Layer: ftypes.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - }, - }, - }, - }, - fillInfoExpectation: vulnerability.OperationFillInfoExpectation{ - Args: vulnerability.OperationFillInfoArgs{ - Vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "test", - InstalledVersion: "1", - FixedVersion: "2", - Vulnerability: dbTypes.Vulnerability{ - Title: "title", - Description: "description", - Severity: "MEDIUM", - References: []string{"http://example.com"}, - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - CweIDs: []string{"CWE-78"}, - }, - Layer: ftypes.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - }, - }, - }, - }, - wantRes: &proto.DetectResponse{ - Vulnerabilities: []*common.Vulnerability{ - { - VulnerabilityId: "CVE-2019-0001", - PkgName: "test", - InstalledVersion: "1", - FixedVersion: "2", - Title: "title", - Description: "description", - Severity: common.Severity_MEDIUM, - Cvss: make(map[string]*common.CVSS), - References: []string{"http://example.com"}, - LastModifiedDate: ×tamp.Timestamp{ - Seconds: 1577840460, - }, - PublishedDate: ×tamp.Timestamp{ - Seconds: 978310860, - }, - CweIds: []string{"CWE-78"}, - Layer: &common.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - }, - }, - }, - }, - { - name: "Detect returns an error", - args: args{ - req: &proto.LibDetectRequest{ - ImageName: "alpine:3.10", - FilePath: "app/Pipfile.lock", - Libraries: []*common.Library{ - {Name: "django", Version: "3.0.0"}, - }, - }, - }, - detectExpectation: library.OperationDetectExpectation{ - Args: library.OperationDetectArgs{ - FilePath: "app/Pipfile.lock", - Pkgs: []ftypes.LibraryInfo{ - {Library: ptypes.Library{Name: "django", Version: "3.0.0"}}, - }, - }, - Returns: library.OperationDetectReturns{ - Err: xerrors.New("error"), - }, - }, - wantErr: "failed to detect library vulnerabilities", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockDetector := new(library.MockOperation) - mockDetector.ApplyDetectExpectation(tt.detectExpectation) - mockVulnClient := new(vulnerability.MockOperation) - mockVulnClient.ApplyFillInfoExpectation(tt.fillInfoExpectation) - - s := NewServer(mockDetector, mockVulnClient) - ctx := context.TODO() - gotRes, err := s.Detect(ctx, tt.args.req) - if tt.wantErr != "" { - require.NotNil(t, err, tt.name) - assert.Contains(t, err.Error(), tt.wantErr, tt.name) - return - } else { - assert.NoError(t, err, tt.name) - } - - assert.Equal(t, tt.wantRes, gotRes, tt.name) - mockDetector.AssertExpectations(t) - mockVulnClient.AssertExpectations(t) - }) - } -} diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 3d29df12c3..3c9f641e94 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -19,8 +19,6 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils" rpcCache "github.com/aquasecurity/trivy/rpc/cache" - "github.com/aquasecurity/trivy/rpc/detector" - rpcDetector "github.com/aquasecurity/trivy/rpc/detector" rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" ) @@ -75,14 +73,6 @@ func newServeMux(serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, layerHandler := rpcCache.NewCacheServer(NewCacheServer(serverCache), nil) mux.Handle(rpcCache.CachePathPrefix, withToken(withWaitGroup(layerHandler), token, tokenHeader)) - // osHandler is for backward compatibility - osHandler := rpcDetector.NewOSDetectorServer(initializeOspkgServer(), nil) - mux.Handle(rpcDetector.OSDetectorPathPrefix, withToken(withWaitGroup(osHandler), token, tokenHeader)) - - // libHandler is for backward compatibility - libHandler := rpcDetector.NewLibDetectorServer(initializeLibServer(), nil) - mux.Handle(rpcDetector.LibDetectorPathPrefix, withToken(withWaitGroup(libHandler), token, tokenHeader)) - mux.HandleFunc("/healthz", func(rw http.ResponseWriter, r *http.Request) { if _, err := rw.Write([]byte("ok")); err != nil { log.Logger.Errorf("health check error: %s", err) @@ -95,7 +85,7 @@ func newServeMux(serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, func withToken(base http.Handler, token, tokenHeader string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if token != "" && token != r.Header.Get(tokenHeader) { - detector.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) + rpcScanner.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) return } base.ServeHTTP(w, r) diff --git a/pkg/rpc/server/ospkg/server.go b/pkg/rpc/server/ospkg/server.go deleted file mode 100644 index 09bcfdbc1e..0000000000 --- a/pkg/rpc/server/ospkg/server.go +++ /dev/null @@ -1,47 +0,0 @@ -package ospkg - -import ( - "context" - "time" - - "github.com/google/wire" - "golang.org/x/xerrors" - - detector "github.com/aquasecurity/trivy/pkg/detector/ospkg" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/rpc" - "github.com/aquasecurity/trivy/pkg/vulnerability" - proto "github.com/aquasecurity/trivy/rpc/detector" -) - -// SuperSet binds dependencies for RPC server -var SuperSet = wire.NewSet( - detector.SuperSet, - vulnerability.SuperSet, - NewServer, -) - -// Server is for backward compatibility -type Server struct { - detector detector.Operation - vulnClient vulnerability.Operation -} - -// NewServer is the factory method to return Server -func NewServer(detector detector.Operation, vulnClient vulnerability.Operation) *Server { - return &Server{detector: detector, vulnClient: vulnClient} -} - -// Detect is for backward compatibility -func (s *Server) Detect(_ context.Context, req *proto.OSDetectRequest) (res *proto.DetectResponse, err error) { - vulns, eosl, err := s.detector.Detect("", req.OsFamily, req.OsName, time.Time{}, rpc.ConvertFromRPCPkgs(req.Packages)) - if err != nil { - err = xerrors.Errorf("failed to detect vulnerabilities of OS packages: %w", err) - log.Logger.Error(err) - return nil, err - } - - s.vulnClient.FillInfo(vulns, "") - - return &proto.DetectResponse{Vulnerabilities: rpc.ConvertToRPCVulns(vulns), Eosl: eosl}, nil -} diff --git a/pkg/rpc/server/ospkg/server_test.go b/pkg/rpc/server/ospkg/server_test.go deleted file mode 100644 index 11bbd1f8ad..0000000000 --- a/pkg/rpc/server/ospkg/server_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package ospkg - -import ( - "context" - "os" - "testing" - - "github.com/golang/protobuf/ptypes/timestamp" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - - ftypes "github.com/aquasecurity/fanal/types" - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy-db/pkg/utils" - "github.com/aquasecurity/trivy/pkg/detector/ospkg" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/vulnerability" - "github.com/aquasecurity/trivy/rpc/common" - proto "github.com/aquasecurity/trivy/rpc/detector" -) - -func TestMain(m *testing.M) { - _ = log.InitLogger(false, false) - code := m.Run() - os.Exit(code) -} - -func TestServer_Detect(t *testing.T) { - type args struct { - req *proto.OSDetectRequest - } - tests := []struct { - name string - args args - detectExpectation ospkg.DetectExpectation - fillInfoExpectation vulnerability.OperationFillInfoExpectation - wantRes *proto.DetectResponse - wantErr string - }{ - { - name: "happy path", - args: args{ - req: &proto.OSDetectRequest{ - OsFamily: "alpine", - OsName: "3.10.2", - Packages: []*common.Package{ - {Name: "musl", Version: "1.1.22-r3"}, - }, - }, - }, - detectExpectation: ospkg.DetectExpectation{ - Args: ospkg.DetectInput{ - OSFamily: "alpine", - OSName: "3.10.2", - Pkgs: []ftypes.Package{ - {Name: "musl", Version: "1.1.22-r3"}, - }, - }, - ReturnArgs: ospkg.DetectOutput{ - Eosl: false, - Vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "musl", - Vulnerability: dbTypes.Vulnerability{ - Severity: "HIGH", - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - Layer: ftypes.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - }, - }, - }, - }, - fillInfoExpectation: vulnerability.OperationFillInfoExpectation{ - Args: vulnerability.OperationFillInfoArgs{ - Vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "musl", - Vulnerability: dbTypes.Vulnerability{ - Severity: "HIGH", - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - Layer: ftypes.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffID: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - }, - }, - }, - }, - wantRes: &proto.DetectResponse{ - Vulnerabilities: []*common.Vulnerability{ - { - VulnerabilityId: "CVE-2019-0001", - PkgName: "musl", - Severity: common.Severity_HIGH, - Cvss: make(map[string]*common.CVSS), - Layer: &common.Layer{ - Digest: "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", - DiffId: "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", - }, - LastModifiedDate: ×tamp.Timestamp{ - Seconds: 1577840460, - }, - PublishedDate: ×tamp.Timestamp{ - Seconds: 978310860, - }, - }, - }, - }, - }, - { - name: "Detect returns an error", - args: args{ - req: &proto.OSDetectRequest{ - OsFamily: "alpine", - OsName: "3.10.2", - Packages: []*common.Package{ - {Name: "musl", Version: "1.1.22-r3"}, - }, - }, - }, - detectExpectation: ospkg.DetectExpectation{ - Args: ospkg.DetectInput{ - OSFamily: "alpine", - OSName: "3.10.2", - Pkgs: []ftypes.Package{ - {Name: "musl", Version: "1.1.22-r3"}, - }, - }, - ReturnArgs: ospkg.DetectOutput{ - Err: xerrors.New("error"), - }, - }, - wantErr: "failed to detect vulnerabilities of OS packages: error", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockDetector := ospkg.NewMockDetector([]ospkg.DetectExpectation{tt.detectExpectation}) - mockVulnClient := new(vulnerability.MockOperation) - mockVulnClient.ApplyFillInfoExpectation(tt.fillInfoExpectation) - - s := NewServer(mockDetector, mockVulnClient) - gotRes, err := s.Detect(context.TODO(), tt.args.req) - if tt.wantErr != "" { - require.NotNil(t, err, tt.name) - assert.Contains(t, err.Error(), tt.wantErr, tt.name) - return - } else { - assert.NoError(t, err, tt.name) - } - - assert.Equal(t, tt.wantRes, gotRes, tt.name) - mockDetector.AssertExpectations(t) - mockVulnClient.AssertExpectations(t) - }) - } -} diff --git a/pkg/rpc/server/wire_gen.go b/pkg/rpc/server/wire_gen.go index e2bbb98d02..c7d0e24245 100644 --- a/pkg/rpc/server/wire_gen.go +++ b/pkg/rpc/server/wire_gen.go @@ -10,12 +10,9 @@ import ( "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy-db/pkg/db" db2 "github.com/aquasecurity/trivy/pkg/db" - "github.com/aquasecurity/trivy/pkg/detector/library" "github.com/aquasecurity/trivy/pkg/detector/ospkg" "github.com/aquasecurity/trivy/pkg/github" "github.com/aquasecurity/trivy/pkg/indicator" - library2 "github.com/aquasecurity/trivy/pkg/rpc/server/library" - ospkg2 "github.com/aquasecurity/trivy/pkg/rpc/server/ospkg" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/vulnerability" "github.com/spf13/afero" @@ -27,32 +24,13 @@ import ( func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} - driverFactory := library.DriverFactory{} - libraryDetector := library.NewDetector(driverFactory) - scanner := local.NewScanner(applierApplier, detector, libraryDetector) + scanner := local.NewScanner(applierApplier, detector) config := db.Config{} client := vulnerability.NewClient(config) scanServer := NewScanServer(scanner, client) return scanServer } -func initializeOspkgServer() *ospkg2.Server { - detector := ospkg.Detector{} - config := db.Config{} - client := vulnerability.NewClient(config) - server := ospkg2.NewServer(detector, client) - return server -} - -func initializeLibServer() *library2.Server { - driverFactory := library.DriverFactory{} - detector := library.NewDetector(driverFactory) - config := db.Config{} - client := vulnerability.NewClient(config) - server := library2.NewServer(detector, client) - return server -} - func initializeDBWorker(cacheDir string, quiet bool) dbWorker { config := db.Config{} client := github.NewClient() diff --git a/pkg/scanner/local/mock_library_detector.go b/pkg/scanner/local/mock_library_detector.go deleted file mode 100644 index 97d7f0dd6d..0000000000 --- a/pkg/scanner/local/mock_library_detector.go +++ /dev/null @@ -1,88 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package local - -import mock "github.com/stretchr/testify/mock" -import pkgtypes "github.com/aquasecurity/trivy/pkg/types" -import time "time" -import types "github.com/aquasecurity/fanal/types" - -// MockLibraryDetector is an autogenerated mock type for the LibraryDetector type -type MockLibraryDetector struct { - mock.Mock -} - -type LibraryDetectorDetectArgs struct { - ImageName string - ImageNameAnything bool - FilePath string - FilePathAnything bool - Created time.Time - CreatedAnything bool - Pkgs []types.LibraryInfo - PkgsAnything bool -} - -type LibraryDetectorDetectReturns struct { - DetectedVulns []pkgtypes.DetectedVulnerability - Err error -} - -type LibraryDetectorDetectExpectation struct { - Args LibraryDetectorDetectArgs - Returns LibraryDetectorDetectReturns -} - -func (_m *MockLibraryDetector) ApplyDetectExpectation(e LibraryDetectorDetectExpectation) { - var args []interface{} - if e.Args.ImageNameAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.ImageName) - } - if e.Args.FilePathAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.FilePath) - } - if e.Args.CreatedAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Created) - } - if e.Args.PkgsAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Pkgs) - } - _m.On("Detect", args...).Return(e.Returns.DetectedVulns, e.Returns.Err) -} - -func (_m *MockLibraryDetector) ApplyDetectExpectations(expectations []LibraryDetectorDetectExpectation) { - for _, e := range expectations { - _m.ApplyDetectExpectation(e) - } -} - -// Detect provides a mock function with given fields: imageName, filePath, created, pkgs -func (_m *MockLibraryDetector) Detect(imageName string, filePath string, created time.Time, pkgs []types.LibraryInfo) ([]pkgtypes.DetectedVulnerability, error) { - ret := _m.Called(imageName, filePath, created, pkgs) - - var r0 []pkgtypes.DetectedVulnerability - if rf, ok := ret.Get(0).(func(string, string, time.Time, []types.LibraryInfo) []pkgtypes.DetectedVulnerability); ok { - r0 = rf(imageName, filePath, created, pkgs) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]pkgtypes.DetectedVulnerability) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, string, time.Time, []types.LibraryInfo) error); ok { - r1 = rf(imageName, filePath, created, pkgs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 6fd53446eb..7d416e8a6a 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/aquasecurity/trivy/pkg/detector/library" + "github.com/google/wire" "golang.org/x/xerrors" @@ -16,6 +18,7 @@ import ( _ "github.com/aquasecurity/fanal/analyzer/library/bundler" _ "github.com/aquasecurity/fanal/analyzer/library/cargo" _ "github.com/aquasecurity/fanal/analyzer/library/composer" + _ "github.com/aquasecurity/fanal/analyzer/library/jar" _ "github.com/aquasecurity/fanal/analyzer/library/npm" _ "github.com/aquasecurity/fanal/analyzer/library/nuget" _ "github.com/aquasecurity/fanal/analyzer/library/pipenv" @@ -33,7 +36,6 @@ import ( _ "github.com/aquasecurity/fanal/analyzer/pkg/rpm" "github.com/aquasecurity/fanal/applier" ftypes "github.com/aquasecurity/fanal/types" - libDetector "github.com/aquasecurity/trivy/pkg/detector/library" ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report" @@ -47,8 +49,6 @@ var SuperSet = wire.NewSet( wire.Bind(new(Applier), new(applier.Applier)), ospkgDetector.SuperSet, wire.Bind(new(OspkgDetector), new(ospkgDetector.Detector)), - libDetector.SuperSet, - wire.Bind(new(LibraryDetector), new(libDetector.Detector)), NewScanner, ) @@ -62,21 +62,15 @@ type OspkgDetector interface { Detect(imageName, osFamily, osName string, created time.Time, pkgs []ftypes.Package) (detectedVulns []types.DetectedVulnerability, eosl bool, err error) } -// LibraryDetector defines operation to detect library vulnerabilities -type LibraryDetector interface { - Detect(imageName, filePath string, created time.Time, pkgs []ftypes.LibraryInfo) (detectedVulns []types.DetectedVulnerability, err error) -} - // Scanner implements the OspkgDetector and LibraryDetector type Scanner struct { applier Applier ospkgDetector OspkgDetector - libDetector LibraryDetector } // NewScanner is the factory method for Scanner -func NewScanner(applier Applier, ospkgDetector OspkgDetector, libDetector LibraryDetector) Scanner { - return Scanner{applier: applier, ospkgDetector: ospkgDetector, libDetector: libDetector} +func NewScanner(applier Applier, ospkgDetector OspkgDetector) Scanner { + return Scanner{applier: applier, ospkgDetector: ospkgDetector} } // Scan scans the local image and return results. TODO: fix cyclometic complexity @@ -156,17 +150,29 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOption log.Logger.Info("Trivy skips scanning programming language libraries because no supported file was detected") return nil, nil } - var results report.Results - for _, app := range apps { - vulns, err := s.libDetector.Detect("", app.FilePath, time.Time{}, app.Libraries) - if err != nil { - return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) - } + var results report.Results + printedTypes := map[string]struct{}{} + for _, app := range apps { + if len(app.Libraries) == 0 { + continue + } if skipped(app.FilePath, options.SkipFiles, options.SkipDirectories) { continue } + // Prevent the same log messages from being displayed many times for the same type. + if _, ok := printedTypes[app.Type]; !ok { + log.Logger.Infof("Detecting %s vulnerabilities...", app.Type) + printedTypes[app.Type] = struct{}{} + } + + log.Logger.Debugf("Detecting library vulnerabilities, type: %s, path: %s", app.Type, app.FilePath) + vulns, err := library.Detect(app.Type, app.Libraries) + if err != nil { + return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err) + } + libReport := report.Result{ Target: app.FilePath, Vulnerabilities: vulns, diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 1e4e882fa6..10a4d85634 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -4,6 +4,12 @@ import ( "errors" "testing" + "github.com/aquasecurity/fanal/analyzer/library" + + "github.com/aquasecurity/trivy-db/pkg/db" + + "github.com/aquasecurity/trivy/pkg/dbtest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,9 +32,9 @@ func TestScanner_Scan(t *testing.T) { tests := []struct { name string args args + fixtures []string applyLayersExpectation ApplierApplyLayersExpectation ospkgDetectExpectations []OspkgDetectorDetectExpectation - libDetectExpectations []LibraryDetectorDetectExpectation wantResults report.Results wantOS *ftypes.OS wantEosl bool @@ -41,6 +47,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -62,11 +69,11 @@ func TestScanner_Scan(t *testing.T) { }, Applications: []ftypes.Application{ { - Type: "bundler", + Type: library.Bundler, FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -108,34 +115,6 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-10000", - PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "alpine:latest (alpine 3.11)", @@ -156,10 +135,10 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/Gemfile.lock", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-10000", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -180,6 +159,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}, ListAllPackages: true}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -212,7 +192,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -261,34 +241,6 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-10000", - PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "alpine:latest (alpine 3.11)", @@ -326,7 +278,7 @@ func TestScanner_Scan(t *testing.T) { Packages: []ftypes.Package{ { Name: "rails", - Version: "6.0", + Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -334,10 +286,10 @@ func TestScanner_Scan(t *testing.T) { }, Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-10000", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -358,6 +310,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -371,7 +324,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -382,43 +335,15 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-10000", - PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "/app/Gemfile.lock", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-10000", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -436,6 +361,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -452,7 +378,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -475,34 +401,6 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-10000", - PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", - Layer: ftypes.Layer{ - DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "alpine:latest (alpine 3.11)", @@ -512,10 +410,10 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/Gemfile.lock", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-10000", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, @@ -536,6 +434,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -552,7 +451,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -574,43 +473,15 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-10000", - PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "/app/Gemfile.lock", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-10000", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "6.0", - FixedVersion: "6.1", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -631,6 +502,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, @@ -652,6 +524,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -671,7 +544,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "rails", Version: "5.1"}, + Library: dtypes.Library{Name: "rails", Version: "4.0.2"}, Layer: ftypes.Layer{ DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", }, @@ -683,7 +556,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "/app/composer-lock.json", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "laravel", Version: "6.0.0"}, + Library: dtypes.Library{Name: "laravel/framework", Version: "6.0.0"}, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -694,69 +567,15 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "5.1"}, - Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-11111", - PkgName: "rails", - InstalledVersion: "5.1", - FixedVersion: "5.2", - Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", - }, - }, - }, - }, - }, - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/composer-lock.json", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "laravel", Version: "6.0.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-22222", - PkgName: "laravel", - InstalledVersion: "6.0.0", - FixedVersion: "6.1.0", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "/app/Gemfile.lock", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-11111", + VulnerabilityID: "CVE-2014-0081", PkgName: "rails", - InstalledVersion: "5.1", - FixedVersion: "5.2", + InstalledVersion: "4.0.2", + FixedVersion: "4.0.3, 3.2.17", Layer: ftypes.Layer{ DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", }, @@ -768,10 +587,10 @@ func TestScanner_Scan(t *testing.T) { Target: "/app/composer-lock.json", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-22222", - PkgName: "laravel", + VulnerabilityID: "CVE-2021-21263", + PkgName: "laravel/framework", InstalledVersion: "6.0.0", - FixedVersion: "6.1.0", + FixedVersion: "8.22.1, 7.30.3, 6.20.12", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -795,6 +614,7 @@ func TestScanner_Scan(t *testing.T) { SkipDirectories: []string{"/usr/lib/ruby/gems"}, }, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -826,7 +646,7 @@ func TestScanner_Scan(t *testing.T) { FilePath: "app/composer-lock.json", Libraries: []ftypes.LibraryInfo{ { - Library: dtypes.Library{Name: "laravel", Version: "6.0.0"}, + Library: dtypes.Library{Name: "laravel/framework", Version: "6.0.0"}, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -837,69 +657,15 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "usr/lib/ruby/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "5.1"}, - Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-11111", - PkgName: "rails", - InstalledVersion: "5.1", - FixedVersion: "5.2", - Layer: ftypes.Layer{ - DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", - }, - }, - }, - }, - }, - { - Args: LibraryDetectorDetectArgs{ - FilePath: "app/composer-lock.json", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "laravel", Version: "6.0.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - DetectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-22222", - PkgName: "laravel", - InstalledVersion: "6.0.0", - FixedVersion: "6.1.0", - Layer: ftypes.Layer{ - DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", - }, - }, - }, - }, - }, - }, wantResults: report.Results{ { Target: "app/composer-lock.json", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-22222", - PkgName: "laravel", + VulnerabilityID: "CVE-2021-21263", + PkgName: "laravel/framework", InstalledVersion: "6.0.0", - FixedVersion: "6.1.0", + FixedVersion: "8.22.1, 7.30.3, 6.20.12", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, @@ -920,6 +686,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -937,6 +704,7 @@ func TestScanner_Scan(t *testing.T) { layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"os", "library"}}, }, + fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -982,12 +750,13 @@ func TestScanner_Scan(t *testing.T) { wantErr: "failed to scan OS packages", }, { - name: "sad path: libDetector.Detect returns an error", + name: "sad path: library.Detect returns an error", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{VulnType: []string{"library"}}, }, + fixtures: []string{"testdata/fixtures/sad.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, @@ -1024,24 +793,6 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - libDetectExpectations: []LibraryDetectorDetectExpectation{ - { - Args: LibraryDetectorDetectArgs{ - FilePath: "/app/Gemfile.lock", - Pkgs: []ftypes.LibraryInfo{ - { - Library: dtypes.Library{Name: "rails", Version: "6.0"}, - Layer: ftypes.Layer{ - DiffID: "sha256:9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", - }, - }, - }, - }, - Returns: LibraryDetectorDetectReturns{ - Err: errors.New("error"), - }, - }, - }, wantErr: "failed to scan application libraries", }, } @@ -1049,16 +800,16 @@ func TestScanner_Scan(t *testing.T) { log.InitLogger(false, true) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + _ = dbtest.InitDB(t, tt.fixtures) + defer db.Close() + applier := new(MockApplier) applier.ApplyApplyLayersExpectation(tt.applyLayersExpectation) ospkgDetector := new(MockOspkgDetector) ospkgDetector.ApplyDetectExpectations(tt.ospkgDetectExpectations) - libDetector := new(MockLibraryDetector) - libDetector.ApplyDetectExpectations(tt.libDetectExpectations) - - s := NewScanner(applier, ospkgDetector, libDetector) + s := NewScanner(applier, ospkgDetector) gotResults, gotOS, gotEosl, err := s.Scan(tt.args.target, "", tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { require.NotNil(t, err, tt.name) @@ -1074,7 +825,6 @@ func TestScanner_Scan(t *testing.T) { applier.AssertExpectations(t) ospkgDetector.AssertExpectations(t) - libDetector.AssertExpectations(t) }) } } diff --git a/pkg/scanner/local/testdata/fixtures/happy.yaml b/pkg/scanner/local/testdata/fixtures/happy.yaml new file mode 100644 index 0000000000..ba9f0d5d8b --- /dev/null +++ b/pkg/scanner/local/testdata/fixtures/happy.yaml @@ -0,0 +1,28 @@ +- bucket: "GitHub Security Advisory Rubygems" + pairs: + - bucket: rails + pairs: + - key: CVE-2014-0081 + value: + PatchedVersions: + - "4.0.3" + - "3.2.17" + VulnerableVersions: + - ">= 4.0.0, < 4.0.3" + - ">= 3.0.0, < 3.2.17" + +- bucket: "composer::GitHub Security Advisory Composer" + pairs: + - bucket: laravel/framework + pairs: + - key: CVE-2021-21263 + value: + PatchedVersions: + - 8.22.1 + - 7.30.3 + - 6.20.12 + VulnerableVersions: + - ">= 8.0.0, < 8.22.1" + - ">= 7.0.0, < 7.30.3" + - "< 6.20.12" + diff --git a/pkg/scanner/local/testdata/fixtures/sad.yaml b/pkg/scanner/local/testdata/fixtures/sad.yaml new file mode 100644 index 0000000000..ea14c83c22 --- /dev/null +++ b/pkg/scanner/local/testdata/fixtures/sad.yaml @@ -0,0 +1,7 @@ +- bucket: "ruby-advisory-db" + pairs: + - bucket: rails + pairs: + - key: CVE-2014-0081 + value: + PatchedVersions: "invalid" diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f51b69e311..7ac3c943ce 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,17 +3,12 @@ package utils import ( "fmt" "io" - "io/ioutil" "os" "path/filepath" "strings" - "testing" - "github.com/stretchr/testify/require" "golang.org/x/xerrors" - fixtures "github.com/aquasecurity/bolt-fixtures" - "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/log" ) @@ -132,26 +127,3 @@ func CopyFile(src, dst string) (int64, error) { n, err := io.Copy(destination, source) return n, err } - -// InitTestDB is a utility function initializing BoltDB for unit testing -func InitTestDB(t *testing.T, fixtureFiles []string) string { - // Create a temp dir - dir, err := ioutil.TempDir("", "TestDB") - require.NoError(t, err) - - dbPath := db.Path(dir) - dbDir := filepath.Dir(dbPath) - err = os.MkdirAll(dbDir, 0700) - require.NoError(t, err) - - // Load testdata into BoltDB - loader, err := fixtures.New(dbPath, fixtureFiles) - require.NoError(t, err) - require.NoError(t, loader.Load()) - require.NoError(t, loader.Close()) - - // Initialize DB - require.NoError(t, db.Init(dir)) - - return dir -} diff --git a/rpc/detector/service.pb.go b/rpc/detector/service.pb.go deleted file mode 100644 index 5d012010fa..0000000000 --- a/rpc/detector/service.pb.go +++ /dev/null @@ -1,243 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: rpc/detector/service.proto - -package detector - -import ( - fmt "fmt" - common "github.com/aquasecurity/trivy/rpc/common" - proto "github.com/golang/protobuf/proto" - timestamp "github.com/golang/protobuf/ptypes/timestamp" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type OSDetectRequest struct { - OsFamily string `protobuf:"bytes,1,opt,name=os_family,json=osFamily,proto3" json:"os_family,omitempty"` - OsName string `protobuf:"bytes,2,opt,name=os_name,json=osName,proto3" json:"os_name,omitempty"` - Packages []*common.Package `protobuf:"bytes,3,rep,name=packages,proto3" json:"packages,omitempty"` - ImageName string `protobuf:"bytes,4,opt,name=image_name,json=imageName,proto3" json:"image_name,omitempty"` - Created *timestamp.Timestamp `protobuf:"bytes,5,opt,name=created,proto3" json:"created,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *OSDetectRequest) Reset() { *m = OSDetectRequest{} } -func (m *OSDetectRequest) String() string { return proto.CompactTextString(m) } -func (*OSDetectRequest) ProtoMessage() {} -func (*OSDetectRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_93e16dbd737b8924, []int{0} -} - -func (m *OSDetectRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_OSDetectRequest.Unmarshal(m, b) -} -func (m *OSDetectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_OSDetectRequest.Marshal(b, m, deterministic) -} -func (m *OSDetectRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_OSDetectRequest.Merge(m, src) -} -func (m *OSDetectRequest) XXX_Size() int { - return xxx_messageInfo_OSDetectRequest.Size(m) -} -func (m *OSDetectRequest) XXX_DiscardUnknown() { - xxx_messageInfo_OSDetectRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_OSDetectRequest proto.InternalMessageInfo - -func (m *OSDetectRequest) GetOsFamily() string { - if m != nil { - return m.OsFamily - } - return "" -} - -func (m *OSDetectRequest) GetOsName() string { - if m != nil { - return m.OsName - } - return "" -} - -func (m *OSDetectRequest) GetPackages() []*common.Package { - if m != nil { - return m.Packages - } - return nil -} - -func (m *OSDetectRequest) GetImageName() string { - if m != nil { - return m.ImageName - } - return "" -} - -func (m *OSDetectRequest) GetCreated() *timestamp.Timestamp { - if m != nil { - return m.Created - } - return nil -} - -type DetectResponse struct { - Vulnerabilities []*common.Vulnerability `protobuf:"bytes,1,rep,name=vulnerabilities,proto3" json:"vulnerabilities,omitempty"` - Eosl bool `protobuf:"varint,2,opt,name=eosl,proto3" json:"eosl,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *DetectResponse) Reset() { *m = DetectResponse{} } -func (m *DetectResponse) String() string { return proto.CompactTextString(m) } -func (*DetectResponse) ProtoMessage() {} -func (*DetectResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_93e16dbd737b8924, []int{1} -} - -func (m *DetectResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DetectResponse.Unmarshal(m, b) -} -func (m *DetectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DetectResponse.Marshal(b, m, deterministic) -} -func (m *DetectResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_DetectResponse.Merge(m, src) -} -func (m *DetectResponse) XXX_Size() int { - return xxx_messageInfo_DetectResponse.Size(m) -} -func (m *DetectResponse) XXX_DiscardUnknown() { - xxx_messageInfo_DetectResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_DetectResponse proto.InternalMessageInfo - -func (m *DetectResponse) GetVulnerabilities() []*common.Vulnerability { - if m != nil { - return m.Vulnerabilities - } - return nil -} - -func (m *DetectResponse) GetEosl() bool { - if m != nil { - return m.Eosl - } - return false -} - -type LibDetectRequest struct { - FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` - Libraries []*common.Library `protobuf:"bytes,2,rep,name=libraries,proto3" json:"libraries,omitempty"` - ImageName string `protobuf:"bytes,3,opt,name=image_name,json=imageName,proto3" json:"image_name,omitempty"` - Created *timestamp.Timestamp `protobuf:"bytes,4,opt,name=created,proto3" json:"created,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *LibDetectRequest) Reset() { *m = LibDetectRequest{} } -func (m *LibDetectRequest) String() string { return proto.CompactTextString(m) } -func (*LibDetectRequest) ProtoMessage() {} -func (*LibDetectRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_93e16dbd737b8924, []int{2} -} - -func (m *LibDetectRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_LibDetectRequest.Unmarshal(m, b) -} -func (m *LibDetectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_LibDetectRequest.Marshal(b, m, deterministic) -} -func (m *LibDetectRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_LibDetectRequest.Merge(m, src) -} -func (m *LibDetectRequest) XXX_Size() int { - return xxx_messageInfo_LibDetectRequest.Size(m) -} -func (m *LibDetectRequest) XXX_DiscardUnknown() { - xxx_messageInfo_LibDetectRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_LibDetectRequest proto.InternalMessageInfo - -func (m *LibDetectRequest) GetFilePath() string { - if m != nil { - return m.FilePath - } - return "" -} - -func (m *LibDetectRequest) GetLibraries() []*common.Library { - if m != nil { - return m.Libraries - } - return nil -} - -func (m *LibDetectRequest) GetImageName() string { - if m != nil { - return m.ImageName - } - return "" -} - -func (m *LibDetectRequest) GetCreated() *timestamp.Timestamp { - if m != nil { - return m.Created - } - return nil -} - -func init() { - proto.RegisterType((*OSDetectRequest)(nil), "trivy.detector.OSDetectRequest") - proto.RegisterType((*DetectResponse)(nil), "trivy.detector.DetectResponse") - proto.RegisterType((*LibDetectRequest)(nil), "trivy.detector.LibDetectRequest") -} - -func init() { proto.RegisterFile("rpc/detector/service.proto", fileDescriptor_93e16dbd737b8924) } - -var fileDescriptor_93e16dbd737b8924 = []byte{ - // 422 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xd1, 0x8a, 0xd3, 0x40, - 0x14, 0x86, 0xc9, 0xb6, 0x76, 0xdb, 0x53, 0xd8, 0x95, 0x01, 0x31, 0x64, 0xd1, 0x2d, 0xbd, 0xea, - 0xd5, 0x04, 0xbb, 0xe2, 0x03, 0x88, 0x0a, 0xca, 0xa2, 0x4b, 0x14, 0x45, 0x6f, 0xca, 0x64, 0xf6, - 0x34, 0x1d, 0x36, 0xd3, 0x93, 0xce, 0x4c, 0x0a, 0x79, 0x34, 0x9f, 0xc2, 0x57, 0x92, 0x4e, 0x98, - 0x6a, 0xa2, 0xe8, 0xde, 0x4d, 0xe6, 0xff, 0xcf, 0x7f, 0xce, 0xf9, 0x32, 0x90, 0x98, 0x4a, 0xa6, - 0xb7, 0xe8, 0x50, 0x3a, 0x32, 0xa9, 0x45, 0xb3, 0x57, 0x12, 0x79, 0x65, 0xc8, 0x11, 0x3b, 0x73, - 0x46, 0xed, 0x1b, 0x1e, 0xd4, 0xe4, 0xb2, 0x20, 0x2a, 0x4a, 0x4c, 0xbd, 0x9a, 0xd7, 0xeb, 0xd4, - 0x29, 0x8d, 0xd6, 0x09, 0x5d, 0xb5, 0x05, 0xc9, 0x8b, 0x42, 0xb9, 0x4d, 0x9d, 0x73, 0x49, 0x3a, - 0x15, 0xbb, 0x5a, 0x58, 0x94, 0xb5, 0x51, 0xae, 0x49, 0x7d, 0x50, 0x7a, 0x68, 0x25, 0x49, 0x6b, - 0xda, 0x76, 0x1b, 0xcd, 0x7f, 0x44, 0x70, 0xfe, 0xe1, 0xe3, 0x2b, 0xdf, 0x27, 0xc3, 0x5d, 0x8d, - 0xd6, 0xb1, 0x0b, 0x98, 0x90, 0x5d, 0xad, 0x85, 0x56, 0x65, 0x13, 0x47, 0xb3, 0x68, 0x31, 0xc9, - 0xc6, 0x64, 0xdf, 0xf8, 0x6f, 0xf6, 0x18, 0x4e, 0xc9, 0xae, 0xb6, 0x42, 0x63, 0x7c, 0xe2, 0xa5, - 0x11, 0xd9, 0xf7, 0x42, 0x23, 0x7b, 0x06, 0xe3, 0x4a, 0xc8, 0x3b, 0x51, 0xa0, 0x8d, 0x07, 0xb3, - 0xc1, 0x62, 0xba, 0x7c, 0xc4, 0xdb, 0x2d, 0xda, 0xc6, 0xfc, 0xa6, 0x55, 0xb3, 0xa3, 0x8d, 0x3d, - 0x01, 0x50, 0x5a, 0x14, 0xd8, 0xc6, 0x0d, 0x7d, 0xdc, 0xc4, 0xdf, 0xf8, 0xc4, 0xe7, 0x70, 0x2a, - 0x0d, 0x0a, 0x87, 0xb7, 0xf1, 0x83, 0x59, 0xb4, 0x98, 0x2e, 0x13, 0xde, 0x62, 0xe0, 0x01, 0x03, - 0xff, 0x14, 0x30, 0x64, 0xc1, 0x3a, 0xbf, 0x83, 0xb3, 0xb0, 0x8e, 0xad, 0x68, 0x6b, 0x91, 0xbd, - 0x86, 0xf3, 0x7d, 0x5d, 0x6e, 0xd1, 0x88, 0x5c, 0x95, 0xca, 0x29, 0xb4, 0x71, 0xe4, 0x07, 0xbc, - 0xe8, 0x0e, 0xf8, 0xf9, 0x37, 0x53, 0x93, 0xf5, 0x6b, 0x18, 0x83, 0x21, 0x92, 0x2d, 0xfd, 0xda, - 0xe3, 0xcc, 0x9f, 0xe7, 0xdf, 0x23, 0x78, 0x78, 0xad, 0xf2, 0x3f, 0xf8, 0xad, 0x55, 0x89, 0xab, - 0x4a, 0xb8, 0x4d, 0xe0, 0x77, 0xb8, 0xb8, 0x11, 0x6e, 0xc3, 0xae, 0x60, 0x52, 0xaa, 0xdc, 0x08, - 0x73, 0x18, 0xe3, 0xe4, 0x6f, 0x9c, 0xae, 0xbd, 0xdc, 0x64, 0xbf, 0x7c, 0x3d, 0x50, 0x83, 0x7f, - 0x80, 0x1a, 0xde, 0x1b, 0xd4, 0xf2, 0x0b, 0x40, 0xf8, 0xf3, 0x64, 0xd8, 0x5b, 0x18, 0xb5, 0x67, - 0x76, 0xc9, 0xbb, 0x8f, 0x8f, 0xf7, 0xde, 0x47, 0xf2, 0xb4, 0x6f, 0xe8, 0xf2, 0x5e, 0x7e, 0x85, - 0xe9, 0x91, 0x09, 0x19, 0xf6, 0xee, 0x98, 0x3c, 0xeb, 0x17, 0xf6, 0xd1, 0xfd, 0x2f, 0xfa, 0x25, - 0x7c, 0x1b, 0x07, 0x29, 0x1f, 0xf9, 0xe5, 0xae, 0x7e, 0x06, 0x00, 0x00, 0xff, 0xff, 0x60, 0x68, - 0xcc, 0x2b, 0x48, 0x03, 0x00, 0x00, -} diff --git a/rpc/detector/service.proto b/rpc/detector/service.proto deleted file mode 100644 index bd7c3c3bfe..0000000000 --- a/rpc/detector/service.proto +++ /dev/null @@ -1,37 +0,0 @@ -// for backward compatibility -syntax = "proto3"; - -import "google/protobuf/timestamp.proto"; - -package trivy.detector; -option go_package = "detector"; - -import "github.com/aquasecurity/trivy/rpc/common/service.proto"; - -service OSDetector { - rpc Detect(OSDetectRequest) returns (DetectResponse); -} - -message OSDetectRequest { - string os_family = 1; - string os_name = 2; - repeated common.Package packages = 3; - string image_name = 4; - google.protobuf.Timestamp created = 5; -} - -message DetectResponse { - repeated common.Vulnerability vulnerabilities = 1; - bool eosl = 2; -} - -service LibDetector { - rpc Detect(LibDetectRequest) returns (DetectResponse); -} - -message LibDetectRequest { - string file_path = 1; - repeated common.Library libraries = 2; - string image_name = 3; - google.protobuf.Timestamp created = 4; -} diff --git a/rpc/detector/service.twirp.go b/rpc/detector/service.twirp.go deleted file mode 100644 index 1e8e7e9e30..0000000000 --- a/rpc/detector/service.twirp.go +++ /dev/null @@ -1,1267 +0,0 @@ -// Code generated by protoc-gen-twirp v5.10.1, DO NOT EDIT. -// source: rpc/detector/service.proto - -/* -Package detector is a generated twirp stub package. -This code was generated with github.com/twitchtv/twirp/protoc-gen-twirp v5.10.1. - -It is generated from these files: - rpc/detector/service.proto -*/ -package detector - -import bytes "bytes" -import strings "strings" -import context "context" -import fmt "fmt" -import ioutil "io/ioutil" -import http "net/http" -import strconv "strconv" -import gzip "compress/gzip" - -import jsonpb "github.com/golang/protobuf/jsonpb" -import proto "github.com/golang/protobuf/proto" -import twirp "github.com/twitchtv/twirp" -import ctxsetters "github.com/twitchtv/twirp/ctxsetters" - -// Imports only used by utility functions: -import io "io" -import json "encoding/json" -import url "net/url" - -// A response is compressed with gzip when the response size exceeds this threshold. -const CompressThreshold = 10000 - -// ==================== -// OSDetector Interface -// ==================== - -type OSDetector interface { - Detect(context.Context, *OSDetectRequest) (*DetectResponse, error) -} - -// ========================== -// OSDetector Protobuf Client -// ========================== - -type oSDetectorProtobufClient struct { - client HTTPClient - urls [1]string - opts twirp.ClientOptions -} - -// NewOSDetectorProtobufClient creates a Protobuf client that implements the OSDetector interface. -// It communicates using Protobuf and can be configured with a custom HTTPClient. -func NewOSDetectorProtobufClient(addr string, client HTTPClient, opts ...twirp.ClientOption) OSDetector { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - prefix := urlBase(addr) + OSDetectorPathPrefix - urls := [1]string{ - prefix + "Detect", - } - - return &oSDetectorProtobufClient{ - client: client, - urls: urls, - opts: clientOpts, - } -} - -func (c *oSDetectorProtobufClient) Detect(ctx context.Context, in *OSDetectRequest) (*DetectResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "OSDetector") - ctx = ctxsetters.WithMethodName(ctx, "Detect") - out := new(DetectResponse) - err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ====================== -// OSDetector JSON Client -// ====================== - -type oSDetectorJSONClient struct { - client HTTPClient - urls [1]string - opts twirp.ClientOptions -} - -// NewOSDetectorJSONClient creates a JSON client that implements the OSDetector interface. -// It communicates using JSON and can be configured with a custom HTTPClient. -func NewOSDetectorJSONClient(addr string, client HTTPClient, opts ...twirp.ClientOption) OSDetector { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - prefix := urlBase(addr) + OSDetectorPathPrefix - urls := [1]string{ - prefix + "Detect", - } - - return &oSDetectorJSONClient{ - client: client, - urls: urls, - opts: clientOpts, - } -} - -func (c *oSDetectorJSONClient) Detect(ctx context.Context, in *OSDetectRequest) (*DetectResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "OSDetector") - ctx = ctxsetters.WithMethodName(ctx, "Detect") - out := new(DetectResponse) - err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ========================= -// OSDetector Server Handler -// ========================= - -type oSDetectorServer struct { - OSDetector - hooks *twirp.ServerHooks -} - -func NewOSDetectorServer(svc OSDetector, hooks *twirp.ServerHooks) TwirpServer { - return &oSDetectorServer{ - OSDetector: svc, - hooks: hooks, - } -} - -// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func (s *oSDetectorServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { - writeError(ctx, resp, err, s.hooks) -} - -// OSDetectorPathPrefix is used for all URL paths on a twirp OSDetector server. -// Requests are always: POST OSDetectorPathPrefix/method -// It can be used in an HTTP mux to route twirp requests along with non-twirp requests on other routes. -const OSDetectorPathPrefix = "/twirp/trivy.detector.OSDetector/" - -func (s *oSDetectorServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - ctx := req.Context() - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "OSDetector") - ctx = ctxsetters.WithResponseWriter(ctx, resp) - - var err error - ctx, err = callRequestReceived(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - if req.Method != "POST" { - msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) - err = badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, err) - return - } - - switch req.URL.Path { - case "/twirp/trivy.detector.OSDetector/Detect": - s.serveDetect(ctx, resp, req) - return - default: - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - err = badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, err) - return - } -} - -func (s *oSDetectorServer) serveDetect(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveDetectJSON(ctx, resp, req) - case "application/protobuf": - s.serveDetectProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *oSDetectorServer) serveDetectJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Detect") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - reqContent := new(OSDetectRequest) - unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true} - if err = unmarshaler.Unmarshal(req.Body, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the json request could not be decoded")) - return - } - - // Call service method - var respContent *DetectResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = s.OSDetector.Detect(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *DetectResponse and nil error while calling Detect. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - var buf bytes.Buffer - marshaler := &jsonpb.Marshaler{OrigName: true} - if err = marshaler.Marshal(&buf, respContent); err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - respBytes := buf.Bytes() - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *oSDetectorServer) serveDetectProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Detect") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := ioutil.ReadAll(req.Body) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to read request body")) - return - } - reqContent := new(OSDetectRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - // Call service method - var respContent *DetectResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = s.OSDetector.Detect(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *DetectResponse and nil error while calling Detect. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *oSDetectorServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor0, 0 -} - -func (s *oSDetectorServer) ProtocGenTwirpVersion() string { - return "v5.10.1" -} - -func (s *oSDetectorServer) PathPrefix() string { - return OSDetectorPathPrefix -} - -// ===================== -// LibDetector Interface -// ===================== - -type LibDetector interface { - Detect(context.Context, *LibDetectRequest) (*DetectResponse, error) -} - -// =========================== -// LibDetector Protobuf Client -// =========================== - -type libDetectorProtobufClient struct { - client HTTPClient - urls [1]string - opts twirp.ClientOptions -} - -// NewLibDetectorProtobufClient creates a Protobuf client that implements the LibDetector interface. -// It communicates using Protobuf and can be configured with a custom HTTPClient. -func NewLibDetectorProtobufClient(addr string, client HTTPClient, opts ...twirp.ClientOption) LibDetector { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - prefix := urlBase(addr) + LibDetectorPathPrefix - urls := [1]string{ - prefix + "Detect", - } - - return &libDetectorProtobufClient{ - client: client, - urls: urls, - opts: clientOpts, - } -} - -func (c *libDetectorProtobufClient) Detect(ctx context.Context, in *LibDetectRequest) (*DetectResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "LibDetector") - ctx = ctxsetters.WithMethodName(ctx, "Detect") - out := new(DetectResponse) - err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ======================= -// LibDetector JSON Client -// ======================= - -type libDetectorJSONClient struct { - client HTTPClient - urls [1]string - opts twirp.ClientOptions -} - -// NewLibDetectorJSONClient creates a JSON client that implements the LibDetector interface. -// It communicates using JSON and can be configured with a custom HTTPClient. -func NewLibDetectorJSONClient(addr string, client HTTPClient, opts ...twirp.ClientOption) LibDetector { - if c, ok := client.(*http.Client); ok { - client = withoutRedirects(c) - } - - clientOpts := twirp.ClientOptions{} - for _, o := range opts { - o(&clientOpts) - } - - prefix := urlBase(addr) + LibDetectorPathPrefix - urls := [1]string{ - prefix + "Detect", - } - - return &libDetectorJSONClient{ - client: client, - urls: urls, - opts: clientOpts, - } -} - -func (c *libDetectorJSONClient) Detect(ctx context.Context, in *LibDetectRequest) (*DetectResponse, error) { - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "LibDetector") - ctx = ctxsetters.WithMethodName(ctx, "Detect") - out := new(DetectResponse) - err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) - if err != nil { - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - callClientError(ctx, c.opts.Hooks, twerr) - return nil, err - } - - callClientResponseReceived(ctx, c.opts.Hooks) - - return out, nil -} - -// ========================== -// LibDetector Server Handler -// ========================== - -type libDetectorServer struct { - LibDetector - hooks *twirp.ServerHooks -} - -func NewLibDetectorServer(svc LibDetector, hooks *twirp.ServerHooks) TwirpServer { - return &libDetectorServer{ - LibDetector: svc, - hooks: hooks, - } -} - -// writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func (s *libDetectorServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { - writeError(ctx, resp, err, s.hooks) -} - -// LibDetectorPathPrefix is used for all URL paths on a twirp LibDetector server. -// Requests are always: POST LibDetectorPathPrefix/method -// It can be used in an HTTP mux to route twirp requests along with non-twirp requests on other routes. -const LibDetectorPathPrefix = "/twirp/trivy.detector.LibDetector/" - -func (s *libDetectorServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - ctx := req.Context() - ctx = ctxsetters.WithPackageName(ctx, "trivy.detector") - ctx = ctxsetters.WithServiceName(ctx, "LibDetector") - ctx = ctxsetters.WithResponseWriter(ctx, resp) - - var err error - ctx, err = callRequestReceived(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - if req.Method != "POST" { - msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) - err = badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, err) - return - } - - switch req.URL.Path { - case "/twirp/trivy.detector.LibDetector/Detect": - s.serveDetect(ctx, resp, req) - return - default: - msg := fmt.Sprintf("no handler for path %q", req.URL.Path) - err = badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, err) - return - } -} - -func (s *libDetectorServer) serveDetect(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - header := req.Header.Get("Content-Type") - i := strings.Index(header, ";") - if i == -1 { - i = len(header) - } - switch strings.TrimSpace(strings.ToLower(header[:i])) { - case "application/json": - s.serveDetectJSON(ctx, resp, req) - case "application/protobuf": - s.serveDetectProtobuf(ctx, resp, req) - default: - msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) - twerr := badRouteError(msg, req.Method, req.URL.Path) - s.writeError(ctx, resp, twerr) - } -} - -func (s *libDetectorServer) serveDetectJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Detect") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - reqContent := new(LibDetectRequest) - unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true} - if err = unmarshaler.Unmarshal(req.Body, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the json request could not be decoded")) - return - } - - // Call service method - var respContent *DetectResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = s.LibDetector.Detect(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *DetectResponse and nil error while calling Detect. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - var buf bytes.Buffer - marshaler := &jsonpb.Marshaler{OrigName: true} - if err = marshaler.Marshal(&buf, respContent); err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) - return - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - respBytes := buf.Bytes() - resp.Header().Set("Content-Type", "application/json") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *libDetectorServer) serveDetectProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { - var err error - ctx = ctxsetters.WithMethodName(ctx, "Detect") - ctx, err = callRequestRouted(ctx, s.hooks) - if err != nil { - s.writeError(ctx, resp, err) - return - } - - buf, err := ioutil.ReadAll(req.Body) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to read request body")) - return - } - reqContent := new(LibDetectRequest) - if err = proto.Unmarshal(buf, reqContent); err != nil { - s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) - return - } - - // Call service method - var respContent *DetectResponse - func() { - defer ensurePanicResponses(ctx, resp, s.hooks) - respContent, err = s.LibDetector.Detect(ctx, reqContent) - }() - - if err != nil { - s.writeError(ctx, resp, err) - return - } - if respContent == nil { - s.writeError(ctx, resp, twirp.InternalError("received a nil *DetectResponse and nil error while calling Detect. nil responses are not supported")) - return - } - - ctx = callResponsePrepared(ctx, s.hooks) - - respBytes, err := proto.Marshal(respContent) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) - return - } - - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) - resp.Header().Set("Content-Type", "application/protobuf") - resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) - resp.WriteHeader(http.StatusOK) - if n, err := resp.Write(respBytes); err != nil { - msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) - twerr := twirp.NewError(twirp.Unknown, msg) - callError(ctx, s.hooks, twerr) - } - callResponseSent(ctx, s.hooks) -} - -func (s *libDetectorServer) ServiceDescriptor() ([]byte, int) { - return twirpFileDescriptor0, 1 -} - -func (s *libDetectorServer) ProtocGenTwirpVersion() string { - return "v5.10.1" -} - -func (s *libDetectorServer) PathPrefix() string { - return LibDetectorPathPrefix -} - -// ===== -// Utils -// ===== - -// HTTPClient is the interface used by generated clients to send HTTP requests. -// It is fulfilled by *(net/http).Client, which is sufficient for most users. -// Users can provide their own implementation for special retry policies. -// -// HTTPClient implementations should not follow redirects. Redirects are -// automatically disabled if *(net/http).Client is passed to client -// constructors. See the withoutRedirects function in this file for more -// details. -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// TwirpServer is the interface generated server structs will support: they're -// HTTP handlers with additional methods for accessing metadata about the -// service. Those accessors are a low-level API for building reflection tools. -// Most people can think of TwirpServers as just http.Handlers. -type TwirpServer interface { - http.Handler - // ServiceDescriptor returns gzipped bytes describing the .proto file that - // this service was generated from. Once unzipped, the bytes can be - // unmarshalled as a - // github.com/golang/protobuf/protoc-gen-go/descriptor.FileDescriptorProto. - // - // The returned integer is the index of this particular service within that - // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a - // low-level field, expected to be used for reflection. - ServiceDescriptor() ([]byte, int) - // ProtocGenTwirpVersion is the semantic version string of the version of - // twirp used to generate this file. - ProtocGenTwirpVersion() string - // PathPrefix returns the HTTP URL path prefix for all methods handled by this - // service. This can be used with an HTTP mux to route twirp requests - // alongside non-twirp requests on one HTTP listener. - PathPrefix() string -} - -// WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). -// Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. -// If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) -func WriteError(resp http.ResponseWriter, err error) { - writeError(context.Background(), resp, err, nil) -} - -// writeError writes Twirp errors in the response and triggers hooks. -func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { - // Non-twirp errors are wrapped as Internal (default) - twerr, ok := err.(twirp.Error) - if !ok { - twerr = twirp.InternalErrorWith(err) - } - - statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) - ctx = ctxsetters.WithStatusCode(ctx, statusCode) - ctx = callError(ctx, hooks, twerr) - - respBody := marshalErrorToJSON(twerr) - - resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON - resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) - resp.WriteHeader(statusCode) // set HTTP status code and send response - - _, writeErr := resp.Write(respBody) - if writeErr != nil { - // We have three options here. We could log the error, call the Error - // hook, or just silently ignore the error. - // - // Logging is unacceptable because we don't have a user-controlled - // logger; writing out to stderr without permission is too rude. - // - // Calling the Error hook would confuse users: it would mean the Error - // hook got called twice for one request, which is likely to lead to - // duplicated log messages and metrics, no matter how well we document - // the behavior. - // - // Silently ignoring the error is our least-bad option. It's highly - // likely that the connection is broken and the original 'err' says - // so anyway. - _ = writeErr - } - - callResponseSent(ctx, hooks) -} - -// urlBase helps ensure that addr specifies a scheme. If it is unparsable -// as a URL, it returns addr unchanged. -func urlBase(addr string) string { - // If the addr specifies a scheme, use it. If not, default to - // http. If url.Parse fails on it, return it unchanged. - url, err := url.Parse(addr) - if err != nil { - return addr - } - if url.Scheme == "" { - url.Scheme = "http" - } - return url.String() -} - -// getCustomHTTPReqHeaders retrieves a copy of any headers that are set in -// a context through the twirp.WithHTTPRequestHeaders function. -// If there are no headers set, or if they have the wrong type, nil is returned. -func getCustomHTTPReqHeaders(ctx context.Context) http.Header { - header, ok := twirp.HTTPRequestHeaders(ctx) - if !ok || header == nil { - return nil - } - copied := make(http.Header) - for k, vv := range header { - if vv == nil { - copied[k] = nil - continue - } - copied[k] = make([]string, len(vv)) - copy(copied[k], vv) - } - return copied -} - -// newRequest makes an http.Request from a client, adding common headers. -func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { - req, err := http.NewRequest("POST", url, reqBody) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { - req.Header = customHeader - } - req.Header.Set("Accept", contentType) - req.Header.Set("Content-Type", contentType) - req.Header.Set("Accept-Encoding", "gzip") - req.Header.Set("Twirp-Version", "v5.10.1") - return req, nil -} - -// JSON serialization for errors -type twerrJSON struct { - Code string `json:"code"` - Msg string `json:"msg"` - Meta map[string]string `json:"meta,omitempty"` -} - -// marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. -// If serialization fails, it will use a descriptive Internal error instead. -func marshalErrorToJSON(twerr twirp.Error) []byte { - // make sure that msg is not too large - msg := twerr.Msg() - if len(msg) > 1e6 { - msg = msg[:1e6] - } - - tj := twerrJSON{ - Code: string(twerr.Code()), - Msg: msg, - Meta: twerr.MetaMap(), - } - - buf, err := json.Marshal(&tj) - if err != nil { - buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback - } - - return buf -} - -// errorFromResponse builds a twirp.Error from a non-200 HTTP response. -// If the response has a valid serialized Twirp error, then it's returned. -// If not, the response status code is used to generate a similar twirp -// error. See twirpErrorFromIntermediary for more info on intermediary errors. -func errorFromResponse(resp *http.Response) twirp.Error { - statusCode := resp.StatusCode - statusText := http.StatusText(statusCode) - - if isHTTPRedirect(statusCode) { - // Unexpected redirect: it must be an error from an intermediary. - // Twirp clients don't follow redirects automatically, Twirp only handles - // POST requests, redirects should only happen on GET and HEAD requests. - location := resp.Header.Get("Location") - msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) - return twirpErrorFromIntermediary(statusCode, msg, location) - } - - respBodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return wrapInternal(err, "failed to read server error response body") - } - - var tj twerrJSON - dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) - dec.DisallowUnknownFields() - if err := dec.Decode(&tj); err != nil || tj.Code == "" { - // Invalid JSON response; it must be an error from an intermediary. - msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) - return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) - } - - errorCode := twirp.ErrorCode(tj.Code) - if !twirp.IsValidErrorCode(errorCode) { - msg := "invalid type returned from server error response: " + tj.Code - return twirp.InternalError(msg) - } - - twerr := twirp.NewError(errorCode, tj.Msg) - for k, v := range tj.Meta { - twerr = twerr.WithMeta(k, v) - } - return twerr -} - -// twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. -// The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. -// Returned twirp Errors have some additional metadata for inspection. -func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { - var code twirp.ErrorCode - if isHTTPRedirect(status) { // 3xx - code = twirp.Internal - } else { - switch status { - case 400: // Bad Request - code = twirp.Internal - case 401: // Unauthorized - code = twirp.Unauthenticated - case 403: // Forbidden - code = twirp.PermissionDenied - case 404: // Not Found - code = twirp.BadRoute - case 429, 502, 503, 504: // Too Many Requests, Bad Gateway, Service Unavailable, Gateway Timeout - code = twirp.Unavailable - default: // All other codes - code = twirp.Unknown - } - } - - twerr := twirp.NewError(code, msg) - twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary - twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) - if isHTTPRedirect(status) { - twerr = twerr.WithMeta("location", bodyOrLocation) - } else { - twerr = twerr.WithMeta("body", bodyOrLocation) - } - return twerr -} - -func isHTTPRedirect(status int) bool { - return status >= 300 && status <= 399 -} - -// wrapInternal wraps an error with a prefix as an Internal error. -// The original error cause is accessible by github.com/pkg/errors.Cause. -func wrapInternal(err error, prefix string) twirp.Error { - return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) -} - -type wrappedError struct { - prefix string - cause error -} - -func (e *wrappedError) Cause() error { return e.cause } -func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } - -// ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal -// error response (status 500), and error hooks are properly called with the panic wrapped as an error. -// The panic is re-raised so it can be handled normally with middleware. -func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { - if r := recover(); r != nil { - // Wrap the panic as an error so it can be passed to error hooks. - // The original error is accessible from error hooks, but not visible in the response. - err := errFromPanic(r) - twerr := &internalWithCause{msg: "Internal service panic", cause: err} - // Actually write the error - writeError(ctx, resp, twerr, hooks) - // If possible, flush the error to the wire. - f, ok := resp.(http.Flusher) - if ok { - f.Flush() - } - - panic(r) - } -} - -// errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. -func errFromPanic(p interface{}) error { - if err, ok := p.(error); ok { - return err - } - return fmt.Errorf("panic: %v", p) -} - -// internalWithCause is a Twirp Internal error wrapping an original error cause, accessible -// by github.com/pkg/errors.Cause, but the original error message is not exposed on Msg(). -type internalWithCause struct { - msg string - cause error -} - -func (e *internalWithCause) Cause() error { return e.cause } -func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } -func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } -func (e *internalWithCause) Msg() string { return e.msg } -func (e *internalWithCause) Meta(key string) string { return "" } -func (e *internalWithCause) MetaMap() map[string]string { return nil } -func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } - -// malformedRequestError is used when the twirp server cannot unmarshal a request -func malformedRequestError(msg string) twirp.Error { - return twirp.NewError(twirp.Malformed, msg) -} - -// badRouteError is used when the twirp server cannot route a request -func badRouteError(msg string, method, url string) twirp.Error { - err := twirp.NewError(twirp.BadRoute, msg) - err = err.WithMeta("twirp_invalid_route", method+" "+url) - return err -} - -// withoutRedirects makes sure that the POST request can not be redirected. -// The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or -// 303 response, and also 301s in go1.8. It redirects by making a second request, changing the -// method to GET and removing the body. This produces very confusing error messages, so instead we -// set a redirect policy that always errors. This stops Go from executing the redirect. -// -// We have to be a little careful in case the user-provided http.Client has its own CheckRedirect -// policy - if so, we'll run through that policy first. -// -// Because this requires modifying the http.Client, we make a new copy of the client and return it. -func withoutRedirects(in *http.Client) *http.Client { - copy := *in - copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { - if in.CheckRedirect != nil { - // Run the input's redirect if it exists, in case it has side effects, but ignore any error it - // returns, since we want to use ErrUseLastResponse. - err := in.CheckRedirect(req, via) - _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. - } - return http.ErrUseLastResponse - } - return © -} - -// doProtobufRequest makes a Protobuf request to the remote Twirp service. -func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (err error) { - reqBodyBytes, err := proto.Marshal(in) - if err != nil { - return wrapInternal(err, "failed to marshal proto request") - } - reqBody := bytes.NewBuffer(reqBodyBytes) - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - - req, err := newRequest(ctx, url, reqBody, "application/protobuf") - if err != nil { - return wrapInternal(err, "could not build request") - } - ctx, err = callClientRequestPrepared(ctx, hooks, req) - if err != nil { - return err - } - - req = req.WithContext(ctx) - resp, err := client.Do(req) - if err != nil { - return wrapInternal(err, "failed to do request") - } - - defer func() { - cerr := resp.Body.Close() - if err == nil && cerr != nil { - err = wrapInternal(cerr, "failed to close response body") - } - }() - - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - - if resp.StatusCode != 200 { - return errorFromResponse(resp) - } - - r := resp.Body - if resp.Header.Get("Content-Encoding") == "gzip" { - r, err = gzip.NewReader(r) - if err != nil { - return wrapInternal(err, "invalid gzip") - } - } - - respBodyBytes, err := ioutil.ReadAll(r) - if err != nil { - return wrapInternal(err, "failed to read response body") - } - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - - if err = proto.Unmarshal(respBodyBytes, out); err != nil { - return wrapInternal(err, "failed to unmarshal proto response") - } - return nil -} - -// doJSONRequest makes a JSON request to the remote Twirp service. -func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (err error) { - reqBody := bytes.NewBuffer(nil) - marshaler := &jsonpb.Marshaler{OrigName: true} - if err = marshaler.Marshal(reqBody, in); err != nil { - return wrapInternal(err, "failed to marshal json request") - } - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - - req, err := newRequest(ctx, url, reqBody, "application/json") - if err != nil { - return wrapInternal(err, "could not build request") - } - ctx, err = callClientRequestPrepared(ctx, hooks, req) - if err != nil { - return err - } - - req = req.WithContext(ctx) - resp, err := client.Do(req) - if err != nil { - return wrapInternal(err, "failed to do request") - } - - defer func() { - cerr := resp.Body.Close() - if err == nil && cerr != nil { - err = wrapInternal(cerr, "failed to close response body") - } - }() - - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - - if resp.StatusCode != 200 { - return errorFromResponse(resp) - } - - unmarshaler := jsonpb.Unmarshaler{AllowUnknownFields: true} - if err = unmarshaler.Unmarshal(resp.Body, out); err != nil { - return wrapInternal(err, "failed to unmarshal json response") - } - if err = ctx.Err(); err != nil { - return wrapInternal(err, "aborted because context was done") - } - return nil -} - -// Call twirp.ServerHooks.RequestReceived if the hook is available -func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { - if h == nil || h.RequestReceived == nil { - return ctx, nil - } - return h.RequestReceived(ctx) -} - -// Call twirp.ServerHooks.RequestRouted if the hook is available -func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { - if h == nil || h.RequestRouted == nil { - return ctx, nil - } - return h.RequestRouted(ctx) -} - -// Call twirp.ServerHooks.ResponsePrepared if the hook is available -func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { - if h == nil || h.ResponsePrepared == nil { - return ctx - } - return h.ResponsePrepared(ctx) -} - -// Call twirp.ServerHooks.ResponseSent if the hook is available -func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { - if h == nil || h.ResponseSent == nil { - return - } - h.ResponseSent(ctx) -} - -// Call twirp.ServerHooks.Error if the hook is available -func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { - if h == nil || h.Error == nil { - return ctx - } - return h.Error(ctx, err) -} - -// compressWithGzip compresses the data with gzip -func compressWithGzip(data []byte) ([]byte, error) { - var b bytes.Buffer - gz := gzip.NewWriter(&b) - defer gz.Close() - - if _, err := gz.Write(data); err != nil { - return nil, err - } - - if err := gz.Flush(); err != nil { - return nil, err - } - - if err := gz.Close(); err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -func isGZipAcceptable(request *http.Request) bool { - for _, encoding := range request.Header["Accept-Encoding"] { - if encoding == "gzip" { - return true - } - } - return false -} - -func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { - if h == nil || h.ResponseReceived == nil { - return - } - h.ResponseReceived(ctx) -} - -func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { - if h == nil || h.RequestPrepared == nil { - return ctx, nil - } - return h.RequestPrepared(ctx, req) -} - -func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { - if h == nil || h.Error == nil { - return - } - h.Error(ctx, err) -} - -var twirpFileDescriptor0 = []byte{ - // 422 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xd1, 0x8a, 0xd3, 0x40, - 0x14, 0x86, 0xc9, 0xb6, 0x76, 0xdb, 0x53, 0xd8, 0x95, 0x01, 0x31, 0x64, 0xd1, 0x2d, 0xbd, 0xea, - 0xd5, 0x04, 0xbb, 0xe2, 0x03, 0x88, 0x0a, 0xca, 0xa2, 0x4b, 0x14, 0x45, 0x6f, 0xca, 0x64, 0xf6, - 0x34, 0x1d, 0x36, 0xd3, 0x93, 0xce, 0x4c, 0x0a, 0x79, 0x34, 0x9f, 0xc2, 0x57, 0x92, 0x4e, 0x98, - 0x6a, 0xa2, 0xe8, 0xde, 0x4d, 0xe6, 0xff, 0xcf, 0x7f, 0xce, 0xf9, 0x32, 0x90, 0x98, 0x4a, 0xa6, - 0xb7, 0xe8, 0x50, 0x3a, 0x32, 0xa9, 0x45, 0xb3, 0x57, 0x12, 0x79, 0x65, 0xc8, 0x11, 0x3b, 0x73, - 0x46, 0xed, 0x1b, 0x1e, 0xd4, 0xe4, 0xb2, 0x20, 0x2a, 0x4a, 0x4c, 0xbd, 0x9a, 0xd7, 0xeb, 0xd4, - 0x29, 0x8d, 0xd6, 0x09, 0x5d, 0xb5, 0x05, 0xc9, 0x8b, 0x42, 0xb9, 0x4d, 0x9d, 0x73, 0x49, 0x3a, - 0x15, 0xbb, 0x5a, 0x58, 0x94, 0xb5, 0x51, 0xae, 0x49, 0x7d, 0x50, 0x7a, 0x68, 0x25, 0x49, 0x6b, - 0xda, 0x76, 0x1b, 0xcd, 0x7f, 0x44, 0x70, 0xfe, 0xe1, 0xe3, 0x2b, 0xdf, 0x27, 0xc3, 0x5d, 0x8d, - 0xd6, 0xb1, 0x0b, 0x98, 0x90, 0x5d, 0xad, 0x85, 0x56, 0x65, 0x13, 0x47, 0xb3, 0x68, 0x31, 0xc9, - 0xc6, 0x64, 0xdf, 0xf8, 0x6f, 0xf6, 0x18, 0x4e, 0xc9, 0xae, 0xb6, 0x42, 0x63, 0x7c, 0xe2, 0xa5, - 0x11, 0xd9, 0xf7, 0x42, 0x23, 0x7b, 0x06, 0xe3, 0x4a, 0xc8, 0x3b, 0x51, 0xa0, 0x8d, 0x07, 0xb3, - 0xc1, 0x62, 0xba, 0x7c, 0xc4, 0xdb, 0x2d, 0xda, 0xc6, 0xfc, 0xa6, 0x55, 0xb3, 0xa3, 0x8d, 0x3d, - 0x01, 0x50, 0x5a, 0x14, 0xd8, 0xc6, 0x0d, 0x7d, 0xdc, 0xc4, 0xdf, 0xf8, 0xc4, 0xe7, 0x70, 0x2a, - 0x0d, 0x0a, 0x87, 0xb7, 0xf1, 0x83, 0x59, 0xb4, 0x98, 0x2e, 0x13, 0xde, 0x62, 0xe0, 0x01, 0x03, - 0xff, 0x14, 0x30, 0x64, 0xc1, 0x3a, 0xbf, 0x83, 0xb3, 0xb0, 0x8e, 0xad, 0x68, 0x6b, 0x91, 0xbd, - 0x86, 0xf3, 0x7d, 0x5d, 0x6e, 0xd1, 0x88, 0x5c, 0x95, 0xca, 0x29, 0xb4, 0x71, 0xe4, 0x07, 0xbc, - 0xe8, 0x0e, 0xf8, 0xf9, 0x37, 0x53, 0x93, 0xf5, 0x6b, 0x18, 0x83, 0x21, 0x92, 0x2d, 0xfd, 0xda, - 0xe3, 0xcc, 0x9f, 0xe7, 0xdf, 0x23, 0x78, 0x78, 0xad, 0xf2, 0x3f, 0xf8, 0xad, 0x55, 0x89, 0xab, - 0x4a, 0xb8, 0x4d, 0xe0, 0x77, 0xb8, 0xb8, 0x11, 0x6e, 0xc3, 0xae, 0x60, 0x52, 0xaa, 0xdc, 0x08, - 0x73, 0x18, 0xe3, 0xe4, 0x6f, 0x9c, 0xae, 0xbd, 0xdc, 0x64, 0xbf, 0x7c, 0x3d, 0x50, 0x83, 0x7f, - 0x80, 0x1a, 0xde, 0x1b, 0xd4, 0xf2, 0x0b, 0x40, 0xf8, 0xf3, 0x64, 0xd8, 0x5b, 0x18, 0xb5, 0x67, - 0x76, 0xc9, 0xbb, 0x8f, 0x8f, 0xf7, 0xde, 0x47, 0xf2, 0xb4, 0x6f, 0xe8, 0xf2, 0x5e, 0x7e, 0x85, - 0xe9, 0x91, 0x09, 0x19, 0xf6, 0xee, 0x98, 0x3c, 0xeb, 0x17, 0xf6, 0xd1, 0xfd, 0x2f, 0xfa, 0x25, - 0x7c, 0x1b, 0x07, 0x29, 0x1f, 0xf9, 0xe5, 0xae, 0x7e, 0x06, 0x00, 0x00, 0xff, 0xff, 0x60, 0x68, - 0xcc, 0x2b, 0x48, 0x03, 0x00, 0x00, -}