mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 23:26:39 -08:00
fix: version comparison (#740)
* feat: add comparer * refactor: rename lang with ecosystem * feat(bundler): add comparer * feat(node): add comparer * feat(bundler): integrate comparer * feat(cargo): integrate comparer * feat(composer): add comparer * feat(ghsa): integrate comparer * feat(node): integrate comparer * feat(python): integrate comparer * test(bundler): add tests * test(cargo): add tests * test(composer): add tests * test(ghsa): add tests * test(node): add tests * test(python): add tests * refactor(utils): remove unnecessary functions * test(utils): add tests * test: rename bucket prefixes * fix(detect): use string * chore: update dependencies * docs: add comments * fix(cargo): handle unpatched vulnerability * test(db): update trivy-db for integration tests * test(integration): update a golden file * test(cargo): Add a case for missing patched version Signed-off-by: Simarpreet Singh <simar@linux.com> * refactor(advisory): update comments * refactor(node/advisory): change the receiver * chore(mod): update dependencies * refactor(comparer): unexport MatchVersion * refactor: fix maligned structs * test(node): add empty value * refactor * refactor: sort imports * chore(mod): update trivy-db Co-authored-by: Simarpreet Singh <simar@linux.com>
This commit is contained in:
8
go.mod
8
go.mod
@@ -3,11 +3,13 @@ module github.com/aquasecurity/trivy
|
|||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver/v3 v3.1.0
|
|
||||||
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
|
||||||
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882
|
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882
|
||||||
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b
|
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b
|
||||||
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b
|
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-version v0.0.0-20201115065329-578079e4ab05
|
||||||
|
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2
|
||||||
github.com/caarlos0/env/v6 v6.0.0
|
github.com/caarlos0/env/v6 v6.0.0
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||||
github.com/cheggaaa/pb/v3 v3.0.3
|
github.com/cheggaaa/pb/v3 v3.0.3
|
||||||
@@ -27,7 +29,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/testcontainers/testcontainers-go v0.3.1
|
github.com/testcontainers/testcontainers-go v0.3.1
|
||||||
github.com/twitchtv/twirp v5.10.1+incompatible
|
github.com/twitchtv/twirp v5.10.1+incompatible
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
github.com/urfave/cli/v2 v2.3.0
|
||||||
go.uber.org/atomic v1.5.1 // indirect
|
go.uber.org/atomic v1.5.1 // indirect
|
||||||
go.uber.org/multierr v1.4.0 // indirect
|
go.uber.org/multierr v1.4.0 // indirect
|
||||||
go.uber.org/zap v1.13.0
|
go.uber.org/zap v1.13.0
|
||||||
|
|||||||
23
go.sum
23
go.sum
@@ -34,15 +34,19 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
|||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
|
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible h1:3D2O4g8AwDwyWkM1HpMFVux/ccQJmGJHXsE004Wsu1Q=
|
||||||
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||||
|
github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4=
|
||||||
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
|
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk=
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
|
||||||
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
|
||||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
@@ -50,7 +54,9 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN
|
|||||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||||
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
|
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||||
|
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
|
||||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
|
||||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@@ -58,8 +64,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||||||
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0 h1:wykTgKwhVr2t2qs+xI020s6W5dt614QqCHV+7W9dg64=
|
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0 h1:wykTgKwhVr2t2qs+xI020s6W5dt614QqCHV+7W9dg64=
|
||||||
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs=
|
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs=
|
||||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
|
||||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
||||||
@@ -87,10 +91,18 @@ github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882 h1:65VcAKqhkKwM
|
|||||||
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882/go.mod h1:VP1+n6hMi6krpA0umEl0CkJp4hbg0R21kXWg0mjrekc=
|
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882/go.mod h1:VP1+n6hMi6krpA0umEl0CkJp4hbg0R21kXWg0mjrekc=
|
||||||
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b h1:55Ulc/gvfWm4ylhVaR7MxOwujRjA6et7KhmUbSgUFf4=
|
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b h1:55Ulc/gvfWm4ylhVaR7MxOwujRjA6et7KhmUbSgUFf4=
|
||||||
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b/go.mod h1:BpNTD9vHfrejKsED9rx04ldM1WIbeyXGYxUrqTVwxVQ=
|
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b/go.mod h1:BpNTD9vHfrejKsED9rx04ldM1WIbeyXGYxUrqTVwxVQ=
|
||||||
|
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=
|
||||||
|
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod h1:hxbJZtKlO4P8sZ9nztizR6XLoE33O+BkPmuYQ4ACyz0=
|
||||||
|
github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a h1:SMEtDBnLyP/EVOeJhj4yeR8GYPFpBsFBk3lSrpjZ8yI=
|
||||||
|
github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
|
||||||
|
github.com/aquasecurity/go-version v0.0.0-20201115065329-578079e4ab05 h1:q0ZpFBjwzDk1ofey7gJ2kfA6ZNi2PeBWxNzmRPrfetA=
|
||||||
|
github.com/aquasecurity/go-version v0.0.0-20201115065329-578079e4ab05/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
|
||||||
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a h1:hsw7PpiymXP64evn/K7gsj3hWzMqLrdoeE6JkqDocVg=
|
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a h1:hsw7PpiymXP64evn/K7gsj3hWzMqLrdoeE6JkqDocVg=
|
||||||
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
|
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
|
||||||
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b h1:y4DTBr/S6WRhZ7jQX9nZ00bElwYM7k/653M/JfoaJgI=
|
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2 h1:AXA9aW464copH1GTKv35yCwztJsqDVZWKfCtBuMpI9U=
|
||||||
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b/go.mod h1:+3+NEz0U0NCgO87Cyk0dy3SwH7CI6J4HUeCqqPj1fvQ=
|
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2/go.mod h1:+3+NEz0U0NCgO87Cyk0dy3SwH7CI6J4HUeCqqPj1fvQ=
|
||||||
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 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA=
|
||||||
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ=
|
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ=
|
||||||
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||||
@@ -560,6 +572,8 @@ github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
|||||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
|
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
|
||||||
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||||
@@ -937,6 +951,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
|
|||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
"VulnerabilityID": "RUSTSEC-2019-0001",
|
"VulnerabilityID": "RUSTSEC-2019-0001",
|
||||||
"PkgName": "ammonia",
|
"PkgName": "ammonia",
|
||||||
"InstalledVersion": "1.9.0",
|
"InstalledVersion": "1.9.0",
|
||||||
|
"FixedVersion": "\u003e= 2.1.0",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "Uncontrolled recursion leads to abort in HTML serialization",
|
"Title": "Uncontrolled recursion leads to abort in HTML serialization",
|
||||||
"Description": "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.\n",
|
"Description": "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210"
|
"https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210"
|
||||||
@@ -21,53 +22,72 @@
|
|||||||
"VulnerabilityID": "RUSTSEC-2016-0001",
|
"VulnerabilityID": "RUSTSEC-2016-0001",
|
||||||
"PkgName": "openssl",
|
"PkgName": "openssl",
|
||||||
"InstalledVersion": "0.8.3",
|
"InstalledVersion": "0.8.3",
|
||||||
|
"FixedVersion": "\u003e= 0.9.0",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "SSL/TLS MitM vulnerability due to insecure defaults",
|
"Title": "SSL/TLS MitM vulnerability due to insecure defaults",
|
||||||
"Description": "All versions of rust-openssl prior to 0.9.0 contained numerous insecure defaults\nincluding off-by-default certificate verification and no API to perform hostname\nverification.\n\nUnless configured correctly by a developer, these defaults could allow an attacker\nto perform man-in-the-middle attacks.\n\nThe problem was addressed in newer versions by enabling certificate verification\nby default and exposing APIs to perform hostname verification. Use the\n`SslConnector` and `SslAcceptor` types to take advantage of these new features\n(as opposed to the lower-level `SslContext` type).\n",
|
"Description": "All versions of rust-openssl prior to 0.9.0 contained numerous insecure defaults\nincluding off-by-default certificate verification and no API to perform hostname\nverification.\n\nUnless configured correctly by a developer, these defaults could allow an attacker\nto perform man-in-the-middle attacks.\n\nThe problem was addressed in newer versions by enabling certificate verification\nby default and exposing APIs to perform hostname verification. Use the\n`SslConnector` and `SslAcceptor` types to take advantage of these new features\n(as opposed to the lower-level `SslContext` type).",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/sfackler/rust-openssl/releases/tag/v0.9.0"
|
"https://github.com/sfackler/rust-openssl/releases/tag/v0.9.0"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"VulnerabilityID": "RUSTSEC-2018-0010",
|
"VulnerabilityID": "RUSTSEC-2019-0035",
|
||||||
"PkgName": "openssl",
|
"PkgName": "rand_core",
|
||||||
"InstalledVersion": "0.8.3",
|
"InstalledVersion": "0.3.1",
|
||||||
|
"FixedVersion": "\u003e= 0.4.2",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "Use after free in CMS Signing",
|
"Title": "Unaligned memory access",
|
||||||
"Description": "Affected versions of the OpenSSL crate used structures after they'd been freed.",
|
"Description": "Affected versions of this crate violated alignment when casting byte slices to\ninteger slices, resulting in undefined behavior.\n\nThe flaw was corrected by Ralf Jung and Diggory Hardy.",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/sfackler/rust-openssl/pull/942"
|
"https://github.com/rust-random/rand/blob/master/rand_core/CHANGELOG.md#050---2019-06-06"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"VulnerabilityID": "RUSTSEC-2018-0003",
|
"VulnerabilityID": "RUSTSEC-2019-0035",
|
||||||
"PkgName": "smallvec",
|
"PkgName": "rand_core",
|
||||||
"InstalledVersion": "0.6.9",
|
"InstalledVersion": "0.4.0",
|
||||||
|
"FixedVersion": "\u003e= 0.4.2",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "Possible double free during unwinding in SmallVec::insert_many",
|
"Title": "Unaligned memory access",
|
||||||
"Description": "If an iterator passed to `SmallVec::insert_many` panicked in `Iterator::next`,\ndestructors were run during unwinding while the vector was in an inconsistent\nstate, possibly causing a double free (a destructor running on two copies of\nthe same value).\n\nThis is fixed in smallvec 0.6.3 by ensuring that the vector's length is not\nupdated to include moved items until they have been removed from their\noriginal positions. Items may now be leaked if `Iterator::next` panics, but\nthey will not be dropped more than once.\n\nThank you to @Vurich for reporting this bug.\n",
|
"Description": "Affected versions of this crate violated alignment when casting byte slices to\ninteger slices, resulting in undefined behavior.\n\nThe flaw was corrected by Ralf Jung and Diggory Hardy.",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/servo/rust-smallvec/issues/96"
|
"https://github.com/rust-random/rand/blob/master/rand_core/CHANGELOG.md#050---2019-06-06"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"VulnerabilityID": "RUSTSEC-2018-0018",
|
||||||
|
"PkgName": "smallvec",
|
||||||
|
"InstalledVersion": "0.6.9",
|
||||||
|
"FixedVersion": "\u003e= 0.6.13",
|
||||||
|
"Layer": {
|
||||||
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
|
},
|
||||||
|
"Title": "smallvec creates uninitialized value of any type",
|
||||||
|
"Description": "Affected versions of this crate called `mem::uninitialized()` to create values of a user-supplied type `T`.\nThis is unsound e.g. if `T` is a reference type (which must be non-null and thus may not remain uninitialized).\n \nThe flaw was corrected by avoiding the use of `mem::uninitialized()`, using `MaybeUninit` instead.",
|
||||||
|
"Severity": "UNKNOWN",
|
||||||
|
"References": [
|
||||||
|
"https://github.com/servo/rust-smallvec/issues/126"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"VulnerabilityID": "RUSTSEC-2019-0009",
|
"VulnerabilityID": "RUSTSEC-2019-0009",
|
||||||
"PkgName": "smallvec",
|
"PkgName": "smallvec",
|
||||||
"InstalledVersion": "0.6.9",
|
"InstalledVersion": "0.6.9",
|
||||||
|
"FixedVersion": "\u003e= 0.6.10",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "Double-free and use-after-free in SmallVec::grow()",
|
"Title": "Double-free and use-after-free in SmallVec::grow()",
|
||||||
"Description": "Attempting to call `grow` on a spilled SmallVec with a value equal to the current capacity causes it to free the existing data. This performs a double free immediately and may lead to use-after-free on subsequent accesses to the SmallVec contents.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.\n",
|
"Description": "Attempting to call `grow` on a spilled SmallVec with a value equal to the current capacity causes it to free the existing data. This performs a double free immediately and may lead to use-after-free on subsequent accesses to the SmallVec contents.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/servo/rust-smallvec/issues/148"
|
"https://github.com/servo/rust-smallvec/issues/148"
|
||||||
@@ -77,15 +97,30 @@
|
|||||||
"VulnerabilityID": "RUSTSEC-2019-0012",
|
"VulnerabilityID": "RUSTSEC-2019-0012",
|
||||||
"PkgName": "smallvec",
|
"PkgName": "smallvec",
|
||||||
"InstalledVersion": "0.6.9",
|
"InstalledVersion": "0.6.9",
|
||||||
|
"FixedVersion": "\u003e= 0.6.10",
|
||||||
"Layer": {
|
"Layer": {
|
||||||
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
},
|
},
|
||||||
"Title": "Memory corruption in SmallVec::grow()",
|
"Title": "Memory corruption in SmallVec::grow()",
|
||||||
"Description": "Attempting to call `grow` on a spilled SmallVec with a value less than the current capacity causes corruption of memory allocator data structures.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.\n",
|
"Description": "Attempting to call `grow` on a spilled SmallVec with a value less than the current capacity causes corruption of memory allocator data structures.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.",
|
||||||
"Severity": "UNKNOWN",
|
"Severity": "UNKNOWN",
|
||||||
"References": [
|
"References": [
|
||||||
"https://github.com/servo/rust-smallvec/issues/149"
|
"https://github.com/servo/rust-smallvec/issues/149"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"VulnerabilityID": "RUSTSEC-2018-0017",
|
||||||
|
"PkgName": "tempdir",
|
||||||
|
"InstalledVersion": "0.3.7",
|
||||||
|
"Layer": {
|
||||||
|
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
|
||||||
|
},
|
||||||
|
"Title": "`tempdir` crate has been deprecated; use `tempfile` instead",
|
||||||
|
"Description": "The [`tempdir`](https://crates.io/crates/tempdir) crate has been deprecated\nand the functionality is merged into [`tempfile`](https://crates.io/crates/tempfile).",
|
||||||
|
"Severity": "UNKNOWN",
|
||||||
|
"References": [
|
||||||
|
"https://github.com/rust-lang-deprecated/tempdir/pull/46"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
integration/testdata/trivy.db.gz
vendored
BIN
integration/testdata/trivy.db.gz
vendored
Binary file not shown.
@@ -4,51 +4,50 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Advisory represents security advisories for each programming language
|
// Advisory represents security advisories for each programming language
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
lang string
|
ecosystem string
|
||||||
comparer comparer
|
comparer comparer.Comparer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method of Advisory
|
// NewAdvisory is the factory method of Advisory
|
||||||
func NewAdvisory(lang string) *Advisory {
|
func NewAdvisory(ecosystem string, comparer comparer.Comparer) *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
lang: lang,
|
ecosystem: ecosystem,
|
||||||
comparer: newComparer(lang),
|
comparer: comparer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans buckets with the prefix according to the programming language in "Advisory".
|
// DetectVulnerabilities scans buckets with the prefix according to the ecosystem in "Advisory".
|
||||||
// If "lang" is python, it looks for buckets with "python::" and gets security advisories from those buckets.
|
// If "ecosystem" is pip, it looks for buckets with "pip::" and gets security advisories from those buckets.
|
||||||
// It allows us to add a new data source with the lang prefix (e.g. python::new-data-source)
|
// It allows us to add a new data source with the ecosystem prefix (e.g. pip::new-data-source)
|
||||||
// and detect vulnerabilities without specifying a specific bucket name.
|
// and detect vulnerabilities without specifying a specific bucket name.
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
// e.g. "python::"
|
// e.g. "pip::", "npm::"
|
||||||
prefix := fmt.Sprintf("%s::", s.lang)
|
prefix := fmt.Sprintf("%s::", s.ecosystem)
|
||||||
advisories, err := db.Config{}.GetAdvisories(prefix, pkgName)
|
advisories, err := db.Config{}.GetAdvisories(prefix, pkgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.lang, err)
|
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.ecosystem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
if !s.comparer.isVulnerable(pkgVer, advisory) {
|
if !s.comparer.IsVulnerable(pkgVer, advisory) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: pkgName,
|
PkgName: pkgName,
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: s.createFixedVersions(advisory),
|
FixedVersion: s.createFixedVersions(advisory),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
@@ -74,29 +73,3 @@ func (s *Advisory) createFixedVersions(advisory dbTypes.Advisory) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(fixedVersions, ", ")
|
return strings.Join(fixedVersions, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
type comparer interface {
|
|
||||||
isVulnerable(pkgVer *semver.Version, advisory dbTypes.Advisory) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newComparer(lang string) comparer {
|
|
||||||
switch lang {
|
|
||||||
// When another library is needed for version comparison, it can be added here.
|
|
||||||
}
|
|
||||||
return generalComparer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type generalComparer struct{}
|
|
||||||
|
|
||||||
func (c generalComparer) isVulnerable(pkgVer *semver.Version, advisory dbTypes.Advisory) bool {
|
|
||||||
if len(advisory.VulnerableVersions) != 0 {
|
|
||||||
return utils.MatchVersions(pkgVer, advisory.VulnerableVersions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) ||
|
|
||||||
utils.MatchVersions(pkgVer, advisory.UnaffectedVersions) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,40 +4,40 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
"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/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
||||||
type fields struct {
|
|
||||||
lang string
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
pkgName string
|
pkgName string
|
||||||
pkgVer *semver.Version
|
pkgVer string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixtures []string
|
fixtures []string
|
||||||
fields fields
|
ecosystem string
|
||||||
args args
|
comparer comparer.Comparer
|
||||||
want []types.DetectedVulnerability
|
args args
|
||||||
wantErr string
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy path",
|
name: "happy path",
|
||||||
fixtures: []string{"testdata/fixtures/php.yaml"},
|
fixtures: []string{"testdata/fixtures/php.yaml"},
|
||||||
fields: fields{lang: vulnerability.PHP},
|
ecosystem: vulnerability.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.2.6"),
|
pkgVer: "4.2.6",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -49,12 +49,13 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no patched versions in the advisory",
|
name: "no patched versions in the advisory",
|
||||||
fixtures: []string{"testdata/fixtures/php.yaml"},
|
fixtures: []string{"testdata/fixtures/php.yaml"},
|
||||||
fields: fields{lang: vulnerability.PHP},
|
ecosystem: vulnerability.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.4.6"),
|
pkgVer: "4.4.6",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -66,12 +67,13 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no vulnerable versions in the advisory",
|
name: "no vulnerable versions in the advisory",
|
||||||
fixtures: []string{"testdata/fixtures/ruby.yaml"},
|
fixtures: []string{"testdata/fixtures/ruby.yaml"},
|
||||||
fields: fields{lang: vulnerability.Ruby},
|
ecosystem: vulnerability.RubyGems,
|
||||||
|
comparer: bundler.RubyGemsComparer{},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "activesupport",
|
pkgName: "activesupport",
|
||||||
pkgVer: semver.MustParse("4.1.1"),
|
pkgVer: "4.1.1",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -83,12 +85,13 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no vulnerability",
|
name: "no vulnerability",
|
||||||
fixtures: []string{"testdata/fixtures/php.yaml"},
|
fixtures: []string{"testdata/fixtures/php.yaml"},
|
||||||
fields: fields{lang: vulnerability.PHP},
|
ecosystem: vulnerability.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.4.7"),
|
pkgVer: "4.4.7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,7 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
|||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
adv := library.NewAdvisory(tt.fields.lang)
|
adv := library.NewAdvisory(tt.ecosystem, tt.comparer)
|
||||||
got, err := adv.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
got, err := adv.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package bundler
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
|
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,18 +17,20 @@ type VulnSrc interface {
|
|||||||
|
|
||||||
// Advisory implements the bundler VulnSrc
|
// Advisory implements the bundler VulnSrc
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs VulnSrc
|
comparer RubyGemsComparer
|
||||||
|
vs VulnSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method to return bundler.Advisory
|
// NewAdvisory is the factory method to return bundler.Advisory
|
||||||
func NewAdvisory() *Advisory {
|
func NewAdvisory() *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: bundlerSrc.NewVulnSrc(),
|
vs: bundlerSrc.NewVulnSrc(),
|
||||||
|
comparer: RubyGemsComparer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans and returns Vulnerability in bundler
|
// DetectVulnerabilities scans and returns Vulnerability in bundler
|
||||||
func (a *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (a *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
advisories, err := a.vs.Get(pkgName)
|
advisories, err := a.vs.Get(pkgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get bundler advisories: %w", err)
|
return nil, xerrors.Errorf("failed to get bundler advisories: %w", err)
|
||||||
@@ -37,17 +38,18 @@ func (a *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
|
|||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) {
|
adv := dbTypes.Advisory{
|
||||||
continue
|
UnaffectedVersions: advisory.UnaffectedVersions,
|
||||||
|
PatchedVersions: advisory.PatchedVersions,
|
||||||
}
|
}
|
||||||
if utils.MatchVersions(pkgVer, advisory.UnaffectedVersions) {
|
if !a.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: strings.TrimSpace(pkgName),
|
PkgName: strings.TrimSpace(pkgName),
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
|
|||||||
@@ -1,63 +1,84 @@
|
|||||||
package bundler
|
package bundler_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
|
|
||||||
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockVulnSrc struct {
|
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
||||||
mock.Mock
|
type args struct {
|
||||||
}
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
func (_m *MockVulnSrc) Get(pkgName string) ([]bundlerSrc.Advisory, error) {
|
|
||||||
ret := _m.Called(pkgName)
|
|
||||||
ret0 := ret.Get(0)
|
|
||||||
if ret0 == nil {
|
|
||||||
return nil, ret.Error(1)
|
|
||||||
}
|
}
|
||||||
advisories, ok := ret0.([]bundlerSrc.Advisory)
|
tests := []struct {
|
||||||
if !ok {
|
name string
|
||||||
return nil, ret.Error(1)
|
args args
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "activesupport",
|
||||||
|
pkgVer: "4.1.1",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/gem.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "activesupport",
|
||||||
|
InstalledVersion: "4.1.1",
|
||||||
|
VulnerabilityID: "CVE-2015-3226",
|
||||||
|
FixedVersion: ">= 4.2.2, ~> 4.1.11",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "activesupport",
|
||||||
|
pkgVer: "4.1.0.a",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/gem.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid JSON",
|
||||||
|
args: args{
|
||||||
|
pkgName: "activesupport",
|
||||||
|
pkgVer: "4.1.0",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
want: nil,
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return advisories, ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScanner_Detect(t *testing.T) {
|
|
||||||
log.InitLogger(false, true)
|
log.InitLogger(false, true)
|
||||||
t.Run("Issue #108", func(t *testing.T) {
|
for _, tt := range tests {
|
||||||
// https://github.com/aquasecurity/trivy/issues/108
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Validate that the massaging that happens when parsing the lockfile
|
dir := utils.InitTestDB(t, tt.fixtures)
|
||||||
// allows us to better handle the platform metadata
|
defer os.RemoveAll(dir)
|
||||||
mockVulnSrc := new(MockVulnSrc)
|
|
||||||
mockVulnSrc.On("Get", "ffi").Return(
|
|
||||||
[]bundlerSrc.Advisory{
|
|
||||||
{
|
|
||||||
VulnerabilityID: "NotDetected",
|
|
||||||
PatchedVersions: []string{">= 1.9.24"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
VulnerabilityID: "Detected",
|
|
||||||
PatchedVersions: []string{">= 1.9.26"},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
s := Advisory{
|
|
||||||
vs: mockVulnSrc,
|
|
||||||
}
|
|
||||||
|
|
||||||
versionStr := "1.9.25-x64-mingw32"
|
a := bundler.NewAdvisory()
|
||||||
v, err := semver.NewVersion(versionStr)
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
assert.NoError(t, err)
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
vulns, err := s.DetectVulnerabilities("ffi", v)
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
assert.NoError(t, err)
|
}
|
||||||
assert.Equal(t, 1, len(vulns))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
pkg/detector/library/bundler/compare.go
Normal file
32
pkg/detector/library/bundler/compare.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package bundler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/go-gem-version"
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RubyGemsComparer represents a comparer for RubyGems
|
||||||
|
type RubyGemsComparer struct{}
|
||||||
|
|
||||||
|
// IsVulnerable checks if the package version is vulnerable to the advisory.
|
||||||
|
func (r RubyGemsComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool {
|
||||||
|
return comparer.IsVulnerable(ver, advisory, r.matchVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchVersion checks if the package version satisfies the given constraint.
|
||||||
|
func (r RubyGemsComparer) matchVersion(currentVersion, constraint string) (bool, error) {
|
||||||
|
v, err := gem.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("RubyGems version error (%s): %s", currentVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := gem.NewConstraints(constraint)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("RubyGems constraint error (%s): %s", constraint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Check(v), nil
|
||||||
|
}
|
||||||
104
pkg/detector/library/bundler/compare_test.go
Normal file
104
pkg/detector/library/bundler/compare_test.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package bundler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRubyGemsComparer_IsVulnerable(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
currentVersion string
|
||||||
|
advisory types.Advisory
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.3",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{">=1.2.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.3.a",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{">=1.2.3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release without dot",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "4.1a",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
UnaffectedVersions: []string{"< 4.2b1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://github.com/aquasecurity/trivy/issues/108
|
||||||
|
name: "hyphen",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.9.25-x86-mingw32",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{">=1.9.24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://github.com/aquasecurity/trivy/issues/108
|
||||||
|
name: "pessimistic",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.8.6-java",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{"~> 1.5.5", "~> 1.6.8", ">= 1.7.7"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid version",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2..4",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{">=1.2.3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid constraint",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.4",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
PatchedVersions: []string{"!1.2.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.InitLogger(false, false)
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := bundler.RubyGemsComparer{}
|
||||||
|
got := r.IsVulnerable(tt.args.currentVersion, tt.args.advisory)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
11
pkg/detector/library/bundler/testdata/fixtures/gem.yaml
vendored
Normal file
11
pkg/detector/library/bundler/testdata/fixtures/gem.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- bucket: ruby-advisory-db
|
||||||
|
pairs:
|
||||||
|
- bucket: activesupport
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2015-3226
|
||||||
|
value:
|
||||||
|
PatchedVersions:
|
||||||
|
- ">= 4.2.2"
|
||||||
|
- "~> 4.1.11"
|
||||||
|
UnaffectedVersions:
|
||||||
|
- "< 4.1.0"
|
||||||
7
pkg/detector/library/bundler/testdata/fixtures/invalid-type.yaml
vendored
Normal file
7
pkg/detector/library/bundler/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- bucket: ruby-advisory-db
|
||||||
|
pairs:
|
||||||
|
- bucket: activesupport
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2015-3226
|
||||||
|
value:
|
||||||
|
PatchedVersions: dummy
|
||||||
@@ -3,28 +3,30 @@ package cargo
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
cargoSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/cargo"
|
cargoSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/cargo"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Advisory encapsulates the cargo vulnerability scanner
|
// Advisory encapsulates the cargo vulnerability scanner
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs cargoSrc.VulnSrc
|
vs cargoSrc.VulnSrc
|
||||||
|
comparer comparer.Comparer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method to return cargo Scanner
|
// NewAdvisory is the factory method to return cargo Scanner
|
||||||
func NewAdvisory() *Advisory {
|
func NewAdvisory() *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: cargoSrc.NewVulnSrc(),
|
vs: cargoSrc.NewVulnSrc(),
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans and returns the cargo vulnerabilities
|
// DetectVulnerabilities scans and returns the cargo vulnerabilities
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
advisories, err := s.vs.Get(pkgName)
|
advisories, err := s.vs.Get(pkgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get cargo advisories: %w", err)
|
return nil, xerrors.Errorf("failed to get cargo advisories: %w", err)
|
||||||
@@ -32,14 +34,23 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
|
|||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) {
|
adv := dbTypes.Advisory{
|
||||||
|
UnaffectedVersions: advisory.UnaffectedVersions,
|
||||||
|
PatchedVersions: advisory.PatchedVersions,
|
||||||
|
}
|
||||||
|
if len(adv.UnaffectedVersions) == 0 && len(adv.PatchedVersions) == 0 {
|
||||||
|
// No patched version
|
||||||
|
adv.VulnerableVersions = []string{">=0.0.0"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: strings.TrimSpace(pkgName),
|
PkgName: strings.TrimSpace(pkgName),
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
|
|||||||
100
pkg/detector/library/cargo/advisory_test.go
Normal file
100
pkg/detector/library/cargo/advisory_test.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
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/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "bumpalo",
|
||||||
|
pkgVer: "3.2.0",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/cargo.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "bumpalo",
|
||||||
|
InstalledVersion: "3.2.0",
|
||||||
|
VulnerabilityID: "RUSTSEC-2020-0006",
|
||||||
|
FixedVersion: ">= 3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "bumpalo",
|
||||||
|
pkgVer: "3.2.1",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/cargo.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no patched version",
|
||||||
|
args: args{
|
||||||
|
pkgName: "bumpalo",
|
||||||
|
pkgVer: "3.2.0",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/no-patched-version.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "bumpalo",
|
||||||
|
InstalledVersion: "3.2.0",
|
||||||
|
VulnerabilityID: "RUSTSEC-2020-0006",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid JSON",
|
||||||
|
args: args{
|
||||||
|
pkgName: "bumpalo",
|
||||||
|
pkgVer: "3.2.1",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
want: nil,
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
a := cargo.NewAdvisory()
|
||||||
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
10
pkg/detector/library/cargo/testdata/fixtures/cargo.yaml
vendored
Normal file
10
pkg/detector/library/cargo/testdata/fixtures/cargo.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- bucket: rust-advisory-db
|
||||||
|
pairs:
|
||||||
|
- bucket: bumpalo
|
||||||
|
pairs:
|
||||||
|
- key: RUSTSEC-2020-0006
|
||||||
|
value:
|
||||||
|
PatchedVersions:
|
||||||
|
- ">= 3.2.1"
|
||||||
|
UnaffectedVersions:
|
||||||
|
- "< 3.0.0"
|
||||||
7
pkg/detector/library/cargo/testdata/fixtures/invalid-type.yaml
vendored
Normal file
7
pkg/detector/library/cargo/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- bucket: rust-advisory-db
|
||||||
|
pairs:
|
||||||
|
- bucket: bumpalo
|
||||||
|
pairs:
|
||||||
|
- key: RUSTSEC-2020-0006
|
||||||
|
value:
|
||||||
|
PatchedVersions: foo
|
||||||
8
pkg/detector/library/cargo/testdata/fixtures/no-patched-version.yaml
vendored
Normal file
8
pkg/detector/library/cargo/testdata/fixtures/no-patched-version.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- bucket: rust-advisory-db
|
||||||
|
pairs:
|
||||||
|
- bucket: bumpalo
|
||||||
|
pairs:
|
||||||
|
- key: RUSTSEC-2020-0006
|
||||||
|
value:
|
||||||
|
PatchedVersions:
|
||||||
|
UnaffectedVersions:
|
||||||
72
pkg/detector/library/comparer/compare.go
Normal file
72
pkg/detector/library/comparer/compare.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package comparer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/go-version/pkg/version"
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comparer is an interface for version comparison
|
||||||
|
type Comparer interface {
|
||||||
|
IsVulnerable(currentVersion string, advisory dbTypes.Advisory) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchVersion func(currentVersion, constraint string) (bool, error)
|
||||||
|
|
||||||
|
// IsVulnerable checks if the package version is vulnerable to the advisory.
|
||||||
|
func IsVulnerable(pkgVer string, advisory dbTypes.Advisory, match matchVersion) bool {
|
||||||
|
var matched bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(advisory.VulnerableVersions) != 0 {
|
||||||
|
matched, err = match(pkgVer, strings.Join(advisory.VulnerableVersions, " || "))
|
||||||
|
if err != nil {
|
||||||
|
log.Logger.Warn(err)
|
||||||
|
return false
|
||||||
|
} else if !matched {
|
||||||
|
// the version is not vulnerable
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
secureVersions := append(advisory.PatchedVersions, advisory.UnaffectedVersions...)
|
||||||
|
if len(secureVersions) == 0 {
|
||||||
|
// the version matches vulnerable versions and patched/unaffected versions are not provided
|
||||||
|
// or all values are empty
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, err = match(pkgVer, strings.Join(secureVersions, " || "))
|
||||||
|
if err != nil {
|
||||||
|
log.Logger.Warn(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericComparer represents a comparer for semver-like versioning
|
||||||
|
type GenericComparer struct{}
|
||||||
|
|
||||||
|
// IsVulnerable checks if the package version is vulnerable to the advisory.
|
||||||
|
func (v GenericComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool {
|
||||||
|
return IsVulnerable(ver, advisory, v.matchVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchVersion checks if the package version satisfies the given constraint.
|
||||||
|
func (v GenericComparer) matchVersion(currentVersion, constraint string) (bool, error) {
|
||||||
|
ver, err := version.Parse(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("version error (%s): %s", currentVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := version.NewConstraints(constraint)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("constraint error (%s): %s", currentVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Check(ver), nil
|
||||||
|
}
|
||||||
96
pkg/detector/library/comparer/compare_test.go
Normal file
96
pkg/detector/library/comparer/compare_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package comparer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenericComparer_IsVulnerable(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ver string
|
||||||
|
advisory types.Advisory
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
args: args{
|
||||||
|
ver: "1.2.3",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
VulnerableVersions: []string{"<=1.0"},
|
||||||
|
PatchedVersions: []string{">=1.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no patch",
|
||||||
|
args: args{
|
||||||
|
ver: "1.2.3",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
VulnerableVersions: []string{"<=99.999.99999"},
|
||||||
|
PatchedVersions: []string{"<0.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release",
|
||||||
|
args: args{
|
||||||
|
ver: "1.2.2-alpha",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
VulnerableVersions: []string{"<=1.2.2"},
|
||||||
|
PatchedVersions: []string{">=1.2.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple constraints",
|
||||||
|
args: args{
|
||||||
|
ver: "2.0.0",
|
||||||
|
advisory: types.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 version",
|
||||||
|
args: args{
|
||||||
|
ver: "1.2..4",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
VulnerableVersions: []string{"<1.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "improper constraint",
|
||||||
|
args: args{
|
||||||
|
ver: "1.2.3",
|
||||||
|
advisory: types.Advisory{
|
||||||
|
VulnerableVersions: []string{"*"},
|
||||||
|
PatchedVersions: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.InitLogger(false, false)
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := comparer.GenericComparer{}
|
||||||
|
got := v.IsVulnerable(tt.args.ver, tt.args.advisory)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,30 +4,30 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
|
||||||
composerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/composer"
|
composerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/composer"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Advisory encapsulates composer.VulnSrc
|
// Advisory encapsulates composer.VulnSrc
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs composerSrc.VulnSrc
|
vs composerSrc.VulnSrc
|
||||||
|
comparer comparer.Comparer // TODO: implement a comparer for Composer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method of Advisory
|
// NewAdvisory is the factory method of Advisory
|
||||||
func NewAdvisory() *Advisory {
|
func NewAdvisory() *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: composerSrc.NewVulnSrc(),
|
vs: composerSrc.NewVulnSrc(),
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities returns the vulnerabilities in a package
|
// DetectVulnerabilities returns the vulnerabilities in a package
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
ref := fmt.Sprintf("composer://%s", pkgName)
|
ref := fmt.Sprintf("composer://%s", pkgName)
|
||||||
advisories, err := s.vs.Get(ref)
|
advisories, err := s.vs.Get(ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,14 +47,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
|
|||||||
affectedVersions = append(affectedVersions, strings.Join(branch.Versions, ", "))
|
affectedVersions = append(affectedVersions, strings.Join(branch.Versions, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.MatchVersions(pkgVer, affectedVersions) {
|
adv := dbTypes.Advisory{VulnerableVersions: affectedVersions}
|
||||||
|
if !s.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: pkgName,
|
PkgName: pkgName,
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: strings.Join(patchedVersions, ", "),
|
FixedVersion: strings.Join(patchedVersions, ", "),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
|
|||||||
84
pkg/detector/library/composer/advisory_test.go
Normal file
84
pkg/detector/library/composer/advisory_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "aws/aws-sdk-php",
|
||||||
|
pkgVer: "3.2.0",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/composer.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "aws/aws-sdk-php",
|
||||||
|
InstalledVersion: "3.2.0",
|
||||||
|
VulnerabilityID: "CVE-2015-5723",
|
||||||
|
FixedVersion: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "guzzlehttp/guzzle",
|
||||||
|
pkgVer: "5.3.1",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/composer.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed JSON",
|
||||||
|
args: args{
|
||||||
|
pkgName: "aws/aws-sdk-php",
|
||||||
|
pkgVer: "3.2.0",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
a := composer.NewAdvisory()
|
||||||
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
28
pkg/detector/library/composer/testdata/fixtures/composer.yaml
vendored
Normal file
28
pkg/detector/library/composer/testdata/fixtures/composer.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
- bucket: php-security-advisories
|
||||||
|
pairs:
|
||||||
|
- bucket: "composer://aws/aws-sdk-php"
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2015-5723
|
||||||
|
value:
|
||||||
|
Branches:
|
||||||
|
3.x:
|
||||||
|
Versions:
|
||||||
|
- ">=3.0.0"
|
||||||
|
- "<3.2.1"
|
||||||
|
- bucket: "composer://guzzlehttp/guzzle"
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2016-5385
|
||||||
|
value:
|
||||||
|
Branches:
|
||||||
|
4.x:
|
||||||
|
Versions:
|
||||||
|
- ">=4.0.0rc2"
|
||||||
|
- "<4.2.4"
|
||||||
|
5.3:
|
||||||
|
Versions:
|
||||||
|
- ">=5"
|
||||||
|
- "<5.3.1"
|
||||||
|
master:
|
||||||
|
Versions:
|
||||||
|
- ">=6"
|
||||||
|
- "<6.2.1"
|
||||||
7
pkg/detector/library/composer/testdata/fixtures/invalid-type.yaml
vendored
Normal file
7
pkg/detector/library/composer/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- bucket: php-security-advisories
|
||||||
|
pairs:
|
||||||
|
- bucket: "composer://aws/aws-sdk-php"
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2015-5723
|
||||||
|
value:
|
||||||
|
Branches: invalid
|
||||||
@@ -4,13 +4,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
ftypes "github.com/aquasecurity/fanal/types"
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,14 +55,7 @@ func detect(driver Driver, libs []ftypes.LibraryInfo) ([]types.DetectedVulnerabi
|
|||||||
log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type())
|
log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type())
|
||||||
var vulnerabilities []types.DetectedVulnerability
|
var vulnerabilities []types.DetectedVulnerability
|
||||||
for _, lib := range libs {
|
for _, lib := range libs {
|
||||||
v, err := semver.NewVersion(utils.FormatPatchVersion(lib.Library.Version))
|
vulns, err := driver.Detect(lib.Library.Name, lib.Library.Version)
|
||||||
if err != nil {
|
|
||||||
log.Logger.Debugf("invalid version, library: %s, version: %s, error: %s\n",
|
|
||||||
lib.Library.Name, lib.Library.Version, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
vulns, err := driver.Detect(lib.Library.Name, v)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err)
|
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package library
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
|
ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
|
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
|
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
|
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library/ghsa"
|
"github.com/aquasecurity/trivy/pkg/detector/library/ghsa"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/library/node"
|
"github.com/aquasecurity/trivy/pkg/detector/library/node"
|
||||||
@@ -23,7 +23,7 @@ type Factory interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type advisory interface {
|
type advisory interface {
|
||||||
DetectVulnerabilities(string, *semver.Version) ([]types.DetectedVulnerability, error)
|
DetectVulnerabilities(string, string) ([]types.DetectedVulnerability, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DriverFactory implements Factory
|
// DriverFactory implements Factory
|
||||||
@@ -31,19 +31,18 @@ type DriverFactory struct{}
|
|||||||
|
|
||||||
// NewDriver factory method for driver
|
// NewDriver factory method for driver
|
||||||
func (d DriverFactory) NewDriver(filename string) (Driver, error) {
|
func (d DriverFactory) NewDriver(filename string) (Driver, error) {
|
||||||
// TODO: use DI
|
|
||||||
var driver Driver
|
var driver Driver
|
||||||
switch filename {
|
switch filename {
|
||||||
case "Gemfile.lock":
|
case "Gemfile.lock":
|
||||||
driver = newRubyDriver()
|
driver = newRubyGemsDriver()
|
||||||
case "Cargo.lock":
|
case "Cargo.lock":
|
||||||
driver = newRustDriver()
|
driver = newCargoDriver()
|
||||||
case "composer.lock":
|
case "composer.lock":
|
||||||
driver = newPHPDriver()
|
driver = newComposerDriver()
|
||||||
case "package-lock.json", "yarn.lock":
|
case "package-lock.json", "yarn.lock":
|
||||||
driver = newNodejsDriver()
|
driver = newNpmDriver()
|
||||||
case "Pipfile.lock", "poetry.lock":
|
case "Pipfile.lock", "poetry.lock":
|
||||||
driver = newPythonDriver()
|
driver = newPipDriver()
|
||||||
default:
|
default:
|
||||||
return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename))
|
return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename))
|
||||||
}
|
}
|
||||||
@@ -52,20 +51,20 @@ func (d DriverFactory) NewDriver(filename string) (Driver, error) {
|
|||||||
|
|
||||||
// Driver implements the advisory
|
// Driver implements the advisory
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
lang string
|
ecosystem string
|
||||||
advisories []advisory
|
advisories []advisory
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDriver is the factory method from drier
|
// NewDriver is the factory method from drier
|
||||||
func NewDriver(lang string, advisories ...advisory) Driver {
|
func NewDriver(advisories ...advisory) Driver {
|
||||||
return Driver{lang: lang, advisories: advisories}
|
return Driver{advisories: advisories}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect scans and returns vulnerabilities
|
// Detect scans and returns vulnerabilities
|
||||||
func (d *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (d *Driver) Detect(pkgName string, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
var detectedVulnerabilities []types.DetectedVulnerability
|
var detectedVulnerabilities []types.DetectedVulnerability
|
||||||
uniqVulnIDMap := make(map[string]struct{})
|
uniqVulnIDMap := make(map[string]struct{})
|
||||||
for _, d := range append(d.advisories, NewAdvisory(d.lang)) {
|
for _, d := range d.advisories {
|
||||||
vulns, err := d.DetectVulnerabilities(pkgName, pkgVer)
|
vulns, err := d.DetectVulnerabilities(pkgName, pkgVer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
|
return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
|
||||||
@@ -82,27 +81,36 @@ func (d *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.Detecte
|
|||||||
return detectedVulnerabilities, nil
|
return detectedVulnerabilities, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the driver lang
|
// Type returns the driver ecosystem
|
||||||
func (d *Driver) Type() string {
|
func (d *Driver) Type() string {
|
||||||
return d.lang
|
return d.ecosystem
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRubyDriver() Driver {
|
func newRubyGemsDriver() Driver {
|
||||||
return NewDriver(vulnerability.Ruby, ghsa.NewAdvisory(ecosystem.Rubygems), bundler.NewAdvisory())
|
c := bundler.RubyGemsComparer{}
|
||||||
|
return NewDriver(ghsa.NewAdvisory(ecosystem.Rubygems, c), bundler.NewAdvisory(),
|
||||||
|
NewAdvisory(vulnerability.RubyGems, c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPHPDriver() Driver {
|
func newComposerDriver() Driver {
|
||||||
return NewDriver(vulnerability.PHP, ghsa.NewAdvisory(ecosystem.Composer), composer.NewAdvisory())
|
c := comparer.GenericComparer{}
|
||||||
|
return NewDriver(
|
||||||
|
ghsa.NewAdvisory(ecosystem.Composer, c), composer.NewAdvisory(),
|
||||||
|
NewAdvisory(vulnerability.Composer, c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRustDriver() Driver {
|
func newCargoDriver() Driver {
|
||||||
return NewDriver(vulnerability.Rust, cargo.NewAdvisory())
|
return NewDriver(cargo.NewAdvisory(), NewAdvisory(vulnerability.Cargo, comparer.GenericComparer{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNodejsDriver() Driver {
|
func newNpmDriver() Driver {
|
||||||
return NewDriver(vulnerability.Nodejs, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory())
|
c := node.NpmComparer{}
|
||||||
|
return NewDriver(ghsa.NewAdvisory(ecosystem.Npm, c), node.NewAdvisory(),
|
||||||
|
NewAdvisory(vulnerability.Npm, c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPythonDriver() Driver {
|
func newPipDriver() Driver {
|
||||||
return NewDriver(vulnerability.Python, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory())
|
c := comparer.GenericComparer{}
|
||||||
|
return NewDriver(ghsa.NewAdvisory(ecosystem.Pip, c), python.NewAdvisory(),
|
||||||
|
NewAdvisory(vulnerability.Pip, c))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
pkgName string
|
pkgName string
|
||||||
pkgVer *semver.Version
|
pkgVer string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -36,7 +35,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
fields: fields{fileName: "composer.lock"},
|
fields: fields{fileName: "composer.lock"},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.2.6"),
|
pkgVer: "4.2.6",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -53,7 +52,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
fields: fields{fileName: "composer.lock"},
|
fields: fields{fileName: "composer.lock"},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.2.6"),
|
pkgVer: "4.2.6",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -70,7 +69,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
fields: fields{fileName: "composer.lock"},
|
fields: fields{fileName: "composer.lock"},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.4.6"),
|
pkgVer: "4.4.6",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -87,7 +86,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
fields: fields{fileName: "Gemfile.lock"},
|
fields: fields{fileName: "Gemfile.lock"},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "activesupport",
|
pkgName: "activesupport",
|
||||||
pkgVer: semver.MustParse("4.1.1"),
|
pkgVer: "4.1.1",
|
||||||
},
|
},
|
||||||
want: []types.DetectedVulnerability{
|
want: []types.DetectedVulnerability{
|
||||||
{
|
{
|
||||||
@@ -104,7 +103,7 @@ func TestDriver_Detect(t *testing.T) {
|
|||||||
fields: fields{fileName: "composer.lock"},
|
fields: fields{fileName: "composer.lock"},
|
||||||
args: args{
|
args: args{
|
||||||
pkgName: "symfony/symfony",
|
pkgName: "symfony/symfony",
|
||||||
pkgVer: semver.MustParse("4.4.7"),
|
pkgVer: "4.4.7",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package ghsa
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,18 +18,20 @@ type VulnSrc interface {
|
|||||||
|
|
||||||
// Advisory implements VulnSrc
|
// Advisory implements VulnSrc
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs VulnSrc
|
vs VulnSrc
|
||||||
|
comparer comparer.Comparer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method to return advisory
|
// NewAdvisory is the factory method to return advisory
|
||||||
func NewAdvisory(ecosystem ghsa.Ecosystem) *Advisory {
|
func NewAdvisory(ecosystem ghsa.Ecosystem, comparer comparer.Comparer) *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: ghsa.NewVulnSrc(ecosystem),
|
vs: ghsa.NewVulnSrc(ecosystem),
|
||||||
|
comparer: comparer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans package for vulnerabilities
|
// DetectVulnerabilities scans package for vulnerabilities
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
advisories, err := s.vs.Get(pkgName)
|
advisories, err := s.vs.Get(pkgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get ghsa advisories: %w", err)
|
return nil, xerrors.Errorf("failed to get ghsa advisories: %w", err)
|
||||||
@@ -37,14 +39,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
|
|||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
if !utils.MatchVersions(pkgVer, advisory.VulnerableVersions) {
|
adv := dbTypes.Advisory{VulnerableVersions: advisory.VulnerableVersions}
|
||||||
|
if !s.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: pkgName,
|
PkgName: pkgName,
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
|
|||||||
102
pkg/detector/library/ghsa/advisory_test.go
Normal file
102
pkg/detector/library/ghsa/advisory_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package ghsa_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
ghsaSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
|
||||||
|
"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) {
|
||||||
|
type fields struct {
|
||||||
|
ecosystem ghsaSrc.Ecosystem
|
||||||
|
comparer comparer.Comparer
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
fields fields
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
fields: fields{
|
||||||
|
ecosystem: ghsaSrc.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
pkgName: "symfony/symfony",
|
||||||
|
pkgVer: "5.1.5-alpha",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/ghsa.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "symfony/symfony",
|
||||||
|
InstalledVersion: "5.1.5-alpha",
|
||||||
|
VulnerabilityID: "CVE-2020-15094",
|
||||||
|
FixedVersion: "5.1.5, 4.4.13",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not detected",
|
||||||
|
fields: fields{
|
||||||
|
ecosystem: ghsaSrc.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
pkgName: "symfony/symfony",
|
||||||
|
pkgVer: "5.1.5",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/ghsa.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed JSON",
|
||||||
|
fields: fields{
|
||||||
|
ecosystem: ghsaSrc.Composer,
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
pkgName: "symfony/symfony",
|
||||||
|
pkgVer: "5.1.5",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
a := ghsa.NewAdvisory(tt.fields.ecosystem, tt.fields.comparer)
|
||||||
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
12
pkg/detector/library/ghsa/testdata/fixtures/ghsa.yaml
vendored
Normal file
12
pkg/detector/library/ghsa/testdata/fixtures/ghsa.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
- bucket: GitHub Security Advisory Composer
|
||||||
|
pairs:
|
||||||
|
- bucket: "symfony/symfony"
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2020-15094
|
||||||
|
value:
|
||||||
|
PatchedVersions:
|
||||||
|
- 5.1.5
|
||||||
|
- 4.4.13
|
||||||
|
VulnerableVersions:
|
||||||
|
- ">= 5.0.0, < 5.1.5"
|
||||||
|
- ">= 4.4.0, < 4.4.13"
|
||||||
7
pkg/detector/library/ghsa/testdata/fixtures/invalid-type.yaml
vendored
Normal file
7
pkg/detector/library/ghsa/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- bucket: GitHub Security Advisory Composer
|
||||||
|
pairs:
|
||||||
|
- bucket: "symfony/symfony"
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2020-15094
|
||||||
|
value:
|
||||||
|
PatchedVersions: malformed
|
||||||
@@ -5,64 +5,69 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/node"
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/node"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Advisory encapsulate Node vulnerability source
|
// Advisory encapsulate Node vulnerability source
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs node.VulnSrc
|
comparer NpmComparer
|
||||||
|
vs node.VulnSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method for Node Advisory
|
// NewAdvisory is the factory method for Node Advisory
|
||||||
func NewAdvisory() *Advisory {
|
func NewAdvisory() *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: node.NewVulnSrc(),
|
vs: node.NewVulnSrc(),
|
||||||
|
comparer: NpmComparer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans and return vulnerability using Node package scanner
|
// DetectVulnerabilities scans and return vulnerability using Node package scanner
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (a *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
replacer := strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc", " <", ", <", " >", ", >")
|
advisories, err := a.vs.Get(pkgName)
|
||||||
advisories, err := s.vs.Get(pkgName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get node advisories: %w", err)
|
return nil, xerrors.Errorf("failed to get node advisories: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
// e.g. <= 2.15.0 || >= 3.0.0 <= 3.8.2
|
adv := convertToGenericAdvisory(advisory)
|
||||||
// => {"<=2.15.0", ">= 3.0.0, <= 3.8.2"}
|
if !a.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
var vulnerableVersions []string
|
|
||||||
for _, version := range strings.Split(advisory.VulnerableVersions, " || ") {
|
|
||||||
version = strings.TrimSpace(version)
|
|
||||||
vulnerableVersions = append(vulnerableVersions, replacer.Replace(version))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !utils.MatchVersions(pkgVer, vulnerableVersions) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var patchedVersions []string
|
|
||||||
for _, version := range strings.Split(advisory.PatchedVersions, " || ") {
|
|
||||||
version = strings.TrimSpace(version)
|
|
||||||
patchedVersions = append(patchedVersions, replacer.Replace(version))
|
|
||||||
}
|
|
||||||
|
|
||||||
if utils.MatchVersions(pkgVer, patchedVersions) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: pkgName,
|
PkgName: pkgName,
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: strings.Join(patchedVersions, ", "),
|
FixedVersion: createFixedVersions(advisory.PatchedVersions),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
}
|
}
|
||||||
return vulns, nil
|
return vulns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToGenericAdvisory(advisory node.Advisory) dbTypes.Advisory {
|
||||||
|
var vulnerable, patched []string
|
||||||
|
if advisory.VulnerableVersions != "" {
|
||||||
|
vulnerable = strings.Split(advisory.VulnerableVersions, "||")
|
||||||
|
}
|
||||||
|
if advisory.PatchedVersions != "" {
|
||||||
|
patched = strings.Split(advisory.PatchedVersions, "||")
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbTypes.Advisory{
|
||||||
|
VulnerableVersions: vulnerable,
|
||||||
|
PatchedVersions: patched,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFixedVersions(patchedVersions string) string {
|
||||||
|
var fixedVersions []string
|
||||||
|
for _, s := range strings.Split(patchedVersions, "||") {
|
||||||
|
fixedVersions = append(fixedVersions, strings.TrimSpace(s))
|
||||||
|
}
|
||||||
|
return strings.Join(fixedVersions, ", ")
|
||||||
|
}
|
||||||
|
|||||||
91
pkg/detector/library/node/advisory_test.go
Normal file
91
pkg/detector/library/node/advisory_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package node_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/pkg/log"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "electron",
|
||||||
|
pkgVer: "2.0.17",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/npm.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "electron",
|
||||||
|
InstalledVersion: "2.0.17",
|
||||||
|
VulnerabilityID: "CVE-2019-5786",
|
||||||
|
FixedVersion: "^2.0.18, ^3.0.16, ^3.1.6, ^4.0.8, ^5.0.0-beta.5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "electron",
|
||||||
|
pkgVer: "2.0.18",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/npm.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty value",
|
||||||
|
args: args{
|
||||||
|
pkgName: "electron",
|
||||||
|
pkgVer: "2.0.18",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/no-value.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{name: "malformed JSON",
|
||||||
|
args: args{
|
||||||
|
pkgName: "electron",
|
||||||
|
pkgVer: "2.0.18",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
a := node.NewAdvisory()
|
||||||
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
32
pkg/detector/library/node/compare.go
Normal file
32
pkg/detector/library/node/compare.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
npm "github.com/aquasecurity/go-npm-version/pkg"
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NpmComparer represents a comparer for npm
|
||||||
|
type NpmComparer struct{}
|
||||||
|
|
||||||
|
// IsVulnerable checks if the package version is vulnerable to the advisory.
|
||||||
|
func (n NpmComparer) 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) {
|
||||||
|
v, err := npm.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("npm version error (%s): %s", currentVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := npm.NewConstraints(constraint)
|
||||||
|
if err != nil {
|
||||||
|
return false, xerrors.Errorf("npm constraint error (%s): %s", constraint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Check(v), nil
|
||||||
|
}
|
||||||
140
pkg/detector/library/node/compare_test.go
Normal file
140
pkg/detector/library/node/compare_test.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package node_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNpmComparer_MatchVersion(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: "no patch",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.3",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"<=99.999.99999"},
|
||||||
|
PatchedVersions: []string{"<0.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no patch with wildcard",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.3",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"*"},
|
||||||
|
PatchedVersions: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pre-release",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.3-alpha",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"<=1.2.2"},
|
||||||
|
PatchedVersions: []string{">=1.2.2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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: "x",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "2.0.1",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"2.0.x", "2.1.x"},
|
||||||
|
PatchedVersions: []string{">=2.2.x"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact versions",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "2.1.0-M1",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"2.1.0-M1", "2.1.0-M2"},
|
||||||
|
PatchedVersions: []string{">=2.1.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "caret",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "2.0.18",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"<2.0.18", "<3.0.16"},
|
||||||
|
PatchedVersions: []string{"^2.0.18", "^3.0.16"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid version",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2..4",
|
||||||
|
advisory: dbTypes.Advisory{
|
||||||
|
VulnerableVersions: []string{"<1.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid constraint",
|
||||||
|
args: args{
|
||||||
|
currentVersion: "1.2.4",
|
||||||
|
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 := node.NpmComparer{}
|
||||||
|
got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
9
pkg/detector/library/node/testdata/fixtures/invalid-type.yaml
vendored
Normal file
9
pkg/detector/library/node/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
- bucket: nodejs-security-wg
|
||||||
|
pairs:
|
||||||
|
- bucket: electron
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2019-5786
|
||||||
|
value:
|
||||||
|
PatchedVersions:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
6
pkg/detector/library/node/testdata/fixtures/no-value.yaml
vendored
Normal file
6
pkg/detector/library/node/testdata/fixtures/no-value.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- bucket: nodejs-security-wg
|
||||||
|
pairs:
|
||||||
|
- bucket: electron
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2019-5786
|
||||||
|
value:
|
||||||
8
pkg/detector/library/node/testdata/fixtures/npm.yaml
vendored
Normal file
8
pkg/detector/library/node/testdata/fixtures/npm.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- bucket: nodejs-security-wg
|
||||||
|
pairs:
|
||||||
|
- bucket: electron
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2019-5786
|
||||||
|
value:
|
||||||
|
PatchedVersions: "^2.0.18 || ^3.0.16 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5"
|
||||||
|
VulnerableVersions: "<2.0.18 || <3.0.16 || <3.1.6 || <4.0.8 || <5.0.0-beta.5"
|
||||||
@@ -3,28 +3,30 @@ package python
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/python"
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/python"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/utils"
|
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Advisory encapsulates the python vulnerability scanner
|
// Advisory encapsulates the python vulnerability scanner
|
||||||
type Advisory struct {
|
type Advisory struct {
|
||||||
vs python.VulnSrc
|
vs python.VulnSrc
|
||||||
|
comparer comparer.Comparer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdvisory is the factory method to reutrn Python Advisory
|
// NewAdvisory is the factory method to return Python Advisory
|
||||||
func NewAdvisory() *Advisory {
|
func NewAdvisory() *Advisory {
|
||||||
return &Advisory{
|
return &Advisory{
|
||||||
vs: python.NewVulnSrc(),
|
vs: python.NewVulnSrc(),
|
||||||
|
comparer: comparer.GenericComparer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectVulnerabilities scans and returns pythin vulnerabilities
|
// DetectVulnerabilities scans and returns python vulnerabilities
|
||||||
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
|
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
||||||
advisories, err := s.vs.Get(pkgName)
|
advisories, err := s.vs.Get(pkgName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("failed to get python advisories: %w", err)
|
return nil, xerrors.Errorf("failed to get python advisories: %w", err)
|
||||||
@@ -32,14 +34,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
|
|||||||
|
|
||||||
var vulns []types.DetectedVulnerability
|
var vulns []types.DetectedVulnerability
|
||||||
for _, advisory := range advisories {
|
for _, advisory := range advisories {
|
||||||
if !utils.MatchVersions(pkgVer, advisory.Specs) {
|
adv := dbTypes.Advisory{VulnerableVersions: advisory.Specs}
|
||||||
|
if !s.comparer.IsVulnerable(pkgVer, adv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
vuln := types.DetectedVulnerability{
|
vuln := types.DetectedVulnerability{
|
||||||
VulnerabilityID: advisory.VulnerabilityID,
|
VulnerabilityID: advisory.VulnerabilityID,
|
||||||
PkgName: pkgName,
|
PkgName: pkgName,
|
||||||
InstalledVersion: pkgVer.String(),
|
InstalledVersion: pkgVer,
|
||||||
FixedVersion: createFixedVersions(advisory.Specs),
|
FixedVersion: createFixedVersions(advisory.Specs),
|
||||||
}
|
}
|
||||||
vulns = append(vulns, vuln)
|
vulns = append(vulns, vuln)
|
||||||
|
|||||||
84
pkg/detector/library/python/advisory_test.go
Normal file
84
pkg/detector/library/python/advisory_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package python_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
type args struct {
|
||||||
|
pkgName string
|
||||||
|
pkgVer string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
fixtures []string
|
||||||
|
want []types.DetectedVulnerability
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "django",
|
||||||
|
pkgVer: "2.2.11-alpha",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/pip.yaml"},
|
||||||
|
want: []types.DetectedVulnerability{
|
||||||
|
{
|
||||||
|
PkgName: "django",
|
||||||
|
InstalledVersion: "2.2.11-alpha",
|
||||||
|
VulnerabilityID: "CVE-2020-9402",
|
||||||
|
FixedVersion: "1.11.29, 2.2.11, 3.0.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// https://github.com/aquasecurity/trivy/issues/713
|
||||||
|
name: "not detected",
|
||||||
|
args: args{
|
||||||
|
pkgName: "django",
|
||||||
|
pkgVer: "3.0.10",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/pip.yaml"},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed JSON",
|
||||||
|
args: args{
|
||||||
|
pkgName: "django",
|
||||||
|
pkgVer: "2.0.18",
|
||||||
|
},
|
||||||
|
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
|
||||||
|
wantErr: "failed to unmarshal advisory JSON",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
a := python.NewAdvisory()
|
||||||
|
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tt.wantErr)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
pkg/detector/library/python/testdata/fixtures/invalid-type.yaml
vendored
Normal file
7
pkg/detector/library/python/testdata/fixtures/invalid-type.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
- bucket: python-safety-db
|
||||||
|
pairs:
|
||||||
|
- bucket: django
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2020-9402
|
||||||
|
value:
|
||||||
|
Specs: foo
|
||||||
14
pkg/detector/library/python/testdata/fixtures/pip.yaml
vendored
Normal file
14
pkg/detector/library/python/testdata/fixtures/pip.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
- bucket: python-safety-db
|
||||||
|
pairs:
|
||||||
|
- bucket: django
|
||||||
|
pairs:
|
||||||
|
- key: CVE-2019-19844
|
||||||
|
value:
|
||||||
|
Specs:
|
||||||
|
- "==3.0"
|
||||||
|
- key: CVE-2020-9402
|
||||||
|
value:
|
||||||
|
Specs:
|
||||||
|
- ">=1.11.0,<1.11.29"
|
||||||
|
- ">=2.2.0,<2.2.11"
|
||||||
|
- ">=3.0.0,<3.0.4"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
- bucket: "php::GitHub Security Advisory Composer"
|
- bucket: "composer::GitHub Security Advisory Composer"
|
||||||
pairs:
|
pairs:
|
||||||
- bucket: symfony/symfony
|
- bucket: symfony/symfony
|
||||||
pairs:
|
pairs:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
- 4.2.7
|
- 4.2.7
|
||||||
VulnerableVersions:
|
VulnerableVersions:
|
||||||
- ">= 4.2.0, < 4.2.7"
|
- ">= 4.2.0, < 4.2.7"
|
||||||
- bucket: "php::php-security-advisories"
|
- bucket: "composer::php-security-advisories"
|
||||||
pairs:
|
pairs:
|
||||||
- bucket: symfony/symfony
|
- bucket: symfony/symfony
|
||||||
pairs:
|
pairs:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
- bucket: "ruby::ruby-advisory-db"
|
- bucket: "rubygems::ruby-advisory-db"
|
||||||
pairs:
|
pairs:
|
||||||
- bucket: activesupport
|
- bucket: activesupport
|
||||||
pairs:
|
pairs:
|
||||||
|
|||||||
@@ -2,60 +2,10 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/fanal/types"
|
"github.com/aquasecurity/fanal/types"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
replacer = strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc", "==", "=")
|
|
||||||
preReleaseSplitter = regexp.MustCompile(`(?P<Number>^[0-9]+)(?P<PreRelease>[a-z]*.*)`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// MatchVersions runs comparison on currentVersion based on rangeVersions and return true/false
|
|
||||||
func MatchVersions(currentVersion *semver.Version, rangeVersions []string) bool {
|
|
||||||
for _, v := range rangeVersions {
|
|
||||||
v = replacer.Replace(v)
|
|
||||||
constraintParts := strings.Split(v, ",")
|
|
||||||
for j := range constraintParts {
|
|
||||||
constraintParts[j] = FormatPatchVersion(constraintParts[j])
|
|
||||||
}
|
|
||||||
v = strings.Join(constraintParts, ",")
|
|
||||||
if v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c, err := semver.NewConstraint(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Logger.Debug("NewConstraint", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Validate a version against a constraint.
|
|
||||||
valid, msgs := c.Validate(currentVersion)
|
|
||||||
if valid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, m := range msgs {
|
|
||||||
// re-validate after removing the patch version
|
|
||||||
if strings.HasSuffix(m.Error(), "is a prerelease version and the constraint is only looking for release versions") {
|
|
||||||
v2, err := semver.NewVersion(fmt.Sprintf("%v.%v.%v", currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch()))
|
|
||||||
if err == nil {
|
|
||||||
valid, _ = c.Validate(v2)
|
|
||||||
if valid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatVersion formats the package version based on epoch, version & release
|
// FormatVersion formats the package version based on epoch, version & release
|
||||||
func FormatVersion(pkg types.Package) string {
|
func FormatVersion(pkg types.Package) string {
|
||||||
return formatVersion(pkg.Epoch, pkg.Version, pkg.Release)
|
return formatVersion(pkg.Epoch, pkg.Version, pkg.Release)
|
||||||
@@ -66,34 +16,6 @@ func FormatSrcVersion(pkg types.Package) string {
|
|||||||
return formatVersion(pkg.SrcEpoch, pkg.SrcVersion, pkg.SrcRelease)
|
return formatVersion(pkg.SrcEpoch, pkg.SrcVersion, pkg.SrcRelease)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatPatchVersion returns the semver compatible version string given non-semver version
|
|
||||||
func FormatPatchVersion(version string) string {
|
|
||||||
part := strings.Split(version, ".")
|
|
||||||
if len(part) > 3 {
|
|
||||||
if _, err := strconv.Atoi(part[2]); err == nil {
|
|
||||||
version = strings.Join(part[:3], ".") + "-" + strings.Join(part[3:], ".")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i := range part {
|
|
||||||
res := preReleaseSplitter.FindStringSubmatch(part[i])
|
|
||||||
if res == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
number := res[1]
|
|
||||||
preRelease := res[2]
|
|
||||||
if preRelease != "" {
|
|
||||||
if !strings.HasPrefix(preRelease, "-") {
|
|
||||||
preRelease = "-" + preRelease
|
|
||||||
}
|
|
||||||
part[i] = number + preRelease
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version = strings.Join(part, ".")
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatVersion(epoch int, version, release string) string {
|
func formatVersion(epoch int, version, release string) string {
|
||||||
v := version
|
v := version
|
||||||
if release != "" {
|
if release != "" {
|
||||||
|
|||||||
@@ -3,161 +3,71 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
"github.com/aquasecurity/fanal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMatchVersions(t *testing.T) {
|
func TestFormatSrcVersion(t *testing.T) {
|
||||||
testCases := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
currentVersion string
|
pkg types.Package
|
||||||
rangeVersion []string
|
want string
|
||||||
expectedCheck bool
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "pass: expect true when os/machine is in version string",
|
name: "happy path",
|
||||||
currentVersion: "1.9.25-x86-mingw32",
|
pkg: types.Package{
|
||||||
rangeVersion: []string{`>= 1.9.24`},
|
SrcVersion: "1.2.3",
|
||||||
expectedCheck: true,
|
SrcRelease: "1",
|
||||||
|
},
|
||||||
|
want: "1.2.3-1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pass: expect true when language is in version string",
|
name: "with epoch",
|
||||||
currentVersion: "1.8.6-java",
|
pkg: types.Package{
|
||||||
rangeVersion: []string{`~> 1.5.5`, `~> 1.6.8`, `>= 1.7.7`},
|
SrcEpoch: 2,
|
||||||
expectedCheck: true,
|
SrcVersion: "1.2.3",
|
||||||
},
|
SrcRelease: "alpha",
|
||||||
{
|
},
|
||||||
name: "expect false",
|
want: "2:1.2.3-alpha",
|
||||||
currentVersion: "1.9.23-x86-mingw32",
|
|
||||||
rangeVersion: []string{`>= 1.9.24`},
|
|
||||||
expectedCheck: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// passes if (>= 1.2.3, < 2.0.0)
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "1.2.4",
|
|
||||||
rangeVersion: []string{`^1.2.3`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// passes if (>= 1.2.3, < 2.0.0)
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "2.0.0",
|
|
||||||
rangeVersion: []string{`^1.2.3`},
|
|
||||||
expectedCheck: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "3.1.16",
|
|
||||||
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "6.0.0",
|
|
||||||
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
|
|
||||||
expectedCheck: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "5.0.0-beta.5",
|
|
||||||
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Ruby GEM with more dots
|
|
||||||
name: "expect false",
|
|
||||||
currentVersion: "1.10.9-java",
|
|
||||||
rangeVersion: []string{`>= 1.6.7.1`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "expect prerelease suffixed in minor version to work",
|
|
||||||
currentVersion: "4.1a",
|
|
||||||
rangeVersion: []string{`< 4.2b1`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "expect prerelease suffixed in patch version to work",
|
|
||||||
currentVersion: "4.1.2a",
|
|
||||||
rangeVersion: []string{`< 4.2b1`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "expect prerelease suffixed in patch version to work in failing case",
|
|
||||||
currentVersion: "4.1.2c",
|
|
||||||
rangeVersion: []string{`<= 4.1.2b`},
|
|
||||||
expectedCheck: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "expect double equal to work",
|
|
||||||
currentVersion: "1.7",
|
|
||||||
rangeVersion: []string{`==1.7`},
|
|
||||||
expectedCheck: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
for _, tc := range testCases {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
got := FormatSrcVersion(tt.pkg)
|
||||||
v, err := semver.NewVersion(FormatPatchVersion(tc.currentVersion))
|
assert.Equal(t, tt.want, got)
|
||||||
require.NoError(t, err)
|
|
||||||
match := MatchVersions(v, tc.rangeVersion)
|
|
||||||
assert.Equal(t, tc.expectedCheck, match)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatPatchVersio(t *testing.T) {
|
func TestFormatVersion(t *testing.T) {
|
||||||
testCases := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
currentVersion string
|
pkg types.Package
|
||||||
expectedVersion string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "patch with no dots should return version should be unchanged",
|
name: "happy path",
|
||||||
currentVersion: "1.2.3-beta",
|
pkg: types.Package{
|
||||||
expectedVersion: "1.2.3-beta",
|
Version: "1.2.3",
|
||||||
|
Release: "1",
|
||||||
|
},
|
||||||
|
want: "1.2.3-1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "patch with dots after non-integer patch version should be unchanged",
|
name: "with epoch",
|
||||||
currentVersion: "1.2.3-beta.1",
|
pkg: types.Package{
|
||||||
expectedVersion: "1.2.3-beta.1",
|
Epoch: 2,
|
||||||
},
|
Version: "1.2.3",
|
||||||
{
|
Release: "alpha",
|
||||||
name: "patch with dots after integer patch version should append dash and join rest versions parts",
|
},
|
||||||
currentVersion: "1.2.3.4",
|
want: "2:1.2.3-alpha",
|
||||||
expectedVersion: "1.2.3-4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "patch with dots after integer patch version should append dash and join extra versions parts",
|
|
||||||
currentVersion: "1.2.3.4.5",
|
|
||||||
expectedVersion: "1.2.3-4.5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "unchanged case",
|
|
||||||
currentVersion: "1.2.3.4-5",
|
|
||||||
expectedVersion: "1.2.3-4-5",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "prerelease suffixed in minor",
|
|
||||||
currentVersion: "1.11a",
|
|
||||||
expectedVersion: "1.11-a",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "prerelease suffixed in patch",
|
|
||||||
currentVersion: "1.11.5rc",
|
|
||||||
expectedVersion: "1.11.5-rc",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
for _, tc := range testCases {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
got := FormatVersion(tt.pkg)
|
||||||
got := FormatPatchVersion(tc.currentVersion)
|
assert.Equal(t, tt.want, got)
|
||||||
assert.Equal(t, tc.expectedVersion, got)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user