mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-21 23:00:42 -08:00
feat: support client/server mode (#295)
* chore(app): change dir * feat(rpc): add a proto file and auto-generated files * chore(dep): add dependencies * fix(app): fix import path * fix(integration): fix import path * fix(protoc): use enum for severity * chore(Makefile): add fmt andd protoc * chore(clang): add .clang-format * refactor: split functions for client/server (#296) * refactor(db): split db.Download * refactor(standalone): create a different package * refactor(vulnerability): split FillAndFilter * fix(protoc): use enum for severity * chore(Makefile): add fmt andd protoc * chore(clang): add .clang-format * fix(db): remove an unused variable * fix(db): expose the github client as an argument of constructor * refactor(vulnerability): add the detail message * feat(rpc): add rpc client (#302) * fix(protoc): use enum for severity * chore(Makefile): add fmt andd protoc * chore(clang): add .clang-format * feat(rpc): convert types * feat(rpc): add rpc client * token: Refactor to handle bad headers being set Signed-off-by: Simarpreet Singh <simar@linux.com> * feat(rpc): add rpc server (#303) * feat(rpc): add rpc server * feat(utils): add CopyFile * feat(server/config): add config struct * feat(detector): add detector * feat(scanner): delegate procedures to detector * fix(scanner): fix the interface * test(mock): add mocks * test(rpc/server): add tests * test(rpc/ospkg/server): add tests * tets(os/detector): add tests * refactor(library): move directories * chore(dependency): add google/wire * refactor(library): introduce google/wire * refactor(ospkg/detector): move directory * feat(rpc): add eosl * refactor(ospkg): introduce google/wire * refactor(wire): bind an interface * refactor(client): use wire.Struct * chore(Makefile): fix wire * test(server): add AssertExpectations * test(server): add AssertExpectations * refactor(server): remove debug log * refactor(error): add more context messages * test(server): fix error message * refactor(test): create a constructor of mock * refactor(config): remove an unused variable * test(config): add an assertion to test the config struct * feat(client/server): add sub commands (#304) * feat(rpc): add rpc server * feat(utils): add CopyFile * feat(server/config): add config struct * feat(detector): add detector * feat(scanner): delegate procedures to detector * fix(scanner): fix the interface * feat(client/server): add sub commands * merge(server3) * test(scan): remove an unused mock * refactor(client): generate the constructor by wire * fix(cli): change the default port * fix(server): use auto-generated constructor * feat(ospkg): return eosl * test(integration): add integration tests for client/server (#306) * fix(server): remove unnecessary options * test(integration): add integration tests for client/server * fix(server): wrap an error * fix(server): change the update interval * fix(server): display the error detail * test(config): add an assertion to test the config struct * fix(client): returns an error when failing to initizlie a logger * test(ospkg/server): add eosl * Squashed commit of the following: * test(server): refactor and add tests (#307) * test(github): create a mock * test(db): create a mock * test(server): add tests for DB hot update * chore(db): add a log message * refactor(db): introduce google/wire * refactor(rpc): move directory * refactor(injector): fix import name * refactor(import): remove new lines * fix(server): display the error detail * fix(server): change the update interval * fix(server): wrap an error * test(integration): add integration tests for client/server * fix(server): remove unnecessary options * refactor(server): return an error when failing to initialize a logger * refactor(server): remove unused error * fix(client/server): fix default port * chore(README): add client/server * chore(README): update
This commit is contained in:
5
.clang-format
Normal file
5
.clang-format
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
Language: Proto
|
||||
BasedOnStyle: Google
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignConsecutiveDeclarations: true
|
||||
16
Makefile
16
Makefile
@@ -3,9 +3,17 @@ LDFLAGS=-ldflags "-s -w -X=main.version=$(VERSION)"
|
||||
|
||||
GOPATH=$(shell go env GOPATH)
|
||||
GOBIN=$(GOPATH)/bin
|
||||
GOSRC=$(GOPATH)/src
|
||||
|
||||
u := $(if $(update),-u)
|
||||
|
||||
$(GOBIN)/wire:
|
||||
GO111MODULE=off go get github.com/google/wire/cmd/wire
|
||||
|
||||
.PHONY: wire
|
||||
wire: $(GOBIN)/wire
|
||||
wire gen ./...
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
go get ${u} -d
|
||||
@@ -29,10 +37,18 @@ test-integration: integration/testdata/fixtures/*.tar.gz
|
||||
lint: $(GOBIN)/golangci-lint
|
||||
$(GOBIN)/golangci-lint run
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
find ./ -name "*.proto" | xargs clang-format -i
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build $(LDFLAGS) ./cmd/trivy
|
||||
|
||||
.PHONY: protoc
|
||||
protoc:
|
||||
protoc --proto_path=$(GOSRC):. --twirp_out=. --go_out=. ./rpc/detector/service.proto
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install $(LDFLAGS) ./cmd/trivy
|
||||
|
||||
117
README.md
117
README.md
@@ -28,17 +28,22 @@ A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI
|
||||
- [Basic](#basic)
|
||||
- [Docker](#docker)
|
||||
- [Examples](#examples)
|
||||
- [Scan an image](#scan-an-image)
|
||||
- [Scan an image file](#scan-an-image-file)
|
||||
- [Save the results as JSON](#save-the-results-as-json)
|
||||
- [Filter the vulnerabilities by severities](#filter-the-vulnerabilities-by-severities)
|
||||
- [Filter the vulnerabilities by type](#filter-the-vulnerabilities-by-type)
|
||||
- [Skip an update of vulnerability DB](#skip-update-of-vulnerability-db)
|
||||
- [Ignore unfixed vulnerabilities](#ignore-unfixed-vulnerabilities)
|
||||
- [Specify exit code](#specify-exit-code)
|
||||
- [Ignore the specified vulnerabilities](#ignore-the-specified-vulnerabilities)
|
||||
- [Clear image caches](#clear-image-caches)
|
||||
- [Reset](#reset)
|
||||
- [Standalone](#standalone)
|
||||
- [Scan an image](#scan-an-image)
|
||||
- [Scan an image file](#scan-an-image-file)
|
||||
- [Save the results as JSON](#save-the-results-as-json)
|
||||
- [Filter the vulnerabilities by severities](#filter-the-vulnerabilities-by-severities)
|
||||
- [Filter the vulnerabilities by type](#filter-the-vulnerabilities-by-type)
|
||||
- [Skip an update of vulnerability DB](#skip-update-of-vulnerability-db)
|
||||
- [Ignore unfixed vulnerabilities](#ignore-unfixed-vulnerabilities)
|
||||
- [Specify exit code](#specify-exit-code)
|
||||
- [Ignore the specified vulnerabilities](#ignore-the-specified-vulnerabilities)
|
||||
- [Clear image caches](#clear-image-caches)
|
||||
- [Reset](#reset)
|
||||
- [Lightweight DB](#use-lightweight-db)
|
||||
- [Client/Server](#client--server)
|
||||
- [Server](#server)
|
||||
- [Client](#client)
|
||||
- [Continuous Integration (CI)](#continuous-integration-ci)
|
||||
- [Travis CI](#travis-ci)
|
||||
- [CircleCI](#circleci)
|
||||
@@ -254,6 +259,8 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
# Examples
|
||||
|
||||
## Standalone
|
||||
|
||||
### Scan an image
|
||||
|
||||
Simply specify an image name (and a tag).
|
||||
@@ -1078,6 +1085,46 @@ Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 2, HIGH: 0, CRITICAL: 0)
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## Client / Server
|
||||
Trivy has client/server mode. Trivy server has vulnerability database and Trivy client doesn't have to download vulnerability database. It is useful if you want to scan images at multiple locations and do not want to download the database at every location.
|
||||
|
||||
### Server
|
||||
At first, you need to launch Trivy server. It downloads vulnerability database automatically and continue to fetch the latest DB in the background.
|
||||
```
|
||||
$ trivy server --listen localhost:8080
|
||||
2019-12-12T15:17:06.551+0200 INFO Need to update DB
|
||||
2019-12-12T15:17:56.706+0200 INFO Reopening DB...
|
||||
2019-12-12T15:17:56.707+0200 INFO Listening localhost:8080...
|
||||
```
|
||||
|
||||
### Client
|
||||
Then, specify the remote address.
|
||||
```
|
||||
$ trivy client --remote http://localhost:8080 alpine:3.10
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
alpine:3.10 (alpine 3.10.2)
|
||||
===========================
|
||||
Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 2, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
+---------+------------------+----------+-------------------+---------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION |
|
||||
+---------+------------------+----------+-------------------+---------------+
|
||||
| openssl | CVE-2019-1549 | MEDIUM | 1.1.1c-r0 | 1.1.1d-r0 |
|
||||
+ +------------------+ + + +
|
||||
| | CVE-2019-1563 | | | |
|
||||
+ +------------------+----------+ + +
|
||||
| | CVE-2019-1547 | LOW | | |
|
||||
+---------+------------------+----------+-------------------+---------------+
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
### Deprecated options
|
||||
|
||||
`--only-update`, `--refresh` and `--auto-refresh` are deprecated since they are unnecessary now. These options will be removed at the next version
|
||||
@@ -1297,6 +1344,7 @@ Trivy scans a tar image with the following format.
|
||||
- https://github.com/RustSec/advisory-db
|
||||
|
||||
# Usage
|
||||
## Standalone
|
||||
|
||||
```
|
||||
NAME:
|
||||
@@ -1333,6 +1381,53 @@ OPTIONS:
|
||||
|
||||
```
|
||||
|
||||
## Sub commands
|
||||
Trivy has two sub commands, client and server.
|
||||
|
||||
```
|
||||
NAME:
|
||||
trivy client - client mode
|
||||
|
||||
USAGE:
|
||||
trivy client [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--template value, -t value output template [$TRIVY_TEMPLATE]
|
||||
--format value, -f value format (table, json, template) (default: "table") [$TRIVY_FORMAT]
|
||||
--input value, -i value input file path instead of image name [$TRIVY_INPUT]
|
||||
--severity value, -s value severities of vulnerabilities to be displayed (comma separated) (default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL") [$TRIVY_SEVERITY]
|
||||
--output value, -o value output file name [$TRIVY_OUTPUT]
|
||||
--exit-code value Exit code when vulnerabilities were found (default: 0) [$TRIVY_EXIT_CODE]
|
||||
--clear-cache, -c clear image caches without scanning [$TRIVY_CLEAR_CACHE]
|
||||
--quiet, -q suppress progress bar and log output [$TRIVY_QUIET]
|
||||
--ignore-unfixed display only fixed vulnerabilities [$TRIVY_IGNORE_UNFIXED]
|
||||
--debug, -d debug mode [$TRIVY_DEBUG]
|
||||
--vuln-type value comma-separated list of vulnerability types (os,library) (default: "os,library") [$TRIVY_VULN_TYPE]
|
||||
--ignorefile value specify .trivyignore file (default: ".trivyignore") [$TRIVY_IGNOREFILE]
|
||||
--cache-dir value use as cache directory, but image cache is stored in /path/to/cache/fanal (default: "/Users/teppei/Library/Caches/trivy") [$TRIVY_CACHE_DIR]
|
||||
--timeout value docker timeout (default: 1m0s) [$TRIVY_TIMEOUT]
|
||||
--token value for authentication [$TRIVY_TOKEN]
|
||||
--remote value server address (default: "http://localhost:4954") [$TRIVY_REMOTE]
|
||||
```
|
||||
|
||||
```
|
||||
NAME:
|
||||
trivy server - server mode
|
||||
|
||||
USAGE:
|
||||
trivy server [command options] [arguments...]
|
||||
|
||||
OPTIONS:
|
||||
--skip-update skip db update [$TRIVY_SKIP_UPDATE]
|
||||
--download-db-only download/update vulnerability database but don't run a scan [$TRIVY_DOWNLOAD_DB_ONLY]
|
||||
--reset remove all caches and database [$TRIVY_RESET]
|
||||
--quiet, -q suppress progress bar and log output [$TRIVY_QUIET]
|
||||
--debug, -d debug mode [$TRIVY_DEBUG]
|
||||
--cache-dir value use as cache directory, but image cache is stored in /path/to/cache/fanal (default: "/Users/teppei/Library/Caches/trivy") [$TRIVY_CACHE_DIR]
|
||||
--token value for authentication [$TRIVY_TOKEN]
|
||||
--listen value listen address (default: "localhost:4954") [$TRIVY_LISTEN]
|
||||
```
|
||||
|
||||
# Comparison with other scanners
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
l "log"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/standalone"
|
||||
"github.com/aquasecurity/trivy/internal"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
@@ -14,7 +14,7 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := standalone.NewApp(version)
|
||||
app := internal.NewApp(version)
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
if log.Logger != nil {
|
||||
|
||||
3
go.mod
3
go.mod
@@ -9,13 +9,16 @@ require (
|
||||
github.com/briandowns/spinner v0.0.0-20190319032542-ac46072a5a91
|
||||
github.com/caarlos0/env/v6 v6.0.0
|
||||
github.com/genuinetools/reg v0.16.1
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/google/go-github/v28 v28.1.1
|
||||
github.com/google/wire v0.3.0
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
||||
github.com/knqyf263/go-version v1.1.1
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/twitchtv/twirp v5.9.0+incompatible
|
||||
github.com/urfave/cli v1.20.0
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
go.uber.org/atomic v1.5.1 // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -19,7 +19,9 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
@@ -127,6 +129,8 @@ github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
@@ -140,6 +144,9 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/wire v0.3.0 h1:imGQZGEVEHpje5056+K+cgdO72p0LQv2xIIFXNGUf60=
|
||||
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -192,8 +199,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed h1:fCWISZq4YN4ulCJx7x0KB15rqxLEe3mtNJL8cSOGKZU=
|
||||
github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed/go.mod h1:SDJ4hurDYyQ9/7nc+eCYtXqdufgK4Cq9TJlwPklqEYA=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
@@ -280,6 +291,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/twitchtv/twirp v5.9.0+incompatible h1:KBCo4NYCpE9alO1HAEcgninDnw/0AhPT1rZnHkkSqi8=
|
||||
github.com/twitchtv/twirp v5.9.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
@@ -379,6 +392,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
381
integration/client_server_test.go
Normal file
381
integration/client_server_test.go
Normal file
@@ -0,0 +1,381 @@
|
||||
// +build integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestClientServer(t *testing.T) {
|
||||
type args struct {
|
||||
Version string
|
||||
IgnoreUnfixed bool
|
||||
Severity []string
|
||||
IgnoreIDs []string
|
||||
Input string
|
||||
ClientToken string
|
||||
ServerToken string
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
testArgs args
|
||||
golden string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "alpine 3.10 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/alpine-310.tar.gz",
|
||||
},
|
||||
golden: "testdata/alpine-310.json.golden",
|
||||
},
|
||||
{
|
||||
name: "alpine 3.10 integration with token",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/alpine-310.tar.gz",
|
||||
ClientToken: "token",
|
||||
ServerToken: "token",
|
||||
},
|
||||
golden: "testdata/alpine-310.json.golden",
|
||||
},
|
||||
{
|
||||
name: "alpine 3.10 integration with --ignore-unfixed option",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Input: "testdata/fixtures/alpine-310.tar.gz",
|
||||
},
|
||||
golden: "testdata/alpine-310-ignore-unfixed.json.golden",
|
||||
},
|
||||
{
|
||||
name: "alpine 3.10 integration with medium and high severity",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Severity: []string{"MEDIUM", "HIGH"},
|
||||
Input: "testdata/fixtures/alpine-310.tar.gz",
|
||||
},
|
||||
golden: "testdata/alpine-310-medium-high.json.golden",
|
||||
},
|
||||
{
|
||||
name: "alpine 3.10 integration with .trivyignore",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: false,
|
||||
IgnoreIDs: []string{"CVE-2019-1549", "CVE-2019-1563"},
|
||||
Input: "testdata/fixtures/alpine-310.tar.gz",
|
||||
},
|
||||
golden: "testdata/alpine-310-ignore-cveids.json.golden",
|
||||
},
|
||||
{
|
||||
name: "alpine 3.9 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/alpine-39.tar.gz",
|
||||
},
|
||||
golden: "testdata/alpine-39.json.golden",
|
||||
},
|
||||
{
|
||||
name: "debian buster integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/debian-buster.tar.gz",
|
||||
},
|
||||
golden: "testdata/debian-buster.json.golden",
|
||||
},
|
||||
{
|
||||
name: "debian buster integration with --ignore-unfixed option",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Input: "testdata/fixtures/debian-buster.tar.gz",
|
||||
},
|
||||
golden: "testdata/debian-buster-ignore-unfixed.json.golden",
|
||||
},
|
||||
{
|
||||
name: "debian stretch integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/debian-stretch.tar.gz",
|
||||
},
|
||||
golden: "testdata/debian-stretch.json.golden",
|
||||
},
|
||||
{
|
||||
name: "ubuntu 18.04 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/ubuntu-1804.tar.gz",
|
||||
},
|
||||
golden: "testdata/ubuntu-1804.json.golden",
|
||||
},
|
||||
{
|
||||
name: "ubuntu 18.04 integration with --ignore-unfixed option",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Input: "testdata/fixtures/ubuntu-1804.tar.gz",
|
||||
},
|
||||
golden: "testdata/ubuntu-1804-ignore-unfixed.json.golden",
|
||||
},
|
||||
{
|
||||
name: "ubuntu 16.04 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/ubuntu-1604.tar.gz",
|
||||
},
|
||||
golden: "testdata/ubuntu-1604.json.golden",
|
||||
},
|
||||
{
|
||||
name: "centos 7 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/centos-7.tar.gz",
|
||||
},
|
||||
golden: "testdata/centos-7.json.golden",
|
||||
},
|
||||
{
|
||||
name: "centos 7 integration with --ignore-unfixed option",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Input: "testdata/fixtures/centos-7.tar.gz",
|
||||
},
|
||||
golden: "testdata/centos-7-ignore-unfixed.json.golden",
|
||||
},
|
||||
{
|
||||
name: "centos 7 integration with critical severity",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Severity: []string{"CRITICAL"},
|
||||
Input: "testdata/fixtures/centos-7.tar.gz",
|
||||
},
|
||||
golden: "testdata/centos-7-critical.json.golden",
|
||||
},
|
||||
{
|
||||
name: "centos 7 integration with low and high severity",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Severity: []string{"LOW", "HIGH"},
|
||||
Input: "testdata/fixtures/centos-7.tar.gz",
|
||||
},
|
||||
golden: "testdata/centos-7-low-high.json.golden",
|
||||
},
|
||||
{
|
||||
name: "centos 6 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/centos-6.tar.gz",
|
||||
},
|
||||
golden: "testdata/centos-6.json.golden",
|
||||
},
|
||||
{
|
||||
name: "ubi 7 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/ubi-7.tar.gz",
|
||||
},
|
||||
golden: "testdata/ubi-7.json.golden",
|
||||
},
|
||||
{
|
||||
name: "distroless base integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/distroless-base.tar.gz",
|
||||
},
|
||||
golden: "testdata/distroless-base.json.golden",
|
||||
},
|
||||
{
|
||||
name: "distroless base integration with --ignore-unfixed option",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
IgnoreUnfixed: true,
|
||||
Input: "testdata/fixtures/distroless-base.tar.gz",
|
||||
},
|
||||
golden: "testdata/distroless-base-ignore-unfixed.json.golden",
|
||||
},
|
||||
{
|
||||
name: "distroless python27 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/distroless-python27.tar.gz",
|
||||
},
|
||||
golden: "testdata/distroless-python27.json.golden",
|
||||
},
|
||||
{
|
||||
name: "amazon 1 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/amazon-1.tar.gz",
|
||||
},
|
||||
golden: "testdata/amazon-1.json.golden",
|
||||
},
|
||||
{
|
||||
name: "amazon 2 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/amazon-2.tar.gz",
|
||||
},
|
||||
golden: "testdata/amazon-2.json.golden",
|
||||
},
|
||||
{
|
||||
name: "oracle 6 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/oraclelinux-6-slim.tar.gz",
|
||||
},
|
||||
golden: "testdata/oraclelinux-6-slim.json.golden",
|
||||
},
|
||||
{
|
||||
name: "oracle 7 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/oraclelinux-7-slim.tar.gz",
|
||||
},
|
||||
golden: "testdata/oraclelinux-7-slim.json.golden",
|
||||
},
|
||||
{
|
||||
name: "oracle 8 integration",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/oraclelinux-8-slim.tar.gz",
|
||||
},
|
||||
golden: "testdata/oraclelinux-8-slim.json.golden",
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
testArgs: args{
|
||||
Version: "dev",
|
||||
Input: "testdata/fixtures/distroless-base.tar.gz",
|
||||
ClientToken: "invalidtoken",
|
||||
ServerToken: "token",
|
||||
},
|
||||
wantErr: "twirp error unauthenticated: invalid token",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
// Copy DB file
|
||||
cacheDir := gunzipDB()
|
||||
defer os.RemoveAll(cacheDir)
|
||||
|
||||
port, err := getFreePort()
|
||||
require.NoError(t, err, c.name)
|
||||
addr := fmt.Sprintf("localhost:%d", port)
|
||||
|
||||
go func() {
|
||||
// Setup CLI App
|
||||
app := internal.NewApp(c.testArgs.Version)
|
||||
app.Writer = ioutil.Discard
|
||||
osArgs := setupServer(addr, c.testArgs.ServerToken, cacheDir)
|
||||
|
||||
// Run Trivy server
|
||||
require.NoError(t, app.Run(osArgs), c.name)
|
||||
}()
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
require.NoError(t, waitPort(ctx, addr), c.name)
|
||||
|
||||
// Setup CLI App
|
||||
app := internal.NewApp(c.testArgs.Version)
|
||||
app.Writer = ioutil.Discard
|
||||
|
||||
osArgs, outputFile, cleanup := setupClient(t, c.testArgs.IgnoreUnfixed, c.testArgs.Severity,
|
||||
c.testArgs.IgnoreIDs, addr, c.testArgs.ClientToken, c.testArgs.Input, cacheDir, c.golden)
|
||||
defer cleanup()
|
||||
|
||||
// Run Trivy client
|
||||
err = app.Run(osArgs)
|
||||
|
||||
if c.wantErr != "" {
|
||||
require.NotNil(t, err, c.name)
|
||||
assert.Contains(t, err.Error(), c.wantErr, c.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, c.name)
|
||||
}
|
||||
|
||||
// Compare want and got
|
||||
want, err := ioutil.ReadFile(c.golden)
|
||||
assert.NoError(t, err)
|
||||
got, err := ioutil.ReadFile(outputFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.JSONEq(t, string(want), string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupServer(addr, token, cacheDir string) []string {
|
||||
osArgs := []string{"trivy", "server", "--skip-update", "--cache-dir", cacheDir, "--listen", addr}
|
||||
if token != "" {
|
||||
osArgs = append(osArgs, []string{"--token", token}...)
|
||||
}
|
||||
return osArgs
|
||||
}
|
||||
|
||||
func setupClient(t *testing.T, ignoreUnfixed bool, severity, ignoreIDs []string,
|
||||
addr, token, input, cacheDir, golden string) ([]string, string, func()) {
|
||||
t.Helper()
|
||||
osArgs := []string{"trivy", "client", "--cache-dir", cacheDir,
|
||||
"--format", "json", "--remote", "http://" + addr}
|
||||
if ignoreUnfixed {
|
||||
osArgs = append(osArgs, "--ignore-unfixed")
|
||||
}
|
||||
if len(severity) != 0 {
|
||||
osArgs = append(osArgs,
|
||||
[]string{"--severity", strings.Join(severity, ",")}...,
|
||||
)
|
||||
}
|
||||
|
||||
var err error
|
||||
var ignoreTmpDir string
|
||||
if len(ignoreIDs) != 0 {
|
||||
ignoreTmpDir, err = ioutil.TempDir("", "ignore")
|
||||
require.NoError(t, err, "failed to create a temp dir")
|
||||
trivyIgnore := filepath.Join(ignoreTmpDir, ".trivyignore")
|
||||
err = ioutil.WriteFile(trivyIgnore, []byte(strings.Join(ignoreIDs, "\n")), 0444)
|
||||
require.NoError(t, err, "failed to write .trivyignore")
|
||||
osArgs = append(osArgs, []string{"--ignorefile", trivyIgnore}...)
|
||||
}
|
||||
if token != "" {
|
||||
osArgs = append(osArgs, []string{"--token", token}...)
|
||||
}
|
||||
if input != "" {
|
||||
osArgs = append(osArgs, []string{"--input", input}...)
|
||||
}
|
||||
|
||||
// Setup the output file
|
||||
var outputFile string
|
||||
if *update {
|
||||
outputFile = golden
|
||||
} else {
|
||||
output, _ := ioutil.TempFile("", "integration")
|
||||
assert.Nil(t, output.Close())
|
||||
outputFile = output.Name()
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
_ = os.Remove(ignoreTmpDir)
|
||||
_ = os.Remove(outputFile)
|
||||
}
|
||||
|
||||
osArgs = append(osArgs, []string{"--output", outputFile}...)
|
||||
return osArgs, outputFile, cleanup
|
||||
}
|
||||
80
integration/integration_test.go
Normal file
80
integration/integration_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// +build integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update golden files")
|
||||
|
||||
func gunzipDB() string {
|
||||
gz, err := os.Open("testdata/trivy.db.gz")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
zr, err := gzip.NewReader(gz)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "integration")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
dbDir := filepath.Join(tmpDir, "db")
|
||||
err = os.MkdirAll(dbDir, 0700)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(dbDir, "trivy.db"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, zr)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
func getFreePort() (int, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer l.Close()
|
||||
return l.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
|
||||
func waitPort(ctx context.Context, addr string) error {
|
||||
for {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil && conn != nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return err
|
||||
default:
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,18 @@
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
package integration
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/standalone"
|
||||
"github.com/aquasecurity/trivy/internal"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update golden files")
|
||||
|
||||
func gunzipDB() string {
|
||||
gz, err := os.Open("testdata/trivy.db.gz")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
zr, err := gzip.NewReader(gz)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "integration")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
dbDir := filepath.Join(tmpDir, "db")
|
||||
err = os.MkdirAll(dbDir, 0700)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Join(dbDir, "trivy.db"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, zr)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
func TestRun_WithTar(t *testing.T) {
|
||||
type args struct {
|
||||
Version string
|
||||
@@ -340,7 +300,7 @@ func TestRun_WithTar(t *testing.T) {
|
||||
defer os.RemoveAll(cacheDir)
|
||||
|
||||
// Setup CLI App
|
||||
app := standalone.NewApp(c.testArgs.Version)
|
||||
app := internal.NewApp(c.testArgs.Version)
|
||||
app.Writer = ioutil.Discard
|
||||
|
||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "--format", c.testArgs.Format}
|
||||
280
internal/app.go
Normal file
280
internal/app.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/client"
|
||||
"github.com/aquasecurity/trivy/internal/server"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/standalone"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
templateFlag = cli.StringFlag{
|
||||
Name: "template, t",
|
||||
Value: "",
|
||||
Usage: "output template",
|
||||
EnvVar: "TRIVY_TEMPLATE",
|
||||
}
|
||||
|
||||
formatFlag = cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: "format (table, json, template)",
|
||||
EnvVar: "TRIVY_FORMAT",
|
||||
}
|
||||
|
||||
inputFlag = cli.StringFlag{
|
||||
Name: "input, i",
|
||||
Value: "",
|
||||
Usage: "input file path instead of image name",
|
||||
EnvVar: "TRIVY_INPUT",
|
||||
}
|
||||
|
||||
severityFlag = cli.StringFlag{
|
||||
Name: "severity, s",
|
||||
Value: strings.Join(types.SeverityNames, ","),
|
||||
Usage: "severities of vulnerabilities to be displayed (comma separated)",
|
||||
EnvVar: "TRIVY_SEVERITY",
|
||||
}
|
||||
|
||||
outputFlag = cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "output file name",
|
||||
EnvVar: "TRIVY_OUTPUT",
|
||||
}
|
||||
|
||||
exitCodeFlag = cli.IntFlag{
|
||||
Name: "exit-code",
|
||||
Usage: "Exit code when vulnerabilities were found",
|
||||
Value: 0,
|
||||
EnvVar: "TRIVY_EXIT_CODE",
|
||||
}
|
||||
|
||||
skipUpdateFlag = cli.BoolFlag{
|
||||
Name: "skip-update",
|
||||
Usage: "skip db update",
|
||||
EnvVar: "TRIVY_SKIP_UPDATE",
|
||||
}
|
||||
|
||||
downloadDBOnlyFlag = cli.BoolFlag{
|
||||
Name: "download-db-only",
|
||||
Usage: "download/update vulnerability database but don't run a scan",
|
||||
EnvVar: "TRIVY_DOWNLOAD_DB_ONLY",
|
||||
}
|
||||
|
||||
resetFlag = cli.BoolFlag{
|
||||
Name: "reset",
|
||||
Usage: "remove all caches and database",
|
||||
EnvVar: "TRIVY_RESET",
|
||||
}
|
||||
|
||||
clearCacheFlag = cli.BoolFlag{
|
||||
Name: "clear-cache, c",
|
||||
Usage: "clear image caches without scanning",
|
||||
EnvVar: "TRIVY_CLEAR_CACHE",
|
||||
}
|
||||
|
||||
quietFlag = cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "suppress progress bar and log output",
|
||||
EnvVar: "TRIVY_QUIET",
|
||||
}
|
||||
|
||||
noProgressFlag = cli.BoolFlag{
|
||||
Name: "no-progress",
|
||||
Usage: "suppress progress bar",
|
||||
EnvVar: "TRIVY_NO_PROGRESS",
|
||||
}
|
||||
|
||||
ignoreUnfixedFlag = cli.BoolFlag{
|
||||
Name: "ignore-unfixed",
|
||||
Usage: "display only fixed vulnerabilities",
|
||||
EnvVar: "TRIVY_IGNORE_UNFIXED",
|
||||
}
|
||||
|
||||
debugFlag = cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "debug mode",
|
||||
EnvVar: "TRIVY_DEBUG",
|
||||
}
|
||||
|
||||
vulnTypeFlag = cli.StringFlag{
|
||||
Name: "vuln-type",
|
||||
Value: "os,library",
|
||||
Usage: "comma-separated list of vulnerability types (os,library)",
|
||||
EnvVar: "TRIVY_VULN_TYPE",
|
||||
}
|
||||
|
||||
cacheDirFlag = cli.StringFlag{
|
||||
Name: "cache-dir",
|
||||
Value: utils.DefaultCacheDir(),
|
||||
Usage: "use as cache directory, but image cache is stored in /path/to/cache/fanal",
|
||||
EnvVar: "TRIVY_CACHE_DIR",
|
||||
}
|
||||
|
||||
ignoreFileFlag = cli.StringFlag{
|
||||
Name: "ignorefile",
|
||||
Value: vulnerability.DefaultIgnoreFile,
|
||||
Usage: "specify .trivyignore file",
|
||||
EnvVar: "TRIVY_IGNOREFILE",
|
||||
}
|
||||
|
||||
timeoutFlag = cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: time.Second * 60,
|
||||
Usage: "docker timeout",
|
||||
EnvVar: "TRIVY_TIMEOUT",
|
||||
}
|
||||
|
||||
lightFlag = cli.BoolFlag{
|
||||
Name: "light",
|
||||
Usage: "light mode: it's faster, but vulnerability descriptions and references are not displayed",
|
||||
EnvVar: "TRIVY_LIGHT",
|
||||
}
|
||||
|
||||
token = cli.StringFlag{
|
||||
Name: "token",
|
||||
Usage: "for authentication",
|
||||
EnvVar: "TRIVY_TOKEN",
|
||||
}
|
||||
)
|
||||
|
||||
func NewApp(version string) *cli.App {
|
||||
cli.AppHelpTemplate = `NAME:
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if len .Authors}}
|
||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
||||
OPTIONS:
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||
{{end}}{{$option}}{{end}}{{end}}
|
||||
`
|
||||
app := cli.NewApp()
|
||||
app.Name = "trivy"
|
||||
app.Version = version
|
||||
app.ArgsUsage = "image_name"
|
||||
|
||||
app.Usage = "A simple and comprehensive vulnerability scanner for containers"
|
||||
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
templateFlag,
|
||||
formatFlag,
|
||||
inputFlag,
|
||||
severityFlag,
|
||||
outputFlag,
|
||||
exitCodeFlag,
|
||||
skipUpdateFlag,
|
||||
downloadDBOnlyFlag,
|
||||
resetFlag,
|
||||
clearCacheFlag,
|
||||
quietFlag,
|
||||
noProgressFlag,
|
||||
ignoreUnfixedFlag,
|
||||
debugFlag,
|
||||
vulnTypeFlag,
|
||||
cacheDirFlag,
|
||||
ignoreFileFlag,
|
||||
timeoutFlag,
|
||||
lightFlag,
|
||||
|
||||
// deprecated options
|
||||
cli.StringFlag{
|
||||
Name: "only-update",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_ONLY_UPDATE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "refresh",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_REFRESH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "auto-refresh",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_AUTO_REFRESH",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
NewClientCommand(),
|
||||
NewServerCommand(),
|
||||
}
|
||||
|
||||
app.Action = standalone.Run
|
||||
return app
|
||||
}
|
||||
|
||||
func NewClientCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "client",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "client mode",
|
||||
Action: client.Run,
|
||||
Flags: []cli.Flag{
|
||||
templateFlag,
|
||||
formatFlag,
|
||||
inputFlag,
|
||||
severityFlag,
|
||||
outputFlag,
|
||||
exitCodeFlag,
|
||||
clearCacheFlag,
|
||||
quietFlag,
|
||||
ignoreUnfixedFlag,
|
||||
debugFlag,
|
||||
vulnTypeFlag,
|
||||
ignoreFileFlag,
|
||||
cacheDirFlag,
|
||||
timeoutFlag,
|
||||
|
||||
// original flags
|
||||
token,
|
||||
cli.StringFlag{
|
||||
Name: "remote",
|
||||
Value: "http://localhost:4954",
|
||||
Usage: "server address",
|
||||
EnvVar: "TRIVY_REMOTE",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewServerCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "server mode",
|
||||
Action: server.Run,
|
||||
Flags: []cli.Flag{
|
||||
skipUpdateFlag,
|
||||
downloadDBOnlyFlag,
|
||||
resetFlag,
|
||||
quietFlag,
|
||||
debugFlag,
|
||||
cacheDirFlag,
|
||||
|
||||
// original flags
|
||||
token,
|
||||
cli.StringFlag{
|
||||
Name: "listen",
|
||||
Value: "localhost:4954",
|
||||
Usage: "listen address",
|
||||
EnvVar: "TRIVY_LISTEN",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
142
internal/client/config/config.go
Normal file
142
internal/client/config/config.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
context *cli.Context
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
Quiet bool
|
||||
Debug bool
|
||||
|
||||
CacheDir string
|
||||
ClearCache bool
|
||||
|
||||
Input string
|
||||
output string
|
||||
Format string
|
||||
Template string
|
||||
|
||||
Timeout time.Duration
|
||||
vulnType string
|
||||
severities string
|
||||
IgnoreFile string
|
||||
IgnoreUnfixed bool
|
||||
ExitCode int
|
||||
|
||||
RemoteAddr string
|
||||
Token string
|
||||
|
||||
// these variables are generated by Init()
|
||||
ImageName string
|
||||
VulnType []string
|
||||
Output *os.File
|
||||
Severities []dbTypes.Severity
|
||||
AppVersion string
|
||||
}
|
||||
|
||||
func New(c *cli.Context) (Config, error) {
|
||||
debug := c.Bool("debug")
|
||||
quiet := c.Bool("quiet")
|
||||
logger, err := log.NewLogger(debug, quiet)
|
||||
if err != nil {
|
||||
return Config{}, xerrors.New("failed to create a logger")
|
||||
}
|
||||
return Config{
|
||||
context: c,
|
||||
logger: logger,
|
||||
|
||||
Quiet: quiet,
|
||||
Debug: debug,
|
||||
|
||||
CacheDir: c.String("cache-dir"),
|
||||
ClearCache: c.Bool("clear-cache"),
|
||||
|
||||
Input: c.String("input"),
|
||||
output: c.String("output"),
|
||||
Format: c.String("format"),
|
||||
Template: c.String("template"),
|
||||
|
||||
Timeout: c.Duration("timeout"),
|
||||
vulnType: c.String("vuln-type"),
|
||||
severities: c.String("severity"),
|
||||
IgnoreFile: c.String("ignorefile"),
|
||||
IgnoreUnfixed: c.Bool("ignore-unfixed"),
|
||||
ExitCode: c.Int("exit-code"),
|
||||
|
||||
RemoteAddr: c.String("remote"),
|
||||
Token: c.String("token"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Config) Init() (err error) {
|
||||
c.Severities = c.splitSeverity(c.severities)
|
||||
c.VulnType = strings.Split(c.vulnType, ",")
|
||||
c.AppVersion = c.context.App.Version
|
||||
|
||||
if c.Quiet {
|
||||
utils.Quiet = true
|
||||
}
|
||||
|
||||
// --clear-cache doesn't conduct the scan
|
||||
if c.ClearCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := c.context.Args()
|
||||
if c.Input == "" && len(args) == 0 {
|
||||
c.logger.Error(`trivy requires at least 1 argument or --input option`)
|
||||
cli.ShowAppHelp(c.context)
|
||||
return xerrors.New("arguments error")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Input == "" {
|
||||
c.ImageName = args[0]
|
||||
}
|
||||
|
||||
// Check whether 'latest' tag is used
|
||||
if c.ImageName != "" {
|
||||
image, err := registry.ParseImage(c.ImageName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid image: %w", err)
|
||||
}
|
||||
if image.Tag == "latest" {
|
||||
c.logger.Warn("You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) splitSeverity(severity string) []dbTypes.Severity {
|
||||
c.logger.Debugf("Severities: %s", severity)
|
||||
var severities []dbTypes.Severity
|
||||
for _, s := range strings.Split(severity, ",") {
|
||||
severity, err := dbTypes.NewSeverity(s)
|
||||
if err != nil {
|
||||
c.logger.Warnf("unknown severity option: %s", err)
|
||||
}
|
||||
severities = append(severities, severity)
|
||||
}
|
||||
return severities
|
||||
}
|
||||
229
internal/client/config/config_test.go
Normal file
229
internal/client/config/config_test.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want Config
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: []string{"-quiet", "--cache-dir", "/tmp/test"},
|
||||
want: Config{
|
||||
Quiet: true,
|
||||
CacheDir: "/tmp/test",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := &cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("quiet", false, "")
|
||||
set.String("cache-dir", "", "")
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
got, err := New(c)
|
||||
|
||||
// avoid to compare these values because these values are pointer
|
||||
tt.want.context = c
|
||||
tt.want.logger = got.logger
|
||||
|
||||
assert.NoError(t, err, tt.name)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Init(t *testing.T) {
|
||||
type fields struct {
|
||||
context *cli.Context
|
||||
Quiet bool
|
||||
NoProgress bool
|
||||
Debug bool
|
||||
CacheDir string
|
||||
Reset bool
|
||||
DownloadDBOnly bool
|
||||
SkipUpdate bool
|
||||
ClearCache bool
|
||||
Input string
|
||||
output string
|
||||
Format string
|
||||
Template string
|
||||
Timeout time.Duration
|
||||
vulnType string
|
||||
Light bool
|
||||
severities string
|
||||
IgnoreFile string
|
||||
IgnoreUnfixed bool
|
||||
ExitCode int
|
||||
ImageName string
|
||||
VulnType []string
|
||||
Output *os.File
|
||||
Severities []dbTypes.Severity
|
||||
AppVersion string
|
||||
onlyUpdate string
|
||||
refresh bool
|
||||
autoRefresh bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args []string
|
||||
logs []string
|
||||
want Config
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
severities: "CRITICAL",
|
||||
vulnType: "os",
|
||||
Quiet: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
severities: "CRITICAL",
|
||||
ImageName: "alpine:3.10",
|
||||
VulnType: []string{"os"},
|
||||
vulnType: "os",
|
||||
Output: os.Stdout,
|
||||
Quiet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
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: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
|
||||
severities: "CRITICAL,INVALID",
|
||||
ImageName: "centos:7",
|
||||
VulnType: []string{"os", "library"},
|
||||
vulnType: "os,library",
|
||||
Output: os.Stdout,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with latest tag",
|
||||
fields: fields{
|
||||
onlyUpdate: "alpine",
|
||||
severities: "LOW",
|
||||
vulnType: "os,library",
|
||||
},
|
||||
args: []string{"gcr.io/distroless/base"},
|
||||
logs: []string{
|
||||
"You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed",
|
||||
},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
severities: "LOW",
|
||||
ImageName: "gcr.io/distroless/base",
|
||||
VulnType: []string{"os", "library"},
|
||||
vulnType: "os,library",
|
||||
Output: os.Stdout,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad: no image name",
|
||||
fields: fields{
|
||||
severities: "MEDIUM",
|
||||
},
|
||||
logs: []string{
|
||||
"trivy requires at least 1 argument or --input option",
|
||||
},
|
||||
wantErr: "arguments error",
|
||||
},
|
||||
{
|
||||
name: "sad: invalid image name",
|
||||
fields: fields{
|
||||
severities: "HIGH",
|
||||
},
|
||||
args: []string{`!"#$%&'()`},
|
||||
wantErr: "invalid image: parsing image",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
core, obs := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core)
|
||||
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
c := &Config{
|
||||
context: ctx,
|
||||
logger: logger.Sugar(),
|
||||
Quiet: tt.fields.Quiet,
|
||||
Debug: tt.fields.Debug,
|
||||
CacheDir: tt.fields.CacheDir,
|
||||
ClearCache: tt.fields.ClearCache,
|
||||
Input: tt.fields.Input,
|
||||
output: tt.fields.output,
|
||||
Format: tt.fields.Format,
|
||||
Template: tt.fields.Template,
|
||||
Timeout: tt.fields.Timeout,
|
||||
vulnType: tt.fields.vulnType,
|
||||
severities: tt.fields.severities,
|
||||
IgnoreFile: tt.fields.IgnoreFile,
|
||||
IgnoreUnfixed: tt.fields.IgnoreUnfixed,
|
||||
ExitCode: tt.fields.ExitCode,
|
||||
ImageName: tt.fields.ImageName,
|
||||
Output: tt.fields.Output,
|
||||
}
|
||||
|
||||
err := c.Init()
|
||||
|
||||
// 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, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
default:
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
tt.want.context = ctx
|
||||
tt.want.logger = logger.Sugar()
|
||||
assert.Equal(t, &tt.want, c, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
21
internal/client/inject.go
Normal file
21
internal/client/inject.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// +build wireinject
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/library"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func initializeScanner(ospkgToken ospkg.Token, libToken library.Token, ospkgURL ospkg.RemoteURL, libURL library.RemoteURL) scanner.Scanner {
|
||||
wire.Build(scanner.ClientSet)
|
||||
return scanner.Scanner{}
|
||||
}
|
||||
|
||||
func initializeVulnerabilityClient() vulnerability.Client {
|
||||
wire.Build(vulnerability.SuperSet)
|
||||
return vulnerability.Client{}
|
||||
}
|
||||
73
internal/client/run.go
Normal file
73
internal/client/run.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/internal/client/config"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/library"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
func Run(cliCtx *cli.Context) error {
|
||||
c, err := config.New(cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return run(c)
|
||||
}
|
||||
|
||||
func run(c config.Config) (err error) {
|
||||
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
|
||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
||||
}
|
||||
|
||||
// initialize config
|
||||
if err = c.Init(); err != nil {
|
||||
return xerrors.Errorf("failed to initialize options: %w", err)
|
||||
}
|
||||
|
||||
// configure cache dir
|
||||
utils.SetCacheDir(c.CacheDir)
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
scanOptions := types.ScanOptions{
|
||||
VulnType: c.VulnType,
|
||||
Timeout: c.Timeout,
|
||||
RemoteURL: c.RemoteAddr,
|
||||
Token: c.Token,
|
||||
}
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
scanner := initializeScanner(ospkg.Token(c.Token), library.Token(c.Token),
|
||||
ospkg.RemoteURL(c.RemoteAddr), library.RemoteURL(c.RemoteAddr))
|
||||
results, err := scanner.ScanImage(c.ImageName, c.Input, scanOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
vulnClient := initializeVulnerabilityClient()
|
||||
for i := range results {
|
||||
results[i].Vulnerabilities = vulnClient.Filter(results[i].Vulnerabilities,
|
||||
c.Severities, c.IgnoreUnfixed, c.IgnoreFile)
|
||||
}
|
||||
|
||||
if err = report.WriteResults(c.Format, c.Output, results, c.Template, false); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
if c.ExitCode != 0 {
|
||||
for _, result := range results {
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
os.Exit(c.ExitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
35
internal/client/wire_gen.go
Normal file
35
internal/client/wire_gen.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//+build !wireinject
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/library"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
library2 "github.com/aquasecurity/trivy/pkg/scanner/library"
|
||||
ospkg2 "github.com/aquasecurity/trivy/pkg/scanner/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
func initializeScanner(ospkgToken ospkg.Token, libToken library.Token, ospkgURL ospkg.RemoteURL, libURL library.RemoteURL) scanner.Scanner {
|
||||
osDetector := ospkg.NewProtobufClient(ospkgURL)
|
||||
detector := ospkg.NewDetector(ospkgToken, osDetector)
|
||||
ospkgScanner := ospkg2.NewScanner(detector)
|
||||
libDetector := library.NewProtobufClient(libURL)
|
||||
libraryDetector := library.NewDetector(libToken, libDetector)
|
||||
libraryScanner := library2.NewScanner(libraryDetector)
|
||||
scannerScanner := scanner.NewScanner(ospkgScanner, libraryScanner)
|
||||
return scannerScanner
|
||||
}
|
||||
|
||||
func initializeVulnerabilityClient() vulnerability.Client {
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
return client
|
||||
}
|
||||
13
internal/operation/inject.go
Normal file
13
internal/operation/inject.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build wireinject
|
||||
|
||||
package operation
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func initializeDBClient() db.Client {
|
||||
wire.Build(db.SuperSet)
|
||||
return db.Client{}
|
||||
}
|
||||
72
internal/operation/operation.go
Normal file
72
internal/operation/operation.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package operation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
func Reset() (err error) {
|
||||
log.Logger.Info("Resetting...")
|
||||
if err = cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
||||
return xerrors.New("failed to remove cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClearCache() error {
|
||||
log.Logger.Info("Removing image caches...")
|
||||
if err := cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DownloadDB(appVersion, cacheDir string, light, skipUpdate bool) error {
|
||||
client := initializeDBClient()
|
||||
ctx := context.Background()
|
||||
needsUpdate, err := client.NeedsUpdate(ctx, appVersion, light, skipUpdate)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
log.Logger.Info("Need to update DB")
|
||||
if err = db.Close(); err != nil {
|
||||
return xerrors.Errorf("failed db close: %w", err)
|
||||
}
|
||||
if err := client.Download(ctx, cacheDir, light); err != nil {
|
||||
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Info("Reopening DB...")
|
||||
if err = db.Init(cacheDir); err != nil {
|
||||
return xerrors.Errorf("failed db close: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// for debug
|
||||
if err := showDBInfo(); err != nil {
|
||||
return xerrors.Errorf("failed to show database info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showDBInfo() error {
|
||||
metadata, err := db.Config{}.GetMetadata()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("something wrong with DB: %w", err)
|
||||
}
|
||||
log.Logger.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
|
||||
metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
|
||||
return nil
|
||||
}
|
||||
23
internal/operation/wire_gen.go
Normal file
23
internal/operation/wire_gen.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//+build !wireinject
|
||||
|
||||
package operation
|
||||
|
||||
import (
|
||||
db2 "github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/github"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
func initializeDBClient() db.Client {
|
||||
config := db2.Config{}
|
||||
client := github.NewClient()
|
||||
realClock := clock.RealClock{}
|
||||
dbClient := db.NewClient(config, client, realClock)
|
||||
return dbClient
|
||||
}
|
||||
55
internal/server/config/config.go
Normal file
55
internal/server/config/config.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
context *cli.Context
|
||||
|
||||
Quiet bool
|
||||
Debug bool
|
||||
CacheDir string
|
||||
Reset bool
|
||||
DownloadDBOnly bool
|
||||
SkipUpdate bool
|
||||
|
||||
Listen string
|
||||
Token string
|
||||
|
||||
// these variables are generated by Init()
|
||||
AppVersion string
|
||||
}
|
||||
|
||||
func New(c *cli.Context) Config {
|
||||
debug := c.Bool("debug")
|
||||
quiet := c.Bool("quiet")
|
||||
return Config{
|
||||
context: c,
|
||||
|
||||
Quiet: quiet,
|
||||
Debug: debug,
|
||||
CacheDir: c.String("cache-dir"),
|
||||
Reset: c.Bool("reset"),
|
||||
DownloadDBOnly: c.Bool("download-db-only"),
|
||||
SkipUpdate: c.Bool("skip-update"),
|
||||
Listen: c.String("listen"),
|
||||
Token: c.String("token"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Init() (err error) {
|
||||
if c.SkipUpdate && c.DownloadDBOnly {
|
||||
return xerrors.New("The --skip-update and --download-db-only option can not be specified both")
|
||||
}
|
||||
|
||||
c.AppVersion = c.context.App.Version
|
||||
|
||||
// A server always suppresses a progress bar
|
||||
utils.Quiet = true
|
||||
|
||||
return nil
|
||||
}
|
||||
159
internal/server/config/config_test.go
Normal file
159
internal/server/config/config_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want Config
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: []string{"-quiet", "--no-progress", "--reset", "--skip-update"},
|
||||
want: Config{
|
||||
Quiet: true,
|
||||
Reset: true,
|
||||
SkipUpdate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := &cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("quiet", false, "")
|
||||
set.Bool("no-progress", false, "")
|
||||
set.Bool("reset", false, "")
|
||||
set.Bool("skip-update", false, "")
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
tt.want.context = c
|
||||
|
||||
got := New(c)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Init(t *testing.T) {
|
||||
type fields struct {
|
||||
context *cli.Context
|
||||
Quiet bool
|
||||
Debug bool
|
||||
CacheDir string
|
||||
Reset bool
|
||||
DownloadDBOnly bool
|
||||
SkipUpdate bool
|
||||
ClearCache bool
|
||||
Input string
|
||||
output string
|
||||
Format string
|
||||
Template string
|
||||
Timeout time.Duration
|
||||
vulnType string
|
||||
Light bool
|
||||
severities string
|
||||
IgnoreFile string
|
||||
IgnoreUnfixed bool
|
||||
ExitCode int
|
||||
ImageName string
|
||||
VulnType []string
|
||||
Output *os.File
|
||||
Severities []dbTypes.Severity
|
||||
AppVersion string
|
||||
onlyUpdate string
|
||||
refresh bool
|
||||
autoRefresh bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args []string
|
||||
logs []string
|
||||
want Config
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
severities: "CRITICAL",
|
||||
vulnType: "os",
|
||||
Quiet: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Quiet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path: reset",
|
||||
fields: fields{
|
||||
severities: "CRITICAL",
|
||||
vulnType: "os",
|
||||
Reset: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Reset: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad: skip and download db",
|
||||
fields: fields{
|
||||
refresh: true,
|
||||
SkipUpdate: true,
|
||||
DownloadDBOnly: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
wantErr: "The --skip-update and --download-db-only option can not be specified both",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
c := &Config{
|
||||
context: ctx,
|
||||
Quiet: tt.fields.Quiet,
|
||||
Debug: tt.fields.Debug,
|
||||
CacheDir: tt.fields.CacheDir,
|
||||
Reset: tt.fields.Reset,
|
||||
DownloadDBOnly: tt.fields.DownloadDBOnly,
|
||||
SkipUpdate: tt.fields.SkipUpdate,
|
||||
}
|
||||
|
||||
err := c.Init()
|
||||
|
||||
// test the error
|
||||
switch {
|
||||
case tt.wantErr != "":
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
default:
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
tt.want.context = ctx
|
||||
assert.Equal(t, &tt.want, c, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
51
internal/server/run.go
Normal file
51
internal/server/run.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/server"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/internal/operation"
|
||||
"github.com/aquasecurity/trivy/internal/server/config"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Run(ctx *cli.Context) error {
|
||||
return run(config.New(ctx))
|
||||
}
|
||||
|
||||
func run(c config.Config) (err error) {
|
||||
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
|
||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
||||
}
|
||||
|
||||
// initialize config
|
||||
if err = c.Init(); err != nil {
|
||||
return xerrors.Errorf("failed to initialize options: %w", err)
|
||||
}
|
||||
|
||||
// configure cache dir
|
||||
utils.SetCacheDir(c.CacheDir)
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
if c.Reset {
|
||||
return operation.Reset()
|
||||
}
|
||||
|
||||
if err = db.Init(c.CacheDir); err != nil {
|
||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||
}
|
||||
|
||||
// download the database file
|
||||
if err = operation.DownloadDB(c.AppVersion, c.CacheDir, false, c.SkipUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.DownloadDBOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
return server.ListenAndServe(c.Listen, c)
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func NewApp(version string) *cli.App {
|
||||
cli.AppHelpTemplate = `NAME:
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if len .Authors}}
|
||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
|
||||
OPTIONS:
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}}
|
||||
{{end}}{{$option}}{{end}}{{end}}
|
||||
`
|
||||
app := cli.NewApp()
|
||||
app.Name = "trivy"
|
||||
app.Version = version
|
||||
app.ArgsUsage = "image_name"
|
||||
|
||||
app.Usage = "A simple and comprehensive vulnerability scanner for containers"
|
||||
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "template, t",
|
||||
Value: "",
|
||||
Usage: "output template",
|
||||
EnvVar: "TRIVY_TEMPLATE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Value: "table",
|
||||
Usage: "format (table, json, template)",
|
||||
EnvVar: "TRIVY_FORMAT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "input, i",
|
||||
Value: "",
|
||||
Usage: "input file path instead of image name",
|
||||
EnvVar: "TRIVY_INPUT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "severity, s",
|
||||
Value: strings.Join(types.SeverityNames, ","),
|
||||
Usage: "severities of vulnerabilities to be displayed (comma separated)",
|
||||
EnvVar: "TRIVY_SEVERITY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "output file name",
|
||||
EnvVar: "TRIVY_OUTPUT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "exit-code",
|
||||
Usage: "Exit code when vulnerabilities were found",
|
||||
Value: 0,
|
||||
EnvVar: "TRIVY_EXIT_CODE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-update",
|
||||
Usage: "skip db update",
|
||||
EnvVar: "TRIVY_SKIP_UPDATE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "download-db-only",
|
||||
Usage: "download/update vulnerability database but don't run a scan",
|
||||
EnvVar: "TRIVY_DOWNLOAD_DB_ONLY",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "reset",
|
||||
Usage: "remove all caches and database",
|
||||
EnvVar: "TRIVY_RESET",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "clear-cache, c",
|
||||
Usage: "clear image caches without scanning",
|
||||
EnvVar: "TRIVY_CLEAR_CACHE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "suppress progress bar and log output",
|
||||
EnvVar: "TRIVY_QUIET",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-progress",
|
||||
Usage: "suppress progress bar",
|
||||
EnvVar: "TRIVY_NO_PROGRESS",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "ignore-unfixed",
|
||||
Usage: "display only fixed vulnerabilities",
|
||||
EnvVar: "TRIVY_IGNORE_UNFIXED",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "debug mode",
|
||||
EnvVar: "TRIVY_DEBUG",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "vuln-type",
|
||||
Value: "os,library",
|
||||
Usage: "comma-separated list of vulnerability types (os,library)",
|
||||
EnvVar: "TRIVY_VULN_TYPE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cache-dir",
|
||||
Value: utils.DefaultCacheDir(),
|
||||
Usage: "use as cache directory, but image cache is stored in /path/to/cache/fanal",
|
||||
EnvVar: "TRIVY_CACHE_DIR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "ignorefile",
|
||||
Value: vulnerability.DefaultIgnoreFile,
|
||||
Usage: "specify .trivyignore file",
|
||||
EnvVar: "TRIVY_IGNOREFILE",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Value: time.Second * 60,
|
||||
Usage: "docker timeout",
|
||||
EnvVar: "TRIVY_TIMEOUT",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "light",
|
||||
Usage: "light mode: it's faster, but vulnerability descriptions and references are not displayed",
|
||||
},
|
||||
|
||||
// deprecated options
|
||||
cli.StringFlag{
|
||||
Name: "only-update",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_ONLY_UPDATE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "refresh",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_REFRESH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "auto-refresh",
|
||||
Usage: "deprecated",
|
||||
EnvVar: "TRIVY_AUTO_REFRESH",
|
||||
},
|
||||
}
|
||||
|
||||
app.Action = Run
|
||||
return app
|
||||
}
|
||||
19
internal/standalone/inject.go
Normal file
19
internal/standalone/inject.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// +build wireinject
|
||||
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func initializeScanner() scanner.Scanner {
|
||||
wire.Build(scanner.StandaloneSet)
|
||||
return scanner.Scanner{}
|
||||
}
|
||||
|
||||
func initializeVulnerabilityClient() vulnerability.Client {
|
||||
wire.Build(vulnerability.SuperSet)
|
||||
return vulnerability.Client{}
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"context"
|
||||
l "log"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/internal/operation"
|
||||
"github.com/aquasecurity/trivy/internal/standalone/config"
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
@@ -42,11 +38,11 @@ func run(c config.Config) (err error) {
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
if c.Reset {
|
||||
return reset()
|
||||
return operation.Reset()
|
||||
}
|
||||
|
||||
if c.ClearCache {
|
||||
return clearCache()
|
||||
return operation.ClearCache()
|
||||
}
|
||||
|
||||
if err = db.Init(c.CacheDir); err != nil {
|
||||
@@ -54,7 +50,7 @@ func run(c config.Config) (err error) {
|
||||
}
|
||||
|
||||
// download the database file
|
||||
if err = downloadDB(c.AppVersion, c.CacheDir, c.Light, c.SkipUpdate); err != nil {
|
||||
if err = operation.DownloadDB(c.AppVersion, c.CacheDir, c.Light, c.SkipUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,15 +64,17 @@ func run(c config.Config) (err error) {
|
||||
}
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
scanner := initializeScanner()
|
||||
results, err := scanner.ScanImage(c.ImageName, c.Input, scanOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
vulnClient := vulnerability.NewClient()
|
||||
vulnClient := initializeVulnerabilityClient()
|
||||
for i := range results {
|
||||
results[i].Vulnerabilities = vulnClient.FillAndFilter(results[i].Vulnerabilities,
|
||||
c.Severities, c.IgnoreUnfixed, c.IgnoreFile, c.Light)
|
||||
vulnClient.FillInfo(results[i].Vulnerabilities, c.Light)
|
||||
results[i].Vulnerabilities = vulnClient.Filter(results[i].Vulnerabilities,
|
||||
c.Severities, c.IgnoreUnfixed, c.IgnoreFile)
|
||||
}
|
||||
|
||||
if err = report.WriteResults(c.Format, c.Output, results, c.Template, c.Light); err != nil {
|
||||
@@ -92,45 +90,3 @@ func run(c config.Config) (err error) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reset() (err error) {
|
||||
log.Logger.Info("Resetting...")
|
||||
if err = cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
||||
return xerrors.New("failed to remove cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearCache() error {
|
||||
log.Logger.Info("Removing image caches...")
|
||||
if err := cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadDB(appVersion, cacheDir string, light, skipUpdate bool) error {
|
||||
client := dbFile.NewClient()
|
||||
ctx := context.Background()
|
||||
if err := client.Download(ctx, appVersion, cacheDir, light, skipUpdate); err != nil {
|
||||
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
|
||||
}
|
||||
// for debug
|
||||
if err := showDBInfo(); err != nil {
|
||||
return xerrors.Errorf("failed to show database info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showDBInfo() error {
|
||||
metadata, err := db.Config{}.GetMetadata()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("something wrong with DB: %w", err)
|
||||
}
|
||||
log.Logger.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
|
||||
metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
|
||||
return nil
|
||||
}
|
||||
|
||||
34
internal/standalone/wire_gen.go
Normal file
34
internal/standalone/wire_gen.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//+build !wireinject
|
||||
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
library2 "github.com/aquasecurity/trivy/pkg/scanner/library"
|
||||
ospkg2 "github.com/aquasecurity/trivy/pkg/scanner/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
func initializeScanner() scanner.Scanner {
|
||||
detector := ospkg.Detector{}
|
||||
ospkgScanner := ospkg2.NewScanner(detector)
|
||||
driverFactory := library.DriverFactory{}
|
||||
libraryDetector := library.NewDetector(driverFactory)
|
||||
libraryScanner := library2.NewScanner(libraryDetector)
|
||||
scannerScanner := scanner.NewScanner(ospkgScanner, libraryScanner)
|
||||
return scannerScanner
|
||||
}
|
||||
|
||||
func initializeVulnerabilityClient() vulnerability.Client {
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
return client
|
||||
}
|
||||
83
pkg/db/db.go
83
pkg/db/db.go
@@ -5,10 +5,11 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/github"
|
||||
@@ -21,35 +22,42 @@ const (
|
||||
lightDB = "trivy-light.db.gz"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
wire.Struct(new(clock.RealClock)),
|
||||
wire.Bind(new(clock.Clock), new(clock.RealClock)),
|
||||
wire.Struct(new(db.Config)),
|
||||
github.NewClient,
|
||||
wire.Bind(new(github.Operation), new(github.Client)),
|
||||
NewClient,
|
||||
wire.Bind(new(Operation), new(Client)),
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
NeedsUpdate(ctx context.Context, cliVersion string, light, skip bool) (bool, error)
|
||||
Download(ctx context.Context, cacheDir string, light bool) error
|
||||
}
|
||||
|
||||
type dbOperation interface {
|
||||
GetMetadata() (db.Metadata, error)
|
||||
}
|
||||
|
||||
type GitHubOperation interface {
|
||||
DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
dbc Operation
|
||||
dbc dbOperation
|
||||
githubClient github.Operation
|
||||
clock clock.Clock
|
||||
githubClient GitHubOperation
|
||||
}
|
||||
|
||||
func NewClient() Client {
|
||||
func NewClient(dbc db.Config, githubClient github.Operation, clock clock.Clock) Client {
|
||||
return Client{
|
||||
dbc: db.Config{},
|
||||
clock: clock.RealClock{},
|
||||
githubClient: github.NewClient(),
|
||||
dbc: dbc,
|
||||
githubClient: githubClient,
|
||||
clock: clock,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) Download(ctx context.Context, cliVersion, cacheDir string, light, skip bool) error {
|
||||
func (c Client) NeedsUpdate(ctx context.Context, cliVersion string, light, skip bool) (bool, error) {
|
||||
dbType := db.TypeFull
|
||||
dbFile := fullDB
|
||||
message := " Downloading Full DB file..."
|
||||
if light {
|
||||
dbFile = lightDB
|
||||
message = " Downloading Lightweight DB file..."
|
||||
dbType = db.TypeLight
|
||||
}
|
||||
|
||||
@@ -58,54 +66,48 @@ func (c Client) Download(ctx context.Context, cliVersion, cacheDir string, light
|
||||
log.Logger.Debug("This is the first run")
|
||||
if skip {
|
||||
log.Logger.Error("The first run cannot skip downloading DB")
|
||||
return xerrors.New("--skip-update cannot be specified on the first run")
|
||||
return false, xerrors.New("--skip-update cannot be specified on the first run")
|
||||
}
|
||||
metadata = db.Metadata{} // suppress a warning
|
||||
}
|
||||
|
||||
if db.SchemaVersion < metadata.Version {
|
||||
log.Logger.Errorf("Trivy version (%s) is old. Update to the latest version.", cliVersion)
|
||||
return xerrors.Errorf("the version of DB schema doesn't match. Local DB: %d, Expected: %d",
|
||||
return false, xerrors.Errorf("the version of DB schema doesn't match. Local DB: %d, Expected: %d",
|
||||
metadata.Version, db.SchemaVersion)
|
||||
}
|
||||
|
||||
if skip {
|
||||
if db.SchemaVersion != metadata.Version {
|
||||
log.Logger.Error("The local DB is old and needs to be updated")
|
||||
return xerrors.New("--skip-update cannot be specified with the old DB")
|
||||
return false, xerrors.New("--skip-update cannot be specified with the old DB")
|
||||
} else if metadata.Type != dbType {
|
||||
if dbType == db.TypeFull {
|
||||
log.Logger.Error("The local DB is a lightweight DB. You have to download a full DB")
|
||||
} else {
|
||||
log.Logger.Error("The local DB is a full DB. You have to download a lightweight DB")
|
||||
}
|
||||
return xerrors.New("--skip-update cannot be specified with the different schema DB")
|
||||
return false, xerrors.New("--skip-update cannot be specified with the different schema DB")
|
||||
}
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if db.SchemaVersion == metadata.Version && metadata.Type == dbType &&
|
||||
c.clock.Now().Before(metadata.NextUpdate) {
|
||||
log.Logger.Debug("DB update was skipped because DB is the latest")
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err = c.download(ctx, cacheDir, message, dbFile); err != nil {
|
||||
return xerrors.Errorf("failed to download the DB file: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Info("Reopening vulnerability DB")
|
||||
if err = db.Close(); err != nil {
|
||||
return xerrors.Errorf("unable to close old DB: %w", err)
|
||||
}
|
||||
if err = db.Init(cacheDir); err != nil {
|
||||
return xerrors.Errorf("unable to open new DB: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c Client) download(ctx context.Context, cacheDir, message, dbFile string) error {
|
||||
func (c Client) Download(ctx context.Context, cacheDir string, light bool) error {
|
||||
dbFile := fullDB
|
||||
message := " Downloading Full DB file..."
|
||||
if light {
|
||||
dbFile = lightDB
|
||||
message = " Downloading Lightweight DB file..."
|
||||
}
|
||||
|
||||
spinner := utils.NewSpinner(message)
|
||||
spinner.Start()
|
||||
defer spinner.Stop()
|
||||
@@ -122,6 +124,11 @@ func (c Client) download(ctx context.Context, cacheDir, message, dbFile string)
|
||||
}
|
||||
|
||||
dbPath := db.Path(cacheDir)
|
||||
dbDir := filepath.Dir(dbPath)
|
||||
if err = os.MkdirAll(dbDir, 0700); err != nil {
|
||||
return xerrors.Errorf("failed to mkdir: %w", err)
|
||||
}
|
||||
|
||||
file, err := os.Create(dbPath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to open DB file: %w", err)
|
||||
|
||||
22
pkg/db/db_mock.go
Normal file
22
pkg/db/db_mock.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClient) NeedsUpdate(a context.Context, b string, c, d bool) (bool, error) {
|
||||
ret := _m.Called(a, b, c, d)
|
||||
return ret.Bool(0), ret.Error(1)
|
||||
}
|
||||
|
||||
func (_m *MockClient) Download(a context.Context, b string, c bool) error {
|
||||
ret := _m.Called(a, b, c)
|
||||
return ret.Error(0)
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
clocktesting "k8s.io/utils/clock/testing"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/github"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -39,47 +40,20 @@ func (_m *MockConfig) GetMetadata() (db.Metadata, error) {
|
||||
return metadata, ret.Error(1)
|
||||
}
|
||||
|
||||
type MockGitHubClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockGitHubClient) DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error) {
|
||||
ret := _m.Called(ctx, fileName)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
rc, ok := ret0.(io.ReadCloser)
|
||||
if !ok {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
return rc, ret.Error(1)
|
||||
}
|
||||
|
||||
func TestClient_Download(t *testing.T) {
|
||||
func TestClient_NeedsUpdate(t *testing.T) {
|
||||
type getMetadataOutput struct {
|
||||
metadata db.Metadata
|
||||
err error
|
||||
}
|
||||
|
||||
type downloadDBOutput struct {
|
||||
fileName string
|
||||
err error
|
||||
}
|
||||
type downloadDB struct {
|
||||
input string
|
||||
output downloadDBOutput
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
light bool
|
||||
skip bool
|
||||
clock clock.Clock
|
||||
getMetadata getMetadataOutput
|
||||
downloadDB []downloadDB
|
||||
expectedContent []byte
|
||||
expectedError error
|
||||
name string
|
||||
light bool
|
||||
skip bool
|
||||
clock clock.Clock
|
||||
getMetadata getMetadataOutput
|
||||
expected bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
@@ -92,14 +66,7 @@ func TestClient_Download(t *testing.T) {
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/test.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "happy path for first run",
|
||||
@@ -109,14 +76,7 @@ func TestClient_Download(t *testing.T) {
|
||||
metadata: db.Metadata{},
|
||||
err: errors.New("get metadata failed"),
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/test.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "happy path with different type",
|
||||
@@ -129,14 +89,7 @@ func TestClient_Download(t *testing.T) {
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: lightDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/test.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "happy path with old schema version",
|
||||
@@ -149,14 +102,7 @@ func TestClient_Download(t *testing.T) {
|
||||
NextUpdate: time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: lightDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/test.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "happy path with --skip-update",
|
||||
@@ -169,7 +115,8 @@ func TestClient_Download(t *testing.T) {
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
skip: true,
|
||||
skip: true,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "skip downloading DB",
|
||||
@@ -182,6 +129,7 @@ func TestClient_Download(t *testing.T) {
|
||||
NextUpdate: time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "newer schema version",
|
||||
@@ -196,48 +144,6 @@ func TestClient_Download(t *testing.T) {
|
||||
},
|
||||
expectedError: xerrors.New("the version of DB schema doesn't match. Local DB: 2, Expected: 1"),
|
||||
},
|
||||
{
|
||||
name: "DownloadDB returns an error",
|
||||
light: false,
|
||||
clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
|
||||
getMetadata: getMetadataOutput{
|
||||
metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: db.TypeFull,
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
err: xerrors.New("download failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: xerrors.New("failed to download the DB file: failed to download vulnerability DB: download failed"),
|
||||
},
|
||||
{
|
||||
name: "invalid gzip",
|
||||
light: false,
|
||||
clock: clocktesting.NewFakeClock(time.Date(2019, 10, 1, 0, 0, 0, 0, time.UTC)),
|
||||
getMetadata: getMetadataOutput{
|
||||
metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: db.TypeFull,
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/invalid.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: xerrors.New("unable to open new DB: failed to open db: invalid database"),
|
||||
},
|
||||
{
|
||||
name: "--skip-update on the first run",
|
||||
light: false,
|
||||
@@ -274,7 +180,96 @@ func TestClient_Download(t *testing.T) {
|
||||
mockConfig.On("GetMetadata").Return(
|
||||
tc.getMetadata.metadata, tc.getMetadata.err)
|
||||
|
||||
mockGitHubConfig := new(MockGitHubClient)
|
||||
dir, err := ioutil.TempDir("", "db")
|
||||
require.NoError(t, err, tc.name)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
err = db.Init(dir)
|
||||
require.NoError(t, err, tc.name)
|
||||
|
||||
client := Client{
|
||||
dbc: mockConfig,
|
||||
clock: tc.clock,
|
||||
}
|
||||
|
||||
needsUpdate, err := client.NeedsUpdate(context.Background(), "test", tc.light, tc.skip)
|
||||
|
||||
switch {
|
||||
case tc.expectedError != nil:
|
||||
assert.EqualError(t, err, tc.expectedError.Error(), tc.name)
|
||||
default:
|
||||
assert.NoError(t, err, tc.name)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, needsUpdate)
|
||||
mockConfig.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Download(t *testing.T) {
|
||||
type downloadDBOutput struct {
|
||||
fileName string
|
||||
err error
|
||||
}
|
||||
type downloadDB struct {
|
||||
input string
|
||||
output downloadDBOutput
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
light bool
|
||||
downloadDB []downloadDB
|
||||
expectedContent []byte
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
light: false,
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/test.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DownloadDB returns an error",
|
||||
light: false,
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
err: xerrors.New("download failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: xerrors.New("failed to download vulnerability DB: download failed"),
|
||||
},
|
||||
{
|
||||
name: "invalid gzip",
|
||||
light: false,
|
||||
downloadDB: []downloadDB{
|
||||
{
|
||||
input: fullDB,
|
||||
output: downloadDBOutput{
|
||||
fileName: "testdata/invalid.db.gz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: xerrors.New("invalid gzip file: unexpected EOF"),
|
||||
},
|
||||
}
|
||||
|
||||
err := log.InitLogger(false, true)
|
||||
require.NoError(t, err, "failed to init logger")
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockGitHubClient := new(github.MockClient)
|
||||
for _, dd := range tc.downloadDB {
|
||||
var rc io.ReadCloser
|
||||
if dd.output.fileName != "" {
|
||||
@@ -283,7 +278,7 @@ func TestClient_Download(t *testing.T) {
|
||||
rc = f
|
||||
}
|
||||
|
||||
mockGitHubConfig.On("DownloadDB", mock.Anything, dd.input).Return(
|
||||
mockGitHubClient.On("DownloadDB", mock.Anything, dd.input).Return(
|
||||
rc, dd.output.err,
|
||||
)
|
||||
}
|
||||
@@ -295,14 +290,9 @@ func TestClient_Download(t *testing.T) {
|
||||
err = db.Init(dir)
|
||||
require.NoError(t, err, tc.name)
|
||||
|
||||
client := Client{
|
||||
dbc: mockConfig,
|
||||
clock: tc.clock,
|
||||
githubClient: mockGitHubConfig,
|
||||
}
|
||||
|
||||
client := NewClient(db.Config{}, mockGitHubClient, nil)
|
||||
ctx := context.Background()
|
||||
err = client.Download(ctx, "test", dir, tc.light, tc.skip)
|
||||
err = client.Download(ctx, dir, tc.light)
|
||||
|
||||
switch {
|
||||
case tc.expectedError != nil:
|
||||
@@ -311,8 +301,7 @@ func TestClient_Download(t *testing.T) {
|
||||
assert.NoError(t, err, tc.name)
|
||||
}
|
||||
|
||||
mockConfig.AssertExpectations(t)
|
||||
mockGitHubConfig.AssertExpectations(t)
|
||||
mockGitHubClient.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
BIN
pkg/db/testdata/invalid.db.gz
vendored
BIN
pkg/db/testdata/invalid.db.gz
vendored
Binary file not shown.
71
pkg/detector/library/detect.go
Normal file
71
pkg/detector/library/detect.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/knqyf263/go-version"
|
||||
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
wire.Struct(new(DriverFactory)),
|
||||
wire.Bind(new(Factory), new(DriverFactory)),
|
||||
NewDetector,
|
||||
wire.Bind(new(Operation), new(Detector)),
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
Detect(string, []ptypes.Library) ([]types.DetectedVulnerability, error)
|
||||
}
|
||||
|
||||
type Detector struct {
|
||||
driverFactory Factory
|
||||
}
|
||||
|
||||
func NewDetector(factory Factory) Detector {
|
||||
return Detector{driverFactory: factory}
|
||||
}
|
||||
|
||||
func (d Detector) Detect(filePath string, pkgs []ptypes.Library) ([]types.DetectedVulnerability, error) {
|
||||
log.Logger.Debugf("Detecting library vulnerabilities, path: %s", filePath)
|
||||
driver := d.driverFactory.NewDriver(filepath.Base(filePath))
|
||||
if driver == nil {
|
||||
return nil, xerrors.New("unknown file type")
|
||||
}
|
||||
|
||||
vulns, err := detect(driver, pkgs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to scan %s vulnerabilities: %w", driver.Type(), err)
|
||||
}
|
||||
|
||||
return vulns, nil
|
||||
}
|
||||
|
||||
func detect(driver Driver, libs []ptypes.Library) ([]types.DetectedVulnerability, error) {
|
||||
log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type())
|
||||
var vulnerabilities []types.DetectedVulnerability
|
||||
for _, lib := range libs {
|
||||
v, err := version.NewVersion(lib.Version)
|
||||
if err != nil {
|
||||
log.Logger.Debugf("invalid version, library: %s, version: %s, error: %s\n",
|
||||
lib.Name, lib.Version, err)
|
||||
continue
|
||||
}
|
||||
|
||||
vulns, err := driver.Detect(lib.Name, v)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err)
|
||||
}
|
||||
vulnerabilities = append(vulnerabilities, vulns...)
|
||||
}
|
||||
|
||||
return vulnerabilities, nil
|
||||
}
|
||||
46
pkg/detector/library/detector_mock.go
Normal file
46
pkg/detector/library/detector_mock.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type DetectInput struct {
|
||||
FilePath string
|
||||
Libs []ptypes.Library
|
||||
}
|
||||
type DetectOutput struct {
|
||||
Vulns []types.DetectedVulnerability
|
||||
Err error
|
||||
}
|
||||
type DetectExpectation struct {
|
||||
Args DetectInput
|
||||
ReturnArgs DetectOutput
|
||||
}
|
||||
|
||||
func NewMockDetector(detectExpectations []DetectExpectation) *MockDetector {
|
||||
mockDetector := new(MockDetector)
|
||||
for _, e := range detectExpectations {
|
||||
mockDetector.On("Detect", e.Args.FilePath, e.Args.Libs).Return(
|
||||
e.ReturnArgs.Vulns, e.ReturnArgs.Err)
|
||||
}
|
||||
return mockDetector
|
||||
}
|
||||
|
||||
func (_m *MockDetector) Detect(a string, b []ptypes.Library) ([]types.DetectedVulnerability, error) {
|
||||
ret := _m.Called(a, b)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
vulns, ok := ret0.([]types.DetectedVulnerability)
|
||||
if !ok {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
return vulns, ret.Error(1)
|
||||
}
|
||||
50
pkg/detector/library/driver.go
Normal file
50
pkg/detector/library/driver.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/node"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library/python"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/knqyf263/go-version"
|
||||
)
|
||||
|
||||
type Driver interface {
|
||||
ParseLockfile(*os.File) ([]ptypes.Library, error)
|
||||
Detect(string, *version.Version) ([]types.DetectedVulnerability, error)
|
||||
Type() string
|
||||
}
|
||||
|
||||
type Factory interface {
|
||||
NewDriver(filename string) Driver
|
||||
}
|
||||
|
||||
type DriverFactory struct{}
|
||||
|
||||
func (d DriverFactory) NewDriver(filename string) Driver {
|
||||
// TODO: use DI
|
||||
var scanner Driver
|
||||
switch filename {
|
||||
case "Gemfile.lock":
|
||||
scanner = bundler.NewScanner()
|
||||
case "Cargo.lock":
|
||||
scanner = cargo.NewScanner()
|
||||
case "composer.lock":
|
||||
scanner = composer.NewScanner()
|
||||
case "package-lock.json":
|
||||
scanner = node.NewScanner(node.ScannerTypeNpm)
|
||||
case "yarn.lock":
|
||||
scanner = node.NewScanner(node.ScannerTypeYarn)
|
||||
case "Pipfile.lock":
|
||||
scanner = python.NewScanner(python.ScannerTypePipenv)
|
||||
case "poetry.lock":
|
||||
scanner = python.NewScanner(python.ScannerTypePoetry)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return scanner
|
||||
}
|
||||
76
pkg/detector/ospkg/detect.go
Normal file
76
pkg/detector/ospkg/detect.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/alpine"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/amazon"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/debian"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/oracle"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/redhat"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg/ubuntu"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
fos "github.com/aquasecurity/fanal/analyzer/os"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedOS = xerrors.New("unsupported os")
|
||||
|
||||
SuperSet = wire.NewSet(
|
||||
wire.Struct(new(Detector)),
|
||||
wire.Bind(new(Operation), new(Detector)),
|
||||
)
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
Detect(string, string, []analyzer.Package) ([]types.DetectedVulnerability, bool, error)
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
Detect(string, []analyzer.Package) ([]types.DetectedVulnerability, error)
|
||||
IsSupportedVersion(string, string) bool
|
||||
}
|
||||
|
||||
type Detector struct{}
|
||||
|
||||
func (d Detector) Detect(osFamily, osName string, pkgs []analyzer.Package) ([]types.DetectedVulnerability, bool, error) {
|
||||
driver := newDriver(osFamily, osName)
|
||||
if driver == nil {
|
||||
return nil, false, ErrUnsupportedOS
|
||||
}
|
||||
|
||||
eosl := !driver.IsSupportedVersion(osFamily, osName)
|
||||
|
||||
vulns, err := driver.Detect(osName, pkgs)
|
||||
if err != nil {
|
||||
return nil, false, xerrors.Errorf("failed detection: %w", err)
|
||||
}
|
||||
|
||||
return vulns, eosl, nil
|
||||
}
|
||||
|
||||
func newDriver(osFamily, osName string) Driver {
|
||||
// TODO: use DI and change struct names
|
||||
var d Driver
|
||||
switch osFamily {
|
||||
case fos.Alpine:
|
||||
d = alpine.NewScanner()
|
||||
case fos.Debian:
|
||||
d = debian.NewScanner()
|
||||
case fos.Ubuntu:
|
||||
d = ubuntu.NewScanner()
|
||||
case fos.RedHat, fos.CentOS:
|
||||
d = redhat.NewScanner()
|
||||
case fos.Amazon:
|
||||
d = amazon.NewScanner()
|
||||
case fos.Oracle:
|
||||
d = oracle.NewScanner()
|
||||
default:
|
||||
log.Logger.Warnf("unsupported os : %s", osFamily)
|
||||
return nil
|
||||
}
|
||||
return d
|
||||
}
|
||||
48
pkg/detector/ospkg/detector_mock.go
Normal file
48
pkg/detector/ospkg/detector_mock.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type DetectInput struct {
|
||||
OSFamily string
|
||||
OSName string
|
||||
Pkgs []analyzer.Package
|
||||
}
|
||||
type DetectOutput struct {
|
||||
Vulns []types.DetectedVulnerability
|
||||
Eosl bool
|
||||
Err error
|
||||
}
|
||||
type DetectExpectation struct {
|
||||
Args DetectInput
|
||||
ReturnArgs DetectOutput
|
||||
}
|
||||
|
||||
func NewMockDetector(detectExpectations []DetectExpectation) *MockDetector {
|
||||
mockDetector := new(MockDetector)
|
||||
for _, e := range detectExpectations {
|
||||
mockDetector.On("Detect", e.Args.OSFamily, e.Args.OSName, e.Args.Pkgs).Return(
|
||||
e.ReturnArgs.Vulns, e.ReturnArgs.Eosl, e.ReturnArgs.Err)
|
||||
}
|
||||
return mockDetector
|
||||
}
|
||||
|
||||
func (_m *MockDetector) Detect(a, b string, c []analyzer.Package) ([]types.DetectedVulnerability, bool, error) {
|
||||
ret := _m.Called(a, b, c)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, false, ret.Error(2)
|
||||
}
|
||||
vulns, ok := ret0.([]types.DetectedVulnerability)
|
||||
if !ok {
|
||||
return nil, false, ret.Error(2)
|
||||
}
|
||||
return vulns, ret.Bool(1), ret.Error(2)
|
||||
}
|
||||
@@ -41,6 +41,10 @@ func (r Repository) DownloadAsset(ctx context.Context, id int64) (io.ReadCloser,
|
||||
return r.repository.DownloadReleaseAsset(ctx, r.owner, r.repoName, id)
|
||||
}
|
||||
|
||||
type Operation interface {
|
||||
DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Repository RepositoryInterface
|
||||
}
|
||||
|
||||
25
pkg/github/github_mock.go
Normal file
25
pkg/github/github_mock.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClient) DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error) {
|
||||
ret := _m.Called(ctx, fileName)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
rc, ok := ret0.(io.ReadCloser)
|
||||
if !ok {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
return rc, ret.Error(1)
|
||||
}
|
||||
53
pkg/rpc/client/library/client.go
Normal file
53
pkg/rpc/client/library/client.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
r "github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
NewProtobufClient,
|
||||
NewDetector,
|
||||
wire.Bind(new(detector.Operation), new(Detector)),
|
||||
)
|
||||
|
||||
type RemoteURL string
|
||||
|
||||
func NewProtobufClient(remoteURL RemoteURL) rpc.LibDetector {
|
||||
return rpc.NewLibDetectorProtobufClient(string(remoteURL), &http.Client{})
|
||||
}
|
||||
|
||||
type Token string
|
||||
|
||||
type Detector struct {
|
||||
token Token
|
||||
client rpc.LibDetector
|
||||
}
|
||||
|
||||
func NewDetector(token Token, detector rpc.LibDetector) Detector {
|
||||
return Detector{token: token, client: detector}
|
||||
}
|
||||
|
||||
func (d Detector) Detect(filePath string, libs []ptypes.Library) ([]types.DetectedVulnerability, error) {
|
||||
ctx := client.WithToken(context.Background(), string(d.token))
|
||||
res, err := d.client.Detect(ctx, &rpc.LibDetectRequest{
|
||||
FilePath: filePath,
|
||||
Libraries: r.ConvertToRpcLibraries(libs),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect vulnerabilities via RPC: %w", err)
|
||||
}
|
||||
|
||||
return r.ConvertFromRpcVulns(res.Vulnerabilities), nil
|
||||
}
|
||||
157
pkg/rpc/client/library/client_test.go
Normal file
157
pkg/rpc/client/library/client_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/rpc/detector"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type mockDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *mockDetector) Detect(a context.Context, b *detector.LibDetectRequest) (*detector.DetectResponse, error) {
|
||||
ret := _m.Called(a, b)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
res, ok := ret0.(*detector.DetectResponse)
|
||||
if !ok {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
return res, ret.Error(1)
|
||||
}
|
||||
|
||||
func TestDetectClient_Detect(t *testing.T) {
|
||||
type detectInput struct {
|
||||
req *detector.LibDetectRequest
|
||||
}
|
||||
type detectOutput struct {
|
||||
res *detector.DetectResponse
|
||||
err error
|
||||
}
|
||||
type detect struct {
|
||||
input detectInput
|
||||
output detectOutput
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
token Token
|
||||
}
|
||||
type args struct {
|
||||
filePath string
|
||||
libs []ptypes.Library
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
detect detect
|
||||
want []types.DetectedVulnerability
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
token: "token",
|
||||
},
|
||||
args: args{
|
||||
filePath: "app/Pipfile.lock",
|
||||
libs: []ptypes.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{req: &detector.LibDetectRequest{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libraries: []*detector.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
res: &detector.DetectResponse{
|
||||
Vulnerabilities: []*detector.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "django",
|
||||
InstalledVersion: "3.0.0",
|
||||
FixedVersion: "3.0.1",
|
||||
Title: "RCE",
|
||||
Description: "Remote Code Execution",
|
||||
Severity: detector.Severity_CRITICAL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "django",
|
||||
InstalledVersion: "3.0.0",
|
||||
FixedVersion: "3.0.1",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "RCE",
|
||||
Description: "Remote Code Execution",
|
||||
Severity: "CRITICAL",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
fields: fields{},
|
||||
args: args{
|
||||
filePath: "app/Pipfile.lock",
|
||||
libs: []ptypes.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{req: &detector.LibDetectRequest{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libraries: []*detector.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
err: xerrors.New("error"),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to detect vulnerabilities via RPC",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := new(mockDetector)
|
||||
mockDetector.On("Detect", mock.Anything, tt.detect.input.req).Return(
|
||||
tt.detect.output.res, tt.detect.output.err)
|
||||
|
||||
d := NewDetector(tt.fields.token, mockDetector)
|
||||
got, err := d.Detect(tt.args.filePath, tt.args.libs)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
mockDetector.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
54
pkg/rpc/client/ospkg/client.go
Normal file
54
pkg/rpc/client/ospkg/client.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
r "github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
NewProtobufClient,
|
||||
NewDetector,
|
||||
wire.Bind(new(detector.Operation), new(Detector)),
|
||||
)
|
||||
|
||||
type RemoteURL string
|
||||
|
||||
func NewProtobufClient(remoteURL RemoteURL) rpc.OSDetector {
|
||||
return rpc.NewOSDetectorProtobufClient(string(remoteURL), &http.Client{})
|
||||
}
|
||||
|
||||
type Token string
|
||||
|
||||
type Detector struct {
|
||||
token Token
|
||||
client rpc.OSDetector
|
||||
}
|
||||
|
||||
func NewDetector(token Token, detector rpc.OSDetector) Detector {
|
||||
return Detector{token: token, client: detector}
|
||||
}
|
||||
|
||||
func (d Detector) Detect(osFamily, osName string, pkgs []analyzer.Package) ([]types.DetectedVulnerability, bool, error) {
|
||||
ctx := client.WithToken(context.Background(), string(d.token))
|
||||
res, err := d.client.Detect(ctx, &rpc.OSDetectRequest{
|
||||
OsFamily: osFamily,
|
||||
OsName: osName,
|
||||
Packages: r.ConvertToRpcPkgs(pkgs),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, xerrors.Errorf("failed to detect vulnerabilities via RPC: %w", err)
|
||||
}
|
||||
|
||||
return r.ConvertFromRpcVulns(res.Vulnerabilities), res.Eosl, nil
|
||||
}
|
||||
184
pkg/rpc/client/ospkg/client_test.go
Normal file
184
pkg/rpc/client/ospkg/client_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/rpc/detector"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type mockDetector struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *mockDetector) Detect(a context.Context, b *detector.OSDetectRequest) (*detector.DetectResponse, error) {
|
||||
ret := _m.Called(a, b)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
res, ok := ret0.(*detector.DetectResponse)
|
||||
if !ok {
|
||||
return nil, ret.Error(1)
|
||||
}
|
||||
return res, ret.Error(1)
|
||||
}
|
||||
|
||||
func TestDetectClient_Detect(t *testing.T) {
|
||||
type detectInput struct {
|
||||
req *detector.OSDetectRequest
|
||||
}
|
||||
type detectOutput struct {
|
||||
res *detector.DetectResponse
|
||||
err error
|
||||
}
|
||||
type detect struct {
|
||||
input detectInput
|
||||
output detectOutput
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
token Token
|
||||
}
|
||||
type args struct {
|
||||
osFamily string
|
||||
osName string
|
||||
pkgs []analyzer.Package
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
detect detect
|
||||
want []types.DetectedVulnerability
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
token: "token",
|
||||
},
|
||||
args: args{
|
||||
osFamily: "alpine",
|
||||
osName: "3.10.2",
|
||||
pkgs: []analyzer.Package{
|
||||
{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "1",
|
||||
Epoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{
|
||||
req: &detector.OSDetectRequest{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.10.2",
|
||||
Packages: []*detector.Package{
|
||||
{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "1",
|
||||
Epoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
res: &detector.DetectResponse{
|
||||
Vulnerabilities: []*detector.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "bash",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "RCE",
|
||||
Description: "Remote Code Execution",
|
||||
Severity: detector.Severity_HIGH,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "bash",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "RCE",
|
||||
Description: "Remote Code Execution",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
fields: fields{},
|
||||
args: args{
|
||||
osFamily: "alpine",
|
||||
osName: "3.10.2",
|
||||
pkgs: []analyzer.Package{
|
||||
{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "1",
|
||||
Epoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{
|
||||
req: &detector.OSDetectRequest{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.10.2",
|
||||
Packages: []*detector.Package{
|
||||
{
|
||||
Name: "openssl",
|
||||
Version: "1.0.1e",
|
||||
Release: "1",
|
||||
Epoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
err: xerrors.New("error"),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to detect vulnerabilities via RPC",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := new(mockDetector)
|
||||
mockDetector.On("Detect", mock.Anything, tt.detect.input.req).Return(
|
||||
tt.detect.output.res, tt.detect.output.err)
|
||||
|
||||
d := NewDetector(tt.fields.token, mockDetector)
|
||||
got, _, err := d.Detect(tt.args.osFamily, tt.args.osName, tt.args.pkgs)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
mockDetector.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
35
pkg/rpc/client/token.go
Normal file
35
pkg/rpc/client/token.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
buildRequestHeaderFunc = buildRequestHeader
|
||||
)
|
||||
|
||||
func buildRequestHeader(inputHeaders map[string]string) http.Header {
|
||||
header := make(http.Header)
|
||||
for k, v := range inputHeaders {
|
||||
header.Set(k, v)
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func WithToken(ctx context.Context, token string) context.Context {
|
||||
// Prepare custom header
|
||||
header := buildRequestHeaderFunc(map[string]string{"Trivy-Token": token})
|
||||
|
||||
// Attach the headers to a context
|
||||
ctxWithToken, err := twirp.WithHTTPRequestHeaders(ctx, header)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("twirp error setting headers: %s", err)
|
||||
return ctx
|
||||
}
|
||||
return ctxWithToken
|
||||
}
|
||||
72
pkg/rpc/client/token_test.go
Normal file
72
pkg/rpc/client/token_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
_ = log.InitLogger(false, true)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestWithToken(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
token string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
buildRequestHeaderFunc func(map[string]string) http.Header
|
||||
want http.Header
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "token",
|
||||
},
|
||||
want: http.Header{
|
||||
"Trivy-Token": []string{"token"},
|
||||
},
|
||||
buildRequestHeaderFunc: buildRequestHeader,
|
||||
},
|
||||
{
|
||||
name: "sad path, invalid headers passed in",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "token",
|
||||
},
|
||||
want: http.Header(nil),
|
||||
buildRequestHeaderFunc: func(m map[string]string) http.Header {
|
||||
header := make(http.Header)
|
||||
for k, v := range m {
|
||||
header.Set(k, v)
|
||||
}
|
||||
|
||||
// add an extra header that is reserved for twirp
|
||||
header.Set("Content-Type", "foobar")
|
||||
return header
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
oldbuildRequestHeaderFunc := buildRequestHeaderFunc
|
||||
defer func() {
|
||||
buildRequestHeaderFunc = oldbuildRequestHeaderFunc
|
||||
}()
|
||||
buildRequestHeaderFunc = tt.buildRequestHeaderFunc
|
||||
gotCtx := WithToken(tt.args.ctx, tt.args.token)
|
||||
header, _ := twirp.HTTPRequestHeaders(gotCtx)
|
||||
assert.Equal(t, tt.want, header, tt.name)
|
||||
}
|
||||
}
|
||||
110
pkg/rpc/convert.go
Normal file
110
pkg/rpc/convert.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
func ConvertToRpcPkgs(pkgs []analyzer.Package) []*detector.Package {
|
||||
var rpcPkgs []*detector.Package
|
||||
for _, pkg := range pkgs {
|
||||
rpcPkgs = append(rpcPkgs, &detector.Package{
|
||||
Name: pkg.Name,
|
||||
Version: pkg.Version,
|
||||
Release: pkg.Release,
|
||||
Epoch: int32(pkg.Epoch),
|
||||
Arch: pkg.Arch,
|
||||
SrcName: pkg.SrcName,
|
||||
SrcVersion: pkg.SrcVersion,
|
||||
SrcRelease: pkg.SrcRelease,
|
||||
SrcEpoch: int32(pkg.SrcEpoch),
|
||||
})
|
||||
}
|
||||
return rpcPkgs
|
||||
}
|
||||
|
||||
func ConvertFromRpcPkgs(rpcPkgs []*detector.Package) []analyzer.Package {
|
||||
var pkgs []analyzer.Package
|
||||
for _, pkg := range rpcPkgs {
|
||||
pkgs = append(pkgs, analyzer.Package{
|
||||
Name: pkg.Name,
|
||||
Version: pkg.Version,
|
||||
Release: pkg.Release,
|
||||
Epoch: int(pkg.Epoch),
|
||||
Arch: pkg.Arch,
|
||||
SrcName: pkg.SrcName,
|
||||
SrcVersion: pkg.SrcVersion,
|
||||
SrcRelease: pkg.SrcRelease,
|
||||
SrcEpoch: int(pkg.SrcEpoch),
|
||||
})
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func ConvertFromRpcLibraries(rpcLibs []*detector.Library) []ptypes.Library {
|
||||
var libs []ptypes.Library
|
||||
for _, l := range rpcLibs {
|
||||
libs = append(libs, ptypes.Library{
|
||||
Name: l.Name,
|
||||
Version: l.Version,
|
||||
})
|
||||
}
|
||||
return libs
|
||||
}
|
||||
|
||||
func ConvertToRpcLibraries(libs []ptypes.Library) []*detector.Library {
|
||||
var rpcLibs []*detector.Library
|
||||
for _, l := range libs {
|
||||
rpcLibs = append(rpcLibs, &detector.Library{
|
||||
Name: l.Name,
|
||||
Version: l.Version,
|
||||
})
|
||||
}
|
||||
return rpcLibs
|
||||
}
|
||||
|
||||
func ConvertFromRpcVulns(rpcVulns []*detector.Vulnerability) []types.DetectedVulnerability {
|
||||
var vulns []types.DetectedVulnerability
|
||||
for _, vuln := range rpcVulns {
|
||||
severity := dbTypes.Severity(vuln.Severity)
|
||||
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(),
|
||||
References: vuln.References,
|
||||
},
|
||||
})
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
|
||||
func ConvertToRpcVulns(vulns []types.DetectedVulnerability) []*detector.Vulnerability {
|
||||
var rpcVulns []*detector.Vulnerability
|
||||
for _, vuln := range vulns {
|
||||
severity, err := dbTypes.NewSeverity(vuln.Severity)
|
||||
if err != nil {
|
||||
log.Logger.Warn(err)
|
||||
}
|
||||
|
||||
rpcVulns = append(rpcVulns, &detector.Vulnerability{
|
||||
VulnerabilityId: vuln.VulnerabilityID,
|
||||
PkgName: vuln.PkgName,
|
||||
InstalledVersion: vuln.InstalledVersion,
|
||||
FixedVersion: vuln.FixedVersion,
|
||||
Title: vuln.Title,
|
||||
Description: vuln.Description,
|
||||
Severity: detector.Severity(severity),
|
||||
References: vuln.References,
|
||||
})
|
||||
}
|
||||
return rpcVulns
|
||||
}
|
||||
309
pkg/rpc/convert_test.go
Normal file
309
pkg/rpc/convert_test.go
Normal file
@@ -0,0 +1,309 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/rpc/detector"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.InitLogger(false, false)
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestConvertToRpcPkgs(t *testing.T) {
|
||||
type args struct {
|
||||
pkgs []analyzer.Package
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*detector.Package
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
pkgs: []analyzer.Package{
|
||||
{
|
||||
Name: "binary",
|
||||
Version: "1.2.3",
|
||||
Release: "1",
|
||||
Epoch: 2,
|
||||
Arch: "x86_64",
|
||||
SrcName: "src",
|
||||
SrcVersion: "1.2.3",
|
||||
SrcRelease: "1",
|
||||
SrcEpoch: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*detector.Package{
|
||||
{
|
||||
Name: "binary",
|
||||
Version: "1.2.3",
|
||||
Release: "1",
|
||||
Epoch: 2,
|
||||
Arch: "x86_64",
|
||||
SrcName: "src",
|
||||
SrcVersion: "1.2.3",
|
||||
SrcRelease: "1",
|
||||
SrcEpoch: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertToRpcPkgs(tt.args.pkgs)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertFromRpcPkgs(t *testing.T) {
|
||||
type args struct {
|
||||
rpcPkgs []*detector.Package
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []analyzer.Package
|
||||
}{
|
||||
{
|
||||
args: args{
|
||||
rpcPkgs: []*detector.Package{
|
||||
{
|
||||
Name: "binary",
|
||||
Version: "1.2.3",
|
||||
Release: "1",
|
||||
Epoch: 2,
|
||||
Arch: "x86_64",
|
||||
SrcName: "src",
|
||||
SrcVersion: "1.2.3",
|
||||
SrcRelease: "1",
|
||||
SrcEpoch: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []analyzer.Package{
|
||||
{
|
||||
Name: "binary",
|
||||
Version: "1.2.3",
|
||||
Release: "1",
|
||||
Epoch: 2,
|
||||
Arch: "x86_64",
|
||||
SrcName: "src",
|
||||
SrcVersion: "1.2.3",
|
||||
SrcRelease: "1",
|
||||
SrcEpoch: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertFromRpcPkgs(tt.args.rpcPkgs)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertFromRpcLibraries(t *testing.T) {
|
||||
type args struct {
|
||||
rpcLibs []*detector.Library
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []ptypes.Library
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
rpcLibs: []*detector.Library{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "4.5.6"},
|
||||
},
|
||||
},
|
||||
want: []ptypes.Library{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "4.5.6"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertFromRpcLibraries(tt.args.rpcLibs)
|
||||
assert.Equal(t, got, tt.want, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToRpcLibraries(t *testing.T) {
|
||||
type args struct {
|
||||
libs []ptypes.Library
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*detector.Library
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
libs: []ptypes.Library{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "4.5.6"},
|
||||
},
|
||||
},
|
||||
want: []*detector.Library{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "4.5.6"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertToRpcLibraries(tt.args.libs)
|
||||
assert.Equal(t, got, tt.want, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertFromRpcVulns(t *testing.T) {
|
||||
type args struct {
|
||||
rpcVulns []*detector.Vulnerability
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []types.DetectedVulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
rpcVulns: []*detector.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: detector.Severity_CRITICAL,
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: "CRITICAL",
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertFromRpcVulns(tt.args.rpcVulns)
|
||||
assert.Equal(t, got, tt.want, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToRpcVulns(t *testing.T) {
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*detector.Vulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*detector.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: detector.Severity_MEDIUM,
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid severity",
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: "INVALID",
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*detector.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "DoS",
|
||||
Description: "Denial of Service",
|
||||
Severity: detector.Severity_UNKNOWN,
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ConvertToRpcVulns(tt.args.vulns)
|
||||
assert.Equal(t, got, tt.want, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
25
pkg/rpc/server/inject.go
Normal file
25
pkg/rpc/server/inject.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// +build wireinject
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/server/library"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/server/ospkg"
|
||||
)
|
||||
|
||||
func initializeOspkgServer() *ospkg.Server {
|
||||
wire.Build(ospkg.SuperSet)
|
||||
return &ospkg.Server{}
|
||||
}
|
||||
|
||||
func initializeLibServer() *library.Server {
|
||||
wire.Build(library.SuperSet)
|
||||
return &library.Server{}
|
||||
}
|
||||
|
||||
func initializeDBWorker() dbWorker {
|
||||
wire.Build(SuperSet)
|
||||
return dbWorker{}
|
||||
}
|
||||
42
pkg/rpc/server/library/server.go
Normal file
42
pkg/rpc/server/library/server.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
proto "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
detector.SuperSet,
|
||||
vulnerability.SuperSet,
|
||||
NewServer,
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
detector detector.Operation
|
||||
vulnClient vulnerability.Operation
|
||||
}
|
||||
|
||||
func NewServer(detector detector.Operation, vulnClient vulnerability.Operation) *Server {
|
||||
return &Server{detector: detector, vulnClient: vulnClient}
|
||||
}
|
||||
|
||||
func (s *Server) Detect(ctx context.Context, req *proto.LibDetectRequest) (res *proto.DetectResponse, err error) {
|
||||
vulns, err := s.detector.Detect(req.FilePath, rpc.ConvertFromRpcLibraries(req.Libraries))
|
||||
if err != nil {
|
||||
err = xerrors.Errorf("failed to detect library vulnerabilities: %w", err)
|
||||
log.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.vulnClient.FillInfo(vulns, false)
|
||||
|
||||
return &proto.DetectResponse{Vulnerabilities: rpc.ConvertToRpcVulns(vulns)}, nil
|
||||
}
|
||||
135
pkg/rpc/server/library/server_test.go
Normal file
135
pkg/rpc/server/library/server_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
proto "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.InitLogger(false, false)
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestServer_Detect(t *testing.T) {
|
||||
type args struct {
|
||||
req *proto.LibDetectRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
detect library.DetectExpectation
|
||||
wantRes *proto.DetectResponse
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
req: &proto.LibDetectRequest{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libraries: []*proto.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: library.DetectExpectation{
|
||||
Args: library.DetectInput{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libs: []ptypes.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
ReturnArgs: library.DetectOutput{
|
||||
Vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "test",
|
||||
InstalledVersion: "1",
|
||||
FixedVersion: "2",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
Severity: "MEDIUM",
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantRes: &proto.DetectResponse{
|
||||
Vulnerabilities: []*proto.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "test",
|
||||
InstalledVersion: "1",
|
||||
FixedVersion: "2",
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
Severity: proto.Severity_MEDIUM,
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
args: args{
|
||||
req: &proto.LibDetectRequest{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libraries: []*proto.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: library.DetectExpectation{
|
||||
Args: library.DetectInput{
|
||||
FilePath: "app/Pipfile.lock",
|
||||
Libs: []ptypes.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
ReturnArgs: library.DetectOutput{
|
||||
Err: xerrors.New("error"),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to detect library vulnerabilities",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := library.NewMockDetector([]library.DetectExpectation{tt.detect})
|
||||
mockVulnClient := vulnerability.NewMockVulnClient()
|
||||
|
||||
s := NewServer(mockDetector, mockVulnClient)
|
||||
ctx := context.TODO()
|
||||
gotRes, err := s.Detect(ctx, tt.args.req)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantRes, gotRes, tt.name)
|
||||
mockDetector.AssertExpectations(t)
|
||||
mockVulnClient.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
42
pkg/rpc/server/ospkg/server.go
Normal file
42
pkg/rpc/server/ospkg/server.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
proto "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
detector.SuperSet,
|
||||
vulnerability.SuperSet,
|
||||
NewServer,
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
detector detector.Operation
|
||||
vulnClient vulnerability.Operation
|
||||
}
|
||||
|
||||
func NewServer(detector detector.Operation, vulnClient vulnerability.Operation) *Server {
|
||||
return &Server{detector: detector, vulnClient: vulnClient}
|
||||
}
|
||||
|
||||
func (s *Server) Detect(ctx context.Context, req *proto.OSDetectRequest) (res *proto.DetectResponse, err error) {
|
||||
vulns, eosl, err := s.detector.Detect(req.OsFamily, req.OsName, rpc.ConvertFromRpcPkgs(req.Packages))
|
||||
if err != nil {
|
||||
err = xerrors.Errorf("failed to detect vulnerabilities of OS packages: %w", err)
|
||||
log.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.vulnClient.FillInfo(vulns, false)
|
||||
|
||||
return &proto.DetectResponse{Vulnerabilities: rpc.ConvertToRpcVulns(vulns), Eosl: eosl}, nil
|
||||
}
|
||||
125
pkg/rpc/server/ospkg/server_test.go
Normal file
125
pkg/rpc/server/ospkg/server_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
proto "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.InitLogger(false, false)
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestServer_Detect(t *testing.T) {
|
||||
type args struct {
|
||||
req *proto.OSDetectRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
detect ospkg.DetectExpectation
|
||||
wantRes *proto.DetectResponse
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
req: &proto.OSDetectRequest{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.10.2",
|
||||
Packages: []*proto.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: ospkg.DetectExpectation{
|
||||
Args: ospkg.DetectInput{
|
||||
OSFamily: "alpine",
|
||||
OSName: "3.10.2",
|
||||
Pkgs: []analyzer.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
ReturnArgs: ospkg.DetectOutput{
|
||||
Eosl: false,
|
||||
Vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "musl",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: "HIGH",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantRes: &proto.DetectResponse{
|
||||
Vulnerabilities: []*proto.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2019-0001",
|
||||
PkgName: "musl",
|
||||
Severity: proto.Severity_HIGH,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
args: args{
|
||||
req: &proto.OSDetectRequest{
|
||||
OsFamily: "alpine",
|
||||
OsName: "3.10.2",
|
||||
Packages: []*proto.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
detect: ospkg.DetectExpectation{
|
||||
Args: ospkg.DetectInput{
|
||||
OSFamily: "alpine",
|
||||
OSName: "3.10.2",
|
||||
Pkgs: []analyzer.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
ReturnArgs: ospkg.DetectOutput{
|
||||
Err: xerrors.New("error"),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to detect vulnerabilities of OS packages: error",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := ospkg.NewMockDetector([]ospkg.DetectExpectation{tt.detect})
|
||||
mockVulnClient := vulnerability.NewMockVulnClient()
|
||||
|
||||
s := NewServer(mockDetector, mockVulnClient)
|
||||
gotRes, err := s.Detect(context.TODO(), tt.args.req)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.wantRes, gotRes, tt.name)
|
||||
mockDetector.AssertExpectations(t)
|
||||
mockVulnClient.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
137
pkg/rpc/server/server.go
Normal file
137
pkg/rpc/server/server.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/internal/server/config"
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/detector"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
dbFile.SuperSet,
|
||||
newDBWorker,
|
||||
)
|
||||
|
||||
func ListenAndServe(addr string, c config.Config) error {
|
||||
requestWg := &sync.WaitGroup{}
|
||||
dbUpdateWg := &sync.WaitGroup{}
|
||||
|
||||
withWaitGroup := func(base http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Stop processing requests during DB update
|
||||
dbUpdateWg.Wait()
|
||||
|
||||
// Wait for all requests to be processed before DB update
|
||||
requestWg.Add(1)
|
||||
defer requestWg.Done()
|
||||
|
||||
base.ServeHTTP(w, r)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
worker := initializeDBWorker()
|
||||
ctx := context.Background()
|
||||
for {
|
||||
time.Sleep(1 * time.Hour)
|
||||
if err := worker.update(ctx, c.AppVersion, c.CacheDir, dbUpdateWg, requestWg); err != nil {
|
||||
log.Logger.Errorf("%+v\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
osHandler := rpc.NewOSDetectorServer(initializeOspkgServer(), nil)
|
||||
mux.Handle(rpc.OSDetectorPathPrefix, withToken(withWaitGroup(osHandler), c.Token))
|
||||
|
||||
libHandler := rpc.NewLibDetectorServer(initializeLibServer(), nil)
|
||||
mux.Handle(rpc.LibDetectorPathPrefix, withToken(withWaitGroup(libHandler), c.Token))
|
||||
|
||||
log.Logger.Infof("Listening %s...", addr)
|
||||
|
||||
return http.ListenAndServe(addr, mux)
|
||||
}
|
||||
|
||||
func withToken(base http.Handler, token string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if token != "" && token != r.Header.Get("Trivy-Token") {
|
||||
rpc.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token"))
|
||||
return
|
||||
}
|
||||
base.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type dbWorker struct {
|
||||
dbClient dbFile.Operation
|
||||
}
|
||||
|
||||
func newDBWorker(dbClient dbFile.Operation) dbWorker {
|
||||
return dbWorker{dbClient: dbClient}
|
||||
}
|
||||
|
||||
func (w dbWorker) update(ctx context.Context, appVersion, cacheDir string,
|
||||
dbUpdateWg, requestWg *sync.WaitGroup) error {
|
||||
needsUpdate, err := w.dbClient.NeedsUpdate(ctx, appVersion, false, false)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to check if db needs an update")
|
||||
} else if !needsUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Logger.Info("Updating DB...")
|
||||
if err = w.hotUpdate(ctx, cacheDir, dbUpdateWg, requestWg); err != nil {
|
||||
return xerrors.Errorf("failed DB hot update")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w dbWorker) hotUpdate(ctx context.Context, cacheDir string, dbUpdateWg, requestWg *sync.WaitGroup) error {
|
||||
tmpDir, err := ioutil.TempDir("", "db")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to create a temp dir: %w", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if err := w.dbClient.Download(ctx, tmpDir, false); err != nil {
|
||||
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Info("Suspending all requests during DB update")
|
||||
dbUpdateWg.Add(1)
|
||||
defer dbUpdateWg.Done()
|
||||
|
||||
log.Logger.Info("Waiting for all requests to be processed before DB update...")
|
||||
requestWg.Wait()
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
return xerrors.Errorf("failed to close DB: %w", err)
|
||||
}
|
||||
|
||||
if _, err = utils.CopyFile(db.Path(tmpDir), db.Path(cacheDir)); err != nil {
|
||||
return xerrors.Errorf("failed to copy the database file: %w", err)
|
||||
}
|
||||
|
||||
log.Logger.Info("Reopening DB...")
|
||||
if err = db.Init(cacheDir); err != nil {
|
||||
return xerrors.Errorf("failed to open DB: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
157
pkg/rpc/server/server_test.go
Normal file
157
pkg/rpc/server/server_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.InitLogger(false, false)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func Test_dbWorker_update(t *testing.T) {
|
||||
type needsUpdateInput struct {
|
||||
appVersion string
|
||||
skip bool
|
||||
}
|
||||
type needsUpdateOutput struct {
|
||||
needsUpdate bool
|
||||
err error
|
||||
}
|
||||
type needsUpdate struct {
|
||||
input needsUpdateInput
|
||||
output needsUpdateOutput
|
||||
}
|
||||
|
||||
type download struct {
|
||||
call bool
|
||||
err error
|
||||
}
|
||||
|
||||
type args struct {
|
||||
appVersion string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
needsUpdate needsUpdate
|
||||
download download
|
||||
args args
|
||||
want db.Metadata
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
needsUpdate: needsUpdate{
|
||||
input: needsUpdateInput{appVersion: "1", skip: false},
|
||||
output: needsUpdateOutput{needsUpdate: true},
|
||||
},
|
||||
download: download{
|
||||
call: true,
|
||||
},
|
||||
args: args{appVersion: "1"},
|
||||
want: db.Metadata{
|
||||
Version: 1,
|
||||
Type: db.TypeFull,
|
||||
NextUpdate: time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
UpdatedAt: time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not update",
|
||||
needsUpdate: needsUpdate{
|
||||
input: needsUpdateInput{appVersion: "1", skip: false},
|
||||
output: needsUpdateOutput{needsUpdate: false},
|
||||
},
|
||||
args: args{appVersion: "1"},
|
||||
},
|
||||
{
|
||||
name: "NeedsUpdate returns an error",
|
||||
needsUpdate: needsUpdate{
|
||||
input: needsUpdateInput{appVersion: "1", skip: false},
|
||||
output: needsUpdateOutput{err: xerrors.New("fail")},
|
||||
},
|
||||
args: args{appVersion: "1"},
|
||||
wantErr: "failed to check if db needs an update",
|
||||
},
|
||||
{
|
||||
name: "Download returns an error",
|
||||
needsUpdate: needsUpdate{
|
||||
input: needsUpdateInput{appVersion: "1", skip: false},
|
||||
output: needsUpdateOutput{needsUpdate: true},
|
||||
},
|
||||
download: download{
|
||||
call: true,
|
||||
err: xerrors.New("fail"),
|
||||
},
|
||||
args: args{appVersion: "1"},
|
||||
wantErr: "failed DB hot update",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cacheDir, err := ioutil.TempDir("", "server-test")
|
||||
require.NoError(t, err, tt.name)
|
||||
|
||||
require.NoError(t, db.Init(cacheDir), tt.name)
|
||||
|
||||
mockDBClient := new(dbFile.MockClient)
|
||||
mockDBClient.On("NeedsUpdate", mock.Anything,
|
||||
tt.needsUpdate.input.appVersion, false, tt.needsUpdate.input.skip).Return(
|
||||
tt.needsUpdate.output.needsUpdate, tt.needsUpdate.output.err)
|
||||
|
||||
if tt.download.call {
|
||||
mockDBClient.On("Download", mock.Anything, mock.Anything, false).Run(
|
||||
func(args mock.Arguments) {
|
||||
// fake download: copy testdata/new.db to tmpDir/db/trivy.db
|
||||
content, err := ioutil.ReadFile("testdata/new.db")
|
||||
require.NoError(t, err, tt.name)
|
||||
|
||||
tmpDir := args.String(1)
|
||||
dbPath := db.Path(tmpDir)
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(dbPath), 0777), tt.name)
|
||||
err = ioutil.WriteFile(dbPath, content, 0444)
|
||||
require.NoError(t, err, tt.name)
|
||||
}).Return(tt.download.err)
|
||||
}
|
||||
|
||||
w := newDBWorker(mockDBClient)
|
||||
|
||||
var dbUpdateWg, requestWg sync.WaitGroup
|
||||
err = w.update(context.Background(), tt.args.appVersion, cacheDir,
|
||||
&dbUpdateWg, &requestWg)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
if !tt.download.call {
|
||||
return
|
||||
}
|
||||
|
||||
dbc := db.Config{}
|
||||
got, err := dbc.GetMetadata()
|
||||
assert.NoError(t, err, tt.name)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
|
||||
mockDBClient.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
BIN
pkg/rpc/server/testdata/new.db
vendored
Normal file
BIN
pkg/rpc/server/testdata/new.db
vendored
Normal file
Binary file not shown.
46
pkg/rpc/server/wire_gen.go
Normal file
46
pkg/rpc/server/wire_gen.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//+build !wireinject
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
db2 "github.com/aquasecurity/trivy/pkg/db"
|
||||
library2 "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
ospkg2 "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/github"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/server/library"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/server/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
func initializeOspkgServer() *ospkg.Server {
|
||||
detector := ospkg2.Detector{}
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
server := ospkg.NewServer(detector, client)
|
||||
return server
|
||||
}
|
||||
|
||||
func initializeLibServer() *library.Server {
|
||||
driverFactory := library2.DriverFactory{}
|
||||
detector := library2.NewDetector(driverFactory)
|
||||
config := db.Config{}
|
||||
client := vulnerability.NewClient(config)
|
||||
server := library.NewServer(detector, client)
|
||||
return server
|
||||
}
|
||||
|
||||
func initializeDBWorker() dbWorker {
|
||||
config := db.Config{}
|
||||
client := github.NewClient()
|
||||
realClock := clock.RealClock{}
|
||||
dbClient := db2.NewClient(config, client, realClock)
|
||||
serverDbWorker := newDBWorker(dbClient)
|
||||
return serverDbWorker
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/library/bundler"
|
||||
@@ -13,64 +15,29 @@ import (
|
||||
_ "github.com/aquasecurity/fanal/analyzer/library/poetry"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/library/yarn"
|
||||
"github.com/aquasecurity/fanal/extractor"
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library/bundler"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library/cargo"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library/composer"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library/node"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library/python"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/knqyf263/go-version"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type Scanner interface {
|
||||
ParseLockfile(*os.File) ([]ptypes.Library, error)
|
||||
Detect(string, *version.Version) ([]types.DetectedVulnerability, error)
|
||||
Type() string
|
||||
type Scanner struct {
|
||||
detector detector.Operation
|
||||
}
|
||||
|
||||
func NewScanner(filename string) Scanner {
|
||||
var scanner Scanner
|
||||
switch filename {
|
||||
case "Gemfile.lock":
|
||||
scanner = bundler.NewScanner()
|
||||
case "Cargo.lock":
|
||||
scanner = cargo.NewScanner()
|
||||
case "composer.lock":
|
||||
scanner = composer.NewScanner()
|
||||
case "package-lock.json":
|
||||
scanner = node.NewScanner(node.ScannerTypeNpm)
|
||||
case "yarn.lock":
|
||||
scanner = node.NewScanner(node.ScannerTypeYarn)
|
||||
case "Pipfile.lock":
|
||||
scanner = python.NewScanner(python.ScannerTypePipenv)
|
||||
case "poetry.lock":
|
||||
scanner = python.NewScanner(python.ScannerTypePoetry)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return scanner
|
||||
func NewScanner(detector detector.Operation) Scanner {
|
||||
return Scanner{detector: detector}
|
||||
}
|
||||
|
||||
func Scan(files extractor.FileMap, scanOptions types.ScanOptions) (map[string][]types.DetectedVulnerability, error) {
|
||||
func (s Scanner) Scan(files extractor.FileMap) (map[string][]types.DetectedVulnerability, error) {
|
||||
results, err := analyzer.GetLibraries(files)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to analyze libraries: %w", err)
|
||||
}
|
||||
|
||||
vulnerabilities := map[string][]types.DetectedVulnerability{}
|
||||
for path, pkgs := range results {
|
||||
log.Logger.Debugf("Detecting library vulnerabilities, path: %s", path)
|
||||
scanner := NewScanner(filepath.Base(string(path)))
|
||||
if scanner == nil {
|
||||
return nil, xerrors.New("unknown file type")
|
||||
}
|
||||
|
||||
vulns, err := scan(scanner, pkgs)
|
||||
for path, libs := range results {
|
||||
vulns, err := s.detector.Detect(string(path), libs)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to scan %s vulnerabilities: %w", scanner.Type(), err)
|
||||
return nil, xerrors.Errorf("failed library scan: %w", err)
|
||||
}
|
||||
|
||||
vulnerabilities[string(path)] = vulns
|
||||
@@ -78,40 +45,23 @@ func Scan(files extractor.FileMap, scanOptions types.ScanOptions) (map[string][]
|
||||
return vulnerabilities, nil
|
||||
}
|
||||
|
||||
func ScanFile(f *os.File) ([]types.DetectedVulnerability, error) {
|
||||
scanner := NewScanner(filepath.Base(f.Name()))
|
||||
if scanner == nil {
|
||||
return nil, xerrors.New("unknown file type")
|
||||
func (s Scanner) ScanFile(f *os.File) ([]types.DetectedVulnerability, error) {
|
||||
content, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := extractor.FileMap{
|
||||
f.Name(): content,
|
||||
}
|
||||
|
||||
pkgs, err := scanner.ParseLockfile(f)
|
||||
results, err := s.Scan(files)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vulns, err := scan(scanner, pkgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// need only 1 result
|
||||
for _, vulns := range results {
|
||||
return vulns, nil
|
||||
}
|
||||
return vulns, nil
|
||||
}
|
||||
|
||||
func scan(scanner Scanner, pkgs []ptypes.Library) ([]types.DetectedVulnerability, error) {
|
||||
log.Logger.Infof("Detecting %s vulnerabilities...", scanner.Type())
|
||||
var vulnerabilities []types.DetectedVulnerability
|
||||
for _, pkg := range pkgs {
|
||||
v, err := version.NewVersion(pkg.Version)
|
||||
if err != nil {
|
||||
log.Logger.Debug(err)
|
||||
continue
|
||||
}
|
||||
|
||||
vulns, err := scanner.Detect(pkg.Name, v)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", scanner.Type(), err)
|
||||
}
|
||||
vulnerabilities = append(vulnerabilities, vulns...)
|
||||
}
|
||||
|
||||
return vulnerabilities, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
201
pkg/scanner/library/scan_test.go
Normal file
201
pkg/scanner/library/scan_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package library
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
library2 "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/fanal/extractor"
|
||||
ptypes "github.com/aquasecurity/go-dep-parser/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
type detectInput struct {
|
||||
filePath string
|
||||
libs []ptypes.Library
|
||||
}
|
||||
type detectOutput struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
err error
|
||||
}
|
||||
type detect struct {
|
||||
input detectInput
|
||||
output detectOutput
|
||||
}
|
||||
type args struct {
|
||||
files extractor.FileMap
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
detect []detect
|
||||
want map[string][]types.DetectedVulnerability
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
args: args{
|
||||
files: extractor.FileMap{
|
||||
"app/Pipfile.lock": []byte(`{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "ad1805ab0e16cf08032c3fe45eeaa29b79e9c196650411977af14e31b12ff0cd"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:665457d4146bbd34ae9d2970fa3b37082d7b225b0671bfd24c337458f229db78",
|
||||
"sha256:bde46d4dbc410678e89bc95ea5d312dd6eb4c37d0fa0e19c9415cad94addf22f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
`),
|
||||
"app/package-lock.json": []byte(`{
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"react": {
|
||||
"version": "16.8.6",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
|
||||
"integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
detect: []detect{
|
||||
{
|
||||
input: detectInput{
|
||||
filePath: "app/Pipfile.lock",
|
||||
libs: []ptypes.Library{
|
||||
{Name: "django", Version: "3.0.0"},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: detectInput{
|
||||
filePath: "app/package-lock.json",
|
||||
libs: []ptypes.Library{
|
||||
{Name: "react", Version: "16.8.6"},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0002"},
|
||||
{VulnerabilityID: "CVE-2019-0003"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: map[string][]types.DetectedVulnerability{
|
||||
"app/Pipfile.lock": {{VulnerabilityID: "CVE-2019-0001"}},
|
||||
"app/package-lock.json": {
|
||||
{VulnerabilityID: "CVE-2019-0002"},
|
||||
{VulnerabilityID: "CVE-2019-0003"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "broken lock file",
|
||||
args: args{
|
||||
files: extractor.FileMap{
|
||||
"app/Pipfile.lock": []byte(`{broken}`),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to analyze libraries",
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
args: args{
|
||||
files: extractor.FileMap{
|
||||
"app/package-lock.json": []byte(`{
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"react": {
|
||||
"version": "16.8.6",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
|
||||
"integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.13.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
detect: []detect{
|
||||
{
|
||||
input: detectInput{
|
||||
filePath: "app/package-lock.json",
|
||||
libs: []ptypes.Library{
|
||||
{Name: "react", Version: "16.8.6"},
|
||||
},
|
||||
},
|
||||
output: detectOutput{err: xerrors.New("error")},
|
||||
},
|
||||
},
|
||||
wantErr: "failed library scan",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := new(library2.MockDetector)
|
||||
for _, d := range tt.detect {
|
||||
mockDetector.On("Detect", d.input.filePath, d.input.libs).Return(
|
||||
d.output.vulns, d.output.err)
|
||||
}
|
||||
|
||||
s := Scanner{
|
||||
detector: mockDetector,
|
||||
}
|
||||
got, err := s.Scan(tt.args.files)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
mockDetector.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/command/apk"
|
||||
fos "github.com/aquasecurity/fanal/analyzer/os"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/os/alpine"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/os/amazonlinux"
|
||||
_ "github.com/aquasecurity/fanal/analyzer/os/debianbase"
|
||||
@@ -13,47 +14,26 @@ import (
|
||||
_ "github.com/aquasecurity/fanal/analyzer/pkg/dpkg"
|
||||
"github.com/aquasecurity/fanal/extractor"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
detector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/alpine"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/amazon"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/debian"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/oracle"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/redhat"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg/ubuntu"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type Scanner interface {
|
||||
Detect(string, []analyzer.Package) ([]types.DetectedVulnerability, error)
|
||||
IsSupportedVersion(string, string) bool
|
||||
type Scanner struct {
|
||||
detector detector.Operation
|
||||
}
|
||||
|
||||
func Scan(files extractor.FileMap) (string, string, []types.DetectedVulnerability, error) {
|
||||
func NewScanner(detector detector.Operation) Scanner {
|
||||
return Scanner{detector: detector}
|
||||
}
|
||||
|
||||
func (s Scanner) Scan(files extractor.FileMap) (string, string, []types.DetectedVulnerability, error) {
|
||||
os, err := analyzer.GetOS(files)
|
||||
if err != nil {
|
||||
return "", "", nil, xerrors.Errorf("failed to analyze OS: %w", err)
|
||||
}
|
||||
log.Logger.Debugf("OS family: %s, OS version: %s", os.Family, os.Name)
|
||||
|
||||
var s Scanner
|
||||
switch os.Family {
|
||||
case fos.Alpine:
|
||||
s = alpine.NewScanner()
|
||||
case fos.Debian:
|
||||
s = debian.NewScanner()
|
||||
case fos.Ubuntu:
|
||||
s = ubuntu.NewScanner()
|
||||
case fos.RedHat, fos.CentOS:
|
||||
s = redhat.NewScanner()
|
||||
case fos.Amazon:
|
||||
s = amazon.NewScanner()
|
||||
case fos.Oracle:
|
||||
s = oracle.NewScanner()
|
||||
default:
|
||||
log.Logger.Warnf("unsupported os : %s", os.Family)
|
||||
return "", "", nil, nil
|
||||
}
|
||||
pkgs, err := analyzer.GetPackages(files)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, ftypes.ErrNoRpmCmd) {
|
||||
@@ -72,15 +52,14 @@ func Scan(files extractor.FileMap) (string, string, []types.DetectedVulnerabilit
|
||||
pkgs = mergePkgs(pkgs, pkgsFromCommands)
|
||||
log.Logger.Debugf("the number of packages: %d", len(pkgs))
|
||||
|
||||
if !s.IsSupportedVersion(os.Family, os.Name) {
|
||||
log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", os.Family, os.Name)
|
||||
log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
|
||||
}
|
||||
|
||||
vulns, err := s.Detect(os.Name, pkgs)
|
||||
vulns, eosl, err := s.detector.Detect(os.Family, os.Name, pkgs)
|
||||
if err != nil {
|
||||
return "", "", nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
|
||||
}
|
||||
if eosl {
|
||||
log.Logger.Warnf("This OS version is no longer supported by the distribution: %s %s", os.Family, os.Name)
|
||||
log.Logger.Warnf("The vulnerability detection may be insufficient because security updates are not provided")
|
||||
}
|
||||
|
||||
return os.Family, os.Name, vulns, nil
|
||||
}
|
||||
|
||||
213
pkg/scanner/ospkg/scan_test.go
Normal file
213
pkg/scanner/ospkg/scan_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package ospkg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
ospkg2 "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/extractor"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.InitLogger(false, true)
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
type detectInput struct {
|
||||
osFamily string
|
||||
osName string
|
||||
pkgs []analyzer.Package
|
||||
}
|
||||
type detectOutput struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
eosl bool
|
||||
err error
|
||||
}
|
||||
type detect struct {
|
||||
input detectInput
|
||||
output detectOutput
|
||||
}
|
||||
|
||||
type fields struct {
|
||||
files extractor.FileMap
|
||||
}
|
||||
type want struct {
|
||||
osFamily string
|
||||
osName string
|
||||
vulns []types.DetectedVulnerability
|
||||
err string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
detect detect
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
files: extractor.FileMap{
|
||||
"etc/alpine-release": []byte("3.10.2"),
|
||||
"lib/apk/db/installed": []byte(`C:Q11Ing8/u1VIdY9czSxaDO9wJg72I=
|
||||
P:musl
|
||||
V:1.1.22-r3
|
||||
A:x86_64
|
||||
S:368204
|
||||
I:598016
|
||||
T:the musl c library (libc) implementation
|
||||
U:http://www.musl-libc.org/
|
||||
L:MIT
|
||||
o:musl
|
||||
m:Timo Teräs <timo.teras@iki.fi>
|
||||
t:1565162130
|
||||
c:0c777cf840e82cdc528651e3f3f8f9dda6b1b028
|
||||
p:so:libc.musl-x86_64.so.1=1
|
||||
F:lib
|
||||
R:libc.musl-x86_64.so.1
|
||||
a:0:0:777
|
||||
Z:Q17yJ3JFNypA4mxhJJr0ou6CzsJVI=
|
||||
R:ld-musl-x86_64.so.1
|
||||
a:0:0:755
|
||||
Z:Q1TTLtUopPeiF9JrA0cgKQZYggG+c=
|
||||
F:usr
|
||||
F:usr/lib
|
||||
`),
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{
|
||||
osFamily: "alpine",
|
||||
osName: "3.10.2",
|
||||
pkgs: []analyzer.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001", PkgName: "musl"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
osFamily: "alpine",
|
||||
osName: "3.10.2",
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001", PkgName: "musl"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad path",
|
||||
fields: fields{
|
||||
files: extractor.FileMap{
|
||||
"etc/alpine-release": []byte("3.10.2"),
|
||||
"invalid": []byte(`invalid`),
|
||||
},
|
||||
},
|
||||
want: want{err: analyzer.ErrPkgAnalysis.Error()},
|
||||
},
|
||||
{
|
||||
name: "Detect returns an error",
|
||||
fields: fields{
|
||||
files: extractor.FileMap{
|
||||
"etc/alpine-release": []byte("3.10.2"),
|
||||
"lib/apk/db/installed": []byte(`C:Q11Ing8/u1VIdY9czSxaDO9wJg72I=
|
||||
P:musl
|
||||
V:1.1.22-r3
|
||||
A:x86_64
|
||||
`),
|
||||
},
|
||||
},
|
||||
detect: detect{
|
||||
input: detectInput{
|
||||
osFamily: "alpine",
|
||||
osName: "3.10.2",
|
||||
pkgs: []analyzer.Package{
|
||||
{Name: "musl", Version: "1.1.22-r3"},
|
||||
},
|
||||
},
|
||||
output: detectOutput{
|
||||
err: xerrors.New("error"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: "failed to detect vulnerabilities",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDetector := new(ospkg2.MockDetector)
|
||||
mockDetector.On("Detect", tt.detect.input.osFamily, tt.detect.input.osName,
|
||||
tt.detect.input.pkgs).Return(tt.detect.output.vulns, tt.detect.output.eosl, tt.detect.output.err)
|
||||
|
||||
s := NewScanner(mockDetector)
|
||||
got, got1, got2, err := s.Scan(tt.fields.files)
|
||||
|
||||
if tt.want.err != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Contains(t, err.Error(), tt.want.err, tt.name)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want.osFamily, got)
|
||||
assert.Equal(t, tt.want.osName, got1)
|
||||
assert.Equal(t, tt.want.vulns, got2)
|
||||
mockDetector.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mergePkgs(t *testing.T) {
|
||||
type args struct {
|
||||
pkgs []analyzer.Package
|
||||
pkgsFromCommands []analyzer.Package
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []analyzer.Package
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
pkgs: []analyzer.Package{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "3.4.5"},
|
||||
{Name: "baz", Version: "6.7.8"},
|
||||
},
|
||||
pkgsFromCommands: []analyzer.Package{
|
||||
{Name: "bar", Version: "1.1.1"},
|
||||
{Name: "hoge", Version: "9.0.1"},
|
||||
},
|
||||
},
|
||||
want: []analyzer.Package{
|
||||
{Name: "foo", Version: "1.2.3"},
|
||||
{Name: "bar", Version: "3.4.5"},
|
||||
{Name: "baz", Version: "6.7.8"},
|
||||
{Name: "hoge", Version: "9.0.1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := mergePkgs(tt.args.pkgs, tt.args.pkgsFromCommands)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,52 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/extractor"
|
||||
libDetector "github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
ospkgDetector "github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
rpcLibDetector "github.com/aquasecurity/trivy/pkg/rpc/client/library"
|
||||
rpcOSDetector "github.com/aquasecurity/trivy/pkg/rpc/client/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/library"
|
||||
libScanner "github.com/aquasecurity/trivy/pkg/scanner/library"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/ospkg"
|
||||
ospkgScanner "github.com/aquasecurity/trivy/pkg/scanner/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func ScanImage(imageName, filePath string, scanOptions types.ScanOptions) (report.Results, error) {
|
||||
var StandaloneSet = wire.NewSet(
|
||||
ospkgDetector.SuperSet,
|
||||
ospkgScanner.NewScanner,
|
||||
libDetector.SuperSet,
|
||||
libScanner.NewScanner,
|
||||
NewScanner,
|
||||
)
|
||||
|
||||
var ClientSet = wire.NewSet(
|
||||
rpcOSDetector.SuperSet,
|
||||
ospkgScanner.NewScanner,
|
||||
rpcLibDetector.SuperSet,
|
||||
libScanner.NewScanner,
|
||||
NewScanner,
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
ospkgScanner ospkg.Scanner
|
||||
libScanner library.Scanner
|
||||
}
|
||||
|
||||
func NewScanner(ospkgScanner ospkg.Scanner, libScanner library.Scanner) Scanner {
|
||||
return Scanner{ospkgScanner: ospkgScanner, libScanner: libScanner}
|
||||
}
|
||||
|
||||
func (s Scanner) ScanImage(imageName, filePath string, scanOptions types.ScanOptions) (report.Results, error) {
|
||||
results := report.Results{}
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -53,9 +86,9 @@ func ScanImage(imageName, filePath string, scanOptions types.ScanOptions) (repor
|
||||
}
|
||||
|
||||
if utils.StringInSlice("os", scanOptions.VulnType) {
|
||||
osFamily, osVersion, osVulns, err := ospkg.Scan(files)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to scan image: %w", err)
|
||||
osFamily, osVersion, osVulns, err := s.ospkgScanner.Scan(files)
|
||||
if err != nil && err != ospkgDetector.ErrUnsupportedOS {
|
||||
return nil, xerrors.Errorf("failed to scan the image: %w", err)
|
||||
}
|
||||
if osFamily != "" {
|
||||
imageDetail := fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion)
|
||||
@@ -67,7 +100,7 @@ func ScanImage(imageName, filePath string, scanOptions types.ScanOptions) (repor
|
||||
}
|
||||
|
||||
if utils.StringInSlice("library", scanOptions.VulnType) {
|
||||
libVulns, err := library.Scan(files, scanOptions)
|
||||
libVulns, err := s.libScanner.Scan(files)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to scan libraries: %w", err)
|
||||
}
|
||||
@@ -88,8 +121,8 @@ func ScanImage(imageName, filePath string, scanOptions types.ScanOptions) (repor
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func ScanFile(f *os.File) (report.Results, error) {
|
||||
vulns, err := library.ScanFile(f)
|
||||
func (s Scanner) ScanFile(f *os.File) (report.Results, error) {
|
||||
vulns, err := s.libScanner.ScanFile(f)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to scan libraries in file: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,4 +5,8 @@ import "time"
|
||||
type ScanOptions struct {
|
||||
VulnType []string
|
||||
Timeout time.Duration
|
||||
|
||||
// for client/server
|
||||
RemoteURL string
|
||||
Token string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -126,3 +127,28 @@ func FilterTargets(prefixPath string, targets map[string]struct{}) (map[string]s
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
sourceFileStat, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !sourceFileStat.Mode().IsRegular() {
|
||||
return 0, fmt.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
destination, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer destination.Close()
|
||||
n, err := io.Copy(destination, source)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
@@ -142,3 +145,51 @@ func TestFilterTargets(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
type args struct {
|
||||
src string
|
||||
dst string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
content []byte
|
||||
want string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
content: []byte("this is a content"),
|
||||
args: args{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
src := tt.args.src
|
||||
if tt.args.src == "" {
|
||||
s, err := ioutil.TempFile("", "src")
|
||||
require.NoError(t, err, tt.name)
|
||||
_, err = s.Write(tt.content)
|
||||
require.NoError(t, err, tt.name)
|
||||
src = s.Name()
|
||||
}
|
||||
|
||||
dst := tt.args.dst
|
||||
if tt.args.dst == "" {
|
||||
d, err := ioutil.TempFile("", "dst")
|
||||
require.NoError(t, err, tt.name)
|
||||
dst = d.Name()
|
||||
require.NoError(t, d.Close(), tt.name)
|
||||
}
|
||||
|
||||
_, err := CopyFile(src, dst)
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
assert.Equal(t, err.Error(), tt.wantErr, tt.name)
|
||||
} else {
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
3
pkg/vulnerability/testdata/.trivyignore
vendored
Normal file
3
pkg/vulnerability/testdata/.trivyignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# test
|
||||
CVE-2019-0001
|
||||
CVE-2019-0002
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
@@ -18,41 +20,52 @@ const (
|
||||
DefaultIgnoreFile = ".trivyignore"
|
||||
)
|
||||
|
||||
var SuperSet = wire.NewSet(
|
||||
wire.Struct(new(db.Config)),
|
||||
NewClient,
|
||||
wire.Bind(new(Operation), new(Client)),
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
FillInfo(vulns []types.DetectedVulnerability, light bool)
|
||||
Filter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
|
||||
ignoreUnfixed bool, ignoreFile string) []types.DetectedVulnerability
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
dbc db.Operations
|
||||
}
|
||||
|
||||
func NewClient() Client {
|
||||
return Client{
|
||||
dbc: db.Config{},
|
||||
}
|
||||
func NewClient(dbc db.Config) Client {
|
||||
return Client{dbc: dbc}
|
||||
}
|
||||
|
||||
func (c Client) FillAndFilter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
|
||||
ignoreUnfixed bool, ignoreFile string, light bool) []types.DetectedVulnerability {
|
||||
func (c Client) FillInfo(vulns []types.DetectedVulnerability, light bool) {
|
||||
var err error
|
||||
var severity dbTypes.Severity
|
||||
|
||||
for i := range vulns {
|
||||
if light {
|
||||
severity, err = c.dbc.GetSeverity(vulns[i].VulnerabilityID)
|
||||
vulns[i].Vulnerability.Severity = severity.String()
|
||||
} else {
|
||||
vulns[i].Vulnerability, err = c.dbc.GetVulnerability(vulns[i].VulnerabilityID)
|
||||
}
|
||||
if err != nil {
|
||||
log.Logger.Warnf("Error while getting vulnerability details: %s\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) Filter(vulns []types.DetectedVulnerability, severities []dbTypes.Severity,
|
||||
ignoreUnfixed bool, ignoreFile string) []types.DetectedVulnerability {
|
||||
ignoredIDs := getIgnoredIDs(ignoreFile)
|
||||
var vulnerabilities []types.DetectedVulnerability
|
||||
for _, vuln := range vulns {
|
||||
var vulnerability dbTypes.Vulnerability
|
||||
if light {
|
||||
severity, err = c.dbc.GetSeverity(vuln.VulnerabilityID)
|
||||
vulnerability.Severity = severity.String()
|
||||
} else {
|
||||
vulnerability, err = c.dbc.GetVulnerability(vuln.VulnerabilityID)
|
||||
}
|
||||
if err != nil {
|
||||
log.Logger.Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter vulnerabilities by severity
|
||||
for _, s := range severities {
|
||||
if s.String() == vulnerability.Severity {
|
||||
vuln.Vulnerability = vulnerability
|
||||
|
||||
if s.String() == vuln.Severity {
|
||||
// Ignore unfixed vulnerabilities
|
||||
if ignoreUnfixed && vuln.FixedVersion == "" {
|
||||
continue
|
||||
|
||||
35
pkg/vulnerability/vulnerability_mock.go
Normal file
35
pkg/vulnerability/vulnerability_mock.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package vulnerability
|
||||
|
||||
import (
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockVulnClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func NewMockVulnClient() *MockVulnClient {
|
||||
mockVulnClient := new(MockVulnClient)
|
||||
mockVulnClient.On("FillInfo", mock.Anything, mock.Anything)
|
||||
return mockVulnClient
|
||||
}
|
||||
|
||||
func (_m *MockVulnClient) FillInfo(a []types.DetectedVulnerability, b bool) {
|
||||
_m.Called(a, b)
|
||||
}
|
||||
|
||||
func (_m *MockVulnClient) Filter(a []types.DetectedVulnerability, b []dbTypes.Severity,
|
||||
c bool, d string) []types.DetectedVulnerability {
|
||||
ret := _m.Called(a, b, c, d)
|
||||
ret0 := ret.Get(0)
|
||||
if ret0 == nil {
|
||||
return nil
|
||||
}
|
||||
vulns, ok := ret0.([]types.DetectedVulnerability)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return vulns
|
||||
}
|
||||
@@ -1,190 +1,386 @@
|
||||
package vulnerability
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestFillAndFilter(t *testing.T) {
|
||||
detectedVulns := []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "foo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityHigh],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "piyo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityCritical],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "bar",
|
||||
PkgName: "barpkg",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityLow],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "hoge",
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "baz",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityMedium],
|
||||
},
|
||||
},
|
||||
func TestMain(m *testing.M) {
|
||||
if err := log.InitLogger(false, true); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
severities := []dbTypes.Severity{dbTypes.SeverityLow, dbTypes.SeverityCritical,
|
||||
dbTypes.SeverityMedium, dbTypes.SeverityHigh, dbTypes.SeverityUnknown}
|
||||
|
||||
mockDBConfig := new(db.MockDBConfig)
|
||||
getVulnerability := map[string]dbTypes.Vulnerability{
|
||||
"foo": {
|
||||
Title: "footitle",
|
||||
Description: "foodesc",
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
References: []string{"fooref"},
|
||||
},
|
||||
"bar": {
|
||||
Title: "bartitle",
|
||||
Description: "bardesc",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
References: []string{"barref"},
|
||||
},
|
||||
"baz": {
|
||||
Title: "baztitle",
|
||||
Description: "bazdesc",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
References: []string{"bazref"},
|
||||
},
|
||||
"piyo": {
|
||||
Title: "piyotitle",
|
||||
Description: "piyodesc",
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
References: []string{"piyoref"},
|
||||
},
|
||||
"hoge": {
|
||||
Title: "hogetitle",
|
||||
Description: "hogedesc",
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
References: []string{"hogeref"},
|
||||
},
|
||||
}
|
||||
|
||||
for pkgName, vulnerability := range getVulnerability {
|
||||
mockDBConfig.On("GetVulnerability", pkgName).Return(vulnerability, nil)
|
||||
|
||||
}
|
||||
getSeverity := map[string]dbTypes.Severity{
|
||||
"foo": dbTypes.SeverityHigh,
|
||||
"bar": dbTypes.SeverityLow,
|
||||
"baz": dbTypes.SeverityMedium,
|
||||
"piyo": dbTypes.SeverityCritical,
|
||||
"hoge": dbTypes.SeverityUnknown,
|
||||
}
|
||||
|
||||
for pkgName, severity := range getSeverity {
|
||||
mockDBConfig.On("GetSeverity", pkgName).Return(severity, nil)
|
||||
}
|
||||
|
||||
expected := []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "piyo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "piyotitle",
|
||||
Description: "piyodesc",
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityCritical],
|
||||
References: []string{"piyoref"},
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "foo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "footitle",
|
||||
Description: "foodesc",
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityHigh],
|
||||
References: []string{"fooref"},
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "baz",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "baztitle",
|
||||
Description: "bazdesc",
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityMedium],
|
||||
References: []string{"bazref"},
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "hoge",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "hogetitle",
|
||||
Description: "hogedesc",
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityUnknown],
|
||||
References: []string{"hogeref"},
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "bar",
|
||||
PkgName: "barpkg",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "bartitle",
|
||||
Description: "bardesc",
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityLow],
|
||||
References: []string{"barref"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client := Client{
|
||||
dbc: mockDBConfig,
|
||||
}
|
||||
actual := client.FillAndFilter(detectedVulns, severities, false, ".trivyignore", false)
|
||||
assert.Equal(t, expected, actual, "full db")
|
||||
|
||||
expected = []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "piyo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityCritical],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "foo",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityHigh],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "baz",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityMedium],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "hoge",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityUnknown],
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "bar",
|
||||
PkgName: "barpkg",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityNames[dbTypes.SeverityLow],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual = client.FillAndFilter(detectedVulns, severities, false, ".trivyignore", true)
|
||||
assert.Equal(t, expected, actual, "light db")
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestClient_FillInfo(t *testing.T) {
|
||||
type getSeverityOutput struct {
|
||||
severity dbTypes.Severity
|
||||
err error
|
||||
}
|
||||
type getSeverity struct {
|
||||
input string
|
||||
output getSeverityOutput
|
||||
}
|
||||
type getVulnerabilityOutput struct {
|
||||
vulnerability dbTypes.Vulnerability
|
||||
err error
|
||||
}
|
||||
type getVulnerability struct {
|
||||
input string
|
||||
output getVulnerabilityOutput
|
||||
}
|
||||
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
light bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
getSeverity []getSeverity
|
||||
getVulnerability []getVulnerability
|
||||
args args
|
||||
expected []types.DetectedVulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
getVulnerability: []getVulnerability{
|
||||
{
|
||||
input: "CVE-2019-0001",
|
||||
output: getVulnerabilityOutput{
|
||||
vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
},
|
||||
light: false,
|
||||
},
|
||||
expected: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with light option",
|
||||
getSeverity: []getSeverity{
|
||||
{
|
||||
input: "CVE-2019-0001",
|
||||
output: getSeverityOutput{
|
||||
severity: dbTypes.SeverityCritical,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "CVE-2019-0002",
|
||||
output: getSeverityOutput{
|
||||
severity: dbTypes.SeverityHigh,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
{VulnerabilityID: "CVE-2019-0002"},
|
||||
},
|
||||
light: true,
|
||||
},
|
||||
expected: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetVulnerability returns an error",
|
||||
getVulnerability: []getVulnerability{
|
||||
{
|
||||
input: "CVE-2019-0004",
|
||||
output: getVulnerabilityOutput{
|
||||
err: xerrors.New("failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
light: false,
|
||||
},
|
||||
expected: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0004"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetSeverity returns an error",
|
||||
getSeverity: []getSeverity{
|
||||
{
|
||||
input: "CVE-2019-0003",
|
||||
output: getSeverityOutput{
|
||||
err: xerrors.New("failed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0003"},
|
||||
},
|
||||
light: true,
|
||||
},
|
||||
expected: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0003",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityUnknown.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockDBConfig := new(db.MockDBConfig)
|
||||
c := Client{
|
||||
dbc: mockDBConfig,
|
||||
}
|
||||
for _, gs := range tt.getSeverity {
|
||||
mockDBConfig.On("GetSeverity", gs.input).Return(
|
||||
gs.output.severity, gs.output.err)
|
||||
}
|
||||
for _, gv := range tt.getVulnerability {
|
||||
mockDBConfig.On("GetVulnerability", gv.input).Return(
|
||||
gv.output.vulnerability, gv.output.err)
|
||||
}
|
||||
|
||||
c.FillInfo(tt.args.vulns, tt.args.light)
|
||||
assert.Equal(t, tt.expected, tt.args.vulns, tt.name)
|
||||
mockDBConfig.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Filter(t *testing.T) {
|
||||
type args struct {
|
||||
vulns []types.DetectedVulnerability
|
||||
severities []dbTypes.Severity
|
||||
ignoreUnfixed bool
|
||||
ignoreFile string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []types.DetectedVulnerability
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0001",
|
||||
PkgName: "baz",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0001",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityHigh},
|
||||
ignoreUnfixed: false,
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0001",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityCritical.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0001",
|
||||
PkgName: "baz",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with ignore-unfixed",
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2018-0002",
|
||||
PkgName: "bar",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityHigh.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
severities: []dbTypes.Severity{dbTypes.SeverityHigh},
|
||||
ignoreUnfixed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with ignore-file",
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{
|
||||
// this vulnerability is ignored
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
// this vulnerability is ignored
|
||||
VulnerabilityID: "CVE-2019-0002",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0003",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
ignoreUnfixed: false,
|
||||
ignoreFile: "testdata/.trivyignore",
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0003",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := Client{}
|
||||
got := c.Filter(tt.args.vulns, tt.args.severities, tt.args.ignoreUnfixed, tt.args.ignoreFile)
|
||||
assert.Equal(t, tt.want, got, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
508
rpc/detector/service.pb.go
Normal file
508
rpc/detector/service.pb.go
Normal file
@@ -0,0 +1,508 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: rpc/detector/service.proto
|
||||
|
||||
package detector
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Severity int32
|
||||
|
||||
const (
|
||||
Severity_UNKNOWN Severity = 0
|
||||
Severity_LOW Severity = 1
|
||||
Severity_MEDIUM Severity = 2
|
||||
Severity_HIGH Severity = 3
|
||||
Severity_CRITICAL Severity = 4
|
||||
)
|
||||
|
||||
var Severity_name = map[int32]string{
|
||||
0: "UNKNOWN",
|
||||
1: "LOW",
|
||||
2: "MEDIUM",
|
||||
3: "HIGH",
|
||||
4: "CRITICAL",
|
||||
}
|
||||
|
||||
var Severity_value = map[string]int32{
|
||||
"UNKNOWN": 0,
|
||||
"LOW": 1,
|
||||
"MEDIUM": 2,
|
||||
"HIGH": 3,
|
||||
"CRITICAL": 4,
|
||||
}
|
||||
|
||||
func (x Severity) String() string {
|
||||
return proto.EnumName(Severity_name, int32(x))
|
||||
}
|
||||
|
||||
func (Severity) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{0}
|
||||
}
|
||||
|
||||
type OSDetectRequest struct {
|
||||
OsFamily string `protobuf:"bytes,1,opt,name=os_family,json=osFamily,proto3" json:"os_family,omitempty"`
|
||||
OsName string `protobuf:"bytes,2,opt,name=os_name,json=osName,proto3" json:"os_name,omitempty"`
|
||||
Packages []*Package `protobuf:"bytes,3,rep,name=packages,proto3" json:"packages,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *OSDetectRequest) Reset() { *m = OSDetectRequest{} }
|
||||
func (m *OSDetectRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*OSDetectRequest) ProtoMessage() {}
|
||||
func (*OSDetectRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{0}
|
||||
}
|
||||
|
||||
func (m *OSDetectRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_OSDetectRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *OSDetectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_OSDetectRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *OSDetectRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_OSDetectRequest.Merge(m, src)
|
||||
}
|
||||
func (m *OSDetectRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_OSDetectRequest.Size(m)
|
||||
}
|
||||
func (m *OSDetectRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_OSDetectRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_OSDetectRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *OSDetectRequest) GetOsFamily() string {
|
||||
if m != nil {
|
||||
return m.OsFamily
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *OSDetectRequest) GetOsName() string {
|
||||
if m != nil {
|
||||
return m.OsName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *OSDetectRequest) GetPackages() []*Package {
|
||||
if m != nil {
|
||||
return m.Packages
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DetectResponse struct {
|
||||
Vulnerabilities []*Vulnerability `protobuf:"bytes,1,rep,name=vulnerabilities,proto3" json:"vulnerabilities,omitempty"`
|
||||
Eosl bool `protobuf:"varint,2,opt,name=eosl,proto3" json:"eosl,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DetectResponse) Reset() { *m = DetectResponse{} }
|
||||
func (m *DetectResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*DetectResponse) ProtoMessage() {}
|
||||
func (*DetectResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{1}
|
||||
}
|
||||
|
||||
func (m *DetectResponse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_DetectResponse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *DetectResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_DetectResponse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *DetectResponse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_DetectResponse.Merge(m, src)
|
||||
}
|
||||
func (m *DetectResponse) XXX_Size() int {
|
||||
return xxx_messageInfo_DetectResponse.Size(m)
|
||||
}
|
||||
func (m *DetectResponse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_DetectResponse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_DetectResponse proto.InternalMessageInfo
|
||||
|
||||
func (m *DetectResponse) GetVulnerabilities() []*Vulnerability {
|
||||
if m != nil {
|
||||
return m.Vulnerabilities
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *DetectResponse) GetEosl() bool {
|
||||
if m != nil {
|
||||
return m.Eosl
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
// binary package
|
||||
// e.g. bind-utils
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Release string `protobuf:"bytes,3,opt,name=release,proto3" json:"release,omitempty"`
|
||||
Epoch int32 `protobuf:"varint,4,opt,name=epoch,proto3" json:"epoch,omitempty"`
|
||||
Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"`
|
||||
// src package containing some binary packages
|
||||
// e.g. bind
|
||||
SrcName string `protobuf:"bytes,6,opt,name=src_name,json=srcName,proto3" json:"src_name,omitempty"`
|
||||
SrcVersion string `protobuf:"bytes,7,opt,name=src_version,json=srcVersion,proto3" json:"src_version,omitempty"`
|
||||
SrcRelease string `protobuf:"bytes,8,opt,name=src_release,json=srcRelease,proto3" json:"src_release,omitempty"`
|
||||
SrcEpoch int32 `protobuf:"varint,9,opt,name=src_epoch,json=srcEpoch,proto3" json:"src_epoch,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Package) Reset() { *m = Package{} }
|
||||
func (m *Package) String() string { return proto.CompactTextString(m) }
|
||||
func (*Package) ProtoMessage() {}
|
||||
func (*Package) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{2}
|
||||
}
|
||||
|
||||
func (m *Package) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Package.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Package) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Package.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Package) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Package.Merge(m, src)
|
||||
}
|
||||
func (m *Package) XXX_Size() int {
|
||||
return xxx_messageInfo_Package.Size(m)
|
||||
}
|
||||
func (m *Package) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Package.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Package proto.InternalMessageInfo
|
||||
|
||||
func (m *Package) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetVersion() string {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetRelease() string {
|
||||
if m != nil {
|
||||
return m.Release
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetEpoch() int32 {
|
||||
if m != nil {
|
||||
return m.Epoch
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Package) GetArch() string {
|
||||
if m != nil {
|
||||
return m.Arch
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetSrcName() string {
|
||||
if m != nil {
|
||||
return m.SrcName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetSrcVersion() string {
|
||||
if m != nil {
|
||||
return m.SrcVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetSrcRelease() string {
|
||||
if m != nil {
|
||||
return m.SrcRelease
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Package) GetSrcEpoch() int32 {
|
||||
if m != nil {
|
||||
return m.SrcEpoch
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type LibDetectRequest struct {
|
||||
FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"`
|
||||
Libraries []*Library `protobuf:"bytes,2,rep,name=libraries,proto3" json:"libraries,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *LibDetectRequest) Reset() { *m = LibDetectRequest{} }
|
||||
func (m *LibDetectRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*LibDetectRequest) ProtoMessage() {}
|
||||
func (*LibDetectRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{3}
|
||||
}
|
||||
|
||||
func (m *LibDetectRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_LibDetectRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *LibDetectRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_LibDetectRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *LibDetectRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_LibDetectRequest.Merge(m, src)
|
||||
}
|
||||
func (m *LibDetectRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_LibDetectRequest.Size(m)
|
||||
}
|
||||
func (m *LibDetectRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_LibDetectRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_LibDetectRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *LibDetectRequest) GetFilePath() string {
|
||||
if m != nil {
|
||||
return m.FilePath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *LibDetectRequest) GetLibraries() []*Library {
|
||||
if m != nil {
|
||||
return m.Libraries
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Library struct {
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Library) Reset() { *m = Library{} }
|
||||
func (m *Library) String() string { return proto.CompactTextString(m) }
|
||||
func (*Library) ProtoMessage() {}
|
||||
func (*Library) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{4}
|
||||
}
|
||||
|
||||
func (m *Library) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Library.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Library) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Library.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Library) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Library.Merge(m, src)
|
||||
}
|
||||
func (m *Library) XXX_Size() int {
|
||||
return xxx_messageInfo_Library.Size(m)
|
||||
}
|
||||
func (m *Library) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Library.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Library proto.InternalMessageInfo
|
||||
|
||||
func (m *Library) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Library) GetVersion() string {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
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.detector.Severity" json:"severity,omitempty"`
|
||||
References []string `protobuf:"bytes,8,rep,name=references,proto3" json:"references,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Vulnerability) Reset() { *m = Vulnerability{} }
|
||||
func (m *Vulnerability) String() string { return proto.CompactTextString(m) }
|
||||
func (*Vulnerability) ProtoMessage() {}
|
||||
func (*Vulnerability) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_93e16dbd737b8924, []int{5}
|
||||
}
|
||||
|
||||
func (m *Vulnerability) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Vulnerability.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Vulnerability) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Vulnerability.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Vulnerability) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Vulnerability.Merge(m, src)
|
||||
}
|
||||
func (m *Vulnerability) XXX_Size() int {
|
||||
return xxx_messageInfo_Vulnerability.Size(m)
|
||||
}
|
||||
func (m *Vulnerability) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Vulnerability.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Vulnerability proto.InternalMessageInfo
|
||||
|
||||
func (m *Vulnerability) GetVulnerabilityId() string {
|
||||
if m != nil {
|
||||
return m.VulnerabilityId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetPkgName() string {
|
||||
if m != nil {
|
||||
return m.PkgName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetInstalledVersion() string {
|
||||
if m != nil {
|
||||
return m.InstalledVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetFixedVersion() string {
|
||||
if m != nil {
|
||||
return m.FixedVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetTitle() string {
|
||||
if m != nil {
|
||||
return m.Title
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetDescription() string {
|
||||
if m != nil {
|
||||
return m.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetSeverity() Severity {
|
||||
if m != nil {
|
||||
return m.Severity
|
||||
}
|
||||
return Severity_UNKNOWN
|
||||
}
|
||||
|
||||
func (m *Vulnerability) GetReferences() []string {
|
||||
if m != nil {
|
||||
return m.References
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("trivy.detector.Severity", Severity_name, Severity_value)
|
||||
proto.RegisterType((*OSDetectRequest)(nil), "trivy.detector.OSDetectRequest")
|
||||
proto.RegisterType((*DetectResponse)(nil), "trivy.detector.DetectResponse")
|
||||
proto.RegisterType((*Package)(nil), "trivy.detector.Package")
|
||||
proto.RegisterType((*LibDetectRequest)(nil), "trivy.detector.LibDetectRequest")
|
||||
proto.RegisterType((*Library)(nil), "trivy.detector.Library")
|
||||
proto.RegisterType((*Vulnerability)(nil), "trivy.detector.Vulnerability")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("rpc/detector/service.proto", fileDescriptor_93e16dbd737b8924) }
|
||||
|
||||
var fileDescriptor_93e16dbd737b8924 = []byte{
|
||||
// 618 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4d, 0x4f, 0xdb, 0x40,
|
||||
0x10, 0x6d, 0x3e, 0xed, 0x4c, 0xf8, 0x70, 0x57, 0x48, 0xb8, 0xa0, 0x42, 0x94, 0x5e, 0x68, 0x2b,
|
||||
0x05, 0x29, 0xb4, 0xea, 0xb9, 0x05, 0x0a, 0x69, 0x43, 0x40, 0xa6, 0x80, 0xda, 0x4b, 0xb4, 0x71,
|
||||
0x26, 0x64, 0x85, 0x93, 0x75, 0x77, 0x97, 0xa8, 0x96, 0xfa, 0xb7, 0xfa, 0xd3, 0x7a, 0xaf, 0x76,
|
||||
0xd7, 0x36, 0x49, 0xca, 0x85, 0xdb, 0xcc, 0xbc, 0xe7, 0x99, 0xd9, 0xf7, 0xd6, 0x0b, 0x5b, 0x22,
|
||||
0x0e, 0xf7, 0x87, 0xa8, 0x30, 0x54, 0x5c, 0xec, 0x4b, 0x14, 0x33, 0x16, 0x62, 0x2b, 0x16, 0x5c,
|
||||
0x71, 0xb2, 0xa6, 0x04, 0x9b, 0x25, 0xad, 0x0c, 0x6d, 0xfe, 0x86, 0xf5, 0xf3, 0xcb, 0x23, 0x93,
|
||||
0x05, 0xf8, 0xf3, 0x1e, 0xa5, 0x22, 0xdb, 0x50, 0xe3, 0xb2, 0x3f, 0xa2, 0x13, 0x16, 0x25, 0x7e,
|
||||
0xa1, 0x51, 0xd8, 0xab, 0x05, 0x2e, 0x97, 0x9f, 0x4d, 0x4e, 0x36, 0xc1, 0xe1, 0xb2, 0x3f, 0xa5,
|
||||
0x13, 0xf4, 0x8b, 0x06, 0xaa, 0x72, 0xd9, 0xa3, 0x13, 0x24, 0x07, 0xe0, 0xc6, 0x34, 0xbc, 0xa3,
|
||||
0xb7, 0x28, 0xfd, 0x52, 0xa3, 0xb4, 0x57, 0x6f, 0x6f, 0xb6, 0x16, 0x67, 0xb5, 0x2e, 0x2c, 0x1e,
|
||||
0xe4, 0xc4, 0xe6, 0x04, 0xd6, 0xb2, 0xd9, 0x32, 0xe6, 0x53, 0x89, 0xe4, 0x04, 0xd6, 0x67, 0xf7,
|
||||
0xd1, 0x14, 0x05, 0x1d, 0xb0, 0x88, 0x29, 0x86, 0xd2, 0x2f, 0x98, 0x6e, 0x2f, 0x97, 0xbb, 0x5d,
|
||||
0xcf, 0xd1, 0x92, 0x60, 0xf9, 0x2b, 0x42, 0xa0, 0x8c, 0x5c, 0x46, 0x66, 0x4b, 0x37, 0x30, 0x71,
|
||||
0xf3, 0x6f, 0x01, 0x9c, 0x74, 0x09, 0x8d, 0x9b, 0x53, 0xd8, 0x03, 0x9a, 0x98, 0xf8, 0xe0, 0xcc,
|
||||
0x50, 0x48, 0xc6, 0xa7, 0xe9, 0xe1, 0xb2, 0x54, 0x23, 0x02, 0x23, 0xa4, 0x12, 0xfd, 0x92, 0x45,
|
||||
0xd2, 0x94, 0x6c, 0x40, 0x05, 0x63, 0x1e, 0x8e, 0xfd, 0x72, 0xa3, 0xb0, 0x57, 0x09, 0x6c, 0xa2,
|
||||
0xbb, 0x53, 0x11, 0x8e, 0xfd, 0x8a, 0xed, 0xae, 0x63, 0xf2, 0x02, 0x5c, 0x29, 0x42, 0xab, 0x5d,
|
||||
0xd5, 0x36, 0x91, 0x22, 0x34, 0xe2, 0xed, 0x42, 0x5d, 0x43, 0xd9, 0x70, 0xc7, 0xa0, 0x20, 0x45,
|
||||
0x78, 0x9d, 0xce, 0x4f, 0x09, 0xd9, 0x0e, 0x6e, 0x4e, 0x08, 0xd2, 0x35, 0xb6, 0xa1, 0xa6, 0x09,
|
||||
0x76, 0x95, 0x9a, 0x59, 0x45, 0x4f, 0x3b, 0xd6, 0x79, 0x73, 0x04, 0x5e, 0x97, 0x0d, 0xfe, 0x73,
|
||||
0x79, 0xc4, 0x22, 0xec, 0xc7, 0x54, 0x8d, 0x33, 0x97, 0x75, 0xe1, 0x82, 0xaa, 0x31, 0x79, 0x0f,
|
||||
0xb5, 0x88, 0x0d, 0x04, 0x15, 0x5a, 0xff, 0xe2, 0xe3, 0x6e, 0x76, 0x0d, 0x21, 0x09, 0x1e, 0x98,
|
||||
0xcd, 0x0f, 0xe0, 0xa4, 0xd5, 0xa7, 0xc9, 0xdb, 0xfc, 0x53, 0x84, 0xd5, 0x05, 0x3f, 0xc9, 0x6b,
|
||||
0xf0, 0xe6, 0x1d, 0x4d, 0xfa, 0x6c, 0x98, 0xf6, 0x5a, 0x70, 0x3a, 0xe9, 0x0c, 0xb5, 0xae, 0xf1,
|
||||
0xdd, 0xed, 0xfc, 0x9d, 0x74, 0xe2, 0xbb, 0x5b, 0xa3, 0xeb, 0x5b, 0x78, 0xce, 0xa6, 0x52, 0xd1,
|
||||
0x28, 0xc2, 0x61, 0xae, 0xae, 0x35, 0xd0, 0xcb, 0x81, 0x4c, 0xe3, 0x57, 0xb0, 0x3a, 0x62, 0xbf,
|
||||
0xe6, 0x88, 0x65, 0x43, 0x5c, 0x31, 0xc5, 0x8c, 0xb4, 0x01, 0x15, 0xc5, 0x54, 0x84, 0xa9, 0xb3,
|
||||
0x36, 0x21, 0x0d, 0xa8, 0x0f, 0x51, 0x86, 0x82, 0xc5, 0x4a, 0x7f, 0x68, 0xdd, 0x9d, 0x2f, 0x91,
|
||||
0x77, 0xe0, 0x4a, 0x9c, 0xa1, 0x60, 0x2a, 0x31, 0xf6, 0xae, 0xb5, 0xfd, 0x65, 0x41, 0x2f, 0x53,
|
||||
0x3c, 0xc8, 0x99, 0x64, 0x07, 0x40, 0xe0, 0x08, 0x05, 0x4e, 0x43, 0x94, 0xbe, 0xdb, 0x28, 0x69,
|
||||
0xd7, 0x1f, 0x2a, 0x6f, 0x8e, 0xc0, 0xcd, 0xbe, 0x22, 0x75, 0x70, 0xae, 0x7a, 0x5f, 0x7b, 0xe7,
|
||||
0x37, 0x3d, 0xef, 0x19, 0x71, 0xa0, 0xd4, 0x3d, 0xbf, 0xf1, 0x0a, 0x04, 0xa0, 0x7a, 0x76, 0x7c,
|
||||
0xd4, 0xb9, 0x3a, 0xf3, 0x8a, 0xc4, 0x85, 0xf2, 0x69, 0xe7, 0xe4, 0xd4, 0x2b, 0x91, 0x15, 0x70,
|
||||
0x0f, 0x83, 0xce, 0xb7, 0xce, 0xe1, 0xc7, 0xae, 0x57, 0x6e, 0xdf, 0x00, 0x64, 0x6f, 0x00, 0x17,
|
||||
0xa4, 0x03, 0x55, 0x1b, 0x93, 0xdd, 0xe5, 0x0d, 0x97, 0x5e, 0x8a, 0xad, 0x9d, 0x65, 0xc2, 0xe2,
|
||||
0xcf, 0xdc, 0xfe, 0x0e, 0xf5, 0xfc, 0xde, 0x71, 0x41, 0xbe, 0xe4, 0x9d, 0x1b, 0x8f, 0x5c, 0xa6,
|
||||
0x27, 0xb5, 0xfe, 0x04, 0x3f, 0xdc, 0x0c, 0x1a, 0x54, 0xcd, 0xd3, 0x76, 0xf0, 0x2f, 0x00, 0x00,
|
||||
0xff, 0xff, 0xde, 0x1c, 0x85, 0x42, 0xf8, 0x04, 0x00, 0x00,
|
||||
}
|
||||
68
rpc/detector/service.proto
Normal file
68
rpc/detector/service.proto
Normal file
@@ -0,0 +1,68 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package trivy.detector;
|
||||
option go_package = "detector";
|
||||
|
||||
service OSDetector {
|
||||
rpc Detect(OSDetectRequest) returns (DetectResponse);
|
||||
}
|
||||
|
||||
message OSDetectRequest {
|
||||
string os_family = 1;
|
||||
string os_name = 2;
|
||||
repeated Package packages = 3;
|
||||
}
|
||||
|
||||
message DetectResponse {
|
||||
repeated Vulnerability vulnerabilities = 1;
|
||||
bool eosl = 2;
|
||||
}
|
||||
|
||||
message Package {
|
||||
// binary package
|
||||
// e.g. bind-utils
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
string release = 3;
|
||||
int32 epoch = 4;
|
||||
string arch = 5;
|
||||
// src package containing some binary packages
|
||||
// e.g. bind
|
||||
string src_name = 6;
|
||||
string src_version = 7;
|
||||
string src_release = 8;
|
||||
int32 src_epoch = 9;
|
||||
}
|
||||
|
||||
service LibDetector {
|
||||
rpc Detect(LibDetectRequest) returns (DetectResponse);
|
||||
}
|
||||
|
||||
message LibDetectRequest {
|
||||
string file_path = 1;
|
||||
repeated Library libraries = 2;
|
||||
}
|
||||
|
||||
message Library {
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
}
|
||||
|
||||
message Vulnerability {
|
||||
string vulnerability_id = 1;
|
||||
string pkg_name = 2;
|
||||
string installed_version = 3;
|
||||
string fixed_version = 4;
|
||||
string title = 5;
|
||||
string description = 6;
|
||||
Severity severity = 7;
|
||||
repeated string references = 8;
|
||||
}
|
||||
|
||||
enum Severity {
|
||||
UNKNOWN = 0;
|
||||
LOW = 1;
|
||||
MEDIUM = 2;
|
||||
HIGH = 3;
|
||||
CRITICAL = 4;
|
||||
}
|
||||
1127
rpc/detector/service.twirp.go
Normal file
1127
rpc/detector/service.twirp.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user