From f50b0ce8af9f4f89fe6c399c783219cb2ad5f2f7 Mon Sep 17 00:00:00 2001 From: Teppei Fukuda Date: Sun, 30 Aug 2020 10:11:32 +0300 Subject: [PATCH] feat(library): support a custom data source (#613) * chore(mod): update trivy-db * refactor(detector/library): use programming language instead of package manager * feat(library): add general advisory * test(utils): add a util function initializing DB * test(advisory): add tests * chore: use aquasecurity/bolt-fixtures * refactor: add comments * chore(mod): revert fanal version * chore(mod): update trivy-db * refactor: update the comment --- go.mod | 4 +- go.sum | 38 +++-- pkg/detector/library/advisory.go | 101 +++++++++++++ pkg/detector/library/advisory_test.go | 117 +++++++++++++++ pkg/detector/library/driver.go | 80 +++++------ pkg/detector/library/driver_test.go | 135 ++++++++++++++++++ .../testdata/fixtures/php-without-prefix.yaml | 18 +++ .../library/testdata/fixtures/php.yaml | 18 +++ .../library/testdata/fixtures/ruby.yaml | 11 ++ pkg/utils/utils.go | 31 +++- 10 files changed, 495 insertions(+), 58 deletions(-) create mode 100644 pkg/detector/library/advisory.go create mode 100644 pkg/detector/library/advisory_test.go create mode 100644 pkg/detector/library/driver_test.go create mode 100644 pkg/detector/library/testdata/fixtures/php-without-prefix.yaml create mode 100644 pkg/detector/library/testdata/fixtures/php.yaml create mode 100644 pkg/detector/library/testdata/fixtures/ruby.yaml diff --git a/go.mod b/go.mod index 829e6d22e0..309acaeff2 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.13 require ( github.com/Masterminds/semver/v3 v3.1.0 + github.com/aquasecurity/bolt-fixtures v0.0.0-20200825112230-c0f517aea2ed github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882 github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b - github.com/aquasecurity/trivy-db v0.0.0-20200816184446-3853f69c6e2f + github.com/aquasecurity/trivy-db v0.0.0-20200826140828-6da6467703aa github.com/caarlos0/env/v6 v6.0.0 github.com/cenkalti/backoff v2.2.1+incompatible github.com/cheggaaa/pb/v3 v3.0.3 @@ -20,7 +21,6 @@ require ( github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 github.com/kylelemons/godebug v1.1.0 - github.com/mattn/go-colorable v0.1.4 // indirect github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a github.com/open-policy-agent/opa v0.21.1 github.com/spf13/afero v1.2.2 diff --git a/go.sum b/go.sum index 89d02406bd..f8321cb6a6 100644 --- a/go.sum +++ b/go.sum @@ -50,14 +50,16 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/aquasecurity/bolt-fixtures v0.0.0-20200825112230-c0f517aea2ed h1:o6vSjobtDn634//l4yBCGCC2RWoRc4K5AUH8W8DZZds= +github.com/aquasecurity/bolt-fixtures v0.0.0-20200825112230-c0f517aea2ed/go.mod h1:eViGpgc5g/JGRHWUfVaNLFJQwXQOVWDDQ40c7uViyZU= github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882 h1:65VcAKqhkKwMpLr9Wz+wNnsk+U+lv+v/qfOK04uXT3E= 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/go.mod h1:BpNTD9vHfrejKsED9rx04ldM1WIbeyXGYxUrqTVwxVQ= 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/trivy-db v0.0.0-20200816184446-3853f69c6e2f h1:JGfRqWKDVAOiHXnoNMpLnipYi2sV527ML+ACLceNu+4= -github.com/aquasecurity/trivy-db v0.0.0-20200816184446-3853f69c6e2f/go.mod h1:e6VoIYR4u3gsi1aPxgvuuhqj+BCyAa1ytmhGHcw+TuA= +github.com/aquasecurity/trivy-db v0.0.0-20200826140828-6da6467703aa h1:v+ghkIw3D3qUP++M3e9XGkkoq59iZdCOT4uxj4l2pXs= +github.com/aquasecurity/trivy-db v0.0.0-20200826140828-6da6467703aa/go.mod h1:/uvzkPkLMA6ZM1M2uIODx5J7b1wYb/goJ34Nidcukaw= github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA= github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ= github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= @@ -146,6 +148,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -181,7 +185,11 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U= github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -189,6 +197,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.8.0 h1:WCe9sBiI0oZb6EC6f3kq3dv0+aEiNdstT7b4xxq4MJQ= +github.com/goccy/go-yaml v1.8.0/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -306,6 +316,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -316,12 +328,17 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed/go.mod h1:SDJ4hurDYyQ9/7nc+eCYtXqdufgK4Cq9TJlwPklqEYA= github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -475,8 +492,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -579,14 +596,17 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE= golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= -golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -625,7 +645,6 @@ golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -667,8 +686,11 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/go-playground/validator.v9 v9.30.0 h1:Wk0Z37oBmKj9/n+tPyBHZmeL19LaCoK3Qq48VwYENss= +gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -683,11 +705,9 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl 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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/detector/library/advisory.go b/pkg/detector/library/advisory.go new file mode 100644 index 0000000000..5857821098 --- /dev/null +++ b/pkg/detector/library/advisory.go @@ -0,0 +1,101 @@ +package library + +import ( + "fmt" + "strings" + + "github.com/Masterminds/semver/v3" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy-db/pkg/db" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/types" +) + +// Advisory represents security advisories for each programming language +type Advisory struct { + lang string + comparer comparer +} + +func NewAdvisory(lang string) *Advisory { + return &Advisory{ + lang: lang, + comparer: newComparer(lang), + } +} + +// DetectVulnerabilities scans buckets with the prefix according to the programming language in "Advisory". +// If "lang" is python, it looks for buckets with "python::" 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) +// and detect vulnerabilities without specifying a specific bucket name. +func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) { + // e.g. "python::" + prefix := fmt.Sprintf("%s::", s.lang) + advisories, err := db.Config{}.GetAdvisories(prefix, pkgName) + if err != nil { + return nil, xerrors.Errorf("failed to get %s advisories: %w", s.lang, err) + } + + var vulns []types.DetectedVulnerability + for _, advisory := range advisories { + if !s.comparer.isVulnerable(pkgVer, advisory) { + continue + } + + vuln := types.DetectedVulnerability{ + VulnerabilityID: advisory.VulnerabilityID, + PkgName: pkgName, + InstalledVersion: pkgVer.String(), + FixedVersion: s.createFixedVersions(advisory), + } + vulns = append(vulns, vuln) + } + + return vulns, nil +} + +func (s *Advisory) createFixedVersions(advisory dbTypes.Advisory) string { + if len(advisory.PatchedVersions) != 0 { + return strings.Join(advisory.PatchedVersions, ", ") + } + + var fixedVersions []string + for _, version := range advisory.VulnerableVersions { + for _, s := range strings.Split(version, ",") { + s = strings.TrimSpace(s) + if !strings.HasPrefix(s, "<=") && strings.HasPrefix(s, "<") { + s = strings.TrimPrefix(s, "<") + fixedVersions = append(fixedVersions, strings.TrimSpace(s)) + } + } + } + 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 +} diff --git a/pkg/detector/library/advisory_test.go b/pkg/detector/library/advisory_test.go new file mode 100644 index 0000000000..b38d4c64b9 --- /dev/null +++ b/pkg/detector/library/advisory_test.go @@ -0,0 +1,117 @@ +package library_test + +import ( + "os" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/detector/library" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils" +) + +func TestAdvisory_DetectVulnerabilities(t *testing.T) { + type fields struct { + lang string + } + type args struct { + pkgName string + pkgVer *semver.Version + } + tests := []struct { + name string + fixtures []string + fields fields + args args + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{lang: vulnerability.PHP}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.2.6"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-10909", + PkgName: "symfony/symfony", + InstalledVersion: "4.2.6", + FixedVersion: "4.2.7", + }, + }, + }, + { + name: "no patched versions in the advisory", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{lang: vulnerability.PHP}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.4.6"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-5275", + PkgName: "symfony/symfony", + InstalledVersion: "4.4.6", + FixedVersion: "4.4.7", + }, + }, + }, + { + name: "no vulnerable versions in the advisory", + fixtures: []string{"testdata/fixtures/ruby.yaml"}, + fields: fields{lang: vulnerability.Ruby}, + args: args{ + pkgName: "activesupport", + pkgVer: semver.MustParse("4.1.1"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2015-3226", + PkgName: "activesupport", + InstalledVersion: "4.1.1", + FixedVersion: ">= 4.2.2, ~> 4.1.11", + }, + }, + }, + { + name: "no vulnerability", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{lang: vulnerability.PHP}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.4.7"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize DB + dir := utils.InitTestDB(t, tt.fixtures) + defer os.RemoveAll(dir) + defer db.Close() + + adv := library.NewAdvisory(tt.fields.lang) + got, err := adv.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer) + + switch { + case tt.wantErr != "": + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + default: + assert.NoError(t, err) + } + + // Compare + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index e3aa30597b..c5862f6a81 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/Masterminds/semver/v3" - "github.com/aquasecurity/fanal/analyzer/library" ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/detector/library/bundler" "github.com/aquasecurity/trivy/pkg/detector/library/cargo" "github.com/aquasecurity/trivy/pkg/detector/library/composer" @@ -31,19 +31,15 @@ func (d DriverFactory) NewDriver(filename string) (Driver, error) { var driver Driver switch filename { case "Gemfile.lock": - driver = NewBundlerDriver() + driver = newRubyDriver() case "Cargo.lock": - driver = NewCargoDriver() + driver = newRustDriver() case "composer.lock": - driver = NewComposerDriver() - case "package-lock.json": - driver = NewNpmDriver() - case "yarn.lock": - driver = NewYarnDriver() - case "Pipfile.lock": - driver = NewPipenvDriver() - case "poetry.lock": - driver = NewPoetryDriver() + driver = newPHPDriver() + case "package-lock.json", "yarn.lock": + driver = newNodejsDriver() + case "Pipfile.lock", "poetry.lock": + driver = newPythonDriver() default: return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename)) } @@ -51,18 +47,18 @@ func (d DriverFactory) NewDriver(filename string) (Driver, error) { } type Driver struct { - pkgManager string + lang string advisories []advisory } -func NewDriver(p string, advisories ...advisory) Driver { - return Driver{pkgManager: p, advisories: advisories} +func NewDriver(lang string, advisories ...advisory) Driver { + return Driver{lang: lang, advisories: advisories} } -func (driver *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) { +func (d *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) { var detectedVulnerabilities []types.DetectedVulnerability uniqVulnIdMap := make(map[string]struct{}) - for _, d := range driver.advisories { + for _, d := range append(d.advisories, NewAdvisory(d.lang)) { vulns, err := d.DetectVulnerabilities(pkgName, pkgVer) if err != nil { return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err) @@ -79,34 +75,26 @@ func (driver *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.De return detectedVulnerabilities, nil } -func NewBundlerDriver() Driver { - return NewDriver(library.Bundler, ghsa.NewAdvisory(ecosystem.Rubygems), bundler.NewAdvisory()) -} - -func NewComposerDriver() Driver { - return NewDriver(library.Composer, ghsa.NewAdvisory(ecosystem.Composer), composer.NewAdvisory()) -} - -func NewCargoDriver() Driver { - return NewDriver(library.Cargo, cargo.NewAdvisory()) -} - -func NewNpmDriver() Driver { - return NewDriver(library.Npm, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory()) -} - -func NewYarnDriver() Driver { - return NewDriver(library.Yarn, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory()) -} - -func NewPipenvDriver() Driver { - return NewDriver(library.Pipenv, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory()) -} - -func NewPoetryDriver() Driver { - return NewDriver(library.Poetry, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory()) -} - func (d *Driver) Type() string { - return d.pkgManager + return d.lang +} + +func newRubyDriver() Driver { + return NewDriver(vulnerability.Ruby, ghsa.NewAdvisory(ecosystem.Rubygems), bundler.NewAdvisory()) +} + +func newPHPDriver() Driver { + return NewDriver(vulnerability.PHP, ghsa.NewAdvisory(ecosystem.Composer), composer.NewAdvisory()) +} + +func newRustDriver() Driver { + return NewDriver(vulnerability.Rust, cargo.NewAdvisory()) +} + +func newNodejsDriver() Driver { + return NewDriver(vulnerability.Nodejs, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory()) +} + +func newPythonDriver() Driver { + return NewDriver(vulnerability.Python, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory()) } diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go new file mode 100644 index 0000000000..6a01108aa8 --- /dev/null +++ b/pkg/detector/library/driver_test.go @@ -0,0 +1,135 @@ +package library_test + +import ( + "os" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/detector/library" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils" +) + +func TestDriver_Detect(t *testing.T) { + type fields struct { + fileName string + } + type args struct { + pkgName string + pkgVer *semver.Version + } + tests := []struct { + name string + fixtures []string + fields fields + args args + want []types.DetectedVulnerability + wantErr string + }{ + { + name: "happy path", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{fileName: "composer.lock"}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.2.6"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-10909", + PkgName: "symfony/symfony", + InstalledVersion: "4.2.6", + FixedVersion: "4.2.7", + }, + }, + }, + { + name: "non-prefix buckets", + fixtures: []string{"testdata/fixtures/php-without-prefix.yaml"}, + fields: fields{fileName: "composer.lock"}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.2.6"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-10909", + PkgName: "symfony/symfony", + InstalledVersion: "4.2.6", + FixedVersion: "4.2.7", + }, + }, + }, + { + name: "no patched versions in the advisory", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{fileName: "composer.lock"}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.4.6"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-5275", + PkgName: "symfony/symfony", + InstalledVersion: "4.4.6", + FixedVersion: "4.4.7", + }, + }, + }, + { + name: "no vulnerable versions in the advisory", + fixtures: []string{"testdata/fixtures/ruby.yaml"}, + fields: fields{fileName: "Gemfile.lock"}, + args: args{ + pkgName: "activesupport", + pkgVer: semver.MustParse("4.1.1"), + }, + want: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2015-3226", + PkgName: "activesupport", + InstalledVersion: "4.1.1", + FixedVersion: ">= 4.2.2, ~> 4.1.11", + }, + }, + }, + { + name: "no vulnerability", + fixtures: []string{"testdata/fixtures/php.yaml"}, + fields: fields{fileName: "composer.lock"}, + args: args{ + pkgName: "symfony/symfony", + pkgVer: semver.MustParse("4.4.7"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Initialize DB + dir := utils.InitTestDB(t, tt.fixtures) + defer os.RemoveAll(dir) + defer db.Close() + + factory := library.DriverFactory{} + driver, err := factory.NewDriver(tt.fields.fileName) + require.NoError(t, err) + + got, err := driver.Detect(tt.args.pkgName, tt.args.pkgVer) + switch { + case tt.wantErr != "": + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + default: + assert.NoError(t, err) + } + + // Compare + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/detector/library/testdata/fixtures/php-without-prefix.yaml b/pkg/detector/library/testdata/fixtures/php-without-prefix.yaml new file mode 100644 index 0000000000..cb3367efc2 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/php-without-prefix.yaml @@ -0,0 +1,18 @@ +- bucket: GitHub Security Advisory Composer + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2019-10909 + value: + PatchedVersions: + - 4.2.7 + VulnerableVersions: + - ">= 4.2.0, < 4.2.7" +- bucket: php-security-advisories + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2020-5275 + value: + VulnerableVersions: + - ">= 4.4.0, < 4.4.7" \ No newline at end of file diff --git a/pkg/detector/library/testdata/fixtures/php.yaml b/pkg/detector/library/testdata/fixtures/php.yaml new file mode 100644 index 0000000000..9c93e64ea2 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/php.yaml @@ -0,0 +1,18 @@ +- bucket: "php::GitHub Security Advisory Composer" + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2019-10909 + value: + PatchedVersions: + - 4.2.7 + VulnerableVersions: + - ">= 4.2.0, < 4.2.7" +- bucket: "php::php-security-advisories" + pairs: + - bucket: symfony/symfony + pairs: + - key: CVE-2020-5275 + value: + VulnerableVersions: + - ">= 4.4.0, < 4.4.7" \ No newline at end of file diff --git a/pkg/detector/library/testdata/fixtures/ruby.yaml b/pkg/detector/library/testdata/fixtures/ruby.yaml new file mode 100644 index 0000000000..1e50b26154 --- /dev/null +++ b/pkg/detector/library/testdata/fixtures/ruby.yaml @@ -0,0 +1,11 @@ +- bucket: "ruby::ruby-advisory-db" + pairs: + - bucket: activesupport + pairs: + - key: CVE-2015-3226 + value: + PatchedVersions: + - ">= 4.2.2" + - "~> 4.1.11" + UnaffectedVersions: + - "< 4.1.0" \ No newline at end of file diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 94be2f307d..8e39a28b4e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,12 +3,18 @@ package utils import ( "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" + "testing" - "github.com/aquasecurity/trivy/pkg/log" + "github.com/stretchr/testify/require" "golang.org/x/xerrors" + + fixtures "github.com/aquasecurity/bolt-fixtures" + "github.com/aquasecurity/trivy-db/pkg/db" + "github.com/aquasecurity/trivy/pkg/log" ) var cacheDir string @@ -119,3 +125,26 @@ func CopyFile(src, dst string) (int64, error) { n, err := io.Copy(destination, source) return n, err } + +// InitTestDB is a utility function initializing BoltDB for unit testing +func InitTestDB(t *testing.T, fixtureFiles []string) string { + // Create a temp dir + dir, err := ioutil.TempDir("", "TestDB") + require.NoError(t, err) + + dbPath := db.Path(dir) + dbDir := filepath.Dir(dbPath) + err = os.MkdirAll(dbDir, 0700) + require.NoError(t, err) + + // Load testdata into BoltDB + loader, err := fixtures.New(dbPath, fixtureFiles) + require.NoError(t, err) + require.NoError(t, loader.Load()) + require.NoError(t, loader.Close()) + + // Initialize DB + require.NoError(t, db.Init(dir)) + + return dir +}