diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3c457dd511..0a754576af 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,7 +17,7 @@ jobs: - name: Lint uses: golangci/golangci-lint-action@v2 with: - version: v1.31 + version: v1.39 args: --deadline=30m - name: Run unit tests diff --git a/Makefile b/Makefile index dbcf8a6386..ccdb76fa50 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ $(GOBIN)/wire: .PHONY: wire wire: $(GOBIN)/wire - wire gen ./pkg/... ./internal/... + wire gen ./pkg/... .PHONY: mock mock: $(GOBIN)/mockery diff --git a/go.mod b/go.mod index 8ed44348db..d7966f709b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/goutils v1.1.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 - github.com/aquasecurity/fanal v0.0.0-20210501235003-c816628070c1 + github.com/aquasecurity/fanal v0.0.0-20210519050514-051631be3f69 github.com/aquasecurity/go-dep-parser v0.0.0-20210427143403-3c97ccc53976 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 @@ -19,11 +19,12 @@ require ( github.com/docker/docker v20.10.3+incompatible github.com/docker/go-connections v0.4.0 github.com/elazarl/goproxy v0.0.0-20200809112317-0581fc3aee2d // indirect + github.com/fatih/color v1.10.0 github.com/go-redis/redis/v8 v8.4.0 github.com/goccy/go-yaml v1.8.2 // indirect github.com/golang/protobuf v1.4.3 github.com/google/go-containerregistry v0.1.2 - github.com/google/go-github/v28 v28.1.1 + github.com/google/go-github/v33 v33.0.0 github.com/google/wire v0.4.0 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/hashicorp/go-getter v1.5.2 @@ -46,6 +47,7 @@ require ( github.com/urfave/cli/v2 v2.3.0 go.uber.org/zap v1.16.0 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/protobuf v1.25.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect diff --git a/go.sum b/go.sum index e23573c522..ef0ee30ef7 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,7 @@ github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdc github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= @@ -166,16 +167,14 @@ github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDw github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30xLN2sUZcMXl50hg+PJCIDdJgIvIbVcKqLJ/ZrtM= github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= -github.com/aquasecurity/fanal v0.0.0-20210430044351-34b55f31bc70 h1:0v2pf+hIElPvGKLyDye08UtPH8AjujTVLUOWOgoM5O4= -github.com/aquasecurity/fanal v0.0.0-20210430044351-34b55f31bc70/go.mod h1:cPTOJcf8bdP24oXhBsPVVczcjkICcApAwAOsn6CpHTI= -github.com/aquasecurity/fanal v0.0.0-20210501093021-8aaac3e8dea7 h1:bY5D5GVthqQCvnNllG2NVXYpOQJJRi7KFhLdVrskaDg= -github.com/aquasecurity/fanal v0.0.0-20210501093021-8aaac3e8dea7/go.mod h1:cPTOJcf8bdP24oXhBsPVVczcjkICcApAwAOsn6CpHTI= -github.com/aquasecurity/fanal v0.0.0-20210501235003-c816628070c1 h1:xgdjcsA4Go/9k9XDXYimVF+BgyMlt7YoeWTMs2DpR8Y= -github.com/aquasecurity/fanal v0.0.0-20210501235003-c816628070c1/go.mod h1:cPTOJcf8bdP24oXhBsPVVczcjkICcApAwAOsn6CpHTI= +github.com/aquasecurity/fanal v0.0.0-20210519050514-051631be3f69 h1:J7uGm7PSBI4yAACkaxH4/PTdfhQYIQy7tSlwUknr4U8= +github.com/aquasecurity/fanal v0.0.0-20210519050514-051631be3f69/go.mod h1://8XzKt4IvM6QUSIYpY5KURCEArOzscy6R0FsmVLEBo= github.com/aquasecurity/go-dep-parser v0.0.0-20210427143403-3c97ccc53976 h1:ypl/IDxujzEymmwtzGJqQyboI2oZr1se+OoYaGqgBzQ= github.com/aquasecurity/go-dep-parser v0.0.0-20210427143403-3c97ccc53976/go.mod h1:Cv/FOCXy6gwvDbz/KX48+y//SmbnKroFwW5hquXn5G4= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= @@ -309,6 +308,7 @@ github.com/containerd/stargz-snapshotter v0.0.0-20201027054423-3a04e4c2c116/go.m github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v1.0.1 h1:PvuK4E3D5S5q6IqsPDCy928FhP0LUIGcmZ/Yhgp5Djw= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -481,6 +481,7 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= @@ -590,8 +591,9 @@ github.com/google/go-containerregistry v0.1.2 h1:YjFNKqxzWUVZND8d4ItF9wuYlE75WQf github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM= +github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg= github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -711,7 +713,9 @@ github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -791,6 +795,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -869,6 +874,7 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= @@ -876,9 +882,12 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/buildkit v0.8.1 h1:zrGxLwffKM8nVxBvaJa7H404eQLfqlg1GB6YVIzXVQ0= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= @@ -951,6 +960,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/open-policy-agent/conftest v0.23.0 h1:i/cmUjNKDz973vR1cm+x3DqTei/jBPosPvjeot6+p9M= github.com/open-policy-agent/conftest v0.23.0/go.mod h1:NA6+vKd93pb04H9jiV3WRGJKLj/pzYdQg7XCdoPPUDI= github.com/open-policy-agent/opa v0.25.2 h1:zTQuUMvB5xkYixKB9LFVbUd7DcUt1jfS0QKTo+/Vfyc= github.com/open-policy-agent/opa v0.25.2/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= @@ -1177,6 +1187,7 @@ github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPf github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmccombs/hcl2json v0.3.1 h1:Pf+Lb9OpZ5lkQuIC0BB5txdCQskZ2ud/l8sz/Nkjf3A= github.com/tmccombs/hcl2json v0.3.1/go.mod h1:ljY0/prd2IFUF3cagQjV3cpPEEQKzqyGqnKI7m5DBVY= github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= @@ -1201,7 +1212,6 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1/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/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= @@ -1243,6 +1253,7 @@ github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.6.1 h1:wHtZ+LSSQVwUSb+XIJ5E9hgAQxyWATZsAWT+ESJ9dQ0= github.com/zclconf/go-cty v1.6.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/integration/testdata/opensuse-leap-423.json.golden b/integration/testdata/opensuse-leap-423.json.golden index 4dc2d6c78a..81c12b1761 100644 --- a/integration/testdata/opensuse-leap-423.json.golden +++ b/integration/testdata/opensuse-leap-423.json.golden @@ -1,7 +1,6 @@ [ { "Target": "testdata/fixtures/opensuse-leap-423.tar.gz (opensuse.leap 42.3)", - "Type": "opensuse.leap", - "Vulnerabilities": null + "Type": "opensuse.leap" } ] \ No newline at end of file diff --git a/pkg/cache/remote.go b/pkg/cache/remote.go index 899d713730..c703338e86 100644 --- a/pkg/cache/remote.go +++ b/pkg/cache/remote.go @@ -30,8 +30,8 @@ func NewRemoteCache(url RemoteURL, customHeaders http.Header) cache.ArtifactCach } // PutArtifact sends artifact to remote client -func (c RemoteCache) PutArtifact(imageID string, imageInfo types.ArtifactInfo) error { - _, err := c.client.PutArtifact(c.ctx, rpc.ConvertToRPCArtifactInfo(imageID, imageInfo)) +func (c RemoteCache) PutArtifact(imageID string, artifactInfo types.ArtifactInfo) error { + _, err := c.client.PutArtifact(c.ctx, rpc.ConvertToRPCArtifactInfo(imageID, artifactInfo)) if err != nil { return xerrors.Errorf("unable to store cache on the server: %w", err) } @@ -39,8 +39,8 @@ func (c RemoteCache) PutArtifact(imageID string, imageInfo types.ArtifactInfo) e } // PutBlob sends blobInfo to remote client -func (c RemoteCache) PutBlob(diffID string, layerInfo types.BlobInfo) error { - _, err := c.client.PutBlob(c.ctx, rpc.ConvertToRPCBlobInfo(diffID, layerInfo)) +func (c RemoteCache) PutBlob(diffID string, blobInfo types.BlobInfo) error { + _, err := c.client.PutBlob(c.ctx, rpc.ConvertToRPCBlobInfo(diffID, blobInfo)) if err != nil { return xerrors.Errorf("unable to store cache on the server: %w", err) } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 976792c9e5..16909223ff 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -12,12 +12,13 @@ import ( "github.com/urfave/cli/v2" "github.com/aquasecurity/trivy-db/pkg/db" - "github.com/aquasecurity/trivy-db/pkg/types" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/commands/client" "github.com/aquasecurity/trivy/pkg/commands/plugin" "github.com/aquasecurity/trivy/pkg/commands/server" tdb "github.com/aquasecurity/trivy/pkg/db" + "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils" "github.com/aquasecurity/trivy/pkg/vulnerability" ) @@ -56,7 +57,7 @@ var ( severityFlag = cli.StringFlag{ Name: "severity", Aliases: []string{"s"}, - Value: strings.Join(types.SeverityNames, ","), + Value: strings.Join(dbTypes.SeverityNames, ","), Usage: "severities of vulnerabilities to be displayed (comma separated)", EnvVars: []string{"TRIVY_SEVERITY"}, } @@ -134,11 +135,19 @@ var ( vulnTypeFlag = cli.StringFlag{ Name: "vuln-type", - Value: "os,library", + Value: strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","), Usage: "comma-separated list of vulnerability types (os,library)", EnvVars: []string{"TRIVY_VULN_TYPE"}, } + securityChecksFlag = cli.StringFlag{ + Name: "security-checks", + Value: types.SecurityCheckVulnerability, + Usage: "comma-separated list of what security issues to detect (vuln,config)", + EnvVars: []string{"TRIVY_SECURITY_CHECKS"}, + Hidden: true, + } + cacheDirFlag = cli.StringFlag{ Name: "cache-dir", Value: utils.DefaultCacheDir(), @@ -231,6 +240,7 @@ var ( &ignoreUnfixedFlag, &removedPkgsFlag, &vulnTypeFlag, + &securityChecksFlag, &ignoreFileFlag, &timeoutFlag, &lightFlag, @@ -405,6 +415,7 @@ func NewFilesystemCommand() *cli.Command { &ignoreUnfixedFlag, &removedPkgsFlag, &vulnTypeFlag, + &securityChecksFlag, &ignoreFileFlag, &cacheBackendFlag, &timeoutFlag, @@ -437,6 +448,7 @@ func NewRepositoryCommand() *cli.Command { &ignoreUnfixedFlag, &removedPkgsFlag, &vulnTypeFlag, + &securityChecksFlag, &ignoreFileFlag, &cacheBackendFlag, &timeoutFlag, @@ -468,6 +480,7 @@ func NewClientCommand() *cli.Command { &ignoreUnfixedFlag, &removedPkgsFlag, &vulnTypeFlag, + &securityChecksFlag, &ignoreFileFlag, &timeoutFlag, &ignorePolicy, @@ -519,9 +532,10 @@ func NewServerCommand() *cli.Command { // NewPluginCommand is the factory method to add plugin command func NewPluginCommand() *cli.Command { return &cli.Command{ - Name: "plugin", - Aliases: []string{"p"}, - Usage: "manage plugins", + Name: "plugin", + Aliases: []string{"p"}, + ArgsUsage: "plugin_uri", + Usage: "manage plugins", Subcommands: cli.Commands{ { Name: "install", diff --git a/pkg/commands/artifact/config.go b/pkg/commands/artifact/config.go deleted file mode 100644 index b06bff046a..0000000000 --- a/pkg/commands/artifact/config.go +++ /dev/null @@ -1,88 +0,0 @@ -package artifact - -import ( - "github.com/urfave/cli/v2" - "golang.org/x/xerrors" - - "github.com/aquasecurity/trivy/pkg/commands/config" -) - -// Config holds the artifact config -type Config struct { - config.GlobalConfig - config.ArtifactConfig - config.DBConfig - config.ImageConfig - config.ReportConfig - config.CacheConfig - - // deprecated - onlyUpdate string - // deprecated - refresh bool - // deprecated - autoRefresh bool -} - -// NewConfig is the factory method to return config -func NewConfig(c *cli.Context) (Config, error) { - gc, err := config.NewGlobalConfig(c) - if err != nil { - return Config{}, xerrors.Errorf("failed to initialize global options: %w", err) - } - - return Config{ - GlobalConfig: gc, - ArtifactConfig: config.NewArtifactConfig(c), - DBConfig: config.NewDBConfig(c), - ImageConfig: config.NewImageConfig(c), - ReportConfig: config.NewReportConfig(c), - CacheConfig: config.NewCacheConfig(c), - - onlyUpdate: c.String("only-update"), - refresh: c.Bool("refresh"), - autoRefresh: c.Bool("auto-refresh"), - }, nil -} - -// Init initializes the artifact config -func (c *Config) Init() error { - if c.onlyUpdate != "" || c.refresh || c.autoRefresh { - c.Logger.Warn("--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.") - } - - if err := c.initPreScanConfigs(); err != nil { - return err - } - - // --clear-cache, --download-db-only and --reset don't conduct the scan - if c.skipScan() { - return nil - } - - if err := c.ArtifactConfig.Init(c.Context, c.Logger); err != nil { - return err - } - - return nil -} - -func (c *Config) initPreScanConfigs() error { - if err := c.ReportConfig.Init(c.Logger); err != nil { - return err - } - if err := c.DBConfig.Init(); err != nil { - return err - } - if err := c.CacheConfig.Init(); err != nil { - return err - } - return nil -} - -func (c *Config) skipScan() bool { - if c.ClearCache || c.DownloadDBOnly || c.Reset { - return true - } - return false -} diff --git a/pkg/commands/artifact/fs.go b/pkg/commands/artifact/fs.go index e0070b6923..b039e45098 100644 --- a/pkg/commands/artifact/fs.go +++ b/pkg/commands/artifact/fs.go @@ -8,13 +8,14 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy/pkg/scanner" ) func filesystemScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, - _ time.Duration, disabled []analyzer.Type) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeFilesystemScanner(ctx, dir, ac, lac, disabled) + _ time.Duration, disabled []analyzer.Type, opt config.ScannerOption) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeFilesystemScanner(ctx, dir, ac, lac, disabled, opt) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) } @@ -23,15 +24,15 @@ func filesystemScanner(ctx context.Context, dir string, ac cache.ArtifactCache, // FilesystemRun runs scan on filesystem func FilesystemRun(ctx *cli.Context) error { - c, err := NewConfig(ctx) + opt, err := NewOption(ctx) if err != nil { - return err + return xerrors.Errorf("option error: %w", err) } - // initialize config - if err = c.Init(); err != nil { + // initialize options + if err = opt.Init(); err != nil { return xerrors.Errorf("failed to initialize options: %w", err) } - return run(ctx.Context, c, filesystemScanner) + return Run(ctx.Context, opt, filesystemScanner, initFSCache) } diff --git a/pkg/commands/artifact/image.go b/pkg/commands/artifact/image.go index 849d9599a5..660b1df9a8 100644 --- a/pkg/commands/artifact/image.go +++ b/pkg/commands/artifact/image.go @@ -8,13 +8,14 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy/pkg/scanner" ) func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, - timeout time.Duration, disabled []analyzer.Type) (scanner.Scanner, func(), error) { - s, err := initializeArchiveScanner(ctx, input, ac, lac, timeout, disabled) + timeout time.Duration, disabled []analyzer.Type, opt config.ScannerOption) (scanner.Scanner, func(), error) { + s, err := initializeArchiveScanner(ctx, input, ac, lac, timeout, disabled, opt) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err) } @@ -22,9 +23,8 @@ func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, l } func dockerScanner(ctx context.Context, imageName string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, - timeout time.Duration, disabled []analyzer.Type) ( - scanner.Scanner, func(), error) { - s, cleanup, err := initializeDockerScanner(ctx, imageName, ac, lac, timeout, disabled) + timeout time.Duration, disabled []analyzer.Type, opt config.ScannerOption) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeDockerScanner(ctx, imageName, ac, lac, timeout, disabled, opt) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err) } @@ -33,20 +33,20 @@ func dockerScanner(ctx context.Context, imageName string, ac cache.ArtifactCache // ImageRun runs scan on docker image func ImageRun(ctx *cli.Context) error { - c, err := NewConfig(ctx) + opt, err := NewOption(ctx) if err != nil { - return err + return xerrors.Errorf("option error: %w", err) } - // initialize config - if err = c.Init(); err != nil { - return xerrors.Errorf("failed to initialize options: %w", err) + // initialize options + if err = opt.Init(); err != nil { + return xerrors.Errorf("option initialize error: %w", err) } - if c.Input != "" { + if opt.Input != "" { // scan tar file - return run(ctx.Context, c, archiveScanner) + return Run(ctx.Context, opt, archiveScanner, initFSCache) } - return run(ctx.Context, c, dockerScanner) + return Run(ctx.Context, opt, dockerScanner, initFSCache) } diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go index 40584659db..7ad391545a 100644 --- a/pkg/commands/artifact/inject.go +++ b/pkg/commands/artifact/inject.go @@ -9,38 +9,41 @@ import ( "github.com/google/wire" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/vulnerability" ) func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type) ( - scanner.Scanner, func(), error) { + localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type, + configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneDockerSet) return scanner.Scanner{}, nil, nil } func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type) ( - scanner.Scanner, error) { + localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type, + configScannerOption config.ScannerOption) (scanner.Scanner, error) { wire.Build(scanner.StandaloneArchiveSet) return scanner.Scanner{}, nil } func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type) (scanner.Scanner, func(), error) { + localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) ( + scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneFilesystemSet) return scanner.Scanner{}, nil, nil } func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type) (scanner.Scanner, func(), error) { + localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) ( + scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneRepositorySet) return scanner.Scanner{}, nil, nil } -func initializeVulnerabilityClient() vulnerability.Client { +func initializeResultClient() vulnerability.Client { wire.Build(vulnerability.SuperSet) return vulnerability.Client{} } diff --git a/pkg/commands/artifact/option.go b/pkg/commands/artifact/option.go new file mode 100644 index 0000000000..17de2f89ba --- /dev/null +++ b/pkg/commands/artifact/option.go @@ -0,0 +1,88 @@ +package artifact + +import ( + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/commands/option" +) + +// Option holds the artifact options +type Option struct { + option.GlobalOption + option.ArtifactOption + option.DBOption + option.ImageOption + option.ReportOption + option.CacheOption + + // deprecated + onlyUpdate string + // deprecated + refresh bool + // deprecated + autoRefresh bool +} + +// NewOption is the factory method to return options +func NewOption(c *cli.Context) (Option, error) { + gc, err := option.NewGlobalOption(c) + if err != nil { + return Option{}, xerrors.Errorf("failed to initialize global options: %w", err) + } + + return Option{ + GlobalOption: gc, + ArtifactOption: option.NewArtifactOption(c), + DBOption: option.NewDBOption(c), + ImageOption: option.NewImageOption(c), + ReportOption: option.NewReportOption(c), + CacheOption: option.NewCacheOption(c), + + onlyUpdate: c.String("only-update"), + refresh: c.Bool("refresh"), + autoRefresh: c.Bool("auto-refresh"), + }, nil +} + +// Init initializes the artifact options +func (c *Option) Init() error { + if c.onlyUpdate != "" || c.refresh || c.autoRefresh { + c.Logger.Warn("--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.") + } + + if err := c.initPreScanOptions(); err != nil { + return err + } + + // --clear-cache, --download-db-only and --reset don't conduct the scan + if c.skipScan() { + return nil + } + + if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil { + return err + } + + return nil +} + +func (c *Option) initPreScanOptions() error { + if err := c.ReportOption.Init(c.Logger); err != nil { + return err + } + if err := c.DBOption.Init(); err != nil { + return err + } + if err := c.CacheOption.Init(); err != nil { + return err + } + return nil +} + +func (c *Option) skipScan() bool { + if c.ClearCache || c.DownloadDBOnly || c.Reset { + return true + } + return false +} diff --git a/pkg/commands/artifact/config_test.go b/pkg/commands/artifact/option_test.go similarity index 57% rename from pkg/commands/artifact/config_test.go rename to pkg/commands/artifact/option_test.go index b84b15ae76..6fcedf3c24 100644 --- a/pkg/commands/artifact/config_test.go +++ b/pkg/commands/artifact/option_test.go @@ -12,53 +12,48 @@ import ( "go.uber.org/zap/zaptest/observer" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" + "github.com/aquasecurity/trivy/pkg/types" ) -func TestConfig_Init(t *testing.T) { +func TestOption_Init(t *testing.T) { tests := []struct { - name string - globalConfig config.GlobalConfig - dbConfig config.DBConfig - imageConfig config.ImageConfig - reportConfig config.ReportConfig - args []string - logs []string - want Config - wantErr string + name string + args []string + logs []string + want Option + wantErr string }{ { name: "happy path", - reportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - VulnType: []string{"os"}, - }, args: []string{"--severity", "CRITICAL", "--vuln-type", "os", "--quiet", "alpine:3.10"}, - want: Config{ - GlobalConfig: config.GlobalConfig{ + want: Option{ + GlobalOption: option.GlobalOption{ Quiet: true, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "alpine:3.10", }, - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - VulnType: []string{"os"}, - Output: os.Stdout, + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Output: os.Stdout, }, }, }, { name: "happy path: reset", args: []string{"--reset"}, - want: Config{ - DBConfig: config.DBConfig{ + want: Option{ + DBOption: option.DBOption{ Reset: true, }, - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, }, }, @@ -68,13 +63,14 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "unknown severity option: unknown severity: INVALID", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "centos:7", }, }, @@ -85,13 +81,14 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityLow}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "debian:buster", }, onlyUpdate: "alpine", @@ -103,14 +100,15 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--template is ignored because --format template is not specified. Use --template option with --format template option.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Template: "@contrib/gitlab.tpl", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Template: "@contrib/gitlab.tpl", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, }, @@ -121,15 +119,16 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--template is ignored because --format json is specified. Use --template option with --format template option.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Template: "@contrib/gitlab.tpl", - Format: "json", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Template: "@contrib/gitlab.tpl", + Format: "json", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, }, @@ -140,14 +139,15 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--format template is ignored because --template not is specified. Specify --template option when you use --format template.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Format: "template", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Format: "template", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, }, @@ -181,6 +181,7 @@ func TestConfig_Init(t *testing.T) { set.Bool("auto-refresh", false, "") set.String("severity", "CRITICAL", "") set.String("vuln-type", "os,library", "") + set.String("security-checks", "vuln", "") set.String("only-update", "", "") set.String("template", "", "") set.String("format", "", "") @@ -188,10 +189,10 @@ func TestConfig_Init(t *testing.T) { ctx := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - c, err := NewConfig(ctx) + c, err := NewOption(ctx) require.NoError(t, err, err) - c.GlobalConfig.Logger = logger.Sugar() + c.GlobalOption.Logger = logger.Sugar() err = c.Init() // tests log messages @@ -211,8 +212,8 @@ func TestConfig_Init(t *testing.T) { assert.NoError(t, err, tt.name) } - tt.want.GlobalConfig.Context = ctx - tt.want.GlobalConfig.Logger = logger.Sugar() + tt.want.GlobalOption.Context = ctx + tt.want.GlobalOption.Logger = logger.Sugar() assert.Equal(t, tt.want, c, tt.name) }) } diff --git a/pkg/commands/artifact/repository.go b/pkg/commands/artifact/repository.go index a294489107..a6f064df7f 100644 --- a/pkg/commands/artifact/repository.go +++ b/pkg/commands/artifact/repository.go @@ -8,14 +8,14 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy/pkg/scanner" ) func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, - _ time.Duration, disabled []analyzer.Type) ( - scanner.Scanner, func(), error) { - s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac, disabled) + _ time.Duration, disabled []analyzer.Type, opt config.ScannerOption) (scanner.Scanner, func(), error) { + s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac, disabled, opt) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) } @@ -24,15 +24,15 @@ func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, // RepositoryRun runs scan on repository func RepositoryRun(ctx *cli.Context) error { - c, err := NewConfig(ctx) + opt, err := NewOption(ctx) if err != nil { - return err + return xerrors.Errorf("option error: %w", err) } - // initialize config - if err = c.Init(); err != nil { + // initialize options + if err = opt.Init(); err != nil { return xerrors.Errorf("failed to initialize options: %w", err) } - return run(ctx.Context, c, repositoryScanner) + return Run(ctx.Context, opt, repositoryScanner, initFSCache) } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 500cda6b8e..20fd7d92e8 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -3,13 +3,13 @@ package artifact import ( "context" "errors" - l "log" "os" "time" "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/commands/operation" @@ -24,25 +24,25 @@ var errSkipScan = errors.New("skip subsequent processes") // InitializeScanner type to define initialize function signature type InitializeScanner func(context.Context, string, cache.ArtifactCache, cache.LocalArtifactCache, time.Duration, - []analyzer.Type) (scanner.Scanner, func(), error) + []analyzer.Type, config.ScannerOption) (scanner.Scanner, func(), error) -func run(ctx context.Context, conf Config, initializeScanner InitializeScanner) error { - ctx, cancel := context.WithTimeout(ctx, conf.Timeout) +// InitCache defines cache initializer +type InitCache func(c Option) (cache.Cache, error) + +// Run performs artifact scanning +func Run(ctx context.Context, opt Option, initializeScanner InitializeScanner, initCache InitCache) error { + ctx, cancel := context.WithTimeout(ctx, opt.Timeout) defer cancel() - err := runWithTimeout(ctx, conf, initializeScanner) - if xerrors.Is(err, context.DeadlineExceeded) { - log.Logger.Warn("Increase --timeout value") - } - return err + return runWithTimeout(ctx, opt, initializeScanner, initCache) } -func runWithTimeout(ctx context.Context, conf Config, initializeScanner InitializeScanner) error { - if err := log.InitLogger(conf.Debug, conf.Quiet); err != nil { - l.Fatal(err) +func runWithTimeout(ctx context.Context, opt Option, initializeScanner InitializeScanner, initCache InitCache) error { + if err := log.InitLogger(opt.Debug, opt.Quiet); err != nil { + return err } - cacheClient, err := initCache(conf) + cacheClient, err := initCache(opt) if err != nil { if errors.Is(err, errSkipScan) { return nil @@ -51,34 +51,37 @@ func runWithTimeout(ctx context.Context, conf Config, initializeScanner Initiali } defer cacheClient.Close() - if err = initDB(conf); err != nil { - if errors.Is(err, errSkipScan) { - return nil + // When scanning config files, it doesn't need to download the vulnerability database. + if utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) { + if err = initDB(opt); err != nil { + if errors.Is(err, errSkipScan) { + return nil + } + return xerrors.Errorf("DB error: %w", err) } - return xerrors.Errorf("DB error: %w", err) + defer db.Close() } - defer db.Close() - results, err := scan(ctx, conf, initializeScanner, cacheClient) + results, err := scan(ctx, opt, initializeScanner, cacheClient) if err != nil { return xerrors.Errorf("scan error: %w", err) } - results, err = filter(ctx, conf, results) + results, err = filter(ctx, opt, results) if err != nil { return xerrors.Errorf("filter error: %w", err) } - if err = report.WriteResults(conf.Format, conf.Output, conf.Severities, results, conf.Template, conf.Light); err != nil { + if err = report.WriteResults(opt.Format, opt.Output, opt.Severities, results, opt.Template, opt.Light); err != nil { return xerrors.Errorf("unable to write results: %w", err) } - exit(conf, results) + exit(opt, results) return nil } -func initCache(c Config) (operation.Cache, error) { +func initFSCache(c Option) (cache.Cache, error) { utils.SetCacheDir(c.CacheDir) cache, err := operation.NewCache(c.CacheBackend) if err != nil { @@ -95,7 +98,7 @@ func initCache(c Config) (operation.Cache, error) { } if c.ClearCache { defer cache.Close() - if err = cache.ClearImages(); err != nil { + if err = cache.ClearArtifacts(); err != nil { return operation.Cache{}, xerrors.Errorf("cache clear error: %w", err) } return operation.Cache{}, errSkipScan @@ -103,7 +106,7 @@ func initCache(c Config) (operation.Cache, error) { return cache, nil } -func initDB(c Config) error { +func initDB(c Option) error { // download the database file noProgress := c.Quiet || c.NoProgress if err := operation.DownloadDB(c.AppVersion, c.CacheDir, noProgress, c.Light, c.SkipUpdate); err != nil { @@ -120,29 +123,36 @@ func initDB(c Config) error { return nil } -func scan(ctx context.Context, conf Config, initializeScanner InitializeScanner, cacheClient cache.Cache) ( +func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner, cacheClient cache.Cache) ( report.Results, error) { - target := conf.Target - if conf.Input != "" { - target = conf.Input + target := opt.Target + if opt.Input != "" { + target = opt.Input } scanOptions := types.ScanOptions{ - VulnType: conf.VulnType, - ScanRemovedPackages: conf.ScanRemovedPkgs, // this is valid only for image subcommand - ListAllPackages: conf.ListAllPkgs, - SkipFiles: conf.SkipFiles, - SkipDirs: conf.SkipDirs, + VulnType: opt.VulnType, + SecurityChecks: opt.SecurityChecks, + ScanRemovedPackages: opt.ScanRemovedPkgs, // this is valid only for image subcommand + ListAllPackages: opt.ListAllPkgs, + SkipFiles: opt.SkipFiles, + SkipDirs: opt.SkipDirs, } log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType) // It doesn't analyze apk commands by default. disabledAnalyzers := []analyzer.Type{analyzer.TypeApkCommand} - if conf.ScanRemovedPkgs { + if opt.ScanRemovedPkgs { disabledAnalyzers = []analyzer.Type{} } - s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, conf.Timeout, disabledAnalyzers) + // TODO: fix the scanner option and enable config analyzers once we finalize the specification of config scanning. + configScannerOptions := config.ScannerOption{} + disabledAnalyzers = append(disabledAnalyzers, analyzer.TypeYaml, analyzer.TypeTOML, analyzer.TypeJSON, + analyzer.TypeDockerfile, analyzer.TypeHCL) + + s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Timeout, + disabledAnalyzers, configScannerOptions) if err != nil { return nil, xerrors.Errorf("unable to initialize a scanner: %w", err) } @@ -155,12 +165,12 @@ func scan(ctx context.Context, conf Config, initializeScanner InitializeScanner, return results, nil } -func filter(ctx context.Context, conf Config, results report.Results) (report.Results, error) { - vulnClient := initializeVulnerabilityClient() +func filter(ctx context.Context, opt Option, results report.Results) (report.Results, error) { + resultClient := initializeResultClient() for i := range results { - vulnClient.FillInfo(results[i].Vulnerabilities, results[i].Type) - vulns, err := vulnClient.Filter(ctx, results[i].Vulnerabilities, - conf.Severities, conf.IgnoreUnfixed, conf.IgnoreFile, conf.IgnorePolicy) + resultClient.FillInfo(results[i].Vulnerabilities, results[i].Type) + vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities, + opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy) if err != nil { return nil, xerrors.Errorf("unable to filter vulnerabilities: %w", err) } @@ -169,12 +179,8 @@ func filter(ctx context.Context, conf Config, results report.Results) (report.Re return results, nil } -func exit(c Config, results report.Results) { - if c.ExitCode != 0 { - for _, result := range results { - if len(result.Vulnerabilities) > 0 { - os.Exit(c.ExitCode) - } - } +func exit(c Option, results report.Results) { + if c.ExitCode != 0 && results.Failed() { + os.Exit(c.ExitCode) } } diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go index 4bf5431002..65ab656d75 100644 --- a/pkg/commands/artifact/wire_gen.go +++ b/pkg/commands/artifact/wire_gen.go @@ -8,6 +8,7 @@ package artifact import ( "context" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/applier" image2 "github.com/aquasecurity/fanal/artifact/image" local2 "github.com/aquasecurity/fanal/artifact/local" @@ -25,7 +26,7 @@ import ( // Injectors from inject.go: -func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type) (scanner.Scanner, func(), error) { +func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} localScanner := local.NewScanner(applierApplier, detector) @@ -37,14 +38,18 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach if err != nil { return scanner.Scanner{}, nil, err } - artifact := image2.NewArtifact(imageImage, artifactCache, disableAnalyzers) + artifact, err := image2.NewArtifact(imageImage, artifactCache, disableAnalyzers, configScannerOption) + if err != nil { + cleanup() + return scanner.Scanner{}, nil, err + } scannerScanner := scanner.NewScanner(localScanner, artifact) return scannerScanner, func() { cleanup() }, nil } -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type) (scanner.Scanner, error) { +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} localScanner := local.NewScanner(applierApplier, detector) @@ -52,26 +57,32 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach if err != nil { return scanner.Scanner{}, err } - artifact := image2.NewArtifact(imageImage, artifactCache, disableAnalyzers) + artifact, err := image2.NewArtifact(imageImage, artifactCache, disableAnalyzers, configScannerOption) + if err != nil { + return scanner.Scanner{}, err + } scannerScanner := scanner.NewScanner(localScanner, artifact) return scannerScanner, nil } -func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type) (scanner.Scanner, func(), error) { +func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} localScanner := local.NewScanner(applierApplier, detector) - artifact := local2.NewArtifact(dir, artifactCache, disableAnalyzers) + artifact, err := local2.NewArtifact(dir, artifactCache, disableAnalyzers, configScannerOption) + if err != nil { + return scanner.Scanner{}, nil, err + } scannerScanner := scanner.NewScanner(localScanner, artifact) return scannerScanner, func() { }, nil } -func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type) (scanner.Scanner, func(), error) { +func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, disableAnalyzers []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) { applierApplier := applier.NewApplier(localArtifactCache) detector := ospkg.Detector{} localScanner := local.NewScanner(applierApplier, detector) - artifact, cleanup, err := remote.NewArtifact(url, artifactCache, disableAnalyzers) + artifact, cleanup, err := remote.NewArtifact(url, artifactCache, disableAnalyzers, configScannerOption) if err != nil { return scanner.Scanner{}, nil, err } @@ -81,8 +92,8 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache }, nil } -func initializeVulnerabilityClient() vulnerability.Client { - config := db.Config{} - client := vulnerability.NewClient(config) +func initializeResultClient() vulnerability.Client { + dbConfig := db.Config{} + client := vulnerability.NewClient(dbConfig) return client } diff --git a/pkg/commands/client/inject.go b/pkg/commands/client/inject.go index 745883e315..1954f979b6 100644 --- a/pkg/commands/client/inject.go +++ b/pkg/commands/client/inject.go @@ -9,6 +9,7 @@ import ( "github.com/google/wire" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/trivy/pkg/rpc/client" "github.com/aquasecurity/trivy/pkg/scanner" @@ -16,18 +17,20 @@ import ( ) func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, - url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type) (scanner.Scanner, func(), error) { + url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type, configScannerOption config.ScannerOption) ( + scanner.Scanner, func(), error) { wire.Build(scanner.RemoteDockerSet) return scanner.Scanner{}, nil, nil } -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, - url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type) (scanner.Scanner, error) { +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, + customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type, + configScannerOption config.ScannerOption) (scanner.Scanner, error) { wire.Build(scanner.RemoteArchiveSet) return scanner.Scanner{}, nil } -func initializeVulnerabilityClient() vulnerability.Client { +func initializeResultClient() vulnerability.Client { wire.Build(vulnerability.SuperSet) return vulnerability.Client{} } diff --git a/pkg/commands/client/config.go b/pkg/commands/client/option.go similarity index 57% rename from pkg/commands/client/config.go rename to pkg/commands/client/option.go index 68d5a67edf..c6494d427f 100644 --- a/pkg/commands/client/config.go +++ b/pkg/commands/client/option.go @@ -7,15 +7,15 @@ import ( "github.com/urfave/cli/v2" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" ) -// Config holds the Trivy client config -type Config struct { - config.GlobalConfig - config.ArtifactConfig - config.ImageConfig - config.ReportConfig +// Option holds the Trivy client options +type Option struct { + option.GlobalOption + option.ArtifactOption + option.ImageOption + option.ReportOption RemoteAddr string token string @@ -26,18 +26,18 @@ type Config struct { CustomHeaders http.Header } -// NewConfig is the factory method for Config -func NewConfig(c *cli.Context) (Config, error) { - gc, err := config.NewGlobalConfig(c) +// NewOption is the factory method for Option +func NewOption(c *cli.Context) (Option, error) { + gc, err := option.NewGlobalOption(c) if err != nil { - return Config{}, xerrors.Errorf("failed to initialize global options: %w", err) + return Option{}, xerrors.Errorf("failed to initialize global options: %w", err) } - return Config{ - GlobalConfig: gc, - ArtifactConfig: config.NewArtifactConfig(c), - ImageConfig: config.NewImageConfig(c), - ReportConfig: config.NewReportConfig(c), + return Option{ + GlobalOption: gc, + ArtifactOption: option.NewArtifactOption(c), + ImageOption: option.NewImageOption(c), + ReportOption: option.NewReportOption(c), RemoteAddr: c.String("remote"), token: c.String("token"), tokenHeader: c.String("token-header"), @@ -45,8 +45,8 @@ func NewConfig(c *cli.Context) (Config, error) { }, nil } -// Init initializes the config -func (c *Config) Init() (err error) { +// Init initializes the options +func (c *Option) Init() (err error) { // --clear-cache doesn't conduct the scan if c.ClearCache { return nil @@ -59,11 +59,11 @@ func (c *Config) Init() (err error) { c.CustomHeaders.Set(c.tokenHeader, c.token) } - if err := c.ReportConfig.Init(c.Logger); err != nil { + if err := c.ReportOption.Init(c.Logger); err != nil { return err } - if err := c.ArtifactConfig.Init(c.Context, c.Logger); err != nil { + if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil { return err } diff --git a/pkg/commands/client/config_test.go b/pkg/commands/client/option_test.go similarity index 58% rename from pkg/commands/client/config_test.go rename to pkg/commands/client/option_test.go index 585cb5ac55..3488972e4e 100644 --- a/pkg/commands/client/config_test.go +++ b/pkg/commands/client/option_test.go @@ -4,7 +4,6 @@ import ( "flag" "net/http" "os" - "reflect" "testing" "github.com/stretchr/testify/assert" @@ -14,38 +13,33 @@ import ( "go.uber.org/zap/zaptest/observer" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" + "github.com/aquasecurity/trivy/pkg/types" ) func TestConfig_Init(t *testing.T) { tests := []struct { - name string - globalConfig config.GlobalConfig - imageConfig config.ImageConfig - reportConfig config.ReportConfig - args []string - logs []string - want Config - wantErr string + name string + args []string + logs []string + want Option + wantErr string }{ { name: "happy path", - reportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - VulnType: []string{"os"}, - }, args: []string{"--severity", "CRITICAL", "--vuln-type", "os", "--quiet", "alpine:3.10"}, - want: Config{ - GlobalConfig: config.GlobalConfig{ + want: Option{ + GlobalOption: option.GlobalOption{ Quiet: true, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "alpine:3.10", }, - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - VulnType: []string{"os"}, - Output: os.Stdout, + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Output: os.Stdout, }, CustomHeaders: http.Header{}, }, @@ -53,13 +47,14 @@ func TestConfig_Init(t *testing.T) { { name: "happy path with token and token header", args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"}, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "alpine:3.11", }, token: "secret", @@ -72,13 +67,14 @@ func TestConfig_Init(t *testing.T) { { name: "happy path with good custom headers", args: []string{"--custom-headers", "foo:bar", "alpine:3.11"}, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "alpine:3.11", }, customHeaders: []string{"foo:bar"}, @@ -90,13 +86,14 @@ func TestConfig_Init(t *testing.T) { { name: "happy path with bad custom headers", args: []string{"--custom-headers", "foobaz", "alpine:3.11"}, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "alpine:3.11", }, customHeaders: []string{"foobaz"}, @@ -109,13 +106,14 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "unknown severity option: unknown severity: INVALID", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "centos:7", }, CustomHeaders: http.Header{}, @@ -127,14 +125,15 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--template is ignored because --format template is not specified. Use --template option with --format template option.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Template: "@contrib/gitlab.tpl", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Template: "@contrib/gitlab.tpl", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, CustomHeaders: http.Header{}, @@ -146,15 +145,16 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--template is ignored because --format json is specified. Use --template option with --format template option.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Template: "@contrib/gitlab.tpl", - Format: "json", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Template: "@contrib/gitlab.tpl", + Format: "json", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, CustomHeaders: http.Header{}, @@ -166,14 +166,15 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--format template is ignored because --template not is specified. Specify --template option when you use --format template.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Format: "template", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Format: "template", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, CustomHeaders: http.Header{}, @@ -185,14 +186,15 @@ func TestConfig_Init(t *testing.T) { logs: []string{ "--format template is ignored because --template not is specified. Specify --template option when you use --format template.", }, - want: Config{ - ReportConfig: config.ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, - Output: os.Stdout, - VulnType: []string{"os", "library"}, - Format: "template", + want: Option{ + ReportOption: option.ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityMedium}, + Output: os.Stdout, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Format: "template", }, - ArtifactConfig: config.ArtifactConfig{ + ArtifactOption: option.ArtifactOption{ Target: "gitlab/gitlab-ce:12.7.2-ce.0", }, CustomHeaders: http.Header{}, @@ -219,6 +221,7 @@ func TestConfig_Init(t *testing.T) { set.Bool("clear-cache", false, "") set.String("severity", "CRITICAL", "") set.String("vuln-type", "os,library", "") + set.String("security-checks", "vuln", "") set.String("template", "", "") set.String("format", "", "") set.String("token", "", "") @@ -228,10 +231,10 @@ func TestConfig_Init(t *testing.T) { ctx := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - c, err := NewConfig(ctx) + c, err := NewOption(ctx) require.NoError(t, err, err) - c.GlobalConfig.Logger = logger.Sugar() + c.GlobalOption.Logger = logger.Sugar() err = c.Init() // tests log messages @@ -251,8 +254,8 @@ func TestConfig_Init(t *testing.T) { assert.NoError(t, err, tt.name) } - tt.want.GlobalConfig.Context = ctx - tt.want.GlobalConfig.Logger = logger.Sugar() + tt.want.GlobalOption.Context = ctx + tt.want.GlobalOption.Logger = logger.Sugar() assert.Equal(t, tt.want, c, tt.name) }) } @@ -280,9 +283,8 @@ func Test_splitCustomHeaders(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := splitCustomHeaders(tt.args.headers); !reflect.DeepEqual(got, tt.want) { - t.Errorf("splitCustomHeaders() = %v, want %v", got, tt.want) - } + got := splitCustomHeaders(tt.args.headers) + assert.Equal(t, tt.want, got) }) } } diff --git a/pkg/commands/client/run.go b/pkg/commands/client/run.go index a7a7ced1e0..4aefd1a291 100644 --- a/pkg/commands/client/run.go +++ b/pkg/commands/client/run.go @@ -8,6 +8,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" "github.com/aquasecurity/trivy/pkg/cache" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report" @@ -18,44 +19,42 @@ import ( ) // Run runs the scan -func Run(ctx *cli.Context) error { - c, err := NewConfig(ctx) +func Run(cliCtx *cli.Context) error { + opt, err := NewOption(cliCtx) if err != nil { - return err + return xerrors.Errorf("option error: %w", err) } - return run(ctx.Context, c) -} -func run(ctx context.Context, conf Config) error { - ctx, cancel := context.WithTimeout(context.Background(), conf.Timeout) + ctx, cancel := context.WithTimeout(cliCtx.Context, opt.Timeout) defer cancel() - err := runWithTimeout(ctx, conf) + err = runWithTimeout(ctx, opt) if xerrors.Is(err, context.DeadlineExceeded) { log.Logger.Warn("Increase --timeout value") } return err } -func runWithTimeout(ctx context.Context, conf Config) error { - if err := initialize(&conf); err != nil { +func runWithTimeout(ctx context.Context, opt Option) error { + if err := initialize(&opt); err != nil { return xerrors.Errorf("initialize error: %w", err) } - if conf.ClearCache { + if opt.ClearCache { log.Logger.Warn("A client doesn't have image cache") return nil } - s, cleanup, err := initializeScanner(ctx, conf) + s, cleanup, err := initializeScanner(ctx, opt) if err != nil { return xerrors.Errorf("scanner initialize error: %w", err) } defer cleanup() scanOptions := types.ScanOptions{ - VulnType: conf.VulnType, - ScanRemovedPackages: conf.ScanRemovedPkgs, + VulnType: opt.VulnType, + SecurityChecks: opt.SecurityChecks, + ScanRemovedPackages: opt.ScanRemovedPkgs, } log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType) @@ -64,56 +63,61 @@ func runWithTimeout(ctx context.Context, conf Config) error { return xerrors.Errorf("error in image scan: %w", err) } - vulnClient := initializeVulnerabilityClient() + resultClient := initializeResultClient() for i := range results { - vulns, err := vulnClient.Filter(ctx, results[i].Vulnerabilities, - conf.Severities, conf.IgnoreUnfixed, conf.IgnoreFile, conf.IgnorePolicy) + vulns, err := resultClient.Filter(ctx, results[i].Vulnerabilities, + opt.Severities, opt.IgnoreUnfixed, opt.IgnoreFile, opt.IgnorePolicy) if err != nil { - return err + return xerrors.Errorf("filter error: %w", err) } results[i].Vulnerabilities = vulns } - if err = report.WriteResults(conf.Format, conf.Output, conf.Severities, results, conf.Template, false); err != nil { + if err = report.WriteResults(opt.Format, opt.Output, opt.Severities, results, opt.Template, false); err != nil { return xerrors.Errorf("unable to write results: %w", err) } - exit(conf, results) + exit(opt, results) return nil } -func initialize(conf *Config) error { +func initialize(opt *Option) error { // Initialize logger - if err := log.InitLogger(conf.Debug, conf.Quiet); err != nil { + if err := log.InitLogger(opt.Debug, opt.Quiet); err != nil { return xerrors.Errorf("failed to initialize a logger: %w", err) } - // Initialize config - if err := conf.Init(); err != nil { + // Initialize options + if err := opt.Init(); err != nil { return xerrors.Errorf("failed to initialize options: %w", err) } // configure cache dir - utils.SetCacheDir(conf.CacheDir) + utils.SetCacheDir(opt.CacheDir) log.Logger.Debugf("cache dir: %s", utils.CacheDir()) return nil } -func initializeScanner(ctx context.Context, conf Config) (scanner.Scanner, func(), error) { - remoteCache := cache.NewRemoteCache(cache.RemoteURL(conf.RemoteAddr), conf.CustomHeaders) +func initializeScanner(ctx context.Context, opt Option) (scanner.Scanner, func(), error) { + remoteCache := cache.NewRemoteCache(cache.RemoteURL(opt.RemoteAddr), opt.CustomHeaders) // By default, apk commands are not analyzed. disabledAnalyzers := []analyzer.Type{analyzer.TypeApkCommand} - if conf.ScanRemovedPkgs { + if opt.ScanRemovedPkgs { disabledAnalyzers = []analyzer.Type{} } - if conf.Input != "" { + // TODO: fix the scanner option and enable config analyzers once we finalize the specification of config scanning. + configScannerOptions := config.ScannerOption{} + disabledAnalyzers = append(disabledAnalyzers, analyzer.TypeYaml, analyzer.TypeTOML, analyzer.TypeJSON, + analyzer.TypeDockerfile, analyzer.TypeHCL) + + if opt.Input != "" { // Scan tar file - s, err := initializeArchiveScanner(ctx, conf.Input, remoteCache, - client.CustomHeaders(conf.CustomHeaders), client.RemoteURL(conf.RemoteAddr), conf.Timeout, disabledAnalyzers) + s, err := initializeArchiveScanner(ctx, opt.Input, remoteCache, client.CustomHeaders(opt.CustomHeaders), + client.RemoteURL(opt.RemoteAddr), opt.Timeout, disabledAnalyzers, configScannerOptions) if err != nil { return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the archive scanner: %w", err) } @@ -121,8 +125,8 @@ func initializeScanner(ctx context.Context, conf Config) (scanner.Scanner, func( } // Scan an image in Docker Engine or Docker Registry - s, cleanup, err := initializeDockerScanner(ctx, conf.Target, remoteCache, - client.CustomHeaders(conf.CustomHeaders), client.RemoteURL(conf.RemoteAddr), conf.Timeout, disabledAnalyzers) + s, cleanup, err := initializeDockerScanner(ctx, opt.Target, remoteCache, client.CustomHeaders(opt.CustomHeaders), + client.RemoteURL(opt.RemoteAddr), opt.Timeout, disabledAnalyzers, configScannerOptions) if err != nil { return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the docker scanner: %w", err) } @@ -130,7 +134,7 @@ func initializeScanner(ctx context.Context, conf Config) (scanner.Scanner, func( return s, cleanup, nil } -func exit(c Config, results report.Results) { +func exit(c Option, results report.Results) { if c.ExitCode != 0 { for _, result := range results { if len(result.Vulnerabilities) > 0 { diff --git a/pkg/commands/client/wire_gen.go b/pkg/commands/client/wire_gen.go index af27d42238..e843b4b426 100644 --- a/pkg/commands/client/wire_gen.go +++ b/pkg/commands/client/wire_gen.go @@ -8,6 +8,7 @@ package client import ( "context" "github.com/aquasecurity/fanal/analyzer" + "github.com/aquasecurity/fanal/analyzer/config" image2 "github.com/aquasecurity/fanal/artifact/image" "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/fanal/image" @@ -21,7 +22,7 @@ import ( // Injectors from inject.go: -func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type) (scanner.Scanner, func(), error) { +func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) { scannerScanner := client.NewProtobufClient(url) clientScanner := client.NewScanner(customHeaders, scannerScanner) dockerOption, err := types.GetDockerOption(timeout) @@ -32,27 +33,34 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach if err != nil { return scanner.Scanner{}, nil, err } - artifact := image2.NewArtifact(imageImage, artifactCache, disabled) + artifact, err := image2.NewArtifact(imageImage, artifactCache, disabled, configScannerOption) + if err != nil { + cleanup() + return scanner.Scanner{}, nil, err + } scanner2 := scanner.NewScanner(clientScanner, artifact) return scanner2, func() { cleanup() }, nil } -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type) (scanner.Scanner, error) { +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration, disabled []analyzer.Type, configScannerOption config.ScannerOption) (scanner.Scanner, error) { scannerScanner := client.NewProtobufClient(url) clientScanner := client.NewScanner(customHeaders, scannerScanner) imageImage, err := image.NewArchiveImage(filePath) if err != nil { return scanner.Scanner{}, err } - artifact := image2.NewArtifact(imageImage, artifactCache, disabled) + artifact, err := image2.NewArtifact(imageImage, artifactCache, disabled, configScannerOption) + if err != nil { + return scanner.Scanner{}, err + } scanner2 := scanner.NewScanner(clientScanner, artifact) return scanner2, nil } -func initializeVulnerabilityClient() vulnerability.Client { - config := db.Config{} - vulnerabilityClient := vulnerability.NewClient(config) +func initializeResultClient() vulnerability.Client { + dbConfig := db.Config{} + vulnerabilityClient := vulnerability.NewClient(dbConfig) return vulnerabilityClient } diff --git a/pkg/commands/config/image.go b/pkg/commands/config/image.go deleted file mode 100644 index a3797f476a..0000000000 --- a/pkg/commands/config/image.go +++ /dev/null @@ -1,19 +0,0 @@ -package config - -import ( - "github.com/urfave/cli/v2" -) - -// ImageConfig holds the config for scanning images -type ImageConfig struct { - ScanRemovedPkgs bool - ListAllPkgs bool -} - -// NewImageConfig is the factory method to return imageConfig -func NewImageConfig(c *cli.Context) ImageConfig { - return ImageConfig{ - ScanRemovedPkgs: c.Bool("removed-pkgs"), - ListAllPkgs: c.Bool("list-all-pkgs"), - } -} diff --git a/pkg/commands/config/report.go b/pkg/commands/config/report.go deleted file mode 100644 index 6dce7efaf5..0000000000 --- a/pkg/commands/config/report.go +++ /dev/null @@ -1,92 +0,0 @@ -package config - -import ( - "os" - "strings" - - "github.com/urfave/cli/v2" - "go.uber.org/zap" - "golang.org/x/xerrors" - - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" -) - -// ReportConfig holds the config for reporting scan results -type ReportConfig struct { - Format string - Template string - - IgnoreFile string - IgnoreUnfixed bool - ExitCode int - IgnorePolicy string - - // these variables are not exported - vulnType string - output string - severities string - - // these variables are populated by Init() - VulnType []string - Output *os.File - Severities []dbTypes.Severity -} - -// NewReportConfig is the factory method to return ReportConfig -func NewReportConfig(c *cli.Context) ReportConfig { - return ReportConfig{ - output: c.String("output"), - Format: c.String("format"), - Template: c.String("template"), - IgnorePolicy: c.String("ignore-policy"), - - vulnType: c.String("vuln-type"), - severities: c.String("severity"), - IgnoreFile: c.String("ignorefile"), - IgnoreUnfixed: c.Bool("ignore-unfixed"), - ExitCode: c.Int("exit-code"), - } -} - -// Init initializes the ReportConfig -func (c *ReportConfig) Init(logger *zap.SugaredLogger) (err error) { - if c.Template != "" { - if c.Format == "" { - logger.Warn("--template is ignored because --format template is not specified. Use --template option with --format template option.") - } else if c.Format != "template" { - logger.Warnf("--template is ignored because --format %s is specified. Use --template option with --format template option.", c.Format) - } - } - if c.Format == "template" && c.Template == "" { - logger.Warn("--format template is ignored because --template not is specified. Specify --template option when you use --format template.") - } - - c.Severities = c.splitSeverity(logger, c.severities) - c.VulnType = strings.Split(c.vulnType, ",") - - // for testability - c.severities = "" - c.vulnType = "" - - c.Output = os.Stdout - if c.output != "" { - if c.Output, err = os.Create(c.output); err != nil { - return xerrors.Errorf("failed to create an output file: %w", err) - } - } - - return nil -} - -func (c *ReportConfig) splitSeverity(logger *zap.SugaredLogger, severity string) []dbTypes.Severity { - logger.Debugf("Severities: %s", severity) - var severities []dbTypes.Severity - for _, s := range strings.Split(severity, ",") { - severity, err := dbTypes.NewSeverity(s) - if err != nil { - logger.Warnf("unknown severity option: %s", err) - } - severities = append(severities, severity) - } - return severities -} diff --git a/pkg/commands/config/report_test.go b/pkg/commands/config/report_test.go deleted file mode 100644 index ddf2e3abd8..0000000000 --- a/pkg/commands/config/report_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package config - -import ( - "flag" - "os" - "testing" - - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "go.uber.org/zap/zaptest/observer" -) - -func TestReportReportConfig_Init(t *testing.T) { - type fields struct { - output string - Format string - Template string - vulnType string - severities string - IgnoreFile string - IgnoreUnfixed bool - ExitCode int - VulnType []string - Output *os.File - Severities []dbTypes.Severity - } - tests := []struct { - name string - fields fields - args []string - logs []string - want ReportConfig - wantErr string - }{ - { - name: "happy path", - fields: fields{ - severities: "CRITICAL", - vulnType: "os", - }, - args: []string{"alpine:3.10"}, - want: ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, - VulnType: []string{"os"}, - Output: os.Stdout, - }, - }, - { - name: "happy path with an unknown severity", - fields: fields{ - severities: "CRITICAL,INVALID", - vulnType: "os,library", - }, - args: []string{"centos:7"}, - logs: []string{ - "unknown severity option: unknown severity: INVALID", - }, - want: ReportConfig{ - Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, - VulnType: []string{"os", "library"}, - Output: os.Stdout, - }, - }, - { - name: "invalid option combination: --template enabled without --format", - fields: fields{ - Template: "@contrib/gitlab.tpl", - severities: "LOW", - }, - args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, - logs: []string{ - "--template is ignored because --format template is not specified. Use --template option with --format template option.", - }, - want: ReportConfig{ - Output: os.Stdout, - Severities: []dbTypes.Severity{dbTypes.SeverityLow}, - Template: "@contrib/gitlab.tpl", - VulnType: []string{""}, - }, - }, - { - name: "invalid option combination: --template and --format json", - fields: fields{ - Format: "json", - Template: "@contrib/gitlab.tpl", - severities: "LOW", - }, - args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, - logs: []string{ - "--template is ignored because --format json is specified. Use --template option with --format template option.", - }, - want: ReportConfig{ - Format: "json", - Output: os.Stdout, - Severities: []dbTypes.Severity{dbTypes.SeverityLow}, - Template: "@contrib/gitlab.tpl", - VulnType: []string{""}, - }, - }, - { - name: "invalid option combination: --format template without --template", - fields: fields{ - Format: "template", - severities: "LOW", - }, - args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, - logs: []string{ - "--format template is ignored because --template not is specified. Specify --template option when you use --format template.", - }, - want: ReportConfig{ - Format: "template", - Output: os.Stdout, - Severities: []dbTypes.Severity{dbTypes.SeverityLow}, - VulnType: []string{""}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - core, obs := observer.New(zap.InfoLevel) - logger := zap.New(core) - - set := flag.NewFlagSet("test", 0) - _ = set.Parse(tt.args) - - c := &ReportConfig{ - output: tt.fields.output, - Format: tt.fields.Format, - Template: tt.fields.Template, - vulnType: tt.fields.vulnType, - severities: tt.fields.severities, - IgnoreFile: tt.fields.IgnoreFile, - IgnoreUnfixed: tt.fields.IgnoreUnfixed, - ExitCode: tt.fields.ExitCode, - Output: tt.fields.Output, - } - - err := c.Init(logger.Sugar()) - - // tests log messages - var gotMessages []string - for _, entry := range obs.AllUntimed() { - gotMessages = append(gotMessages, entry.Message) - } - assert.Equal(t, tt.logs, gotMessages, tt.name) - - // test the error - switch { - case tt.wantErr != "": - require.NotNil(t, err) - assert.Contains(t, err.Error(), tt.wantErr, tt.name) - return - default: - assert.NoError(t, err, tt.name) - } - - assert.Equal(t, &tt.want, c, tt.name) - }) - } -} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 4577356144..5b08aefd57 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -51,8 +51,8 @@ func (c Cache) Reset() (err error) { if err := c.ClearDB(); err != nil { return xerrors.Errorf("failed to clear the database: %w", err) } - if err := c.ClearImages(); err != nil { - return xerrors.Errorf("failed to clear the image cache: %w", err) + if err := c.ClearArtifacts(); err != nil { + return xerrors.Errorf("failed to clear the artifact cache: %w", err) } return nil } @@ -66,9 +66,9 @@ func (c Cache) ClearDB() (err error) { return nil } -// ClearImages clears the cache images -func (c Cache) ClearImages() error { - log.Logger.Info("Removing image caches...") +// ClearArtifacts clears the artifact cache +func (c Cache) ClearArtifacts() error { + log.Logger.Info("Removing artifact caches...") if err := c.Clear(); err != nil { return xerrors.Errorf("failed to remove the cache: %w", err) } @@ -96,7 +96,7 @@ func DownloadDB(appVersion, cacheDir string, quiet, light, skipUpdate bool) erro } // for debug - if err := showDBInfo(cacheDir); err != nil { + if err = showDBInfo(cacheDir); err != nil { return xerrors.Errorf("failed to show database info: %w", err) } return nil diff --git a/pkg/commands/config/artifact.go b/pkg/commands/option/artifact.go similarity index 74% rename from pkg/commands/config/artifact.go rename to pkg/commands/option/artifact.go index 5e1d25c9f2..eed2f1a0de 100644 --- a/pkg/commands/config/artifact.go +++ b/pkg/commands/option/artifact.go @@ -1,4 +1,4 @@ -package config +package option import ( "os" @@ -9,8 +9,8 @@ import ( "golang.org/x/xerrors" ) -// ArtifactConfig holds the config for a artifact scanning -type ArtifactConfig struct { +// ArtifactOption holds the options for an artifact scanning +type ArtifactOption struct { Input string Timeout time.Duration ClearCache bool @@ -22,9 +22,9 @@ type ArtifactConfig struct { Target string } -// NewArtifactConfig is the factory method to return artifact config -func NewArtifactConfig(c *cli.Context) ArtifactConfig { - return ArtifactConfig{ +// NewArtifactOption is the factory method to return artifact option +func NewArtifactOption(c *cli.Context) ArtifactOption { + return ArtifactOption{ Input: c.String("input"), Timeout: c.Duration("timeout"), ClearCache: c.Bool("clear-cache"), @@ -34,7 +34,7 @@ func NewArtifactConfig(c *cli.Context) ArtifactConfig { } // Init initialize the CLI context for artifact scanning -func (c *ArtifactConfig) Init(ctx *cli.Context, logger *zap.SugaredLogger) (err error) { +func (c *ArtifactOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) (err error) { if c.Input == "" && ctx.Args().Len() == 0 { logger.Debug(`trivy requires at least 1 argument or --input option`) _ = cli.ShowSubcommandHelp(ctx) // nolint: errcheck diff --git a/pkg/commands/config/artifact_test.go b/pkg/commands/option/artifact_test.go similarity index 86% rename from pkg/commands/config/artifact_test.go rename to pkg/commands/option/artifact_test.go index 0d91645b82..e2fc02349e 100644 --- a/pkg/commands/config/artifact_test.go +++ b/pkg/commands/option/artifact_test.go @@ -1,10 +1,10 @@ -package config_test +package option_test import ( "flag" "testing" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" @@ -12,18 +12,18 @@ import ( "go.uber.org/zap/zaptest/observer" ) -func TestArtifactConfig_Init(t *testing.T) { +func TestArtifactOption_Init(t *testing.T) { tests := []struct { name string args []string logs []string - want config.ArtifactConfig + want option.ArtifactOption wantErr string }{ { name: "happy path", args: []string{"alpine:3.10"}, - want: config.ArtifactConfig{ + want: option.ArtifactOption{ Target: "alpine:3.10", }, }, @@ -46,7 +46,7 @@ func TestArtifactConfig_Init(t *testing.T) { ctx := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - c := config.NewArtifactConfig(ctx) + c := option.NewArtifactOption(ctx) err := c.Init(ctx, logger.Sugar()) diff --git a/pkg/commands/config/cache.go b/pkg/commands/option/cache.go similarity index 61% rename from pkg/commands/config/cache.go rename to pkg/commands/option/cache.go index 1b7e827714..2ee23d0379 100644 --- a/pkg/commands/config/cache.go +++ b/pkg/commands/option/cache.go @@ -1,4 +1,4 @@ -package config +package option import ( "strings" @@ -7,20 +7,20 @@ import ( "golang.org/x/xerrors" ) -// CacheConfig holds the config for cache -type CacheConfig struct { +// CacheOption holds the options for cache +type CacheOption struct { CacheBackend string } -// NewCacheConfig returns an instance of CacheConfig -func NewCacheConfig(c *cli.Context) CacheConfig { - return CacheConfig{ +// NewCacheOption returns an instance of CacheOption +func NewCacheOption(c *cli.Context) CacheOption { + return CacheOption{ CacheBackend: c.String("cache-backend"), } } -// Init initialize the CacheConfig -func (c *CacheConfig) Init() error { +// Init initialize the CacheOption +func (c *CacheOption) Init() error { // "redis://" or "fs" are allowed for now // An empty value is also allowed for testability if !strings.HasPrefix(c.CacheBackend, "redis://") && diff --git a/pkg/commands/config/cache_test.go b/pkg/commands/option/cache_test.go similarity index 81% rename from pkg/commands/config/cache_test.go rename to pkg/commands/option/cache_test.go index 7172c09cda..0cb0fd1496 100644 --- a/pkg/commands/config/cache_test.go +++ b/pkg/commands/option/cache_test.go @@ -1,4 +1,4 @@ -package config_test +package option_test import ( "flag" @@ -7,26 +7,26 @@ import ( "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" ) -func TestNewCacheConfig(t *testing.T) { +func TestNewCacheOption(t *testing.T) { tests := []struct { name string args []string - want config.CacheConfig + want option.CacheOption }{ { name: "happy path", args: []string{"--cache-backend", "redis://localhost:6379"}, - want: config.CacheConfig{ + want: option.CacheOption{ CacheBackend: "redis://localhost:6379", }, }, { name: "default", args: []string{}, - want: config.CacheConfig{ + want: option.CacheOption{ CacheBackend: "fs", }, }, @@ -40,13 +40,13 @@ func TestNewCacheConfig(t *testing.T) { c := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - got := config.NewCacheConfig(c) + got := option.NewCacheOption(c) assert.Equal(t, tt.want, got, tt.name) }) } } -func TestCacheConfig_Init(t *testing.T) { +func TestCacheOption_Init(t *testing.T) { type fields struct { backend string } @@ -77,7 +77,7 @@ func TestCacheConfig_Init(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &config.CacheConfig{ + c := &option.CacheOption{ CacheBackend: tt.fields.backend, } diff --git a/pkg/commands/config/db.go b/pkg/commands/option/db.go similarity index 65% rename from pkg/commands/config/db.go rename to pkg/commands/option/db.go index 73fb36d692..de68eb6b81 100644 --- a/pkg/commands/config/db.go +++ b/pkg/commands/option/db.go @@ -1,12 +1,12 @@ -package config +package option import ( "github.com/urfave/cli/v2" "golang.org/x/xerrors" ) -// DBConfig holds the config for trivy DB -type DBConfig struct { +// DBOption holds the options for trivy DB +type DBOption struct { Reset bool DownloadDBOnly bool SkipUpdate bool @@ -14,9 +14,9 @@ type DBConfig struct { NoProgress bool } -// NewDBConfig is the factory method to return the DBConfig -func NewDBConfig(c *cli.Context) DBConfig { - return DBConfig{ +// NewDBOption is the factory method to return the DBOption +func NewDBOption(c *cli.Context) DBOption { + return DBOption{ Reset: c.Bool("reset"), DownloadDBOnly: c.Bool("download-db-only"), SkipUpdate: c.Bool("skip-update"), @@ -25,8 +25,8 @@ func NewDBConfig(c *cli.Context) DBConfig { } } -// Init initialize the DBConfig -func (c *DBConfig) Init() (err error) { +// Init initialize the DBOption +func (c *DBOption) Init() (err error) { if c.SkipUpdate && c.DownloadDBOnly { return xerrors.New("--skip-update and --download-db-only options can not be specified both") } diff --git a/pkg/commands/config/db_test.go b/pkg/commands/option/db_test.go similarity index 84% rename from pkg/commands/config/db_test.go rename to pkg/commands/option/db_test.go index 3551e61123..57f748de85 100644 --- a/pkg/commands/config/db_test.go +++ b/pkg/commands/option/db_test.go @@ -1,25 +1,24 @@ -package config_test +package option_test import ( "flag" "testing" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v2" - - "github.com/aquasecurity/trivy/pkg/commands/config" ) -func TestNewDBConfig(t *testing.T) { +func TestNewDBOption(t *testing.T) { tests := []struct { name string args []string - want config.DBConfig + want option.DBOption }{ { name: "happy path", args: []string{"--reset", "--skip-update"}, - want: config.DBConfig{ + want: option.DBOption{ Reset: true, SkipUpdate: true, }, @@ -35,13 +34,13 @@ func TestNewDBConfig(t *testing.T) { c := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - got := config.NewDBConfig(c) + got := option.NewDBOption(c) assert.Equal(t, tt.want, got, tt.name) }) } } -func TestDBConfig_Init(t *testing.T) { +func TestDBOption_Init(t *testing.T) { type fields struct { Reset bool DownloadDBOnly bool @@ -70,7 +69,7 @@ func TestDBConfig_Init(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := &config.DBConfig{ + c := &option.DBOption{ Reset: tt.fields.Reset, DownloadDBOnly: tt.fields.DownloadDBOnly, SkipUpdate: tt.fields.SkipUpdate, diff --git a/pkg/commands/config/global.go b/pkg/commands/option/global.go similarity index 63% rename from pkg/commands/config/global.go rename to pkg/commands/option/global.go index 85fa50ee78..f0a11b3b74 100644 --- a/pkg/commands/config/global.go +++ b/pkg/commands/option/global.go @@ -1,4 +1,4 @@ -package config +package option import ( "github.com/urfave/cli/v2" @@ -8,8 +8,8 @@ import ( "github.com/aquasecurity/trivy/pkg/log" ) -// GlobalConfig holds the global config for trivy -type GlobalConfig struct { +// GlobalOption holds the global options for trivy +type GlobalOption struct { Context *cli.Context Logger *zap.SugaredLogger @@ -19,16 +19,16 @@ type GlobalConfig struct { CacheDir string } -// NewGlobalConfig is the factory method to return GlobalConfig -func NewGlobalConfig(c *cli.Context) (GlobalConfig, error) { +// NewGlobalOption is the factory method to return GlobalOption +func NewGlobalOption(c *cli.Context) (GlobalOption, error) { quiet := c.Bool("quiet") debug := c.Bool("debug") logger, err := log.NewLogger(debug, quiet) if err != nil { - return GlobalConfig{}, xerrors.New("failed to create a logger") + return GlobalOption{}, xerrors.New("failed to create a logger") } - return GlobalConfig{ + return GlobalOption{ Context: c, Logger: logger, diff --git a/pkg/commands/config/global_test.go b/pkg/commands/option/global_test.go similarity index 82% rename from pkg/commands/config/global_test.go rename to pkg/commands/option/global_test.go index 543cfda53b..3816e0ff16 100644 --- a/pkg/commands/config/global_test.go +++ b/pkg/commands/option/global_test.go @@ -1,26 +1,25 @@ -package config_test +package option_test import ( "flag" "testing" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" - - "github.com/aquasecurity/trivy/pkg/commands/config" ) func TestNewGlobalConfig(t *testing.T) { tests := []struct { name string args []string - want config.GlobalConfig + want option.GlobalOption }{ { name: "happy path", args: []string{"--quiet", "--debug"}, - want: config.GlobalConfig{ + want: option.GlobalOption{ Quiet: true, Debug: true, }, @@ -36,7 +35,7 @@ func TestNewGlobalConfig(t *testing.T) { c := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - got, err := config.NewGlobalConfig(c) + got, err := option.NewGlobalOption(c) require.NoError(t, err, err) assert.Equal(t, tt.want.Quiet, got.Quiet, tt.name) assert.Equal(t, tt.want.Debug, got.Debug, tt.name) diff --git a/pkg/commands/option/image.go b/pkg/commands/option/image.go new file mode 100644 index 0000000000..c734f81db1 --- /dev/null +++ b/pkg/commands/option/image.go @@ -0,0 +1,19 @@ +package option + +import ( + "github.com/urfave/cli/v2" +) + +// ImageOption holds the options for scanning images +type ImageOption struct { + ScanRemovedPkgs bool + ListAllPkgs bool +} + +// NewImageOption is the factory method to return ImageOption +func NewImageOption(c *cli.Context) ImageOption { + return ImageOption{ + ScanRemovedPkgs: c.Bool("removed-pkgs"), + ListAllPkgs: c.Bool("list-all-pkgs"), + } +} diff --git a/pkg/commands/option/report.go b/pkg/commands/option/report.go new file mode 100644 index 0000000000..83e0382ad7 --- /dev/null +++ b/pkg/commands/option/report.go @@ -0,0 +1,127 @@ +package option + +import ( + "os" + "strings" + + "github.com/aquasecurity/trivy/pkg/types" + + "github.com/urfave/cli/v2" + "go.uber.org/zap" + "golang.org/x/xerrors" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" +) + +// ReportOption holds the options for reporting scan results +type ReportOption struct { + Format string + Template string + + IgnoreFile string + IgnoreUnfixed bool + ExitCode int + IgnorePolicy string + + // these variables are not exported + vulnType string + securityChecks string + output string + severities string + + // these variables are populated by Init() + VulnType []string + SecurityChecks []string + Output *os.File + Severities []dbTypes.Severity +} + +// NewReportOption is the factory method to return ReportOption +func NewReportOption(c *cli.Context) ReportOption { + return ReportOption{ + output: c.String("output"), + Format: c.String("format"), + Template: c.String("template"), + IgnorePolicy: c.String("ignore-policy"), + + vulnType: c.String("vuln-type"), + securityChecks: c.String("security-checks"), + severities: c.String("severity"), + IgnoreFile: c.String("ignorefile"), + IgnoreUnfixed: c.Bool("ignore-unfixed"), + ExitCode: c.Int("exit-code"), + } +} + +// Init initializes the ReportOption +func (c *ReportOption) Init(logger *zap.SugaredLogger) error { + var err error + + if c.Template != "" { + if c.Format == "" { + logger.Warn("--template is ignored because --format template is not specified. Use --template option with --format template option.") + } else if c.Format != "template" { + logger.Warnf("--template is ignored because --format %s is specified. Use --template option with --format template option.", c.Format) + } + } + if c.Format == "template" && c.Template == "" { + logger.Warn("--format template is ignored because --template not is specified. Specify --template option when you use --format template.") + } + + c.Severities = splitSeverity(logger, c.severities) + + if err = c.populateVulnTypes(); err != nil { + return xerrors.Errorf("vuln type: %w", err) + } + + if err = c.populateSecurityChecks(); err != nil { + return xerrors.Errorf("security checks: %w", err) + } + + // for testability + c.severities = "" + c.vulnType = "" + c.securityChecks = "" + + c.Output = os.Stdout + if c.output != "" { + if c.Output, err = os.Create(c.output); err != nil { + return xerrors.Errorf("failed to create an output file: %w", err) + } + } + + return nil +} + +func (c *ReportOption) populateVulnTypes() error { + for _, v := range strings.Split(c.vulnType, ",") { + if types.NewVulnType(v) == types.VulnTypeUnknown { + return xerrors.Errorf("unknown vulnerability type (%s)", v) + } + c.VulnType = append(c.VulnType, v) + } + return nil +} + +func (c *ReportOption) populateSecurityChecks() error { + for _, v := range strings.Split(c.securityChecks, ",") { + if types.NewSecurityCheck(v) == types.SecurityCheckUnknown { + return xerrors.Errorf("unknown security check (%s)", v) + } + c.SecurityChecks = append(c.SecurityChecks, v) + } + return nil +} + +func splitSeverity(logger *zap.SugaredLogger, severity string) []dbTypes.Severity { + logger.Debugf("Severities: %s", severity) + var severities []dbTypes.Severity + for _, s := range strings.Split(severity, ",") { + severity, err := dbTypes.NewSeverity(s) + if err != nil { + logger.Warnf("unknown severity option: %s", err) + } + severities = append(severities, severity) + } + return severities +} diff --git a/pkg/commands/option/report_test.go b/pkg/commands/option/report_test.go new file mode 100644 index 0000000000..f002ad2224 --- /dev/null +++ b/pkg/commands/option/report_test.go @@ -0,0 +1,178 @@ +package option + +import ( + "flag" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportReportConfig_Init(t *testing.T) { + type fields struct { + output string + Format string + Template string + vulnType string + securityChecks string + severities string + IgnoreFile string + IgnoreUnfixed bool + ExitCode int + VulnType []string + Output *os.File + Severities []dbTypes.Severity + } + tests := []struct { + name string + fields fields + args []string + logs []string + want ReportOption + wantErr string + }{ + { + name: "happy path", + fields: fields{ + severities: "CRITICAL", + vulnType: "os", + securityChecks: "vuln", + }, + args: []string{"alpine:3.10"}, + want: ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical}, + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Output: os.Stdout, + }, + }, + { + name: "happy path with an unknown severity", + fields: fields{ + severities: "CRITICAL,INVALID", + vulnType: "os,library", + securityChecks: "vuln", + }, + args: []string{"centos:7"}, + logs: []string{ + "unknown severity option: unknown severity: INVALID", + }, + want: ReportOption{ + Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown}, + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + Output: os.Stdout, + }, + }, + { + name: "invalid option combination: --template enabled without --format", + fields: fields{ + Template: "@contrib/gitlab.tpl", + severities: "LOW", + vulnType: "os", + securityChecks: "vuln", + }, + args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, + logs: []string{ + "--template is ignored because --format template is not specified. Use --template option with --format template option.", + }, + want: ReportOption{ + Output: os.Stdout, + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + Template: "@contrib/gitlab.tpl", + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, + }, + { + name: "invalid option combination: --template and --format json", + fields: fields{ + Format: "json", + Template: "@contrib/gitlab.tpl", + severities: "LOW", + vulnType: "os", + securityChecks: "vuln", + }, + args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, + logs: []string{ + "--template is ignored because --format json is specified. Use --template option with --format template option.", + }, + want: ReportOption{ + Format: "json", + Output: os.Stdout, + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + Template: "@contrib/gitlab.tpl", + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, + }, + { + name: "invalid option combination: --format template without --template", + fields: fields{ + Format: "template", + severities: "LOW", + vulnType: "os", + securityChecks: "vuln", + }, + args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"}, + logs: []string{ + "--format template is ignored because --template not is specified. Specify --template option when you use --format template.", + }, + want: ReportOption{ + Format: "template", + Output: os.Stdout, + Severities: []dbTypes.Severity{dbTypes.SeverityLow}, + VulnType: []string{types.VulnTypeOS}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + core, obs := observer.New(zap.InfoLevel) + logger := zap.New(core) + + set := flag.NewFlagSet("test", 0) + _ = set.Parse(tt.args) + + c := &ReportOption{ + output: tt.fields.output, + Format: tt.fields.Format, + Template: tt.fields.Template, + vulnType: tt.fields.vulnType, + securityChecks: tt.fields.securityChecks, + severities: tt.fields.severities, + IgnoreFile: tt.fields.IgnoreFile, + IgnoreUnfixed: tt.fields.IgnoreUnfixed, + ExitCode: tt.fields.ExitCode, + Output: tt.fields.Output, + } + + err := c.Init(logger.Sugar()) + + // tests log messages + var gotMessages []string + for _, entry := range obs.AllUntimed() { + gotMessages = append(gotMessages, entry.Message) + } + assert.Equal(t, tt.logs, gotMessages, tt.name) + + // test the error + switch { + case tt.wantErr != "": + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr, tt.name) + return + } + + assert.NoError(t, err, tt.name) + assert.Equal(t, &tt.want, c, tt.name) + }) + } +} diff --git a/pkg/commands/plugin/plugin.go b/pkg/commands/plugin/plugin.go index 609ac6841f..a027bec1e2 100644 --- a/pkg/commands/plugin/plugin.go +++ b/pkg/commands/plugin/plugin.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/plugin" ) @@ -105,7 +105,7 @@ func LoadCommands() cli.Commands { } func initLogger(ctx *cli.Context) error { - conf, err := config.NewGlobalConfig(ctx) + conf, err := option.NewGlobalOption(ctx) if err != nil { return xerrors.Errorf("config error: %w", err) } diff --git a/pkg/commands/server/config.go b/pkg/commands/server/config.go index 766cb68904..0c0f031531 100644 --- a/pkg/commands/server/config.go +++ b/pkg/commands/server/config.go @@ -3,14 +3,14 @@ package server import ( "github.com/urfave/cli/v2" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" ) // Config holds the Trivy config type Config struct { - config.GlobalConfig - config.DBConfig - config.CacheConfig + option.GlobalOption + option.DBOption + option.CacheOption Listen string Token string @@ -20,11 +20,11 @@ type Config struct { // NewConfig is the factory method to return config func NewConfig(c *cli.Context) Config { // the error is ignored because logger is unnecessary - gc, _ := config.NewGlobalConfig(c) // nolint: errcheck + gc, _ := option.NewGlobalOption(c) // nolint: errcheck return Config{ - GlobalConfig: gc, - DBConfig: config.NewDBConfig(c), - CacheConfig: config.NewCacheConfig(c), + GlobalOption: gc, + DBOption: option.NewDBOption(c), + CacheOption: option.NewCacheOption(c), Listen: c.String("listen"), Token: c.String("token"), @@ -34,10 +34,10 @@ func NewConfig(c *cli.Context) Config { // Init initializes the config func (c *Config) Init() (err error) { - if err := c.DBConfig.Init(); err != nil { + if err := c.DBOption.Init(); err != nil { return err } - if err := c.CacheConfig.Init(); err != nil { + if err := c.CacheOption.Init(); err != nil { return err } diff --git a/pkg/commands/server/config_test.go b/pkg/commands/server/config_test.go index a8ed668eb2..355c22d0ae 100644 --- a/pkg/commands/server/config_test.go +++ b/pkg/commands/server/config_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" - "github.com/aquasecurity/trivy/pkg/commands/config" + "github.com/aquasecurity/trivy/pkg/commands/option" "github.com/aquasecurity/trivy/pkg/commands/server" ) @@ -22,10 +22,10 @@ func TestNew(t *testing.T) { name: "happy path", args: []string{"-quiet", "--no-progress", "--reset", "--skip-update", "--listen", "localhost:8080"}, want: server.Config{ - GlobalConfig: config.GlobalConfig{ + GlobalOption: option.GlobalOption{ Quiet: true, }, - DBConfig: config.DBConfig{ + DBOption: option.DBOption{ Reset: true, SkipUpdate: true, NoProgress: true, @@ -47,11 +47,11 @@ func TestNew(t *testing.T) { ctx := cli.NewContext(app, set, nil) _ = set.Parse(tt.args) - tt.want.GlobalConfig.Context = ctx + tt.want.GlobalOption.Context = ctx got := server.NewConfig(ctx) - assert.Equal(t, tt.want.GlobalConfig.Quiet, got.Quiet, tt.name) - assert.Equal(t, tt.want.DBConfig, got.DBConfig, tt.name) + assert.Equal(t, tt.want.GlobalOption.Quiet, got.Quiet, tt.name) + assert.Equal(t, tt.want.DBOption, got.DBOption, tt.name) assert.Equal(t, tt.want.Listen, got.Listen, tt.name) }) } @@ -60,8 +60,8 @@ func TestNew(t *testing.T) { func TestConfig_Init(t *testing.T) { tests := []struct { name string - globalConfig config.GlobalConfig - dbConfig config.DBConfig + globalConfig option.GlobalOption + dbConfig option.DBOption args []string wantErr string }{ @@ -71,14 +71,14 @@ func TestConfig_Init(t *testing.T) { }, { name: "happy path: reset", - dbConfig: config.DBConfig{ + dbConfig: option.DBOption{ Reset: true, }, args: []string{"alpine:3.10"}, }, { name: "sad: skip and download db", - dbConfig: config.DBConfig{ + dbConfig: option.DBOption{ SkipUpdate: true, DownloadDBOnly: true, }, @@ -89,7 +89,7 @@ func TestConfig_Init(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &server.Config{ - DBConfig: tt.dbConfig, + DBOption: tt.dbConfig, } err := c.Init() diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index 75e26a999a..a383f6b563 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -3,7 +3,7 @@ package library import ( "golang.org/x/xerrors" - "github.com/aquasecurity/fanal/analyzer/library" + ftypes "github.com/aquasecurity/fanal/types" 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" @@ -25,21 +25,21 @@ type advisory interface { func NewDriver(libType string) (Driver, error) { var driver Driver switch libType { - case library.Bundler: + case ftypes.Bundler: driver = newRubyGemsDriver() - case library.Cargo: + case ftypes.Cargo: driver = newCargoDriver() - case library.Composer: + case ftypes.Composer: driver = newComposerDriver() - case library.Npm, library.Yarn: + case ftypes.Npm, ftypes.Yarn: driver = newNpmDriver() - case library.Pipenv, library.Poetry: + case ftypes.Pipenv, ftypes.Poetry: driver = newPipDriver() - case library.NuGet: + case ftypes.NuGet: driver = newNugetDriver() - case library.Jar: + case ftypes.Jar: driver = newMavenDriver() - case library.GoBinary, library.GoMod: + case ftypes.GoBinary, ftypes.GoMod: driver = Driver{ ecosystem: vulnerability.Go, advisories: []advisory{NewAdvisory(vulnerability.Go, comparer.GenericComparer{})}, diff --git a/pkg/detector/library/driver_test.go b/pkg/detector/library/driver_test.go index b1f0e3e1b4..61159087ad 100644 --- a/pkg/detector/library/driver_test.go +++ b/pkg/detector/library/driver_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - lib "github.com/aquasecurity/fanal/analyzer/library" + ftypes "github.com/aquasecurity/fanal/types" "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/detector/library" @@ -29,7 +29,7 @@ func TestDriver_Detect(t *testing.T) { { name: "happy path", fixtures: []string{"testdata/fixtures/php.yaml"}, - libType: lib.Composer, + libType: ftypes.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.2.6", @@ -46,7 +46,7 @@ func TestDriver_Detect(t *testing.T) { { name: "non-prefix buckets", fixtures: []string{"testdata/fixtures/php-without-prefix.yaml"}, - libType: lib.Composer, + libType: ftypes.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.2.6", @@ -63,7 +63,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no patched versions in the advisory", fixtures: []string{"testdata/fixtures/php.yaml"}, - libType: lib.Composer, + libType: ftypes.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.4.6", @@ -80,7 +80,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no vulnerable versions in the advisory", fixtures: []string{"testdata/fixtures/ruby.yaml"}, - libType: lib.Bundler, + libType: ftypes.Bundler, args: args{ pkgName: "activesupport", pkgVer: "4.1.1", @@ -97,7 +97,7 @@ func TestDriver_Detect(t *testing.T) { { name: "no vulnerability", fixtures: []string{"testdata/fixtures/php.yaml"}, - libType: lib.Composer, + libType: ftypes.Composer, args: args{ pkgName: "symfony/symfony", pkgVer: "4.4.7", diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go new file mode 100644 index 0000000000..b97f681d7e --- /dev/null +++ b/pkg/downloader/download.go @@ -0,0 +1,56 @@ +package downloader + +import ( + "context" + "os" + + getter "github.com/hashicorp/go-getter" + "golang.org/x/xerrors" +) + +// DownloadToTempDir downloads the configured source to a temp dir. +func DownloadToTempDir(ctx context.Context, url string) (string, error) { + tempDir, err := os.MkdirTemp("", "trivy-plugin") + if err != nil { + return "", xerrors.Errorf("failed to create a temp dir: %w", err) + } + + pwd, err := os.Getwd() + if err != nil { + return "", xerrors.Errorf("unable to get the current dir: %w", err) + } + + if err = Download(ctx, url, tempDir, pwd); err != nil { + return "", xerrors.Errorf("download error: %w", err) + } + + return tempDir, nil +} + +// Download downloads the configured source to the destination. +func Download(ctx context.Context, src, dst, pwd string) error { + // go-getter doesn't allow the dst directory already exists if the src is directory. + _ = os.RemoveAll(dst) + + var opts []getter.ClientOption + + // Overwrite the file getter so that a file will be copied + getter.Getters["file"] = &getter.FileGetter{Copy: true} + + // Build the client + client := &getter.Client{ + Ctx: ctx, + Src: src, + Dst: dst, + Pwd: pwd, + Getters: getter.Getters, + Mode: getter.ClientModeAny, + Options: opts, + } + + if err := client.Get(); err != nil { + return xerrors.Errorf("failed to download: %w", err) + } + + return nil +} diff --git a/pkg/github/github.go b/pkg/github/github.go index 5e3e966dc7..88d2fce08e 100644 --- a/pkg/github/github.go +++ b/pkg/github/github.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/google/go-github/v28/github" + "github.com/google/go-github/v33/github" "golang.org/x/oauth2" "golang.org/x/xerrors" @@ -44,7 +44,7 @@ func (r Repository) ListReleases(ctx context.Context, opt *github.ListOptions) ( // DownloadAsset returns reader object of downloaded object func (r Repository) DownloadAsset(ctx context.Context, id int64) (io.ReadCloser, string, error) { - return r.repository.DownloadReleaseAsset(ctx, r.owner, r.repoName, id) + return r.repository.DownloadReleaseAsset(ctx, r.owner, r.repoName, id, nil) } // Operation defines the file operations @@ -113,7 +113,7 @@ func (c Client) DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, return nil, 0, xerrors.New("DB file not found") } -func (c Client) downloadAsset(ctx context.Context, asset github.ReleaseAsset, fileName string) (io.ReadCloser, int, error) { +func (c Client) downloadAsset(ctx context.Context, asset *github.ReleaseAsset, fileName string) (io.ReadCloser, int, error) { log.Logger.Debugf("asset name: %s", asset.GetName()) if asset.GetName() != fileName { return nil, 0, xerrors.New("file name doesn't match") diff --git a/pkg/github/github_test.go b/pkg/github/github_test.go index 1266906911..cb0480570b 100644 --- a/pkg/github/github_test.go +++ b/pkg/github/github_test.go @@ -13,12 +13,10 @@ import ( "testing" "time" - "golang.org/x/xerrors" - + "github.com/google/go-github/v33/github" "github.com/stretchr/testify/assert" - - "github.com/google/go-github/v28/github" "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" ) type MockRepository struct { @@ -96,7 +94,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 1, 1, 1, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(200), Name: github.String("trivy.db.gz"), @@ -109,7 +107,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 12, 31, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), @@ -143,7 +141,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 12, 31, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), @@ -177,7 +175,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2019, 10, 1, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), @@ -191,7 +189,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2019, 10, 2, 0, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(300), Name: github.String("trivy.db.gz"), @@ -204,7 +202,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2019, 10, 1, 22, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(200), Name: github.String("trivy.db.gz"), @@ -255,7 +253,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2019, 10, 1, 22, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(200), Name: github.String("trivy.db.gz"), @@ -310,7 +308,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 12, 31, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), @@ -350,7 +348,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 12, 31, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), @@ -385,7 +383,7 @@ func TestClient_DownloadDB(t *testing.T) { PublishedAt: &github.Timestamp{ Time: time.Date(2020, 12, 31, 23, 59, 59, 0, time.UTC), }, - Assets: []github.ReleaseAsset{ + Assets: []*github.ReleaseAsset{ { ID: github.Int64(100), Name: github.String("trivy.db.gz"), diff --git a/pkg/log/logger.go b/pkg/log/logger.go index aaa905afc7..d708e6e635 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -7,7 +7,8 @@ import ( "go.uber.org/zap/zapcore" "golang.org/x/xerrors" - "github.com/aquasecurity/go-dep-parser/pkg/log" + flog "github.com/aquasecurity/fanal/log" + dlog "github.com/aquasecurity/go-dep-parser/pkg/log" ) var ( @@ -30,7 +31,10 @@ func InitLogger(debug, disable bool) (err error) { } // Set logger for go-dep-parser - log.SetLogger(Logger) + dlog.SetLogger(Logger) + + // Set logger for fanal + flog.SetLogger(Logger) return nil diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 562f9ca6ac..b257da54cd 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -7,10 +7,10 @@ import ( "path/filepath" "runtime" - getter "github.com/hashicorp/go-getter" "golang.org/x/xerrors" yaml "gopkg.in/yaml.v3" + "github.com/aquasecurity/trivy/pkg/downloader" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils" ) @@ -116,7 +116,7 @@ func (p Plugin) install(ctx context.Context, dst, pwd string) error { } log.Logger.Debugf("Downloading the execution file from %s...", platform.URI) - if err = download(ctx, platform.URI, dst, pwd); err != nil { + if err = downloader.Download(ctx, platform.URI, dst, pwd); err != nil { return xerrors.Errorf("unable to download the execution file (%s): %w", platform.URI, err) } return nil @@ -147,7 +147,7 @@ func Install(ctx context.Context, url string, force bool) (Plugin, error) { } log.Logger.Infof("Installing the plugin from %s...", url) - tempDir, err := downloadToTempDir(ctx, url) + tempDir, err := downloader.DownloadToTempDir(ctx, url) if err != nil { return Plugin{}, xerrors.Errorf("download failed: %w", err) } @@ -182,51 +182,6 @@ func Uninstall(name string) error { return os.RemoveAll(pluginDir) } -func downloadToTempDir(ctx context.Context, url string) (string, error) { - tempDir, err := os.MkdirTemp("", "trivy-plugin") - if err != nil { - return "", xerrors.Errorf("failed to create a temp dir: %w", err) - } - - pwd, err := os.Getwd() - if err != nil { - return "", xerrors.Errorf("unable to get the current dir: %w", err) - } - - if err = download(ctx, url, tempDir, pwd); err != nil { - return "", xerrors.Errorf("download error: %w", err) - } - - return tempDir, nil -} - -func download(ctx context.Context, src, dst, pwd string) error { - // go-getter doesn't allow the dst directory already exists if the src is directory. - _ = os.RemoveAll(dst) - - var opts []getter.ClientOption - - // Overwrite the file getter so that a file will be copied - getter.Getters["file"] = &getter.FileGetter{Copy: true} - - // Build the client - client := &getter.Client{ - Ctx: ctx, - Src: src, - Dst: dst, - Pwd: pwd, - Getters: getter.Getters, - Mode: getter.ClientModeAny, - Options: opts, - } - - if err := client.Get(); err != nil { - return xerrors.Errorf("failed to download: %w", err) - } - - return nil -} - // LoadAll loads all plugins func LoadAll() ([]Plugin, error) { pluginsDir := dir() diff --git a/pkg/report/json.go b/pkg/report/json.go new file mode 100644 index 0000000000..8ca7c87d9f --- /dev/null +++ b/pkg/report/json.go @@ -0,0 +1,27 @@ +package report + +import ( + "encoding/json" + "fmt" + "io" + + "golang.org/x/xerrors" +) + +// JSONWriter implements result Writer +type JSONWriter struct { + Output io.Writer +} + +// Write writes the results in JSON format +func (jw JSONWriter) Write(results Results) error { + output, err := json.MarshalIndent(results, "", " ") + if err != nil { + return xerrors.Errorf("failed to marshal json: %w", err) + } + + if _, err = fmt.Fprint(jw.Output, string(output)); err != nil { + return xerrors.Errorf("failed to write json: %w", err) + } + return nil +} diff --git a/pkg/report/json_test.go b/pkg/report/json_test.go new file mode 100644 index 0000000000..e269410a43 --- /dev/null +++ b/pkg/report/json_test.go @@ -0,0 +1,82 @@ +package report_test + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_JSON(t *testing.T) { + testCases := []struct { + name string + detectedVulns []types.DetectedVulnerability + expectedJSON report.Results + }{ + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + expectedJSON: report.Results{ + report.Result{ + Target: "foojson", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + jw := report.JSONWriter{} + jsonWritten := bytes.Buffer{} + jw.Output = &jsonWritten + + inputResults := report.Results{ + { + Target: "foojson", + Vulnerabilities: tc.detectedVulns, + }, + } + + err := report.WriteResults("json", &jsonWritten, nil, inputResults, "", false) + assert.NoError(t, err) + + writtenResults := report.Results{} + err = json.Unmarshal([]byte(jsonWritten.String()), &writtenResults) + assert.NoError(t, err, "invalid json written", tc.name) + + assert.Equal(t, tc.expectedJSON, writtenResults, tc.name) + }) + } +} diff --git a/pkg/report/table.go b/pkg/report/table.go new file mode 100644 index 0000000000..5ba448a387 --- /dev/null +++ b/pkg/report/table.go @@ -0,0 +1,113 @@ +package report + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/olekukonko/tablewriter" + + ftypes "github.com/aquasecurity/fanal/types" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/utils" +) + +// TableWriter implements Writer and output in tabular form +type TableWriter struct { + Severities []dbTypes.Severity + Output io.Writer + Light bool +} + +// Write writes the result on standard output +func (tw TableWriter) Write(results Results) error { + for _, result := range results { + // Skip zero vulnerabilities on Java archives (JAR/WAR/EAR) + if result.Type == ftypes.Jar && len(result.Vulnerabilities) == 0 { + continue + } + tw.write(result) + } + return nil +} + +func (tw TableWriter) write(result Result) { + table := tablewriter.NewWriter(tw.Output) + + total, severityCount := tw.writeVulnerabilities(table, result.Vulnerabilities) + + var severities []string + for _, sev := range tw.Severities { + severities = append(severities, sev.String()) + } + + var results []string + for _, severity := range dbTypes.SeverityNames { + if !utils.StringInSlice(severity, severities) { + continue + } + r := fmt.Sprintf("%s: %d", severity, severityCount[severity]) + results = append(results, r) + } + + fmt.Printf("\n%s\n", result.Target) + fmt.Println(strings.Repeat("=", len(result.Target))) + fmt.Printf("Total: %d (%s)\n\n", total, strings.Join(results, ", ")) + + if len(result.Vulnerabilities) == 0 { + return + } + + table.SetAutoMergeCells(true) + table.SetRowLine(true) + table.Render() + return +} + +func (tw TableWriter) writeVulnerabilities(table *tablewriter.Table, vulns []types.DetectedVulnerability) (int, map[string]int) { + header := []string{"Library", "Vulnerability ID", "Severity", "Installed Version", "Fixed Version"} + if !tw.Light { + header = append(header, "Title") + } + table.SetHeader(header) + severityCount := tw.setVulnerabilityRows(table, vulns) + + return len(vulns), severityCount +} + +func (tw TableWriter) setVulnerabilityRows(table *tablewriter.Table, vulns []types.DetectedVulnerability) map[string]int { + severityCount := map[string]int{} + for _, v := range vulns { + severityCount[v.Severity]++ + + title := v.Title + if title == "" { + title = v.Description + } + splitTitle := strings.Split(title, " ") + if len(splitTitle) >= 12 { + title = strings.Join(splitTitle[:12], " ") + "..." + } + + if len(v.PrimaryURL) > 0 { + r := strings.NewReplacer("https://", "", "http://", "") + title = fmt.Sprintf("%s -->%s", title, r.Replace(v.PrimaryURL)) + } + + var row []string + if tw.Output == os.Stdout { + row = []string{v.PkgName, v.VulnerabilityID, dbTypes.ColorizeSeverity(v.Severity), + v.InstalledVersion, v.FixedVersion} + } else { + row = []string{v.PkgName, v.VulnerabilityID, v.Severity, v.InstalledVersion, v.FixedVersion} + } + + if !tw.Light { + row = append(row, strings.TrimSpace(title)) + } + table.Append(row) + } + return severityCount +} diff --git a/pkg/report/table_test.go b/pkg/report/table_test.go new file mode 100644 index 0000000000..3174348c81 --- /dev/null +++ b/pkg/report/table_test.go @@ -0,0 +1,145 @@ +package report_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_Table(t *testing.T) { + testCases := []struct { + name string + results report.Results + expectedOutput string + light bool + }{ + { + name: "happy path full", + results: report.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + }, + }, + expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------------------------------------+ +| foo | CVE-2020-0001 | HIGH | 1.2.3 | 3.4.5 | foobar | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-0001 | ++---------+------------------+----------+-------------------+---------------+--------------------------------------+ +`, + }, + { + name: "happy path light", + light: true, + results: report.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Severity: "HIGH", + }, + }, + }, + }, + }, + expectedOutput: `+---------+------------------+----------+-------------------+---------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | ++---------+------------------+----------+-------------------+---------------+ +| foo | CVE-2020-0001 | HIGH | 1.2.3 | 3.4.5 | ++---------+------------------+----------+-------------------+---------------+ +`, + }, + { + name: "no title for vuln and missing primary link", + results: report.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + Vulnerability: dbTypes.Vulnerability{ + Description: "foobar", + Severity: "HIGH", + }, + }, + }, + }, + }, + expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------+ +| foo | CVE-2020-0001 | HIGH | 1.2.3 | 3.4.5 | foobar | ++---------+------------------+----------+-------------------+---------------+--------+ +`, + }, + { + name: "long title for vuln", + results: report.Results{ + { + Target: "test", + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-1234", + PkgName: "foo", + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-1234", + Vulnerability: dbTypes.Vulnerability{ + Title: "a b c d e f g h i j k l m n o p q r s t u v", + Description: "foobar", + Severity: "HIGH", + }, + }, + }, + }, + }, + expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------------------------------------+ +| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | ++---------+------------------+----------+-------------------+---------------+--------------------------------------+ +| foo | CVE-2020-1234 | HIGH | 1.2.3 | 3.4.5 | a b c d e f g h i j k l... | +| | | | | | -->avd.aquasec.com/nvd/cve-2020-1234 | ++---------+------------------+----------+-------------------+---------------+--------------------------------------+ +`, + }, + { + name: "no vulns", + expectedOutput: ``, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tableWritten := bytes.Buffer{} + assert.NoError(t, report.WriteResults("table", &tableWritten, nil, tc.results, "", tc.light), tc.name) + assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) + }) + } +} diff --git a/pkg/report/template.go b/pkg/report/template.go new file mode 100644 index 0000000000..acaab51c5e --- /dev/null +++ b/pkg/report/template.go @@ -0,0 +1,121 @@ +package report + +import ( + "bytes" + "encoding/xml" + "fmt" + "html" + "io" + "io/ioutil" + "os" + "regexp" + "strings" + "text/template" + "time" + + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + + "github.com/Masterminds/sprig" + "golang.org/x/xerrors" +) + +// regex to extract file path in case string includes (distro:version) +var re = regexp.MustCompile(`(?P.+?)(?:\s*\((?:.*?)\).*?)?$`) + +// TemplateWriter write result in custom format defined by user's template +type TemplateWriter struct { + Output io.Writer + Template *template.Template +} + +// NewTemplateWriter is the factory method to return TemplateWriter object +func NewTemplateWriter(output io.Writer, outputTemplate string) (*TemplateWriter, error) { + if strings.HasPrefix(outputTemplate, "@") { + buf, err := ioutil.ReadFile(strings.TrimPrefix(outputTemplate, "@")) + if err != nil { + return nil, xerrors.Errorf("error retrieving template from path: %w", err) + } + outputTemplate = string(buf) + } + var templateFuncMap template.FuncMap + templateFuncMap = sprig.GenericFuncMap() + templateFuncMap["escapeXML"] = func(input string) string { + escaped := &bytes.Buffer{} + if err := xml.EscapeText(escaped, []byte(input)); err != nil { + fmt.Printf("error while escapeString to XML: %v", err.Error()) + return input + } + return escaped.String() + } + templateFuncMap["toSarifErrorLevel"] = toSarifErrorLevel + templateFuncMap["toSarifRuleName"] = toSarifRuleName + templateFuncMap["endWithPeriod"] = func(input string) string { + if !strings.HasSuffix(input, ".") { + input += "." + } + return input + } + templateFuncMap["toLower"] = func(input string) string { + return strings.ToLower(input) + } + templateFuncMap["escapeString"] = func(input string) string { + return html.EscapeString(input) + } + templateFuncMap["toPathUri"] = func(input string) string { + var matches = re.FindStringSubmatch(input) + if matches != nil { + input = matches[re.SubexpIndex("path")] + } + input = strings.ReplaceAll(input, "\\", "/") + return input + } + templateFuncMap["getEnv"] = func(key string) string { + return os.Getenv(key) + } + templateFuncMap["getCurrentTime"] = func() string { + return Now().UTC().Format(time.RFC3339Nano) + } + tmpl, err := template.New("output template").Funcs(templateFuncMap).Parse(outputTemplate) + if err != nil { + return nil, xerrors.Errorf("error parsing template: %w", err) + } + return &TemplateWriter{Output: output, Template: tmpl}, nil +} + +// Write writes result +func (tw TemplateWriter) Write(results Results) error { + err := tw.Template.Execute(tw.Output, results) + if err != nil { + return xerrors.Errorf("failed to write with template: %w", err) + } + return nil +} + +func toSarifRuleName(vulnerabilityType string) string { + var ruleName string + switch vulnerabilityType { + case vulnerability.Ubuntu, vulnerability.Alpine, vulnerability.RedHat, vulnerability.RedHatOVAL, + vulnerability.Debian, vulnerability.DebianOVAL, vulnerability.Fedora, vulnerability.Amazon, + vulnerability.OracleOVAL, vulnerability.SuseCVRF, vulnerability.OpenSuseCVRF, vulnerability.Photon, + vulnerability.CentOS: + ruleName = "OS Package Vulnerability" + case "npm", "yarn", "nuget", "pipenv", "poetry", "bundler", "cargo", "composer": + ruleName = "Programming Language Vulnerability" + default: + ruleName = "Other Vulnerability" + } + return fmt.Sprintf("%s (%s)", ruleName, strings.Title(vulnerabilityType)) +} + +func toSarifErrorLevel(severity string) string { + switch severity { + case "CRITICAL", "HIGH": + return "error" + case "MEDIUM": + return "warning" + case "LOW", "UNKNOWN": + return "note" + default: + return "none" + } +} diff --git a/pkg/report/template_test.go b/pkg/report/template_test.go new file mode 100644 index 0000000000..766e8b8c91 --- /dev/null +++ b/pkg/report/template_test.go @@ -0,0 +1,211 @@ +package report_test + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/types" +) + +func TestReportWriter_Template(t *testing.T) { + testCases := []struct { + name string + detectedVulns []types.DetectedVulnerability + template string + expected string + }{ + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityHigh.String()}, + }, + { + VulnerabilityID: "CVE-2019-0001", + PkgName: "baz", + Vulnerability: dbTypes.Vulnerability{ + Severity: dbTypes.SeverityCritical.String(), + }, + }, + }, + template: "{{ range . }}{{ range .Vulnerabilities}}{{ println .VulnerabilityID .Severity }}{{ end }}{{ end }}", + expected: "CVE-2019-0000 HIGH\nCVE-2019-0000 HIGH\nCVE-2019-0001 CRITICAL\n", + }, + { + name: "happy path", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "123", + PkgName: `foo \ test`, + InstalledVersion: "1.2.3", + FixedVersion: "3.4.5", + Vulnerability: dbTypes.Vulnerability{ + Title: `gcc: POWER9 "DARN" RNG intrinsic produces repeated output`, + Description: `curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0.`, + Severity: "HIGH", + }, + }, + }, + template: ` +{{- range . -}} +{{- $failures := len .Vulnerabilities }} + + {{- if not (eq .Type "") }} + + + + {{- end -}} + {{ range .Vulnerabilities }} + + {{ escapeXML .Description }} + + {{- end }} + +{{- end }} +`, + + expected: ` + + + + + + curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0. + + +`, + }, + { + name: "happy path with/without period description should return with period", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Description: "without period", + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: "with period.", + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, + }, + }, + }, + template: `{{ range . }}{{ range .Vulnerabilities}}{{.VulnerabilityID}} {{ endWithPeriod (escapeString .Description) | printf "%q" }}{{ end }}{{ end }}`, + expected: `CVE-2019-0000 "without period."CVE-2019-0000 "with period."CVE-2019-0000 "with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close()."`, + }, + { + name: "Calculate using sprig", + detectedVulns: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "foo", + Vulnerability: dbTypes.Vulnerability{ + Description: "without period", + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: "with period.", + Severity: dbTypes.SeverityCritical.String(), + }, + }, + { + VulnerabilityID: "CVE-2019-0000", + PkgName: "bar", + Vulnerability: dbTypes.Vulnerability{ + Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, + Severity: dbTypes.SeverityHigh.String(), + }, + }, + }, + template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, + expected: `Critical: 2, High: 1`, + }, + { + name: "happy path: env var parsing and getCurrentTime", + detectedVulns: []types.DetectedVulnerability{}, + template: `{{ toLower (getEnv "AWS_ACCOUNT_ID") }} {{ getCurrentTime }}`, + expected: `123456789012 2020-08-10T07:28:17.000958601Z`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + report.Now = func() time.Time { + return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC) + } + os.Setenv("AWS_ACCOUNT_ID", "123456789012") + tmplWritten := bytes.Buffer{} + inputResults := report.Results{ + { + Target: "foojunit", + Type: "test", + Vulnerabilities: tc.detectedVulns, + }, + } + + assert.NoError(t, report.WriteResults("template", &tmplWritten, nil, inputResults, tc.template, false)) + assert.Equal(t, tc.expected, tmplWritten.String()) + }) + } +} + +func TestReportWriter_Template_SARIF(t *testing.T) { + testCases := []struct { + name string + target string + detectedVulns []types.DetectedVulnerability + want string + }{ + //TODO: refactor tests + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + templateFile := "../../contrib/sarif.tpl" + got := bytes.Buffer{} + + template, err := ioutil.ReadFile(templateFile) + require.NoError(t, err, tc.name) + + inputResults := report.Results{ + report.Result{ + Target: tc.target, + Type: "footype", + Vulnerabilities: tc.detectedVulns, + }, + } + assert.NoError(t, report.WriteResults("template", &got, nil, inputResults, string(template), false), tc.name) + assert.JSONEq(t, tc.want, got.String(), tc.name) + }) + } +} diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 67bdb87683..9cbeb428b4 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -1,37 +1,19 @@ package report import ( - "bytes" - "encoding/json" - "encoding/xml" - "fmt" - "html" "io" - "io/ioutil" - "os" - "regexp" - "strings" - "text/template" "time" - "github.com/Masterminds/sprig" - "github.com/olekukonko/tablewriter" "golang.org/x/xerrors" - "github.com/aquasecurity/fanal/analyzer/library" ftypes "github.com/aquasecurity/fanal/types" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/utils" ) // Now returns the current time var Now = time.Now -// regex to extract file path in case string includes (distro:version) -var re = regexp.MustCompile(`(?P.+?)(?:\s*\((?:.*?)\).*?)?$`) - // Results to hold list of Result type Results []Result @@ -40,7 +22,17 @@ type Result struct { Target string `json:"Target"` Type string `json:"Type,omitempty"` Packages []ftypes.Package `json:"Packages,omitempty"` - Vulnerabilities []types.DetectedVulnerability `json:"Vulnerabilities"` + Vulnerabilities []types.DetectedVulnerability `json:"Vulnerabilities,omitempty"` +} + +// Failed returns whether the result includes any vulnerabilities +func (results Results) Failed() bool { + for _, r := range results { + if len(r.Vulnerabilities) > 0 { + return true + } + } + return false } // WriteResults writes the result to output, format as passed in argument @@ -70,211 +62,3 @@ func WriteResults(format string, output io.Writer, severities []dbTypes.Severity type Writer interface { Write(Results) error } - -// TableWriter implements Writer and output in tabular form -type TableWriter struct { - Severities []dbTypes.Severity - Output io.Writer - Light bool -} - -// Write writes the result on standard output -func (tw TableWriter) Write(results Results) error { - for _, result := range results { - // Skip zero vulnerabilities on Java archives (JAR/WAR/EAR) - if result.Type == library.Jar && len(result.Vulnerabilities) == 0 { - continue - } - tw.write(result) - } - return nil -} - -func (tw TableWriter) write(result Result) { - table := tablewriter.NewWriter(tw.Output) - header := []string{"Library", "Vulnerability ID", "Severity", "Installed Version", "Fixed Version"} - if !tw.Light { - header = append(header, "Title") - } - table.SetHeader(header) - severityCount := tw.setRows(table, result.Vulnerabilities) - - var results []string - - var severities []string - for _, sev := range tw.Severities { - severities = append(severities, sev.String()) - } - - for _, severity := range dbTypes.SeverityNames { - if !utils.StringInSlice(severity, severities) { - continue - } - r := fmt.Sprintf("%s: %d", severity, severityCount[severity]) - results = append(results, r) - } - - fmt.Printf("\n%s\n", result.Target) - fmt.Println(strings.Repeat("=", len(result.Target))) - fmt.Printf("Total: %d (%s)\n\n", len(result.Vulnerabilities), strings.Join(results, ", ")) - - if len(result.Vulnerabilities) == 0 { - return - } - - table.SetAutoMergeCells(true) - table.SetRowLine(true) - table.Render() - return -} - -func (tw TableWriter) setRows(table *tablewriter.Table, vulns []types.DetectedVulnerability) map[string]int { - severityCount := map[string]int{} - for _, v := range vulns { - severityCount[v.Severity]++ - - title := v.Title - if title == "" { - title = v.Description - } - splitTitle := strings.Split(title, " ") - if len(splitTitle) >= 12 { - title = strings.Join(splitTitle[:12], " ") + "..." - } - - if len(v.PrimaryURL) > 0 { - r := strings.NewReplacer("https://", "", "http://", "") - title = fmt.Sprintf("%s -->%s", title, r.Replace(v.PrimaryURL)) - } - - var row []string - if tw.Output == os.Stdout { - row = []string{v.PkgName, v.VulnerabilityID, dbTypes.ColorizeSeverity(v.Severity), - v.InstalledVersion, v.FixedVersion} - } else { - row = []string{v.PkgName, v.VulnerabilityID, v.Severity, v.InstalledVersion, v.FixedVersion} - } - - if !tw.Light { - row = append(row, strings.TrimSpace(title)) - } - table.Append(row) - } - return severityCount -} - -// JSONWriter implements result Writer -type JSONWriter struct { - Output io.Writer -} - -// Write writes the results in JSON format -func (jw JSONWriter) Write(results Results) error { - output, err := json.MarshalIndent(results, "", " ") - if err != nil { - return xerrors.Errorf("failed to marshal json: %w", err) - } - - if _, err = fmt.Fprint(jw.Output, string(output)); err != nil { - return xerrors.Errorf("failed to write json: %w", err) - } - return nil -} - -// TemplateWriter write result in custom format defined by user's template -type TemplateWriter struct { - Output io.Writer - Template *template.Template -} - -// NewTemplateWriter is the factory method to return TemplateWriter object -func NewTemplateWriter(output io.Writer, outputTemplate string) (*TemplateWriter, error) { - if strings.HasPrefix(outputTemplate, "@") { - buf, err := ioutil.ReadFile(strings.TrimPrefix(outputTemplate, "@")) - if err != nil { - return nil, xerrors.Errorf("error retrieving template from path: %w", err) - } - outputTemplate = string(buf) - } - var templateFuncMap template.FuncMap - templateFuncMap = sprig.GenericFuncMap() - templateFuncMap["escapeXML"] = func(input string) string { - escaped := &bytes.Buffer{} - if err := xml.EscapeText(escaped, []byte(input)); err != nil { - fmt.Printf("error while escapeString to XML: %v", err.Error()) - return input - } - return escaped.String() - } - templateFuncMap["toSarifErrorLevel"] = toSarifErrorLevel - templateFuncMap["toSarifRuleName"] = toSarifRuleName - templateFuncMap["endWithPeriod"] = func(input string) string { - if !strings.HasSuffix(input, ".") { - input += "." - } - return input - } - templateFuncMap["toLower"] = func(input string) string { - return strings.ToLower(input) - } - templateFuncMap["escapeString"] = func(input string) string { - return html.EscapeString(input) - } - templateFuncMap["toPathUri"] = func(input string) string { - var matches = re.FindStringSubmatch(input) - if matches != nil { - input = matches[re.SubexpIndex("path")] - } - input = strings.ReplaceAll(input, "\\", "/") - return input - } - templateFuncMap["getEnv"] = func(key string) string { - return os.Getenv(key) - } - templateFuncMap["getCurrentTime"] = func() string { - return Now().UTC().Format(time.RFC3339Nano) - } - tmpl, err := template.New("output template").Funcs(templateFuncMap).Parse(outputTemplate) - if err != nil { - return nil, xerrors.Errorf("error parsing template: %w", err) - } - return &TemplateWriter{Output: output, Template: tmpl}, nil -} - -// Write writes result -func (tw TemplateWriter) Write(results Results) error { - err := tw.Template.Execute(tw.Output, results) - if err != nil { - return xerrors.Errorf("failed to write with template: %w", err) - } - return nil -} - -func toSarifRuleName(vulnerabilityType string) string { - var ruleName string - switch vulnerabilityType { - case vulnerability.Ubuntu, vulnerability.Alpine, vulnerability.RedHat, vulnerability.RedHatOVAL, - vulnerability.Debian, vulnerability.DebianOVAL, vulnerability.Fedora, vulnerability.Amazon, - vulnerability.OracleOVAL, vulnerability.SuseCVRF, vulnerability.OpenSuseCVRF, vulnerability.Photon, - vulnerability.CentOS: - ruleName = "OS Package Vulnerability" - case "npm", "yarn", "nuget", "pipenv", "poetry", "bundler", "cargo", "composer": - ruleName = "Programming Language Vulnerability" - default: - ruleName = "Other Vulnerability" - } - return fmt.Sprintf("%s (%s)", ruleName, strings.Title(vulnerabilityType)) -} - -func toSarifErrorLevel(severity string) string { - switch severity { - case "CRITICAL", "HIGH": - return "error" - case "MEDIUM": - return "warning" - case "LOW", "UNKNOWN": - return "note" - default: - return "none" - } -} diff --git a/pkg/report/writer_test.go b/pkg/report/writer_test.go index 6d36d57a05..e996177535 100644 --- a/pkg/report/writer_test.go +++ b/pkg/report/writer_test.go @@ -1,570 +1,50 @@ package report_test import ( - "bytes" - "encoding/json" - "io/ioutil" - "os" "testing" - "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" ) -func TestReportWriter_Table(t *testing.T) { - testCases := []struct { - name string - detectedVulns []types.DetectedVulnerability - expectedOutput string - light bool +func TestResults_Failed(t *testing.T) { + tests := []struct { + name string + results report.Results + want bool }{ { - name: "happy path full", - detectedVulns: []types.DetectedVulnerability{ + name: "no vulnerabilities and misconfigurations", + results: report.Results{ { - VulnerabilityID: "CVE-2020-0001", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", - Vulnerability: dbTypes.Vulnerability{ - Title: "foobar", - Description: "baz", - Severity: "HIGH", - }, + Target: "test", + Type: "test", }, }, - expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | -+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -| foo | CVE-2020-0001 | HIGH | 1.2.3 | 3.4.5 | foobar | -| | | | | | -->avd.aquasec.com/nvd/cve-2020-0001 | -+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -`, + want: false, }, { - name: "happy path light", - light: true, - detectedVulns: []types.DetectedVulnerability{ + name: "vulnerabilities found", + results: report.Results{ { - VulnerabilityID: "123", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - Vulnerability: dbTypes.Vulnerability{ - Title: "foobar", - Description: "baz", - Severity: "HIGH", - }, - }, - }, - expectedOutput: `+---------+------------------+----------+-------------------+---------------+ -| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | -+---------+------------------+----------+-------------------+---------------+ -| foo | 123 | HIGH | 1.2.3 | 3.4.5 | -+---------+------------------+----------+-------------------+---------------+ -`, - }, - { - name: "no title for vuln and missing primary link", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "123", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - Vulnerability: dbTypes.Vulnerability{ - Description: "foobar", - Severity: "HIGH", - }, - }, - }, - expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------+ -| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | -+---------+------------------+----------+-------------------+---------------+--------+ -| foo | 123 | HIGH | 1.2.3 | 3.4.5 | foobar | -+---------+------------------+----------+-------------------+---------------+--------+ -`, - }, - { - name: "long title for vuln", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-1234", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", - Vulnerability: dbTypes.Vulnerability{ - Title: "a b c d e f g h i j k l m n o p q r s t u v", - Severity: "HIGH", - }, - }, - }, - expectedOutput: `+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE | -+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -| foo | CVE-2020-1234 | HIGH | 1.2.3 | 3.4.5 | a b c d e f g h i j k l... | -| | | | | | -->avd.aquasec.com/nvd/cve-2020-0001 | -+---------+------------------+----------+-------------------+---------------+--------------------------------------+ -`, - }, - { - name: "no vulns", - detectedVulns: []types.DetectedVulnerability{}, - expectedOutput: ``, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - inputResults := report.Results{ - { - Target: "foo", - Vulnerabilities: tc.detectedVulns, - }, - } - tableWritten := bytes.Buffer{} - assert.NoError(t, report.WriteResults("table", &tableWritten, nil, inputResults, "", tc.light), tc.name) - assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) - }) - } -} - -func TestReportWriter_JSON(t *testing.T) { - testCases := []struct { - name string - detectedVulns []types.DetectedVulnerability - expectedJSON report.Results - }{ - { - name: "happy path", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2020-0001", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", - Vulnerability: dbTypes.Vulnerability{ - Title: "foobar", - Description: "baz", - Severity: "HIGH", - }, - }, - }, - expectedJSON: report.Results{ - report.Result{ - Target: "foojson", + Target: "test", + Type: "test", Vulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-0001", - PkgName: "foo", - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", - Vulnerability: dbTypes.Vulnerability{ - Title: "foobar", - Description: "baz", - Severity: "HIGH", - }, + VulnerabilityID: "CVE-2021-0001", + PkgName: "test", }, }, }, }, + want: true, }, } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - jw := report.JSONWriter{} - jsonWritten := bytes.Buffer{} - jw.Output = &jsonWritten - - inputResults := report.Results{ - { - Target: "foojson", - Vulnerabilities: tc.detectedVulns, - }, - } - - assert.NoError(t, report.WriteResults("json", &jsonWritten, nil, inputResults, "", false), tc.name) - - writtenResults := report.Results{} - errJson := json.Unmarshal([]byte(jsonWritten.String()), &writtenResults) - assert.NoError(t, errJson, "invalid json written", tc.name) - - assert.Equal(t, tc.expectedJSON, writtenResults, tc.name) - }) - } - -} - -func TestReportWriter_Template(t *testing.T) { - testCases := []struct { - name string - detectedVulns []types.DetectedVulnerability - template string - expected string - }{ - { - name: "happy path", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "foo", - Vulnerability: dbTypes.Vulnerability{ - Severity: dbTypes.SeverityHigh.String(), - }, - }, - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "bar", - Vulnerability: dbTypes.Vulnerability{ - Severity: dbTypes.SeverityHigh.String()}, - }, - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "baz", - Vulnerability: dbTypes.Vulnerability{ - Severity: dbTypes.SeverityCritical.String(), - }, - }, - }, - template: "{{ range . }}{{ range .Vulnerabilities}}{{ println .VulnerabilityID .Severity }}{{ end }}{{ end }}", - expected: "CVE-2019-0000 HIGH\nCVE-2019-0000 HIGH\nCVE-2019-0001 CRITICAL\n", - }, - { - name: "happy path", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "123", - PkgName: `foo \ test`, - InstalledVersion: "1.2.3", - FixedVersion: "3.4.5", - Vulnerability: dbTypes.Vulnerability{ - Title: `gcc: POWER9 "DARN" RNG intrinsic produces repeated output`, - Description: `curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0.`, - Severity: "HIGH", - }, - }, - }, - template: ` -{{- range . -}} -{{- $failures := len .Vulnerabilities }} - - {{- if not (eq .Type "") }} - - - - {{- end -}} - {{ range .Vulnerabilities }} - - {{ escapeXML .Description }} - - {{- end }} - -{{- end }} -`, - - expected: ` - - - - - - curl version curl \X 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0. - - -`, - }, - { - name: "happy path with/without period description should return with period", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "foo", - Vulnerability: dbTypes.Vulnerability{ - Description: "without period", - }, - }, - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "bar", - Vulnerability: dbTypes.Vulnerability{ - Description: "with period.", - }, - }, - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "bar", - Vulnerability: dbTypes.Vulnerability{ - Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, - }, - }, - }, - template: `{{ range . }}{{ range .Vulnerabilities}}{{.VulnerabilityID}} {{ endWithPeriod (escapeString .Description) | printf "%q" }}{{ end }}{{ end }}`, - expected: `CVE-2019-0000 "without period."CVE-2019-0000 "with period."CVE-2019-0000 "with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close()."`, - }, - { - name: "Calculate using sprig", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "foo", - Vulnerability: dbTypes.Vulnerability{ - Description: "without period", - Severity: dbTypes.SeverityCritical.String(), - }, - }, - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "bar", - Vulnerability: dbTypes.Vulnerability{ - Description: "with period.", - Severity: dbTypes.SeverityCritical.String(), - }, - }, - { - VulnerabilityID: "CVE-2019-0000", - PkgName: "bar", - Vulnerability: dbTypes.Vulnerability{ - Description: `with period and unescaped string curl: Use-after-free when closing 'easy' handle in Curl_close().`, - Severity: dbTypes.SeverityHigh.String(), - }, - }, - }, - template: `{{ $high := 0 }}{{ $critical := 0 }}{{ range . }}{{ range .Vulnerabilities}}{{ if eq .Severity "HIGH" }}{{ $high = add $high 1 }}{{ end }}{{ if eq .Severity "CRITICAL" }}{{ $critical = add $critical 1 }}{{ end }}{{ end }}Critical: {{ $critical }}, High: {{ $high }}{{ end }}`, - expected: `Critical: 2, High: 1`, - }, - { - name: "happy path: env var parsing and getCurrentTime", - detectedVulns: []types.DetectedVulnerability{}, - template: `{{ toLower (getEnv "AWS_ACCOUNT_ID") }} {{ getCurrentTime }}`, - expected: `123456789012 2020-08-10T07:28:17.000958601Z`, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - report.Now = func() time.Time { - return time.Date(2020, 8, 10, 7, 28, 17, 958601, time.UTC) - } - os.Setenv("AWS_ACCOUNT_ID", "123456789012") - tmplWritten := bytes.Buffer{} - inputResults := report.Results{ - { - Target: "foojunit", - Type: "test", - Vulnerabilities: tc.detectedVulns, - }, - } - - assert.NoError(t, report.WriteResults("template", &tmplWritten, nil, inputResults, tc.template, false)) - assert.Equal(t, tc.expected, tmplWritten.String()) - }) - } -} - -func TestReportWriter_Template_SARIF(t *testing.T) { - testCases := []struct { - name string - target string - detectedVulns []types.DetectedVulnerability - want string - }{ - { - name: "no primary url", - target: "foo/target/alpine-310.tar.gz (alpine 3.10.2)", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-1234-5678", - PkgName: "foopackage", - InstalledVersion: "1.2.3", - FixedVersion: "4.5.6", - SeveritySource: "NVD", - PrimaryURL: "", - Vulnerability: dbTypes.Vulnerability{ - Title: "foovuln", - Description: "foodesc", - Severity: "CRITICAL", - }, - }, - }, - want: `{ - "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", - "version": "2.1.0", - "runs": [ - { - "tool": { - "driver": { - "name": "Trivy", - "informationUri": "https://github.com/aquasecurity/trivy", - "fullName": "Trivy Vulnerability Scanner", - "version": "0.15.0", - "rules": [ - { - "id": "foo/target/alpine-310.tar.gz (alpine 3.10.2): foopackage-1.2.3 CVE-1234-5678", - "name": "Other Vulnerability (Footype)", - "shortDescription": { - "text": "CVE-1234-5678 Package: foopackage" - }, - "fullDescription": { - "text": "foovuln." - }, - "defaultConfiguration": { - "level": "error" - }, - "help": { - "text": "Vulnerability CVE-1234-5678\nSeverity: CRITICAL\nPackage: foopackage\nInstalled Version: 1.2.3\nFixed Version: 4.5.6\nLink: [CVE-1234-5678]()", - "markdown": "**Vulnerability CVE-1234-5678**\n| Severity | Package | Installed Version | Fixed Version | Link |\n| --- | --- | --- | --- | --- |\n|CRITICAL|foopackage|1.2.3|4.5.6|[CVE-1234-5678]()|\n" - }, - "properties": { - "tags": [ - "vulnerability", - "CRITICAL", - "foopackage" - ], - "precision": "very-high" - } - }] - } - }, - "results": [ - { - "ruleId": "foo/target/alpine-310.tar.gz (alpine 3.10.2): foopackage-1.2.3 CVE-1234-5678", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "foodesc." - }, - "locations": [{ - "physicalLocation": { - "artifactLocation": { - "uri": "foo/target/alpine-310.tar.gz", - "uriBaseId": "ROOTPATH" - } - } - }] - }], - "columnKind": "utf16CodeUnits", - "originalUriBaseIds": { - "ROOTPATH": { - "uri": "/" - } - } - } - ] -}`, - }, - { - name: "with primary url", - target: "rust-app\\Cargo.lock", - detectedVulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-1234-5678", - PkgName: "foopackage", - InstalledVersion: "1.2.3", - FixedVersion: "4.5.6", - SeveritySource: "NVD", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-1234-5678", - Vulnerability: dbTypes.Vulnerability{ - Title: "foovuln", - Description: "foodesc", - Severity: "CRITICAL", - }, - }, - }, - want: `{ - "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", - "version": "2.1.0", - "runs": [ - { - "tool": { - "driver": { - "name": "Trivy", - "informationUri": "https://github.com/aquasecurity/trivy", - "fullName": "Trivy Vulnerability Scanner", - "version": "0.15.0", - "rules": [ - { - "id": "rust-app\\Cargo.lock: foopackage-1.2.3 CVE-1234-5678", - "name": "Other Vulnerability (Footype)", - "shortDescription": { - "text": "CVE-1234-5678 Package: foopackage" - }, - "fullDescription": { - "text": "foovuln." - }, - "defaultConfiguration": { - "level": "error" - }, - "helpUri": "https://avd.aquasec.com/nvd/cve-1234-5678", - "help": { - "text": "Vulnerability CVE-1234-5678\nSeverity: CRITICAL\nPackage: foopackage\nInstalled Version: 1.2.3\nFixed Version: 4.5.6\nLink: [CVE-1234-5678](https://avd.aquasec.com/nvd/cve-1234-5678)", - "markdown": "**Vulnerability CVE-1234-5678**\n| Severity | Package | Installed Version | Fixed Version | Link |\n| --- | --- | --- | --- | --- |\n|CRITICAL|foopackage|1.2.3|4.5.6|[CVE-1234-5678](https://avd.aquasec.com/nvd/cve-1234-5678)|\n" - }, - "properties": { - "tags": [ - "vulnerability", - "CRITICAL", - "foopackage" - ], - "precision": "very-high" - } - }] - } - }, - "results": [ - { - "ruleId": "rust-app\\Cargo.lock: foopackage-1.2.3 CVE-1234-5678", - "ruleIndex": 0, - "level": "error", - "message": { - "text": "foodesc." - }, - "locations": [{ - "physicalLocation": { - "artifactLocation": { - "uri": "rust-app/Cargo.lock", - "uriBaseId": "ROOTPATH" - } - } - }] - }], - "columnKind": "utf16CodeUnits", - "originalUriBaseIds": { - "ROOTPATH": { - "uri": "/" - } - } - } - ] -}`, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - templateFile := "../../contrib/sarif.tpl" - got := bytes.Buffer{} - - template, err := ioutil.ReadFile(templateFile) - require.NoError(t, err, tc.name) - - inputResults := report.Results{ - report.Result{ - Target: tc.target, - Type: "footype", - Vulnerabilities: tc.detectedVulns, - }, - } - assert.NoError(t, report.WriteResults("template", &got, nil, inputResults, string(template), false), tc.name) - assert.JSONEq(t, tc.want, got.String(), tc.name) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.results.Failed() + assert.Equal(t, tt.want, got) }) } } diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 07920e6377..a0002363be 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -56,7 +56,8 @@ func (s Scanner) Scan(target string, imageID string, layerIDs []string, options ArtifactId: imageID, BlobIds: layerIDs, Options: &rpc.ScanOptions{ - VulnType: options.VulnType, + VulnType: options.VulnType, + SecurityChecks: options.SecurityChecks, }, }) return err diff --git a/pkg/rpc/convert.go b/pkg/rpc/convert.go index 272b8adf43..bc0e169942 100644 --- a/pkg/rpc/convert.go +++ b/pkg/rpc/convert.go @@ -117,10 +117,7 @@ func ConvertToRPCVulns(vulns []types.DetectedVulnerability) []*common.Vulnerabil Description: vuln.Description, Severity: common.Severity(severity), References: vuln.References, - Layer: &common.Layer{ - Digest: vuln.Layer.Digest, - DiffId: vuln.Layer.DiffID, - }, + Layer: ConvertToRPCLayer(vuln.Layer), Cvss: cvssMap, SeveritySource: vuln.SeveritySource, CweIds: vuln.CweIDs, @@ -132,65 +129,83 @@ func ConvertToRPCVulns(vulns []types.DetectedVulnerability) []*common.Vulnerabil return rpcVulns } +// ConvertToRPCLayer returns common.Layer +func ConvertToRPCLayer(layer ftypes.Layer) *common.Layer { + return &common.Layer{ + Digest: layer.Digest, + DiffId: layer.DiffID, + } +} + // ConvertFromRPCResults converts scanner.Result to report.Result func ConvertFromRPCResults(rpcResults []*scanner.Result) []report.Result { var results []report.Result for _, result := range rpcResults { - var vulns []types.DetectedVulnerability - for _, vuln := range result.Vulnerabilities { - severity := dbTypes.Severity(vuln.Severity) - cvssMap := make(dbTypes.VendorCVSS) // This is needed because protobuf generates a map[string]*CVSS type - for vendor, vendorSeverity := range vuln.Cvss { - cvssMap[vendor] = dbTypes.CVSS{ - V2Vector: vendorSeverity.V2Vector, - V3Vector: vendorSeverity.V3Vector, - V2Score: vendorSeverity.V2Score, - V3Score: vendorSeverity.V3Score, - } - } - - var lastModifiedDate, publishedDate *time.Time - if vuln.LastModifiedDate != nil { - t, _ := ptypes.Timestamp(vuln.LastModifiedDate) // nolint: errcheck - lastModifiedDate = &t - } - if vuln.PublishedDate != nil { - t, _ := ptypes.Timestamp(vuln.PublishedDate) // nolint: errcheck - publishedDate = &t - } - - vulns = append(vulns, types.DetectedVulnerability{ - VulnerabilityID: vuln.VulnerabilityId, - PkgName: vuln.PkgName, - InstalledVersion: vuln.InstalledVersion, - FixedVersion: vuln.FixedVersion, - Vulnerability: dbTypes.Vulnerability{ - Title: vuln.Title, - Description: vuln.Description, - Severity: severity.String(), - CVSS: cvssMap, - References: vuln.References, - CweIDs: vuln.CweIds, - LastModifiedDate: lastModifiedDate, - PublishedDate: publishedDate, - }, - Layer: ftypes.Layer{ - Digest: vuln.Layer.Digest, - DiffID: vuln.Layer.DiffId, - }, - SeveritySource: vuln.SeveritySource, - PrimaryURL: vuln.PrimaryUrl, - }) - } results = append(results, report.Result{ Target: result.Target, - Vulnerabilities: vulns, + Vulnerabilities: ConvertFromRPCVulns(result.Vulnerabilities), Type: result.Type, }) } return results } +// ConvertFromRPCVulns converts []*common.Vulnerability to []types.DetectedVulnerability +func ConvertFromRPCVulns(rpcVulns []*common.Vulnerability) []types.DetectedVulnerability { + var vulns []types.DetectedVulnerability + for _, vuln := range rpcVulns { + severity := dbTypes.Severity(vuln.Severity) + cvssMap := make(dbTypes.VendorCVSS) // This is needed because protobuf generates a map[string]*CVSS type + for vendor, vendorSeverity := range vuln.Cvss { + cvssMap[vendor] = dbTypes.CVSS{ + V2Vector: vendorSeverity.V2Vector, + V3Vector: vendorSeverity.V3Vector, + V2Score: vendorSeverity.V2Score, + V3Score: vendorSeverity.V3Score, + } + } + + var lastModifiedDate, publishedDate *time.Time + if vuln.LastModifiedDate != nil { + t, _ := ptypes.Timestamp(vuln.LastModifiedDate) // nolint: errcheck + lastModifiedDate = &t + } + if vuln.PublishedDate != nil { + t, _ := ptypes.Timestamp(vuln.PublishedDate) // nolint: errcheck + publishedDate = &t + } + + vulns = append(vulns, types.DetectedVulnerability{ + VulnerabilityID: vuln.VulnerabilityId, + PkgName: vuln.PkgName, + InstalledVersion: vuln.InstalledVersion, + FixedVersion: vuln.FixedVersion, + Vulnerability: dbTypes.Vulnerability{ + Title: vuln.Title, + Description: vuln.Description, + Severity: severity.String(), + CVSS: cvssMap, + References: vuln.References, + CweIDs: vuln.CweIds, + LastModifiedDate: lastModifiedDate, + PublishedDate: publishedDate, + }, + Layer: ConvertFromRPCLayer(vuln.Layer), + SeveritySource: vuln.SeveritySource, + PrimaryURL: vuln.PrimaryUrl, + }) + } + return vulns +} + +// ConvertFromRPCLayer converts *common.Layer to fanal.Layer +func ConvertFromRPCLayer(rpcLayer *common.Layer) ftypes.Layer { + return ftypes.Layer{ + Digest: rpcLayer.Digest, + DiffID: rpcLayer.DiffId, + } +} + // ConvertFromRPCOS converts common.OS to fanal.OS func ConvertFromRPCOS(rpcOS *common.OS) *ftypes.OS { if rpcOS == nil { @@ -286,9 +301,9 @@ func ConvertToRPCArtifactInfo(imageID string, imageInfo ftypes.ArtifactInfo) *ca } // ConvertToRPCBlobInfo returns PutBlobRequest -func ConvertToRPCBlobInfo(diffID string, layerInfo ftypes.BlobInfo) *cache.PutBlobRequest { +func ConvertToRPCBlobInfo(diffID string, blobInfo ftypes.BlobInfo) *cache.PutBlobRequest { var packageInfos []*common.PackageInfo - for _, pkgInfo := range layerInfo.PackageInfos { + for _, pkgInfo := range blobInfo.PackageInfos { packageInfos = append(packageInfos, &common.PackageInfo{ FilePath: pkgInfo.FilePath, Packages: ConvertToRPCPkgs(pkgInfo.Packages), @@ -296,7 +311,7 @@ func ConvertToRPCBlobInfo(diffID string, layerInfo ftypes.BlobInfo) *cache.PutBl } var applications []*common.Application - for _, app := range layerInfo.Applications { + for _, app := range blobInfo.Applications { var libs []*common.Library for _, lib := range app.Libraries { libs = append(libs, &common.Library{ @@ -315,13 +330,13 @@ func ConvertToRPCBlobInfo(diffID string, layerInfo ftypes.BlobInfo) *cache.PutBl DiffId: diffID, BlobInfo: &cache.BlobInfo{ SchemaVersion: ftypes.BlobJSONSchemaVersion, - Digest: layerInfo.Digest, - DiffId: layerInfo.DiffID, - Os: ConvertToRPCOS(layerInfo.OS), + Digest: blobInfo.Digest, + DiffId: blobInfo.DiffID, + Os: ConvertToRPCOS(blobInfo.OS), PackageInfos: packageInfos, Applications: applications, - OpaqueDirs: layerInfo.OpaqueDirs, - WhiteoutFiles: layerInfo.WhiteoutFiles, + OpaqueDirs: blobInfo.OpaqueDirs, + WhiteoutFiles: blobInfo.WhiteoutFiles, }, } } @@ -346,8 +361,8 @@ func ConvertToRPCScanResponse(results report.Results, os *ftypes.OS, eosl bool) for _, result := range results { rpcResults = append(rpcResults, &scanner.Result{ Target: result.Target, - Vulnerabilities: ConvertToRPCVulns(result.Vulnerabilities), Type: result.Type, + Vulnerabilities: ConvertToRPCVulns(result.Vulnerabilities), }) } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index a490dca08d..935966208a 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -28,24 +28,27 @@ var ScanSuperSet = wire.NewSet( // ScanServer implements the scanner type ScanServer struct { localScanner scanner.Driver - vulnClient vulnerability.Operation + resultClient vulnerability.Client } // NewScanServer is the factory method for scanner -func NewScanServer(s scanner.Driver, vulnClient vulnerability.Operation) *ScanServer { - return &ScanServer{localScanner: s, vulnClient: vulnClient} +func NewScanServer(s scanner.Driver, vulnClient vulnerability.Client) *ScanServer { + return &ScanServer{localScanner: s, resultClient: vulnClient} } // Scan scans and return response func (s *ScanServer) Scan(_ context.Context, in *rpcScanner.ScanRequest) (*rpcScanner.ScanResponse, error) { - options := types.ScanOptions{VulnType: in.Options.VulnType} + options := types.ScanOptions{ + VulnType: in.Options.VulnType, + SecurityChecks: in.Options.SecurityChecks, + } results, os, eosl, err := s.localScanner.Scan(in.Target, in.ArtifactId, in.BlobIds, options) if err != nil { return nil, xerrors.Errorf("failed scan, %s: %w", in.Target, err) } for i := range results { - s.vulnClient.FillInfo(results[i].Vulnerabilities, results[i].Type) + s.resultClient.FillInfo(results[i].Vulnerabilities, results[i].Type) } return rpc.ConvertToRPCScanResponse(results, os, eosl), nil } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 5413d7f6e6..412836127f 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -16,8 +16,10 @@ import ( "github.com/aquasecurity/fanal/cache" ftypes "github.com/aquasecurity/fanal/types" deptypes "github.com/aquasecurity/go-dep-parser/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/utils" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/scanner" "github.com/aquasecurity/trivy/pkg/types" @@ -37,15 +39,16 @@ func TestScanServer_Scan(t *testing.T) { in *rpcScanner.ScanRequest } tests := []struct { - name string - args args - scanExpectation scanner.DriverScanExpectation - fillInfoExpectation vulnerability.OperationFillInfoExpectation - want *rpcScanner.ScanResponse - wantErr string + name string + fixtures []string + args args + scanExpectation scanner.DriverScanExpectation + want *rpcScanner.ScanResponse + wantErr string }{ { - name: "happy path", + name: "happy path", + fixtures: []string{"testdata/fixtures/vulnerability.yaml"}, args: args{ in: &rpcScanner.ScanRequest{ Target: "alpine:3.11", @@ -70,7 +73,6 @@ func TestScanServer_Scan(t *testing.T) { PkgName: "musl", InstalledVersion: "1.2.3", FixedVersion: "1.2.4", - SeveritySource: "nvd", Vulnerability: dbTypes.Vulnerability{ LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), @@ -86,24 +88,6 @@ func TestScanServer_Scan(t *testing.T) { }, }, }, - fillInfoExpectation: vulnerability.OperationFillInfoExpectation{ - Args: vulnerability.OperationFillInfoArgs{ - Vulns: []types.DetectedVulnerability{ - { - VulnerabilityID: "CVE-2019-0001", - PkgName: "musl", - InstalledVersion: "1.2.3", - FixedVersion: "1.2.4", - Vulnerability: dbTypes.Vulnerability{ - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - SeveritySource: "nvd", - }, - }, - ReportType: "alpine", - }, - }, want: &rpcScanner.ScanResponse{ Os: &common.OS{ Family: "alpine", @@ -119,9 +103,14 @@ func TestScanServer_Scan(t *testing.T) { PkgName: "musl", InstalledVersion: "1.2.3", FixedVersion: "1.2.4", + Severity: common.Severity_MEDIUM, SeveritySource: "nvd", Layer: &common.Layer{}, - Cvss: make(map[string]*common.CVSS), + Cvss: map[string]*common.CVSS{}, + PrimaryUrl: "https://avd.aquasec.com/nvd/cve-2019-0001", + Title: "dos", + Description: "dos vulnerability", + References: []string{"http://example.com"}, LastModifiedDate: ×tamp.Timestamp{ Seconds: 1577840460, }, @@ -161,13 +150,13 @@ func TestScanServer_Scan(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + dbtest.InitDB(t, tt.fixtures) + defer db.Close() + mockDriver := new(scanner.MockDriver) mockDriver.ApplyScanExpectation(tt.scanExpectation) - mockVulnClient := new(vulnerability.MockOperation) - mockVulnClient.ApplyFillInfoExpectation(tt.fillInfoExpectation) - - s := NewScanServer(mockDriver, mockVulnClient) + s := NewScanServer(mockDriver, vulnerability.NewClient(db.Config{})) got, err := s.Scan(context.Background(), tt.args.in) if tt.wantErr != "" { require.NotNil(t, err, tt.name) diff --git a/pkg/rpc/server/testdata/fixtures/vulnerability.yaml b/pkg/rpc/server/testdata/fixtures/vulnerability.yaml new file mode 100644 index 0000000000..2b3726d71e --- /dev/null +++ b/pkg/rpc/server/testdata/fixtures/vulnerability.yaml @@ -0,0 +1,13 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + VendorSeverity: + nvd: 2 + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 5dc0292043..4ba1613e1a 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -13,28 +13,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/fanal/analyzer" - _ "github.com/aquasecurity/fanal/analyzer/command/apk" - _ "github.com/aquasecurity/fanal/analyzer/library/bundler" - _ "github.com/aquasecurity/fanal/analyzer/library/cargo" - _ "github.com/aquasecurity/fanal/analyzer/library/composer" - _ "github.com/aquasecurity/fanal/analyzer/library/gobinary" - _ "github.com/aquasecurity/fanal/analyzer/library/gomod" - _ "github.com/aquasecurity/fanal/analyzer/library/jar" - _ "github.com/aquasecurity/fanal/analyzer/library/npm" - _ "github.com/aquasecurity/fanal/analyzer/library/nuget" - _ "github.com/aquasecurity/fanal/analyzer/library/pipenv" - _ "github.com/aquasecurity/fanal/analyzer/library/poetry" - _ "github.com/aquasecurity/fanal/analyzer/library/yarn" - _ "github.com/aquasecurity/fanal/analyzer/os/alpine" - _ "github.com/aquasecurity/fanal/analyzer/os/amazonlinux" - _ "github.com/aquasecurity/fanal/analyzer/os/debian" - _ "github.com/aquasecurity/fanal/analyzer/os/photon" - _ "github.com/aquasecurity/fanal/analyzer/os/redhatbase" - _ "github.com/aquasecurity/fanal/analyzer/os/suse" - _ "github.com/aquasecurity/fanal/analyzer/os/ubuntu" - _ "github.com/aquasecurity/fanal/analyzer/pkg/apk" - _ "github.com/aquasecurity/fanal/analyzer/pkg/dpkg" - _ "github.com/aquasecurity/fanal/analyzer/pkg/rpm" + _ "github.com/aquasecurity/fanal/analyzer/all" "github.com/aquasecurity/fanal/applier" ftypes "github.com/aquasecurity/fanal/types" "github.com/aquasecurity/trivy/pkg/detector/library" @@ -81,7 +60,7 @@ func (s Scanner) Scan(target, versionedArtifactID string, versionedBlobIDs []str artifactDetail, err := s.applier.ApplyLayers(versionedArtifactID, versionedBlobIDs) switch { case errors.Is(err, analyzer.ErrUnknownOS): - log.Logger.Warn("OS is not detected and vulnerabilities in OS packages are not detected.") + log.Logger.Debug("OS is not detected and vulnerabilities in OS packages are not detected.") case errors.Is(err, analyzer.ErrNoPkgsDetected): log.Logger.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.") log.Logger.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`) @@ -92,29 +71,53 @@ func (s Scanner) Scan(target, versionedArtifactID string, versionedBlobIDs []str var eosl bool var results report.Results - if utils.StringInSlice("os", options.VulnType) && artifactDetail.OS != nil { - var result *report.Result - result, eosl, err = s.scanOSPkgs(target, artifactDetail, options) + // Scan OS packages and programming language dependencies + if utils.StringInSlice(types.SecurityCheckVulnerability, options.SecurityChecks) { + var vulnResults report.Results + vulnResults, eosl, err = s.checkVulnerabilities(target, artifactDetail, options) if err != nil { - return nil, nil, false, xerrors.Errorf("unable to scan OS packages: %w", err) - } else if result != nil { - results = append(results, *result) + return nil, nil, false, xerrors.Errorf("failed to detect vulnerabilities: %w", err) } - } - - if utils.StringInSlice("library", options.VulnType) { - libResults, err := s.scanLibrary(artifactDetail.Applications, options) - if err != nil { - return nil, nil, false, xerrors.Errorf("failed to scan application libraries: %w", err) - } - results = append(results, libResults...) + results = append(results, vulnResults...) } return results, artifactDetail.OS, eosl, nil } +func (s Scanner) checkVulnerabilities(target string, detail ftypes.ArtifactDetail, options types.ScanOptions) ( + report.Results, bool, error) { + var eosl bool + var results report.Results + + if utils.StringInSlice(types.VulnTypeOS, options.VulnType) { + result, detectedEosl, err := s.scanOSPkgs(target, detail, options) + if err != nil { + return nil, false, xerrors.Errorf("unable to scan OS packages: %w", err) + } else if result != nil { + results = append(results, *result) + } + eosl = detectedEosl + } + + if utils.StringInSlice(types.VulnTypeLibrary, options.VulnType) { + libResults, err := s.scanLibrary(detail.Applications, options) + if err != nil { + return nil, false, xerrors.Errorf("failed to scan application libraries: %w", err) + } + results = append(results, libResults...) + } + + return results, eosl, nil +} + func (s Scanner) scanOSPkgs(target string, detail ftypes.ArtifactDetail, options types.ScanOptions) ( *report.Result, bool, error) { + if detail.OS == nil { + log.Logger.Infof("Detected OS: unknown") + return nil, false, nil + } + log.Logger.Infof("Detected OS: %s", detail.OS.Family) + pkgs := detail.Packages if options.ScanRemovedPackages { pkgs = mergePkgs(pkgs, detail.HistoryPackages) @@ -158,8 +161,8 @@ func (s Scanner) detectVulnsInOSPkgs(target, osFamily, osName string, pkgs []fty } func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOptions) (report.Results, error) { + log.Logger.Infof("Number of PL dependency files: %d", len(apps)) if len(apps) == 0 { - log.Logger.Info("Trivy skips scanning programming language libraries because no supported file was detected") return nil, nil } @@ -213,6 +216,7 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOption } func skipped(filePath string, skipFiles, skipDirs []string) bool { + filePath = strings.TrimLeft(filepath.Clean(filePath), string(os.PathSeparator)) for _, skipFile := range skipFiles { skipFile = strings.TrimLeft(filepath.Clean(skipFile), string(os.PathSeparator)) if filePath == skipFile { diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index a1a7e88e49..9e3334bb58 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -4,19 +4,15 @@ import ( "errors" "testing" - "github.com/aquasecurity/fanal/analyzer/library" - - "github.com/aquasecurity/trivy-db/pkg/db" - - "github.com/aquasecurity/trivy/pkg/dbtest" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/fanal/analyzer" ftypes "github.com/aquasecurity/fanal/types" dtypes "github.com/aquasecurity/go-dep-parser/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/db" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg" "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/types" @@ -44,7 +40,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -68,7 +67,7 @@ func TestScanner_Scan(t *testing.T) { }, Applications: []ftypes.Application{ { - Type: library.Bundler, + Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Libraries: []ftypes.LibraryInfo{ { @@ -156,7 +155,11 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}, ListAllPackages: true}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + ListAllPackages: true, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -307,7 +310,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -358,7 +364,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -431,7 +440,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "fedora:27", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -499,7 +511,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "busybox:latest", layerIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -521,7 +536,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -609,8 +627,9 @@ func TestScanner_Scan(t *testing.T) { target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ - VulnType: []string{"library"}, - SkipDirs: []string{"/usr/lib/ruby/gems"}, + VulnType: []string{types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + SkipDirs: []string{"/usr/lib/ruby/gems", "/app/k8s"}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, @@ -683,7 +702,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -701,7 +723,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"os", "library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ @@ -753,7 +778,10 @@ func TestScanner_Scan(t *testing.T) { args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, - options: types.ScanOptions{VulnType: []string{"library"}}, + options: types.ScanOptions{ + VulnType: []string{types.VulnTypeLibrary}, + SecurityChecks: []string{types.SecurityCheckVulnerability}, + }, }, fixtures: []string{"testdata/fixtures/sad.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index e54cdf5b97..dc3c9ea5f1 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -10,7 +10,6 @@ import ( aimage "github.com/aquasecurity/fanal/artifact/image" flocal "github.com/aquasecurity/fanal/artifact/local" "github.com/aquasecurity/fanal/artifact/remote" - "github.com/aquasecurity/fanal/cache" "github.com/aquasecurity/fanal/image" ftypes "github.com/aquasecurity/fanal/types" "github.com/aquasecurity/trivy/pkg/log" @@ -83,7 +82,8 @@ type Scanner struct { // Driver defines operations of scanner type Driver interface { - Scan(target string, imageID string, layerIDs []string, options types.ScanOptions) (results report.Results, osFound *ftypes.OS, eols bool, err error) + Scan(target string, imageID string, layerIDs []string, options types.ScanOptions) ( + results report.Results, osFound *ftypes.OS, eols bool, err error) } // NewScanner is the factory method of Scanner @@ -98,14 +98,6 @@ func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (r return nil, xerrors.Errorf("failed analysis: %w", err) } - // Debug information - var blobIDs []string - for _, b := range artifactInfo.BlobIDs { - blobIDs = append(blobIDs, cache.TrimVersionSuffix(b)) - } - log.Logger.Debugf("Artifact ID: %s", cache.TrimVersionSuffix(artifactInfo.ID)) - log.Logger.Debugf("Blob IDs: %v", blobIDs) - results, osFound, eosl, err := s.driver.Scan(artifactInfo.Name, artifactInfo.ID, artifactInfo.BlobIDs, options) if err != nil { return nil, xerrors.Errorf("scan failed: %w", err) diff --git a/pkg/types/scanoptions.go b/pkg/types/scanoptions.go index f8d9a24f0b..8abcc3b6a2 100644 --- a/pkg/types/scanoptions.go +++ b/pkg/types/scanoptions.go @@ -3,6 +3,7 @@ package types // ScanOptions holds the attributes for scanning vulnerabilities type ScanOptions struct { VulnType []string + SecurityChecks []string ScanRemovedPackages bool ListAllPackages bool SkipFiles []string diff --git a/pkg/types/target.go b/pkg/types/target.go new file mode 100644 index 0000000000..5849ceeb4f --- /dev/null +++ b/pkg/types/target.go @@ -0,0 +1,47 @@ +package types + +import "github.com/aquasecurity/trivy/pkg/utils" + +// VulnType represents vulnerability type +type VulnType = string + +// SecurityCheck represents the type of security check +type SecurityCheck = string + +const ( + // VulnTypeUnknown is a vulnerability type of unknown + VulnTypeUnknown = VulnType("unknown") + + // VulnTypeOS is a vulnerability type of OS packages + VulnTypeOS = VulnType("os") + + // VulnTypeLibrary is a vulnerability type of programming language dependencies + VulnTypeLibrary = VulnType("library") + + // SecurityCheckUnknown is a security check of unknown + SecurityCheckUnknown = SecurityCheck("unknown") + + // SecurityCheckVulnerability is a security check of vulnerabilities + SecurityCheckVulnerability = SecurityCheck("vuln") +) + +var ( + vulnTypes = []string{VulnTypeOS, VulnTypeLibrary} + securityChecks = []string{SecurityCheckVulnerability} +) + +// NewVulnType returns an instance of VulnType +func NewVulnType(s string) VulnType { + if utils.StringInSlice(s, vulnTypes) { + return s + } + return VulnTypeUnknown +} + +// NewSecurityCheck returns an instance of SecurityCheck +func NewSecurityCheck(s string) SecurityCheck { + if utils.StringInSlice(s, securityChecks) { + return s + } + return SecurityCheckUnknown +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 7ac3c943ce..7d158ca280 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -28,7 +28,7 @@ func CacheDir() string { return cacheDir } -// SetCacheDir sets the tricy cacheDir +// SetCacheDir sets the trivy cacheDir func SetCacheDir(dir string) { cacheDir = dir } diff --git a/pkg/vulnerability/mock_operation.go b/pkg/vulnerability/mock_operation.go deleted file mode 100644 index 1497951421..0000000000 --- a/pkg/vulnerability/mock_operation.go +++ /dev/null @@ -1,143 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package vulnerability - -import ( - context "context" - - pkgtypes "github.com/aquasecurity/trivy-db/pkg/types" - mock "github.com/stretchr/testify/mock" - - types "github.com/aquasecurity/trivy/pkg/types" -) - -// MockOperation is an autogenerated mock type for the Operation type -type MockOperation struct { - mock.Mock -} - -type OperationFillInfoArgs struct { - Vulns []types.DetectedVulnerability - VulnsAnything bool - ReportType string - ReportTypeAnything bool -} - -type OperationFillInfoExpectation struct { - Args OperationFillInfoArgs -} - -func (_m *MockOperation) ApplyFillInfoExpectation(e OperationFillInfoExpectation) { - var args []interface{} - if e.Args.VulnsAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Vulns) - } - if e.Args.ReportTypeAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.ReportType) - } - _m.On("FillInfo", args...) -} - -func (_m *MockOperation) ApplyFillInfoExpectations(expectations []OperationFillInfoExpectation) { - for _, e := range expectations { - _m.ApplyFillInfoExpectation(e) - } -} - -// FillInfo provides a mock function with given fields: vulns, reportType -func (_m *MockOperation) FillInfo(vulns []types.DetectedVulnerability, reportType string) { - _m.Called(vulns, reportType) -} - -type OperationFilterArgs struct { - Ctx context.Context - CtxAnything bool - Vulns []types.DetectedVulnerability - VulnsAnything bool - Severities []pkgtypes.Severity - SeveritiesAnything bool - IgnoreUnfixed bool - IgnoreUnfixedAnything bool - IgnoreFile string - IgnoreFileAnything bool - Policy string - PolicyAnything bool -} - -type OperationFilterReturns struct { - _a0 []types.DetectedVulnerability - _a1 error -} - -type OperationFilterExpectation struct { - Args OperationFilterArgs - Returns OperationFilterReturns -} - -func (_m *MockOperation) ApplyFilterExpectation(e OperationFilterExpectation) { - var args []interface{} - if e.Args.CtxAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Ctx) - } - if e.Args.VulnsAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Vulns) - } - if e.Args.SeveritiesAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Severities) - } - if e.Args.IgnoreUnfixedAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.IgnoreUnfixed) - } - if e.Args.IgnoreFileAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.IgnoreFile) - } - if e.Args.PolicyAnything { - args = append(args, mock.Anything) - } else { - args = append(args, e.Args.Policy) - } - _m.On("Filter", args...).Return(e.Returns._a0, e.Returns._a1) -} - -func (_m *MockOperation) ApplyFilterExpectations(expectations []OperationFilterExpectation) { - for _, e := range expectations { - _m.ApplyFilterExpectation(e) - } -} - -// Filter provides a mock function with given fields: ctx, vulns, severities, ignoreUnfixed, ignoreFile, policy -func (_m *MockOperation) Filter(ctx context.Context, vulns []types.DetectedVulnerability, severities []pkgtypes.Severity, ignoreUnfixed bool, ignoreFile string, policy string) ([]types.DetectedVulnerability, error) { - ret := _m.Called(ctx, vulns, severities, ignoreUnfixed, ignoreFile, policy) - - var r0 []types.DetectedVulnerability - if rf, ok := ret.Get(0).(func(context.Context, []types.DetectedVulnerability, []pkgtypes.Severity, bool, string, string) []types.DetectedVulnerability); ok { - r0 = rf(ctx, vulns, severities, ignoreUnfixed, ignoreFile, policy) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]types.DetectedVulnerability) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, []types.DetectedVulnerability, []pkgtypes.Severity, bool, string, string) error); ok { - r1 = rf(ctx, vulns, severities, ignoreUnfixed, ignoreFile, policy) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/pkg/vulnerability/testdata/.trivyignore b/pkg/vulnerability/testdata/.trivyignore index b0fe9854d2..b71f8578dc 100644 --- a/pkg/vulnerability/testdata/.trivyignore +++ b/pkg/vulnerability/testdata/.trivyignore @@ -1,3 +1,6 @@ -# test +# vulnerabilities CVE-2019-0001 CVE-2019-0002 + +# misconfigurations +ID100 \ No newline at end of file diff --git a/pkg/vulnerability/testdata/fixtures/full.yaml b/pkg/vulnerability/testdata/fixtures/full.yaml new file mode 100644 index 0000000000..acd059b831 --- /dev/null +++ b/pkg/vulnerability/testdata/fixtures/full.yaml @@ -0,0 +1,59 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: CVE-2019-0002 + value: + Title: dos + Description: dos vulnerability + Severity: UNKNOWN + VendorSeverity: + nvd: 1 + References: + - http://example.com + LastModifiedDate: "2020-01-01T01:01:00Z" + PublishedDate: "2001-01-01T01:01:00Z" + - key: CVE-2019-0003 + value: + Title: dos + Description: dos vulnerability + References: + - http://example.com + - key: CVE-2019-0004 + value: + Title: dos + Description: dos vulnerability + Severity: MEDIUM + VendorSeverity: + redhat: 1 + CVSS: + nvd: + V2Vector: AV:N/AC:L/Au:N/C:P/I:P/A:P + V2Score: 4.5 + V3Vector: CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 5.6 + redhat: + V2Vector: AV:N/AC:M/Au:N/C:N/I:P/A:N + V2Score: 7.8 + V3Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + V3Score: 9.8 + References: + - http://example.com + CweIDs: + - CWE-311 + - key: CVE-2019-0005 + value: + Title: COVID-19 + Description: a nasty virus vulnerability for humans + Severity: MEDIUM + VendorSeverity: + python-safety-db: 4 + References: + - "https://www.who.int/emergencies/diseases/novel-coronavirus-2019" diff --git a/pkg/vulnerability/testdata/fixtures/light.yaml b/pkg/vulnerability/testdata/fixtures/light.yaml new file mode 100644 index 0000000000..c0c2aad240 --- /dev/null +++ b/pkg/vulnerability/testdata/fixtures/light.yaml @@ -0,0 +1,11 @@ +- bucket: vulnerability + pairs: + - key: CVE-2020-0001 + value: + Title: dos + Severity: MEDIUM + VendorSeverity: + ubuntu: 1 + - key: CVE-2020-0002 + value: + Title: dos diff --git a/pkg/vulnerability/testdata/fixtures/sad.yaml b/pkg/vulnerability/testdata/fixtures/sad.yaml new file mode 100644 index 0000000000..f7d55cb4b4 --- /dev/null +++ b/pkg/vulnerability/testdata/fixtures/sad.yaml @@ -0,0 +1,6 @@ +- bucket: vulnerability + pairs: + - key: CVE-2019-0001 + value: + Title: + - aaa diff --git a/pkg/vulnerability/vulnerability_test.go b/pkg/vulnerability/vulnerability_test.go index 1613c3552c..5690ed7222 100644 --- a/pkg/vulnerability/vulnerability_test.go +++ b/pkg/vulnerability/vulnerability_test.go @@ -4,48 +4,32 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/xerrors" + ftypes "github.com/aquasecurity/fanal/types" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy-db/pkg/utils" "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" ) -func TestClient_FillInfo(t *testing.T) { +func TestClient_FillVulnerabilityInfo(t *testing.T) { type args struct { vulns []types.DetectedVulnerability reportType string } tests := []struct { name string - getSeverity []db.OperationGetSeverityExpectation - getVulnerability []db.OperationGetVulnerabilityExpectation + fixtures []string args args expectedVulnerabilities []types.DetectedVulnerability }{ { - name: "happy path, with only OS vulnerability but no vendor severity, no NVD", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: dbTypes.SeverityMedium.String(), - References: []string{"http://example.com"}, - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - }, - }, - }, + name: "happy path, with only OS vulnerability but no vendor severity, no NVD", + fixtures: []string{"testdata/fixtures/full.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ {VulnerabilityID: "CVE-2019-0001"}, @@ -68,36 +52,17 @@ func TestClient_FillInfo(t *testing.T) { }, }, { - name: "happy path, with only OS vulnerability but no vendor severity, yes NVD", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: dbTypes.SeverityUnknown.String(), - VendorSeverity: dbTypes.VendorSeverity{ - vulnerability.Nvd: dbTypes.SeverityLow, - }, - References: []string{"http://example.com"}, - LastModifiedDate: utils.MustTimeParse("2020-01-01T01:01:00Z"), - PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), - }, - }, - }, - }, + name: "happy path, with only OS vulnerability but no vendor severity, yes NVD", + fixtures: []string{"testdata/fixtures/full.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2019-0001"}, + {VulnerabilityID: "CVE-2019-0002"}, }, reportType: vulnerability.Ubuntu, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2019-0001", + VulnerabilityID: "CVE-2019-0002", Vulnerability: dbTypes.Vulnerability{ Title: "dos", Description: "dos vulnerability", @@ -107,89 +72,44 @@ func TestClient_FillInfo(t *testing.T) { PublishedDate: utils.MustTimeParse("2001-01-01T01:01:00Z"), }, SeveritySource: vulnerability.Nvd, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0002", }, }, }, { - name: "happy path, with only OS vulnerability but no severity, no vendor severity, no NVD", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - References: []string{"http://example.com"}, - }, - }, - }, - }, + name: "happy path, with only OS vulnerability but no severity, no vendor severity, no NVD", + fixtures: []string{"testdata/fixtures/full.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2019-0001"}, + {VulnerabilityID: "CVE-2019-0003"}, }, reportType: vulnerability.Ubuntu, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2019-0001", + VulnerabilityID: "CVE-2019-0003", Vulnerability: dbTypes.Vulnerability{ Title: "dos", Description: "dos vulnerability", Severity: dbTypes.SeverityUnknown.String(), References: []string{"http://example.com"}, }, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0003", }, }, }, { - name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Title: "dos", - Description: "dos vulnerability", - Severity: dbTypes.SeverityMedium.String(), - CweIDs: []string{"CWE-311"}, - VendorSeverity: dbTypes.VendorSeverity{ - vulnerability.RedHat: dbTypes.SeverityLow, // CentOS uses RedHat - }, - CVSS: map[string]dbTypes.CVSS{ - vulnerability.Nvd: { - V2Vector: "(AV:N/AC:L/Au:N/C:P/I:P/A:P)", - V2Score: 4.5, - V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H", - V3Score: 5.6, - }, - vulnerability.RedHat: { - V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N", - V2Score: 7.8, - V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - V3Score: 9.8, - }, - }, - References: []string{"http://example.com"}, - }, - }, - }, - }, + name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info", + fixtures: []string{"testdata/fixtures/full.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2019-0001"}, + {VulnerabilityID: "CVE-2019-0004"}, }, reportType: vulnerability.CentOS, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2019-0001", + VulnerabilityID: "CVE-2019-0004", Vulnerability: dbTypes.Vulnerability{ Title: "dos", Description: "dos vulnerability", @@ -198,7 +118,7 @@ func TestClient_FillInfo(t *testing.T) { References: []string{"http://example.com"}, CVSS: map[string]dbTypes.CVSS{ vulnerability.Nvd: { - V2Vector: "(AV:N/AC:L/Au:N/C:P/I:P/A:P)", + V2Vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P", V2Score: 4.5, V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H", V3Score: 5.6, @@ -212,104 +132,63 @@ func TestClient_FillInfo(t *testing.T) { }, }, SeveritySource: vulnerability.RedHat, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0004", }, }, }, { - name: "happy path light db, with only OS vulnerability, yes vendor severity", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Severity: dbTypes.SeverityMedium.String(), - VendorSeverity: dbTypes.VendorSeverity{ - vulnerability.Ubuntu: dbTypes.SeverityLow, - }, - }, - }, - }, - }, + name: "happy path light db, with only OS vulnerability, yes vendor severity", + fixtures: []string{"testdata/fixtures/light.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2019-0001"}, + {VulnerabilityID: "CVE-2020-0001"}, }, reportType: vulnerability.Ubuntu, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2019-0001", + VulnerabilityID: "CVE-2020-0001", Vulnerability: dbTypes.Vulnerability{ + Title: "dos", Severity: dbTypes.SeverityLow.String(), }, SeveritySource: vulnerability.Ubuntu, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0001", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", }, }, }, { - name: "happy path light db, with only OS vulnerability, no vendor severity", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2020-28928", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - VendorSeverity: dbTypes.VendorSeverity{}, - }, - }, - }, - }, + name: "happy path light db, with only OS vulnerability, no vendor severity", + fixtures: []string{"testdata/fixtures/light.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2020-28928"}, + {VulnerabilityID: "CVE-2020-0002"}, }, reportType: vulnerability.Alpine, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-28928", + VulnerabilityID: "CVE-2020-0002", Vulnerability: dbTypes.Vulnerability{ + Title: "dos", Severity: dbTypes.SeverityUnknown.String(), }, - SeveritySource: "", - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-28928", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0002", }, }, }, { - name: "happy path, with only library vulnerability", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2020-0001", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Vulnerability: dbTypes.Vulnerability{ - Title: "COVID-19", - Description: "a nasty virus vulnerability for humans", - Severity: dbTypes.SeverityMedium.String(), - VendorSeverity: dbTypes.VendorSeverity{ - vulnerability.PythonSafetyDB: dbTypes.SeverityCritical, - }, - References: []string{"https://www.who.int/emergencies/diseases/novel-coronavirus-2019"}, - }, - }, - }, - }, + name: "happy path, with only library vulnerability", + fixtures: []string{"testdata/fixtures/full.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ - {VulnerabilityID: "CVE-2020-0001"}, + {VulnerabilityID: "CVE-2019-0005"}, }, - reportType: "poetry", + reportType: ftypes.Poetry, }, expectedVulnerabilities: []types.DetectedVulnerability{ { - VulnerabilityID: "CVE-2020-0001", + VulnerabilityID: "CVE-2019-0005", Vulnerability: dbTypes.Vulnerability{ Title: "COVID-19", Description: "a nasty virus vulnerability for humans", @@ -317,22 +196,13 @@ func TestClient_FillInfo(t *testing.T) { References: []string{"https://www.who.int/emergencies/diseases/novel-coronavirus-2019"}, }, SeveritySource: vulnerability.PythonSafetyDB, - PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-0005", }, }, }, { - name: "GetVulnerability returns an error", - getVulnerability: []db.OperationGetVulnerabilityExpectation{ - { - Args: db.OperationGetVulnerabilityArgs{ - VulnerabilityID: "CVE-2019-0004", - }, - Returns: db.OperationGetVulnerabilityReturns{ - Err: xerrors.New("failed"), - }, - }, - }, + name: "GetVulnerability returns an error", + fixtures: []string{"testdata/fixtures/sad.yaml"}, args: args{ vulns: []types.DetectedVulnerability{ {VulnerabilityID: "CVE-2019-0004"}, @@ -346,17 +216,15 @@ func TestClient_FillInfo(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockDBConfig := new(db.MockOperation) - mockDBConfig.ApplyGetSeverityExpectations(tt.getSeverity) - mockDBConfig.ApplyGetVulnerabilityExpectations(tt.getVulnerability) + dbtest.InitDB(t, tt.fixtures) + defer db.Close() c := Client{ - dbc: mockDBConfig, + dbc: db.Config{}, } c.FillInfo(tt.args.vulns, tt.args.reportType) assert.Equal(t, tt.expectedVulnerabilities, tt.args.vulns, tt.name) - mockDBConfig.AssertExpectations(t) }) } } @@ -452,9 +320,9 @@ func TestClient_Filter(t *testing.T) { policyFile string } tests := []struct { - name string - args args - want []types.DetectedVulnerability + name string + args args + wantVulns []types.DetectedVulnerability }{ { name: "happy path", @@ -509,7 +377,7 @@ func TestClient_Filter(t *testing.T) { severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityHigh, dbTypes.SeverityUnknown}, ignoreUnfixed: false, }, - want: []types.DetectedVulnerability{ + wantVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2018-0001", PkgName: "bar", @@ -613,7 +481,7 @@ func TestClient_Filter(t *testing.T) { ignoreUnfixed: false, ignoreFile: "testdata/.trivyignore", }, - want: []types.DetectedVulnerability{ + wantVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2019-0003", PkgName: "foo", @@ -663,7 +531,7 @@ func TestClient_Filter(t *testing.T) { ignoreUnfixed: false, policyFile: "./testdata/test.rego", }, - want: []types.DetectedVulnerability{ + wantVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2019-0001", PkgName: "foo", @@ -755,7 +623,7 @@ func TestClient_Filter(t *testing.T) { severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityHigh, dbTypes.SeverityUnknown}, ignoreUnfixed: false, }, - want: []types.DetectedVulnerability{ + wantVulns: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2018-0001", PkgName: "bar", @@ -807,10 +675,9 @@ func TestClient_Filter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := Client{} - got, err := c.Filter(context.Background(), tt.args.vulns, tt.args.severities, - tt.args.ignoreUnfixed, tt.args.ignoreFile, tt.args.policyFile) + gotVulns, err := c.Filter(context.Background(), tt.args.vulns, tt.args.severities, tt.args.ignoreUnfixed, tt.args.ignoreFile, tt.args.policyFile) require.NoError(t, err) - assert.Equal(t, tt.want, got, tt.name) + assert.Equal(t, tt.wantVulns, gotVulns) }) } } diff --git a/rpc/cache/service.pb.go b/rpc/cache/service.pb.go index 0374a73258..8c0431908e 100644 --- a/rpc/cache/service.pb.go +++ b/rpc/cache/service.pb.go @@ -7,8 +7,8 @@ import ( fmt "fmt" common "github.com/aquasecurity/trivy/rpc/common" proto "github.com/golang/protobuf/proto" - _ "github.com/golang/protobuf/ptypes/empty" - timestamp "github.com/golang/protobuf/ptypes/timestamp" + _ "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" math "math" ) @@ -24,15 +24,15 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type ArtifactInfo struct { - SchemaVersion int32 `protobuf:"varint,1,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` - Architecture string `protobuf:"bytes,2,opt,name=architecture,proto3" json:"architecture,omitempty"` - Created *timestamp.Timestamp `protobuf:"bytes,3,opt,name=created,proto3" json:"created,omitempty"` - DockerVersion string `protobuf:"bytes,4,opt,name=docker_version,json=dockerVersion,proto3" json:"docker_version,omitempty"` - Os string `protobuf:"bytes,5,opt,name=os,proto3" json:"os,omitempty"` - HistoryPackages []*common.Package `protobuf:"bytes,6,rep,name=history_packages,json=historyPackages,proto3" json:"history_packages,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + SchemaVersion int32 `protobuf:"varint,1,opt,name=schema_version,json=schemaVersion,proto3" json:"schema_version,omitempty"` + Architecture string `protobuf:"bytes,2,opt,name=architecture,proto3" json:"architecture,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created,proto3" json:"created,omitempty"` + DockerVersion string `protobuf:"bytes,4,opt,name=docker_version,json=dockerVersion,proto3" json:"docker_version,omitempty"` + Os string `protobuf:"bytes,5,opt,name=os,proto3" json:"os,omitempty"` + HistoryPackages []*common.Package `protobuf:"bytes,6,rep,name=history_packages,json=historyPackages,proto3" json:"history_packages,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ArtifactInfo) Reset() { *m = ArtifactInfo{} } @@ -74,7 +74,7 @@ func (m *ArtifactInfo) GetArchitecture() string { return "" } -func (m *ArtifactInfo) GetCreated() *timestamp.Timestamp { +func (m *ArtifactInfo) GetCreated() *timestamppb.Timestamp { if m != nil { return m.Created } diff --git a/rpc/cache/service.twirp.go b/rpc/cache/service.twirp.go index 78b7f90ed3..8c98b2c20d 100644 --- a/rpc/cache/service.twirp.go +++ b/rpc/cache/service.twirp.go @@ -17,23 +17,19 @@ import fmt "fmt" import ioutil "io/ioutil" import http "net/http" import strconv "strconv" -import gzip "compress/gzip" import jsonpb "github.com/golang/protobuf/jsonpb" import proto "github.com/golang/protobuf/proto" import twirp "github.com/twitchtv/twirp" import ctxsetters "github.com/twitchtv/twirp/ctxsetters" -import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" +import google_protobuf1 "google.golang.org/protobuf/types/known/emptypb" // Imports only used by utility functions: import io "io" import json "encoding/json" import url "net/url" -// A response is compressed with gzip when the response size exceeds this threshold. -const CompressThreshold = 10000 - // =============== // Cache Interface // =============== @@ -420,16 +416,6 @@ func (s *cacheServer) servePutArtifactProtobuf(ctx context.Context, resp http.Re return } - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) resp.Header().Set("Content-Type", "application/protobuf") resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) @@ -559,16 +545,6 @@ func (s *cacheServer) servePutBlobProtobuf(ctx context.Context, resp http.Respon return } - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) resp.Header().Set("Content-Type", "application/protobuf") resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) @@ -698,16 +674,6 @@ func (s *cacheServer) serveMissingBlobsProtobuf(ctx context.Context, resp http.R return } - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) resp.Header().Set("Content-Type", "application/protobuf") resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) @@ -866,7 +832,6 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType } req.Header.Set("Accept", contentType) req.Header.Set("Content-Type", contentType) - req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("Twirp-Version", "v5.10.1") return req, nil } @@ -1119,15 +1084,7 @@ func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.Clie return errorFromResponse(resp) } - r := resp.Body - if resp.Header.Get("Content-Encoding") == "gzip" { - r, err = gzip.NewReader(r) - if err != nil { - return wrapInternal(err, "invalid gzip") - } - } - - respBodyBytes, err := ioutil.ReadAll(r) + respBodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return wrapInternal(err, "failed to read response body") } @@ -1232,36 +1189,6 @@ func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) conte return h.Error(ctx, err) } -// compressWithGzip compresses the data with gzip -func compressWithGzip(data []byte) ([]byte, error) { - var b bytes.Buffer - gz := gzip.NewWriter(&b) - defer gz.Close() - - if _, err := gz.Write(data); err != nil { - return nil, err - } - - if err := gz.Flush(); err != nil { - return nil, err - } - - if err := gz.Close(); err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -func isGZipAcceptable(request *http.Request) bool { - for _, encoding := range request.Header["Accept-Encoding"] { - if encoding == "gzip" { - return true - } - } - return false -} - func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { if h == nil || h.ResponseReceived == nil { return diff --git a/rpc/common/service.pb.go b/rpc/common/service.pb.go index 2a308488ca..861aef065c 100644 --- a/rpc/common/service.pb.go +++ b/rpc/common/service.pb.go @@ -6,7 +6,7 @@ package common import ( fmt "fmt" proto "github.com/golang/protobuf/proto" - timestamp "github.com/golang/protobuf/ptypes/timestamp" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" math "math" ) @@ -359,24 +359,24 @@ func (m *Library) GetVersion() string { } type Vulnerability struct { - VulnerabilityId string `protobuf:"bytes,1,opt,name=vulnerability_id,json=vulnerabilityId,proto3" json:"vulnerability_id,omitempty"` - PkgName string `protobuf:"bytes,2,opt,name=pkg_name,json=pkgName,proto3" json:"pkg_name,omitempty"` - InstalledVersion string `protobuf:"bytes,3,opt,name=installed_version,json=installedVersion,proto3" json:"installed_version,omitempty"` - FixedVersion string `protobuf:"bytes,4,opt,name=fixed_version,json=fixedVersion,proto3" json:"fixed_version,omitempty"` - Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` - Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` - Severity Severity `protobuf:"varint,7,opt,name=severity,proto3,enum=trivy.common.Severity" json:"severity,omitempty"` - References []string `protobuf:"bytes,8,rep,name=references,proto3" json:"references,omitempty"` - Layer *Layer `protobuf:"bytes,10,opt,name=layer,proto3" json:"layer,omitempty"` - SeveritySource string `protobuf:"bytes,11,opt,name=severity_source,json=severitySource,proto3" json:"severity_source,omitempty"` - Cvss map[string]*CVSS `protobuf:"bytes,12,rep,name=cvss,proto3" json:"cvss,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - CweIds []string `protobuf:"bytes,13,rep,name=cwe_ids,json=cweIds,proto3" json:"cwe_ids,omitempty"` - PrimaryUrl string `protobuf:"bytes,14,opt,name=primary_url,json=primaryUrl,proto3" json:"primary_url,omitempty"` - PublishedDate *timestamp.Timestamp `protobuf:"bytes,15,opt,name=published_date,json=publishedDate,proto3" json:"published_date,omitempty"` - LastModifiedDate *timestamp.Timestamp `protobuf:"bytes,16,opt,name=last_modified_date,json=lastModifiedDate,proto3" json:"last_modified_date,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + VulnerabilityId string `protobuf:"bytes,1,opt,name=vulnerability_id,json=vulnerabilityId,proto3" json:"vulnerability_id,omitempty"` + PkgName string `protobuf:"bytes,2,opt,name=pkg_name,json=pkgName,proto3" json:"pkg_name,omitempty"` + InstalledVersion string `protobuf:"bytes,3,opt,name=installed_version,json=installedVersion,proto3" json:"installed_version,omitempty"` + FixedVersion string `protobuf:"bytes,4,opt,name=fixed_version,json=fixedVersion,proto3" json:"fixed_version,omitempty"` + Title string `protobuf:"bytes,5,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` + Severity Severity `protobuf:"varint,7,opt,name=severity,proto3,enum=trivy.common.Severity" json:"severity,omitempty"` + References []string `protobuf:"bytes,8,rep,name=references,proto3" json:"references,omitempty"` + Layer *Layer `protobuf:"bytes,10,opt,name=layer,proto3" json:"layer,omitempty"` + SeveritySource string `protobuf:"bytes,11,opt,name=severity_source,json=severitySource,proto3" json:"severity_source,omitempty"` + Cvss map[string]*CVSS `protobuf:"bytes,12,rep,name=cvss,proto3" json:"cvss,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + CweIds []string `protobuf:"bytes,13,rep,name=cwe_ids,json=cweIds,proto3" json:"cwe_ids,omitempty"` + PrimaryUrl string `protobuf:"bytes,14,opt,name=primary_url,json=primaryUrl,proto3" json:"primary_url,omitempty"` + PublishedDate *timestamppb.Timestamp `protobuf:"bytes,15,opt,name=published_date,json=publishedDate,proto3" json:"published_date,omitempty"` + LastModifiedDate *timestamppb.Timestamp `protobuf:"bytes,16,opt,name=last_modified_date,json=lastModifiedDate,proto3" json:"last_modified_date,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Vulnerability) Reset() { *m = Vulnerability{} } @@ -495,14 +495,14 @@ func (m *Vulnerability) GetPrimaryUrl() string { return "" } -func (m *Vulnerability) GetPublishedDate() *timestamp.Timestamp { +func (m *Vulnerability) GetPublishedDate() *timestamppb.Timestamp { if m != nil { return m.PublishedDate } return nil } -func (m *Vulnerability) GetLastModifiedDate() *timestamp.Timestamp { +func (m *Vulnerability) GetLastModifiedDate() *timestamppb.Timestamp { if m != nil { return m.LastModifiedDate } diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index 31a9b7a4f0..4167e4a899 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -86,6 +86,7 @@ func (m *ScanRequest) GetOptions() *ScanOptions { type ScanOptions struct { VulnType []string `protobuf:"bytes,1,rep,name=vuln_type,json=vulnType,proto3" json:"vuln_type,omitempty"` + SecurityChecks []string `protobuf:"bytes,2,rep,name=security_checks,json=securityChecks,proto3" json:"security_checks,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -123,6 +124,13 @@ func (m *ScanOptions) GetVulnType() []string { return nil } +func (m *ScanOptions) GetSecurityChecks() []string { + if m != nil { + return m.SecurityChecks + } + return nil +} + type ScanResponse struct { Os *common.OS `protobuf:"bytes,1,opt,name=os,proto3" json:"os,omitempty"` Eosl bool `protobuf:"varint,2,opt,name=eosl,proto3" json:"eosl,omitempty"` @@ -244,29 +252,30 @@ func init() { func init() { proto.RegisterFile("rpc/scanner/service.proto", fileDescriptor_60d0e837512b18d4) } var fileDescriptor_60d0e837512b18d4 = []byte{ - // 377 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xc1, 0x6b, 0xdb, 0x30, - 0x14, 0xc6, 0xb1, 0x13, 0xe2, 0xf8, 0x79, 0xb0, 0xa0, 0xc3, 0x70, 0x12, 0xb6, 0x19, 0x9f, 0xc2, - 0x0e, 0x36, 0xf3, 0x60, 0xbb, 0x0f, 0x72, 0xc8, 0x29, 0x45, 0x29, 0x3d, 0xf4, 0x12, 0x64, 0x59, - 0x4d, 0x05, 0x8e, 0xe5, 0x48, 0xb2, 0xa9, 0xe9, 0x7f, 0xd2, 0xbf, 0xb6, 0x58, 0x72, 0xa0, 0x49, - 0xc9, 0xed, 0xe9, 0xbd, 0xcf, 0x4f, 0xbf, 0xef, 0xb3, 0x60, 0x2e, 0x6b, 0x9a, 0x2a, 0x4a, 0xaa, - 0x8a, 0xc9, 0x54, 0x31, 0xd9, 0x72, 0xca, 0x92, 0x5a, 0x0a, 0x2d, 0xd0, 0x4c, 0x4b, 0xde, 0x76, - 0xc9, 0x30, 0x4c, 0xda, 0xdf, 0x8b, 0xbf, 0x07, 0xae, 0x9f, 0x9b, 0x3c, 0xa1, 0xe2, 0x98, 0x92, - 0x53, 0x43, 0x14, 0xa3, 0x8d, 0xe4, 0xba, 0x4b, 0x8d, 0x32, 0xed, 0x57, 0x51, 0x71, 0x3c, 0x8a, - 0xea, 0x72, 0x53, 0xfc, 0xe6, 0x40, 0xb0, 0xa3, 0xa4, 0xc2, 0xec, 0xd4, 0x30, 0xa5, 0xd1, 0x37, - 0x98, 0x68, 0x22, 0x0f, 0x4c, 0x87, 0x4e, 0xe4, 0xac, 0x7c, 0x3c, 0x9c, 0xd0, 0x4f, 0x08, 0x88, - 0xd4, 0xfc, 0x89, 0x50, 0xbd, 0xe7, 0x45, 0xe8, 0x9a, 0x21, 0x9c, 0x5b, 0x9b, 0x02, 0xcd, 0x61, - 0x9a, 0x97, 0x22, 0xdf, 0xf3, 0x42, 0x85, 0xa3, 0x68, 0xb4, 0xf2, 0xb1, 0xd7, 0x9f, 0x37, 0x85, - 0x42, 0xff, 0xc0, 0x13, 0xb5, 0xe6, 0xa2, 0x52, 0xe1, 0x38, 0x72, 0x56, 0x41, 0xf6, 0x3d, 0xb9, - 0xe6, 0x4f, 0x7a, 0x86, 0xad, 0x15, 0xe1, 0xb3, 0x3a, 0xfe, 0x65, 0xd9, 0x86, 0x3e, 0x5a, 0x82, - 0xdf, 0x36, 0x65, 0xb5, 0xd7, 0x5d, 0xcd, 0x42, 0xc7, 0xdc, 0x31, 0xed, 0x1b, 0xf7, 0x5d, 0xcd, - 0xe2, 0x17, 0xf8, 0x62, 0x7d, 0xa8, 0x5a, 0x54, 0x8a, 0xa1, 0x08, 0x5c, 0xa1, 0x8c, 0x89, 0x20, - 0x9b, 0x0d, 0xf7, 0xd9, 0x04, 0x92, 0xed, 0x0e, 0xbb, 0x42, 0x21, 0x04, 0x63, 0x26, 0x54, 0x69, - 0xbc, 0x4c, 0xb1, 0xa9, 0x51, 0x06, 0x9e, 0x64, 0xaa, 0x29, 0xb5, 0x35, 0x11, 0x64, 0xe1, 0x67, - 0x54, 0x6c, 0x04, 0xf8, 0x2c, 0x8c, 0x5f, 0x61, 0x62, 0x5b, 0x37, 0xc3, 0x5b, 0xc3, 0xd7, 0x9e, - 0x93, 0x49, 0x92, 0xf3, 0x92, 0x6b, 0xce, 0x54, 0xe8, 0x9a, 0xed, 0xcb, 0x4b, 0xb0, 0x87, 0x0f, - 0xa2, 0x0e, 0x5f, 0x7f, 0xd3, 0x03, 0x1b, 0xeb, 0x23, 0xb3, 0xdc, 0xd4, 0xd9, 0x1d, 0x78, 0x3b, - 0x8b, 0x86, 0xd6, 0x30, 0xee, 0x4b, 0x74, 0x23, 0xdd, 0xe1, 0x0f, 0x2f, 0x7e, 0xdc, 0x1a, 0xdb, - 0xe0, 0xfe, 0xfb, 0x8f, 0xde, 0x30, 0xca, 0x27, 0xe6, 0x8d, 0xfc, 0x79, 0x0f, 0x00, 0x00, 0xff, - 0xff, 0xdf, 0x43, 0x89, 0x65, 0x8a, 0x02, 0x00, 0x00, + // 395 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0x41, 0x8f, 0x9b, 0x30, + 0x10, 0x85, 0x05, 0x89, 0x42, 0x18, 0xaa, 0xee, 0xca, 0x87, 0x8a, 0xdd, 0x55, 0x5b, 0xc4, 0xa5, + 0x39, 0x81, 0x4a, 0xa5, 0xf6, 0xde, 0x2a, 0x87, 0x9c, 0x52, 0x99, 0xaa, 0x87, 0x5e, 0x90, 0x31, + 0x6e, 0x62, 0x95, 0x60, 0x62, 0x1b, 0x54, 0xd4, 0x7f, 0xd2, 0x5f, 0x5b, 0x61, 0x83, 0xd4, 0x64, + 0x95, 0xdb, 0xf8, 0xcd, 0x63, 0x78, 0xdf, 0xd8, 0xf0, 0x20, 0x5b, 0x9a, 0x2a, 0x4a, 0x9a, 0x86, + 0xc9, 0x54, 0x31, 0xd9, 0x73, 0xca, 0x92, 0x56, 0x0a, 0x2d, 0xd0, 0xbd, 0x96, 0xbc, 0x1f, 0x92, + 0xa9, 0x99, 0xf4, 0xef, 0x1f, 0x3f, 0x1e, 0xb8, 0x3e, 0x76, 0x65, 0x42, 0xc5, 0x29, 0x25, 0xe7, + 0x8e, 0x28, 0x46, 0x3b, 0xc9, 0xf5, 0x90, 0x1a, 0x67, 0x3a, 0x8e, 0xa2, 0xe2, 0x74, 0x12, 0xcd, + 0xe5, 0xa4, 0xf8, 0xaf, 0x03, 0x41, 0x4e, 0x49, 0x83, 0xd9, 0xb9, 0x63, 0x4a, 0xa3, 0x57, 0xb0, + 0xd2, 0x44, 0x1e, 0x98, 0x0e, 0x9d, 0xc8, 0xd9, 0xf8, 0x78, 0x3a, 0xa1, 0xb7, 0x10, 0x10, 0xa9, + 0xf9, 0x4f, 0x42, 0x75, 0xc1, 0xab, 0xd0, 0x35, 0x4d, 0x98, 0xa5, 0x5d, 0x85, 0x1e, 0x60, 0x5d, + 0xd6, 0xa2, 0x2c, 0x78, 0xa5, 0xc2, 0x45, 0xb4, 0xd8, 0xf8, 0xd8, 0x1b, 0xcf, 0xbb, 0x4a, 0xa1, + 0x4f, 0xe0, 0x89, 0x56, 0x73, 0xd1, 0xa8, 0x70, 0x19, 0x39, 0x9b, 0x20, 0x7b, 0x9d, 0x5c, 0xe7, + 0x4f, 0xc6, 0x0c, 0x7b, 0x6b, 0xc2, 0xb3, 0x3b, 0xce, 0x6d, 0xb6, 0x49, 0x47, 0x4f, 0xe0, 0xf7, + 0x5d, 0xdd, 0x14, 0x7a, 0x68, 0x59, 0xe8, 0x98, 0x7f, 0xac, 0x47, 0xe1, 0xdb, 0xd0, 0x32, 0xf4, + 0x0e, 0xee, 0x66, 0xe6, 0x82, 0x1e, 0x19, 0xfd, 0xa5, 0x42, 0xd7, 0x58, 0x5e, 0xce, 0xf2, 0x17, + 0xa3, 0xc6, 0xbf, 0xe1, 0x85, 0x05, 0x56, 0xad, 0x68, 0x14, 0x43, 0x11, 0xb8, 0x42, 0x19, 0xda, + 0x20, 0xbb, 0x9f, 0x82, 0xd9, 0x55, 0x25, 0xfb, 0x1c, 0xbb, 0x42, 0x21, 0x04, 0x4b, 0x26, 0x54, + 0x6d, 0xa0, 0xd7, 0xd8, 0xd4, 0x28, 0x03, 0x4f, 0x32, 0xd5, 0xd5, 0xda, 0xd2, 0x06, 0x59, 0xf8, + 0x9c, 0x09, 0x1b, 0x03, 0x9e, 0x8d, 0xf1, 0x1f, 0x58, 0x59, 0xe9, 0xe6, 0x96, 0xb7, 0x70, 0x37, + 0x02, 0x31, 0x49, 0x4a, 0x5e, 0x73, 0xcd, 0x99, 0x85, 0x08, 0xb2, 0xa7, 0xcb, 0x60, 0xdf, 0xff, + 0x33, 0x0d, 0xf8, 0xfa, 0x9b, 0x31, 0xb0, 0xd9, 0xd1, 0xc2, 0x0c, 0x37, 0x75, 0xf6, 0x15, 0xbc, + 0xdc, 0x46, 0x43, 0x5b, 0x58, 0x8e, 0x25, 0xba, 0x71, 0x0d, 0xd3, 0x53, 0x78, 0x7c, 0x73, 0xab, + 0x6d, 0x17, 0xf7, 0xd9, 0xff, 0xe1, 0x4d, 0xad, 0x72, 0x65, 0x1e, 0xd3, 0x87, 0x7f, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xfd, 0x72, 0xf0, 0x54, 0xb3, 0x02, 0x00, 0x00, } diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index 40d7181b6a..8e43870695 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -17,7 +17,8 @@ message ScanRequest { } message ScanOptions { - repeated string vuln_type = 1; + repeated string vuln_type = 1; + repeated string security_checks = 2; } message ScanResponse { diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index 2e0618268a..851827eacb 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -17,7 +17,6 @@ import fmt "fmt" import ioutil "io/ioutil" import http "net/http" import strconv "strconv" -import gzip "compress/gzip" import jsonpb "github.com/golang/protobuf/jsonpb" import proto "github.com/golang/protobuf/proto" @@ -29,9 +28,6 @@ import io "io" import json "encoding/json" import url "net/url" -// A response is compressed with gzip when the response size exceeds this threshold. -const CompressThreshold = 10000 - // ================= // Scanner Interface // ================= @@ -324,16 +320,6 @@ func (s *scannerServer) serveScanProtobuf(ctx context.Context, resp http.Respons return } - // Compress the response if the size exceeds the threshold - if len(respBytes) > CompressThreshold && isGZipAcceptable(req) { - respBytes, err = compressWithGzip(respBytes) - if err != nil { - s.writeError(ctx, resp, wrapInternal(err, "failed to compress response")) - return - } - resp.Header().Set("Content-Encoding", "gzip") - } - ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) resp.Header().Set("Content-Type", "application/protobuf") resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) @@ -492,7 +478,6 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType } req.Header.Set("Accept", contentType) req.Header.Set("Content-Type", contentType) - req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("Twirp-Version", "v5.10.1") return req, nil } @@ -745,15 +730,7 @@ func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.Clie return errorFromResponse(resp) } - r := resp.Body - if resp.Header.Get("Content-Encoding") == "gzip" { - r, err = gzip.NewReader(r) - if err != nil { - return wrapInternal(err, "invalid gzip") - } - } - - respBodyBytes, err := ioutil.ReadAll(r) + respBodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return wrapInternal(err, "failed to read response body") } @@ -858,36 +835,6 @@ func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) conte return h.Error(ctx, err) } -// compressWithGzip compresses the data with gzip -func compressWithGzip(data []byte) ([]byte, error) { - var b bytes.Buffer - gz := gzip.NewWriter(&b) - defer gz.Close() - - if _, err := gz.Write(data); err != nil { - return nil, err - } - - if err := gz.Flush(); err != nil { - return nil, err - } - - if err := gz.Close(); err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -func isGZipAcceptable(request *http.Request) bool { - for _, encoding := range request.Header["Accept-Encoding"] { - if encoding == "gzip" { - return true - } - } - return false -} - func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { if h == nil || h.ResponseReceived == nil { return @@ -910,29 +857,30 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 377 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xc1, 0x6b, 0xdb, 0x30, - 0x14, 0xc6, 0xb1, 0x13, 0xe2, 0xf8, 0x79, 0xb0, 0xa0, 0xc3, 0x70, 0x12, 0xb6, 0x19, 0x9f, 0xc2, - 0x0e, 0x36, 0xf3, 0x60, 0xbb, 0x0f, 0x72, 0xc8, 0x29, 0x45, 0x29, 0x3d, 0xf4, 0x12, 0x64, 0x59, - 0x4d, 0x05, 0x8e, 0xe5, 0x48, 0xb2, 0xa9, 0xe9, 0x7f, 0xd2, 0xbf, 0xb6, 0x58, 0x72, 0xa0, 0x49, - 0xc9, 0xed, 0xe9, 0xbd, 0xcf, 0x4f, 0xbf, 0xef, 0xb3, 0x60, 0x2e, 0x6b, 0x9a, 0x2a, 0x4a, 0xaa, - 0x8a, 0xc9, 0x54, 0x31, 0xd9, 0x72, 0xca, 0x92, 0x5a, 0x0a, 0x2d, 0xd0, 0x4c, 0x4b, 0xde, 0x76, - 0xc9, 0x30, 0x4c, 0xda, 0xdf, 0x8b, 0xbf, 0x07, 0xae, 0x9f, 0x9b, 0x3c, 0xa1, 0xe2, 0x98, 0x92, - 0x53, 0x43, 0x14, 0xa3, 0x8d, 0xe4, 0xba, 0x4b, 0x8d, 0x32, 0xed, 0x57, 0x51, 0x71, 0x3c, 0x8a, - 0xea, 0x72, 0x53, 0xfc, 0xe6, 0x40, 0xb0, 0xa3, 0xa4, 0xc2, 0xec, 0xd4, 0x30, 0xa5, 0xd1, 0x37, - 0x98, 0x68, 0x22, 0x0f, 0x4c, 0x87, 0x4e, 0xe4, 0xac, 0x7c, 0x3c, 0x9c, 0xd0, 0x4f, 0x08, 0x88, - 0xd4, 0xfc, 0x89, 0x50, 0xbd, 0xe7, 0x45, 0xe8, 0x9a, 0x21, 0x9c, 0x5b, 0x9b, 0x02, 0xcd, 0x61, - 0x9a, 0x97, 0x22, 0xdf, 0xf3, 0x42, 0x85, 0xa3, 0x68, 0xb4, 0xf2, 0xb1, 0xd7, 0x9f, 0x37, 0x85, - 0x42, 0xff, 0xc0, 0x13, 0xb5, 0xe6, 0xa2, 0x52, 0xe1, 0x38, 0x72, 0x56, 0x41, 0xf6, 0x3d, 0xb9, - 0xe6, 0x4f, 0x7a, 0x86, 0xad, 0x15, 0xe1, 0xb3, 0x3a, 0xfe, 0x65, 0xd9, 0x86, 0x3e, 0x5a, 0x82, - 0xdf, 0x36, 0x65, 0xb5, 0xd7, 0x5d, 0xcd, 0x42, 0xc7, 0xdc, 0x31, 0xed, 0x1b, 0xf7, 0x5d, 0xcd, - 0xe2, 0x17, 0xf8, 0x62, 0x7d, 0xa8, 0x5a, 0x54, 0x8a, 0xa1, 0x08, 0x5c, 0xa1, 0x8c, 0x89, 0x20, - 0x9b, 0x0d, 0xf7, 0xd9, 0x04, 0x92, 0xed, 0x0e, 0xbb, 0x42, 0x21, 0x04, 0x63, 0x26, 0x54, 0x69, - 0xbc, 0x4c, 0xb1, 0xa9, 0x51, 0x06, 0x9e, 0x64, 0xaa, 0x29, 0xb5, 0x35, 0x11, 0x64, 0xe1, 0x67, - 0x54, 0x6c, 0x04, 0xf8, 0x2c, 0x8c, 0x5f, 0x61, 0x62, 0x5b, 0x37, 0xc3, 0x5b, 0xc3, 0xd7, 0x9e, - 0x93, 0x49, 0x92, 0xf3, 0x92, 0x6b, 0xce, 0x54, 0xe8, 0x9a, 0xed, 0xcb, 0x4b, 0xb0, 0x87, 0x0f, - 0xa2, 0x0e, 0x5f, 0x7f, 0xd3, 0x03, 0x1b, 0xeb, 0x23, 0xb3, 0xdc, 0xd4, 0xd9, 0x1d, 0x78, 0x3b, - 0x8b, 0x86, 0xd6, 0x30, 0xee, 0x4b, 0x74, 0x23, 0xdd, 0xe1, 0x0f, 0x2f, 0x7e, 0xdc, 0x1a, 0xdb, - 0xe0, 0xfe, 0xfb, 0x8f, 0xde, 0x30, 0xca, 0x27, 0xe6, 0x8d, 0xfc, 0x79, 0x0f, 0x00, 0x00, 0xff, - 0xff, 0xdf, 0x43, 0x89, 0x65, 0x8a, 0x02, 0x00, 0x00, + // 395 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0x41, 0x8f, 0x9b, 0x30, + 0x10, 0x85, 0x05, 0x89, 0x42, 0x18, 0xaa, 0xee, 0xca, 0x87, 0x8a, 0xdd, 0x55, 0x5b, 0xc4, 0xa5, + 0x39, 0x81, 0x4a, 0xa5, 0xf6, 0xde, 0x2a, 0x87, 0x9c, 0x52, 0x99, 0xaa, 0x87, 0x5e, 0x90, 0x31, + 0x6e, 0x62, 0x95, 0x60, 0x62, 0x1b, 0x54, 0xd4, 0x7f, 0xd2, 0x5f, 0x5b, 0x61, 0x83, 0xd4, 0x64, + 0x95, 0xdb, 0xf8, 0xcd, 0x63, 0x78, 0xdf, 0xd8, 0xf0, 0x20, 0x5b, 0x9a, 0x2a, 0x4a, 0x9a, 0x86, + 0xc9, 0x54, 0x31, 0xd9, 0x73, 0xca, 0x92, 0x56, 0x0a, 0x2d, 0xd0, 0xbd, 0x96, 0xbc, 0x1f, 0x92, + 0xa9, 0x99, 0xf4, 0xef, 0x1f, 0x3f, 0x1e, 0xb8, 0x3e, 0x76, 0x65, 0x42, 0xc5, 0x29, 0x25, 0xe7, + 0x8e, 0x28, 0x46, 0x3b, 0xc9, 0xf5, 0x90, 0x1a, 0x67, 0x3a, 0x8e, 0xa2, 0xe2, 0x74, 0x12, 0xcd, + 0xe5, 0xa4, 0xf8, 0xaf, 0x03, 0x41, 0x4e, 0x49, 0x83, 0xd9, 0xb9, 0x63, 0x4a, 0xa3, 0x57, 0xb0, + 0xd2, 0x44, 0x1e, 0x98, 0x0e, 0x9d, 0xc8, 0xd9, 0xf8, 0x78, 0x3a, 0xa1, 0xb7, 0x10, 0x10, 0xa9, + 0xf9, 0x4f, 0x42, 0x75, 0xc1, 0xab, 0xd0, 0x35, 0x4d, 0x98, 0xa5, 0x5d, 0x85, 0x1e, 0x60, 0x5d, + 0xd6, 0xa2, 0x2c, 0x78, 0xa5, 0xc2, 0x45, 0xb4, 0xd8, 0xf8, 0xd8, 0x1b, 0xcf, 0xbb, 0x4a, 0xa1, + 0x4f, 0xe0, 0x89, 0x56, 0x73, 0xd1, 0xa8, 0x70, 0x19, 0x39, 0x9b, 0x20, 0x7b, 0x9d, 0x5c, 0xe7, + 0x4f, 0xc6, 0x0c, 0x7b, 0x6b, 0xc2, 0xb3, 0x3b, 0xce, 0x6d, 0xb6, 0x49, 0x47, 0x4f, 0xe0, 0xf7, + 0x5d, 0xdd, 0x14, 0x7a, 0x68, 0x59, 0xe8, 0x98, 0x7f, 0xac, 0x47, 0xe1, 0xdb, 0xd0, 0x32, 0xf4, + 0x0e, 0xee, 0x66, 0xe6, 0x82, 0x1e, 0x19, 0xfd, 0xa5, 0x42, 0xd7, 0x58, 0x5e, 0xce, 0xf2, 0x17, + 0xa3, 0xc6, 0xbf, 0xe1, 0x85, 0x05, 0x56, 0xad, 0x68, 0x14, 0x43, 0x11, 0xb8, 0x42, 0x19, 0xda, + 0x20, 0xbb, 0x9f, 0x82, 0xd9, 0x55, 0x25, 0xfb, 0x1c, 0xbb, 0x42, 0x21, 0x04, 0x4b, 0x26, 0x54, + 0x6d, 0xa0, 0xd7, 0xd8, 0xd4, 0x28, 0x03, 0x4f, 0x32, 0xd5, 0xd5, 0xda, 0xd2, 0x06, 0x59, 0xf8, + 0x9c, 0x09, 0x1b, 0x03, 0x9e, 0x8d, 0xf1, 0x1f, 0x58, 0x59, 0xe9, 0xe6, 0x96, 0xb7, 0x70, 0x37, + 0x02, 0x31, 0x49, 0x4a, 0x5e, 0x73, 0xcd, 0x99, 0x85, 0x08, 0xb2, 0xa7, 0xcb, 0x60, 0xdf, 0xff, + 0x33, 0x0d, 0xf8, 0xfa, 0x9b, 0x31, 0xb0, 0xd9, 0xd1, 0xc2, 0x0c, 0x37, 0x75, 0xf6, 0x15, 0xbc, + 0xdc, 0x46, 0x43, 0x5b, 0x58, 0x8e, 0x25, 0xba, 0x71, 0x0d, 0xd3, 0x53, 0x78, 0x7c, 0x73, 0xab, + 0x6d, 0x17, 0xf7, 0xd9, 0xff, 0xe1, 0x4d, 0xad, 0x72, 0x65, 0x1e, 0xd3, 0x87, 0x7f, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xfd, 0x72, 0xf0, 0x54, 0xb3, 0x02, 0x00, 0x00, }