mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-10 14:50:50 -08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9fa353a06 | ||
|
|
9a1d7460f6 | ||
|
|
d18d17b861 | ||
|
|
4b57c0d4e6 | ||
|
|
ccd9b2d2c5 | ||
|
|
ec770cd819 | ||
|
|
b7ec633fb2 | ||
|
|
7aabff1236 | ||
|
|
9dc1bdffb1 | ||
|
|
2ac672a663 | ||
|
|
11ae6b29d5 | ||
|
|
f201f59e27 | ||
|
|
25d45e1ac5 | ||
|
|
298ba99b8f | ||
|
|
65cbe3cac3 | ||
|
|
f94e8dcf04 | ||
|
|
9629303a0f |
@@ -1,5 +1,6 @@
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.12
|
||||
RUN apk --no-cache add ca-certificates git rpm
|
||||
COPY trivy /usr/local/bin/trivy
|
||||
COPY contrib/gitlab.tpl contrib/gitlab.tpl
|
||||
COPY contrib/junit.tpl contrib/junit.tpl
|
||||
ENTRYPOINT ["trivy"]
|
||||
|
||||
404
README.md
404
README.md
@@ -7,7 +7,7 @@
|
||||
[](https://github.com/aquasecurity/trivy/blob/master/LICENSE)
|
||||
[](https://microbadger.com/images/aquasec/trivy "Get your own version badge on microbadger.com")
|
||||
|
||||
A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI.
|
||||
A Simple and Comprehensive Vulnerability Scanner for Containers and other Artifacts, Suitable for CI.
|
||||
|
||||
<img src="imgs/usage.gif" width="700">
|
||||
<img src="imgs/usage1.png" width="600">
|
||||
@@ -18,62 +18,77 @@ A Simple and Comprehensive Vulnerability Scanner for Containers, Suitable for CI
|
||||
- [Abstract](#abstract)
|
||||
- [Features](#features)
|
||||
- [Installation](#installation)
|
||||
- [RHEL/CentOS](#rhelcentos)
|
||||
- [Debian/Ubuntu](#debianubuntu)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Mac OS X / Homebrew](#homebrew)
|
||||
- [Binary](#binary)
|
||||
- [From source](#from-source)
|
||||
* [RHEL/CentOS](#rhelcentos)
|
||||
* [Debian/Ubuntu](#debianubuntu)
|
||||
* [Arch Linux](#arch-linux)
|
||||
* [Homebrew](#homebrew)
|
||||
* [Install Script](#install-script)
|
||||
* [Binary](#binary)
|
||||
* [From source](#from-source)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Basic](#basic)
|
||||
- [Docker](#docker)
|
||||
* [Image](#image)
|
||||
+ [Basic](#basic)
|
||||
+ [Docker](#docker)
|
||||
* [Filesystem](#filesystem)
|
||||
* [Embed in Dockerfile](#embed-in-dockerfile)
|
||||
* [Git Repository](#git-repository)
|
||||
- [Examples](#examples)
|
||||
- [Standalone](#standalone)
|
||||
- [Scan an image](#scan-an-image)
|
||||
- [Scan an image file](#scan-an-image-file)
|
||||
- [Scan an OCI image](#scan-an-oci-image)
|
||||
- [Save the results as JSON](#save-the-results-as-json)
|
||||
- [Save the results using a template](#save-the-results-using-a-template)
|
||||
- [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)
|
||||
- [Authentication](#authentication)
|
||||
* [Standalone](#standalone)
|
||||
+ [Scan an image](#scan-an-image)
|
||||
+ [Scan an image file](#scan-an-image-file)
|
||||
+ [Scan an OCI image](#scan-an-oci-image)
|
||||
+ [Scan a container from inside the container](#scan-a-container-from-inside-the-container)
|
||||
+ [Scan a project including a lock file](#scan-a-project-including-a-lock-file)
|
||||
+ [Embed in Dockerfile](#embed-in-dockerfile)
|
||||
+ [Save the results as JSON](#save-the-results-as-json)
|
||||
+ [Save the results using a template](#save-the-results-using-a-template)
|
||||
+ [Filter the vulnerabilities by severities](#filter-the-vulnerabilities-by-severities)
|
||||
+ [Filter the vulnerabilities by type](#filter-the-vulnerabilities-by-type)
|
||||
+ [Skip update of vulnerability DB](#skip-update-of-vulnerability-db)
|
||||
+ [Only download vulnerability database](#only-download-vulnerability-database)
|
||||
+ [Ignore unfixed vulnerabilities](#ignore-unfixed-vulnerabilities)
|
||||
+ [Specify exit code](#specify-exit-code)
|
||||
+ [Ignore the specified vulnerabilities](#ignore-the-specified-vulnerabilities)
|
||||
+ [Specify cache directory](#specify-cache-directory)
|
||||
+ [Clear caches](#clear-caches)
|
||||
+ [Reset](#reset)
|
||||
+ [Use lightweight DB](#use-lightweight-db)
|
||||
* [Client / Server](#client--server)
|
||||
+ [Server](#server)
|
||||
+ [Client](#client)
|
||||
+ [Authentication](#authentication)
|
||||
+ [Deprecated options](#deprecated-options)
|
||||
- [Continuous Integration (CI)](#continuous-integration-ci)
|
||||
- [Travis CI](#travis-ci)
|
||||
- [CircleCI](#circleci)
|
||||
- [GitLab CI](#gitlab-ci)
|
||||
- [Authorization for Private Docker Registry](#authorization-for-private-docker-registry)
|
||||
* [GitHub Actions](#github-actions)
|
||||
* [Travis CI](#travis-ci)
|
||||
* [CircleCI](#circleci)
|
||||
* [GitLab CI](#gitlab-ci)
|
||||
* [AWS CodePipeline](#aws-codepipeline)
|
||||
* [Authorization for Private Docker Registry](#authorization-for-private-docker-registry)
|
||||
- [Vulnerability Detection](#vulnerability-detection)
|
||||
- [OS Packages](#os-packages)
|
||||
- [Application Dependencies](#application-dependencies)
|
||||
- [Usage](#usage)
|
||||
* [OS Packages](#os-packages)
|
||||
* [Application Dependencies](#application-dependencies)
|
||||
* [Image Tar format](#image-tar-format)
|
||||
* [Data sources](#data-sources)
|
||||
- [Air-gapped environment](#air-gapped-environment)
|
||||
- [Comparison with other scanners](#comparison-with-other-scanners)
|
||||
- [Overview](#overview)
|
||||
- [vs Clair](#vs-clair)
|
||||
- [vs Anchore Engine](#vs-anchore-engine)
|
||||
- [vs Quay, Docker Hub, GCR](#vs-quay-docker-hub-gcr)
|
||||
- [Migration](#migration)
|
||||
- [Usage](#usage)
|
||||
* [Image](#image-1)
|
||||
* [Client](#client-1)
|
||||
* [Server](#server-1)
|
||||
- [Q&A](#qa)
|
||||
- [Homebrew](#homebrew)
|
||||
- [Others](#others)
|
||||
* [Homebrew](#homebrew-2)
|
||||
* [Others](#others)
|
||||
|
||||
|
||||
# Abstract
|
||||
|
||||
`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers.
|
||||
`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers and other artifacts.
|
||||
A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System.
|
||||
`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn etc.).
|
||||
`Trivy` is easy to use. Just install the binary and you're ready to scan. All you need to do for scanning is to specify an image name of the container.
|
||||
`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.).
|
||||
`Trivy` is easy to use. Just install the binary and you're ready to scan. All you need to do for scanning is to specify a target such as an image name of the container.
|
||||
|
||||
It is considered to be used in CI. Before pushing to a container registry, you can scan your local container image easily.
|
||||
It is considered to be used in CI. Before pushing to a container registry or deploying your application, you can scan your local container image and other artifacts easily.
|
||||
See [here](#continuous-integration-ci) for details.
|
||||
|
||||
# Features
|
||||
@@ -82,7 +97,7 @@ See [here](#continuous-integration-ci) for details.
|
||||
- OS packages (Alpine, **Red Hat Universal Base Image**, Red Hat Enterprise Linux, CentOS, Oracle Linux, Debian, Ubuntu, Amazon Linux, openSUSE Leap, SUSE Enterprise Linux, Photon OS and Distroless)
|
||||
- **Application dependencies** (Bundler, Composer, Pipenv, Poetry, npm, yarn and Cargo)
|
||||
- Simple
|
||||
- Specify only an image name
|
||||
- Specify only an image name or artifact name
|
||||
- See [Quick Start](#quick-start) and [Examples](#examples)
|
||||
- Fast
|
||||
- The first scan will finish within 10 seconds (depending on your network). Consequent scans will finish in single seconds.
|
||||
@@ -94,13 +109,16 @@ See [here](#continuous-integration-ci) for details.
|
||||
- **Especially Alpine Linux and RHEL/CentOS**
|
||||
- Other OSes are also high
|
||||
- DevSecOps
|
||||
- **Suitable for CI** such as Travis CI, CircleCI, Jenkins, etc.
|
||||
- **Suitable for CI** such as Travis CI, CircleCI, Jenkins, GitLab CI, etc.
|
||||
- See [CI Example](#continuous-integration-ci)
|
||||
- Support multiple formats
|
||||
- A local image in Docker Engine which is running as a daemon
|
||||
- A remote image in Docker Registry such as Docker Hub, ECR, GCR and ACR
|
||||
- A tar archive stored in the `docker save` formatted file
|
||||
- An image directory compliant with [OCI Image Format](https://github.com/opencontainers/image-spec)
|
||||
- container image
|
||||
- A local image in Docker Engine which is running as a daemon
|
||||
- A remote image in Docker Registry such as Docker Hub, ECR, GCR and ACR
|
||||
- A tar archive stored in the `docker save` formatted file
|
||||
- An image directory compliant with [OCI Image Format](https://github.com/opencontainers/image-spec)
|
||||
- local filesystem
|
||||
- remote git repository
|
||||
|
||||
Please see [LICENSE](https://github.com/aquasecurity/trivy/blob/master/LICENSE) for Trivy licensing information. Note that Trivy uses vulnerability information from a variety of sources, some of which are licensed for non-commercial use only.
|
||||
|
||||
@@ -169,6 +187,13 @@ You can use homebrew on macOS.
|
||||
$ brew install aquasecurity/trivy/trivy
|
||||
```
|
||||
|
||||
## Install Script
|
||||
This script downloads Trivy binary based on your OS and architecture.
|
||||
|
||||
```
|
||||
$ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
```
|
||||
|
||||
## Binary
|
||||
|
||||
Get the latest version from [this page](https://github.com/aquasecurity/trivy/releases/latest), and download the archive file for your operating system/architecture. Unpack the archive, and put the binary somewhere in your `$PATH` (on UNIX-y systems, /usr/local/bin or the like). Make sure it has execution bits turned on.
|
||||
@@ -190,9 +215,11 @@ You also need to install `rpm` command for scanning images based on RHEL/CentOS.
|
||||
|
||||
# Quick Start
|
||||
|
||||
## Image
|
||||
|
||||
Simply specify an image name (and a tag). **The `latest` tag should be avoided as problems occur with the image cache.** See [Clear image caches](#clear-image-caches).
|
||||
|
||||
## Basic
|
||||
### Basic
|
||||
|
||||
```
|
||||
$ trivy image [YOUR_IMAGE_NAME]
|
||||
@@ -225,7 +252,7 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
</details>
|
||||
|
||||
## Docker
|
||||
### Docker
|
||||
|
||||
Replace [YOUR_CACHE_DIR] with the cache directory on your machine.
|
||||
|
||||
@@ -269,6 +296,47 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
</details>
|
||||
|
||||
## Filesystem
|
||||
Scan a filesystem (such as a host machine, a virtual machine image, or an unpacked container image filesystem).
|
||||
|
||||
Trivy will look for vulnerabilities based on lock files such as Gemfile.lock and package-lock.json.
|
||||
|
||||
```
|
||||
$ trivy fs /path/to/project
|
||||
```
|
||||
|
||||
|
||||
Scan your container from inside the container.
|
||||
|
||||
```
|
||||
$ docker run --rm -it alpine:3.11
|
||||
/ # curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
/ # trivy fs /
|
||||
```
|
||||
|
||||
## Embed in Dockerfile
|
||||
Scan your image as part of the build process by embedding Trivy in the Dockerfile. This approach can be used to update Dockerfiles currently using Aqua’s [Microscanner](https://github.com/aquasecurity/microscanner).
|
||||
|
||||
```
|
||||
$ cat Dockerfile
|
||||
FROM alpine:3.7
|
||||
|
||||
RUN apk add curl \
|
||||
&& curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin \
|
||||
&& trivy filesystem --exit-code 1 --no-progress /
|
||||
|
||||
$ docker build -t vulnerable-image .
|
||||
```
|
||||
|
||||
## Git Repository
|
||||
Scan your remote git repository
|
||||
|
||||
```
|
||||
$ trivy repo https://github.com/knqyf263/trivy-ci-test
|
||||
```
|
||||
|
||||
Only public repositories are supported.
|
||||
|
||||
# Examples
|
||||
|
||||
## Standalone
|
||||
@@ -285,17 +353,11 @@ $ trivy image knqyf263/vuln-image:1.2.3
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
2019-05-16T12:58:55.967+0900 INFO Updating vulnerability database...
|
||||
2019-05-16T12:59:03.150+0900 INFO Detecting Alpine vulnerabilities...
|
||||
2019-05-16T12:59:03.156+0900 INFO Updating bundler Security DB...
|
||||
2019-05-16T12:59:04.941+0900 INFO Detecting bundler vulnerabilities...
|
||||
2019-05-16T12:59:04.942+0900 INFO Updating cargo Security DB...
|
||||
2019-05-16T12:59:05.967+0900 INFO Detecting cargo vulnerabilities...
|
||||
2019-05-16T12:59:05.967+0900 INFO Updating composer Security DB...
|
||||
2019-05-16T12:59:07.834+0900 INFO Detecting composer vulnerabilities...
|
||||
2019-05-16T12:59:07.834+0900 INFO Updating npm Security DB...
|
||||
2019-05-16T12:59:10.285+0900 INFO Detecting npm vulnerabilities...
|
||||
2019-05-16T12:59:10.285+0900 INFO Updating pipenv Security DB...
|
||||
2019-05-16T12:59:11.487+0900 INFO Detecting pipenv vulnerabilities...
|
||||
|
||||
knqyf263/vuln-image:1.2.3 (alpine 3.7.1)
|
||||
@@ -555,6 +617,132 @@ $ skopeo copy docker-daemon:alpine:3.11 oci:/path/to/alpine
|
||||
$ trivy image --input /path/to/alpine
|
||||
```
|
||||
|
||||
### Scan a container from inside the container
|
||||
|
||||
```
|
||||
$ docker run --rm -it alpine:3.10.2
|
||||
/ # curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
/ # trivy fs /
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
adb3b9abab80 (alpine 3.10.2)
|
||||
============================
|
||||
Total: 5 (UNKNOWN: 0, LOW: 1, MEDIUM: 4, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
| openssl | CVE-2019-1549 | MEDIUM | 1.1.1c-r0 | 1.1.1d-r0 | openssl: information |
|
||||
| | | | | | disclosure in fork() |
|
||||
+ +------------------+ + +---------------+--------------------------------+
|
||||
| | CVE-2019-1551 | | | 1.1.1d-r2 | openssl: Integer overflow in |
|
||||
| | | | | | RSAZ modular exponentiation on |
|
||||
| | | | | | x86_64 |
|
||||
+ +------------------+ + +---------------+--------------------------------+
|
||||
| | CVE-2019-1563 | | | 1.1.1d-r0 | openssl: information |
|
||||
| | | | | | disclosure in PKCS7_dataDecode |
|
||||
| | | | | | and CMS_decrypt_set1_pkey |
|
||||
+ +------------------+ + +---------------+--------------------------------+
|
||||
| | CVE-2020-1967 | | | 1.1.1g-r0 | openssl: Segmentation fault in |
|
||||
| | | | | | SSL_check_chain causes denial |
|
||||
| | | | | | of service |
|
||||
+ +------------------+----------+ +---------------+--------------------------------+
|
||||
| | CVE-2019-1547 | LOW | | 1.1.1d-r0 | openssl: side-channel weak |
|
||||
| | | | | | encryption vulnerability |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Scan a project including a lock file
|
||||
|
||||
```
|
||||
$ trivy fs ~/src/github.com/aquasecurity/trivy-ci-test
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
2020-06-01T17:06:58.652+0300 WARN OS is not detected and vulnerabilities in OS packages are not detected.
|
||||
2020-06-01T17:06:58.652+0300 INFO Detecting pipenv vulnerabilities...
|
||||
2020-06-01T17:06:58.691+0300 INFO Detecting cargo vulnerabilities...
|
||||
|
||||
Pipfile.lock
|
||||
============
|
||||
Total: 10 (UNKNOWN: 2, LOW: 0, MEDIUM: 6, HIGH: 2, CRITICAL: 0)
|
||||
|
||||
+---------------------+------------------+----------+-------------------+------------------------+------------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------------------+------------------+----------+-------------------+------------------------+------------------------------------+
|
||||
| django | CVE-2020-7471 | HIGH | 2.0.9 | 3.0.3, 2.2.10, 1.11.28 | django: potential |
|
||||
| | | | | | SQL injection via |
|
||||
| | | | | | StringAgg(delimiter) |
|
||||
+ +------------------+----------+ +------------------------+------------------------------------+
|
||||
| | CVE-2019-19844 | MEDIUM | | 3.0.1, 2.2.9, 1.11.27 | Django: crafted email address |
|
||||
| | | | | | allows account takeover |
|
||||
+ +------------------+ + +------------------------+------------------------------------+
|
||||
| | CVE-2019-3498 | | | 2.1.5, 2.0.10, 1.11.18 | python-django: Content |
|
||||
| | | | | | spoofing via URL path in |
|
||||
| | | | | | default 404 page |
|
||||
+ +------------------+ + +------------------------+------------------------------------+
|
||||
| | CVE-2019-6975 | | | 2.1.6, 2.0.11, 1.11.19 | python-django: |
|
||||
| | | | | | memory exhaustion in |
|
||||
| | | | | | django.utils.numberformat.format() |
|
||||
+---------------------+------------------+----------+-------------------+------------------------+------------------------------------+
|
||||
...
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Embed in Dockerfile
|
||||
|
||||
```
|
||||
$ cat Dockerfile
|
||||
FROM alpine:3.7
|
||||
|
||||
RUN apk add curl \
|
||||
&& curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin \
|
||||
&& trivy filesystem --exit-code 1 --no-progress /
|
||||
|
||||
$ docker build -t vulnerable-image .
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
Sending build context to Docker daemon 31.14MB
|
||||
Step 1/2 : FROM alpine:3.7
|
||||
---> 6d1ef012b567
|
||||
Step 2/2 : RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin && trivy filesystem --exit-code 1 --no-progress /
|
||||
---> Running in 27b004205da0
|
||||
2020-06-01T14:10:41.261Z INFO Need to update DB
|
||||
2020-06-01T14:10:41.262Z INFO Downloading DB...
|
||||
2020-06-01T14:10:56.188Z INFO Detecting Alpine vulnerabilities...
|
||||
2020-06-01T14:10:56.188Z WARN This OS version is no longer supported by the distribution: alpine 3.7.3
|
||||
2020-06-01T14:10:56.188Z WARN The vulnerability detection may be insufficient because security updates are not provided
|
||||
|
||||
27b004205da0 (alpine 3.7.3)
|
||||
===========================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
|
||||
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
| musl | CVE-2019-14697 | HIGH | 1.1.18-r3 | 1.1.18-r4 | musl libc through 1.1.23 |
|
||||
| | | | | | has an x87 floating-point |
|
||||
| | | | | | stack adjustment imbalance, |
|
||||
| | | | | | related... |
|
||||
+---------+------------------+----------+-------------------+---------------+--------------------------------+
|
||||
The command '/bin/sh -c trivy filesystem --exit-code 1 --no-progress /' returned a non-zero code: 1
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Save the results as JSON
|
||||
|
||||
@@ -700,6 +888,11 @@ You can load templates from a file prefixing the template path with an @.
|
||||
$ trivy image --format template --template "@/path/to/template" golang:1.12-alpine
|
||||
```
|
||||
|
||||
In the following example using the template `junit.tpl` XML can be generated.
|
||||
```
|
||||
$ trivy image --format template --template "@contrib/junit.tpl" -o junit-report.xml golang:1.12-alpine
|
||||
```
|
||||
|
||||
### Filter the vulnerabilities by severities
|
||||
|
||||
```
|
||||
@@ -1058,9 +1251,9 @@ Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
$ trivy --cache-dir /tmp/trivy/ image python:3.4-alpine3.9
|
||||
```
|
||||
|
||||
### Clear image caches
|
||||
### Clear caches
|
||||
|
||||
The `--clear-cache` option removes image caches. This option is useful if the image which has the same tag is updated (such as when using `latest` tag).
|
||||
The `--clear-cache` option removes caches. This option is useful if the image which has the same tag is updated (such as when using `latest` tag).
|
||||
|
||||
**The scan is not performed.**
|
||||
|
||||
@@ -1194,9 +1387,15 @@ $ trivy client --remote http://localhost:8080 --token dummy alpine:3.10
|
||||
|
||||
# Continuous Integration (CI)
|
||||
|
||||
Scan your image built in Travis CI/CircleCI. The test will fail if a vulnerability is found. When you don't want to fail the test, specify `--exit-code 0` .
|
||||
Scan your image automatically as part of your CI workflow, failing the workflow if a vulnerability is found. When you don't want to fail the test, specify `--exit-code 0`.
|
||||
|
||||
Since in automated scenarios such as CI/CD you only interested in the end result, and not the full report, use the `--light` flag to optimize for this scenario and get fast results.
|
||||
Since in automated scenarios such as CI/CD you are only interested in the end result, and not the full report, use the `--light` flag to optimize for this scenario and get fast results.
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
- Here is the [Trivy Github Action](https://github.com/aquasecurity/trivy-action) (currently Experimental)
|
||||
- The Microsoft Azure team have written a [container-scan action](https://github.com/Azure/container-scan) that uses Trivy and Dockle
|
||||
- For full control over the options specified to Trivy, this [blog post](https://blog.aquasec.com/devsecops-with-trivy-github-actions) describes adding Trivy into your own GitHub action workflows
|
||||
|
||||
## Travis CI
|
||||
|
||||
@@ -1310,6 +1509,10 @@ trivy:
|
||||
container_scanning: gl-container-scanning-report.json
|
||||
```
|
||||
|
||||
## AWS CodePipeline
|
||||
|
||||
See [this blog post](https://aws.amazon.com/blogs/containers/scanning-images-with-trivy-in-an-aws-codepipeline/) for an example of using Trivy within AWS CodePipeline.
|
||||
|
||||
## Authorization for Private Docker Registry
|
||||
|
||||
Trivy can download images from a private registry, without installing `Docker` or any other 3rd party tools.
|
||||
@@ -1364,7 +1567,7 @@ The unfixed/unfixable vulnerabilities mean that the patch has not yet been provi
|
||||
|
||||
| OS | Supported Versions | Target Packages | Detection of unfixed vulnerabilities |
|
||||
| ---------------------------- | ---------------------------------------- | ----------------------------- | :----------------------------------: |
|
||||
| Alpine Linux | 2.2 - 2.7, 3.0 - 3.11 | Installed by apk | NO |
|
||||
| Alpine Linux | 2.2 - 2.7, 3.0 - 3.12 | Installed by apk | NO |
|
||||
| Red Hat Universal Base Image | 7, 8 | Installed by yum/rpm | YES |
|
||||
| Red Hat Enterprise Linux | 6, 7, 8 | Installed by yum/rpm | YES |
|
||||
| CentOS | 6, 7 | Installed by yum/rpm | YES |
|
||||
@@ -1385,13 +1588,18 @@ Distroless: https://github.com/GoogleContainerTools/distroless
|
||||
|
||||
`Trivy` automatically detects the following files in the container and scans vulnerabilities in the application dependencies.
|
||||
|
||||
- Gemfile.lock
|
||||
- Pipfile.lock
|
||||
- poetry.lock
|
||||
- composer.lock
|
||||
- package-lock.json
|
||||
- yarn.lock
|
||||
- Cargo.lock
|
||||
- Ruby
|
||||
- Gemfile.lock
|
||||
- Python
|
||||
- Pipfile.lock
|
||||
- poetry.lock
|
||||
- PHP
|
||||
- composer.lock
|
||||
- Node.js
|
||||
- package-lock.json
|
||||
- yarn.lock
|
||||
- Rust
|
||||
- Cargo.lock
|
||||
|
||||
The path of these files does not matter.
|
||||
|
||||
@@ -1408,7 +1616,7 @@ Trivy scans a tar image with the following format.
|
||||
- Kaniko (https://github.com/GoogleContainerTools/kaniko)
|
||||
|
||||
|
||||
### Data source
|
||||
## Data sources
|
||||
- PHP
|
||||
- https://github.com/FriendsOfPHP/security-advisories
|
||||
- https://github.com/advisories?query=ecosystem%3Acomposer
|
||||
@@ -1425,25 +1633,37 @@ Trivy scans a tar image with the following format.
|
||||
- https://github.com/RustSec/advisory-db
|
||||
|
||||
# Usage
|
||||
## Standalone
|
||||
Trivy has several sub commands, image, fs, repo, client and server.
|
||||
|
||||
```
|
||||
NAME:
|
||||
trivy - A simple and comprehensive vulnerability scanner for containers
|
||||
USAGE:
|
||||
trivy image [options] image_name
|
||||
VERSION:
|
||||
v0.7.0
|
||||
OPTIONS:
|
||||
--quiet suppress progress bar and log output (default: false) [$TRIVY_QUIET]
|
||||
--debug debug mode (default: false) [$TRIVY_DEBUG]
|
||||
--cache-dir value cache directory (default: "/Users/simar/Library/Caches/trivy") [$TRIVY_CACHE_DIR]
|
||||
|
||||
USAGE:
|
||||
trivy [global options] command [command options] image_name
|
||||
|
||||
VERSION:
|
||||
v0.9.0
|
||||
|
||||
COMMANDS:
|
||||
image, i scan an image
|
||||
filesystem, fs scan local filesystem
|
||||
repository, repo scan remote repository
|
||||
client, c client mode
|
||||
server, s server mode
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--quiet, -q suppress progress bar and log output (default: false) [$TRIVY_QUIET]
|
||||
--debug, -d debug mode (default: false) [$TRIVY_DEBUG]
|
||||
--cache-dir value cache directory (default: "/Users/teppei/Library/Caches/trivy") [$TRIVY_CACHE_DIR]
|
||||
--help, -h show help (default: false)
|
||||
--version, -v print the version (default: false)
|
||||
|
||||
```
|
||||
|
||||
## Sub commands
|
||||
Trivy has three sub commands, image, client and server.
|
||||
## Image
|
||||
`fs` and `repo` have the same options as `image`.
|
||||
|
||||
```
|
||||
NAME:
|
||||
@@ -1473,6 +1693,8 @@ OPTIONS:
|
||||
--help, -h show help (default: false)
|
||||
```
|
||||
|
||||
## Client
|
||||
|
||||
```
|
||||
NAME:
|
||||
trivy client - client mode
|
||||
@@ -1499,6 +1721,8 @@ OPTIONS:
|
||||
--remote value server address (default: "http://localhost:4954") [$TRIVY_REMOTE]
|
||||
```
|
||||
|
||||
## Server
|
||||
|
||||
```
|
||||
NAME:
|
||||
trivy server - server mode
|
||||
@@ -1517,6 +1741,9 @@ OPTIONS:
|
||||
--listen value listen address (default: "localhost:4954") [$TRIVY_LISTEN]
|
||||
```
|
||||
|
||||
# Air-gapped environment
|
||||
See [here](docs/air-gap.md)
|
||||
|
||||
# Comparison with other scanners
|
||||
|
||||
## Overview
|
||||
@@ -1531,6 +1758,10 @@ OPTIONS:
|
||||
| Docker Hub | ◯ | × | ◯ | × | × |
|
||||
| GCR | ◯ | × | ◯ | ◯ | × |
|
||||
|
||||
## Blogs
|
||||
- [Open Source CVE Scanner Round-Up: Clair vs Anchore vs Trivy](https://boxboat.com/2020/04/24/image-scanning-tech-compared/)
|
||||
- [Docker Image Security: Static Analysis Tool Comparison – Anchore Engine vs Clair vs Trivy](https://www.a10o.net/devsecops/docker-image-security-static-analysis-tool-comparison-anchore-engine-vs-clair-vs-trivy/)
|
||||
|
||||
## vs Clair
|
||||
|
||||
[Clair](https://github.com/coreos/clair) uses [alpine-secdb](https://github.com/alpinelinux/alpine-secdb/).
|
||||
@@ -1681,11 +1912,6 @@ Try again with `--reset` option:
|
||||
```
|
||||
$ trivy image --reset
|
||||
```
|
||||
|
||||
# Related Projects
|
||||
|
||||
- [Remic](https://github.com/knqyf263/remic)
|
||||
- Vulnerability Scanner for Detecting Publicly Disclosed Vulnerabilities in Application Dependencies
|
||||
---
|
||||
|
||||
# Credits
|
||||
|
||||
18
contrib/junit.tpl
Normal file
18
contrib/junit.tpl
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" ?>
|
||||
<testsuites>
|
||||
{{- range . -}}
|
||||
{{- $failures := len .Vulnerabilities }}
|
||||
<testsuite tests="1" failures="{{ $failures }}" time="" name="{{ .Target }}">
|
||||
{{- if not (eq .Type "") }}
|
||||
<properties>
|
||||
<property name="type" value="{{ .Type }}"></property>
|
||||
</properties>
|
||||
{{- end -}}
|
||||
{{ range .Vulnerabilities }}
|
||||
<testcase classname="{{ .PkgName }}-{{ .InstalledVersion }}" name="[{{ .Vulnerability.Severity }}] {{ .VulnerabilityID }}" time="">
|
||||
<failure message={{escapeXML .Title | printf "%q" }} type="description">{{escapeXML .Description | printf "%q" }}</failure>
|
||||
</testcase>
|
||||
{{- end }}
|
||||
</testsuite>
|
||||
{{- end }}
|
||||
</testsuites>
|
||||
55
docs/air-gap.md
Normal file
55
docs/air-gap.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Air-gapped environment
|
||||
Trivy can be used in air-gapped environments.
|
||||
|
||||
## Download the vulnerability database
|
||||
At first, you need to download the vulnerability database for use in air-gapped environments.
|
||||
Go to [trivy-db](https://github.com/aquasecurity/trivy-db/releases) and download `trivy-offline.db.tgz` in the latest release.
|
||||
If you download `trivy-light-offline.db.tgz`, you have to run Trivy with `--light` option.
|
||||
|
||||
```
|
||||
$ wget https://github.com/aquasecurity/trivy-db/releases/latest/download/trivy-offline.db.tgz
|
||||
```
|
||||
|
||||
|
||||
## Transfer the DB file into the air-gapped environment
|
||||
The way of transfer depends on the environment.
|
||||
|
||||
```
|
||||
$ rsync -av -e ssh /path/to/trivy-offline.db.tgz [user]@[host]:dst
|
||||
```
|
||||
|
||||
## Put the DB file in Trivy's cache directory
|
||||
You have to know where to put the DB file. The following command shows the default cache directory.
|
||||
|
||||
```
|
||||
$ ssh user@host
|
||||
$ trivy -h | grep cache
|
||||
--cache-dir value cache directory (default: "/home/myuser/.cache/trivy") [$TRIVY_CACHE_DIR]
|
||||
```
|
||||
|
||||
Put the DB file in the cache directory + `/db`.
|
||||
|
||||
```
|
||||
$ mkdir -p /home/myuser/.cache/trivy/db
|
||||
$ cd /home/myuser/.cache/trivy/db
|
||||
$ mv /path/to/trivy-offline.db.tgz .
|
||||
```
|
||||
|
||||
Then, decompress it.
|
||||
`trivy-offline.db.tgz` file includes two files, `trivy.db` and `metadata.json`.
|
||||
|
||||
```
|
||||
$ tar xvf trivy-offline.db.tgz
|
||||
x trivy.db
|
||||
x metadata.json
|
||||
$ rm trivy-offline.db.tgz
|
||||
```
|
||||
|
||||
In an air-gapped environment it is your responsibility to update the Trivy database on a regular basis, so that the scanner can detect recently-identified vulnerabilities.
|
||||
|
||||
## Run Trivy with --skip-update option
|
||||
In an air-gapped environment, specify `--skip-update` so that Trivy doesn't attempt to download the latest database file.
|
||||
|
||||
```
|
||||
$ trivy image --skip-update alpine:3.12
|
||||
```
|
||||
5
go.mod
5
go.mod
@@ -5,7 +5,7 @@ go 1.13
|
||||
require (
|
||||
github.com/aquasecurity/fanal v0.0.0-20200528202907-79693bf4a058
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200514134639-7e57e3e02470
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200702223044-f0f6ca684644
|
||||
github.com/caarlos0/env/v6 v6.0.0
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/cheggaaa/pb/v3 v3.0.3
|
||||
@@ -15,6 +15,7 @@ require (
|
||||
github.com/google/go-containerregistry v0.0.0-20200331213917-3d03ed9b1ca2
|
||||
github.com/google/go-github/v28 v28.1.1
|
||||
github.com/google/wire v0.3.0
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
|
||||
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
|
||||
@@ -22,7 +23,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/testcontainers/testcontainers-go v0.3.1
|
||||
github.com/twitchtv/twirp v5.10.1+incompatible
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
|
||||
14
go.sum
14
go.sum
@@ -52,8 +52,8 @@ github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b h1:55Ul
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b/go.mod h1:BpNTD9vHfrejKsED9rx04ldM1WIbeyXGYxUrqTVwxVQ=
|
||||
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a h1:hsw7PpiymXP64evn/K7gsj3hWzMqLrdoeE6JkqDocVg=
|
||||
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200514134639-7e57e3e02470 h1:6VE+g4AK2uivPqZtVk/QtcCBb2rUjAvKqDNexSgqMC0=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200514134639-7e57e3e02470/go.mod h1:F77bF2nRbcH4EIhhcNEP585MoAKdLpEP3dihF9V1Hbw=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200702223044-f0f6ca684644 h1:cqYzeXGz/K0kCIIFa2uYe1vrc3ImoA45kDarAo5dz3Y=
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200702223044-f0f6ca684644/go.mod h1:EiFA908RL0ACrbYo/9HfT7f9QcdC2bZoIO5XAAcvz9A=
|
||||
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA=
|
||||
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ=
|
||||
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||
@@ -272,6 +272,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knqyf263/berkeleydb v0.0.0-20190501065933-fafe01fb9662/go.mod h1:bu1CcN4tUtoRcI/B/RFHhxMNKFHVq/c3SV+UTyduoXg=
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GXhHq+7LeOzx/haG7HSIZokl3/0GkoUFzsRJjg=
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8=
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c=
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 h1:HDjRqotkViMNcGMGicb7cgxklx8OwnjtCBmyWEqrRvM=
|
||||
@@ -427,6 +429,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
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/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/testcontainers/testcontainers-go v0.3.1 h1:KZkEKNfnlsipJblzGCz6fmzd+0DzJ3djulYrislG3Zw=
|
||||
github.com/testcontainers/testcontainers-go v0.3.1/go.mod h1:br7bkzIukhPSIjy07Ma3OuXjjFvl2jm7CDU0LQNsqLw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -468,7 +473,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -508,6 +512,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -533,7 +538,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -644,6 +648,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
|
||||
@@ -94,3 +94,4 @@ dockers:
|
||||
- "--label=org.label-schema.vcs-ref={{ .FullCommit }}"
|
||||
extra_files:
|
||||
- contrib/gitlab.tpl
|
||||
- contrib/junit.tpl
|
||||
|
||||
@@ -5,6 +5,7 @@ package integration
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -13,8 +14,6 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -53,7 +52,9 @@ func gunzipDB() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = dbFile.NewMetadata(afero.NewOsFs(), tmpDir).Store(db.Metadata{
|
||||
fs := afero.NewOsFs()
|
||||
metadataFile := filepath.Join(dbDir, "metadata.json")
|
||||
b, err := json.Marshal(db.Metadata{
|
||||
Version: 1,
|
||||
Type: 1,
|
||||
NextUpdate: time.Time{},
|
||||
@@ -62,6 +63,10 @@ func gunzipDB() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = afero.WriteFile(fs, metadataFile, b, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,7 +11,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
@@ -87,17 +87,20 @@ Vulnerability DB:
|
||||
}
|
||||
|
||||
if tt.createDB {
|
||||
m := dbFile.NewMetadata(afero.NewOsFs(), cacheDir)
|
||||
fs := afero.NewOsFs()
|
||||
err := os.MkdirAll(filepath.Join(cacheDir, "db"), os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
metadataFile := filepath.Join(cacheDir, "db", "metadata.json")
|
||||
|
||||
err = m.Store(db.Metadata{
|
||||
b, err := json.Marshal(db.Metadata{
|
||||
Version: 42,
|
||||
Type: 1,
|
||||
NextUpdate: time.Unix(1584403020, 0),
|
||||
UpdatedAt: time.Unix(1584402020, 0),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = afero.WriteFile(fs, metadataFile, b, 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
fw := new(bytes.Buffer)
|
||||
|
||||
@@ -21,7 +21,7 @@ type GlobalConfig struct {
|
||||
func NewGlobalConfig(c *cli.Context) (GlobalConfig, error) {
|
||||
quiet := c.Bool("quiet")
|
||||
debug := c.Bool("debug")
|
||||
logger, err := log.NewLogger(quiet, debug)
|
||||
logger, err := log.NewLogger(debug, quiet)
|
||||
if err != nil {
|
||||
return GlobalConfig{}, xerrors.New("failed to create a logger")
|
||||
}
|
||||
|
||||
21
pkg/db/db.go
21
pkg/db/db.go
@@ -59,6 +59,7 @@ type Operation interface {
|
||||
|
||||
type dbOperation interface {
|
||||
GetMetadata() (metadata db.Metadata, err error)
|
||||
StoreMetadata(metadata db.Metadata, dir string) (err error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -181,17 +182,17 @@ func (c Client) UpdateMetadata(cacheDir string) error {
|
||||
|
||||
metadata, err := c.dbc.GetMetadata()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to get a metadata: %w", err)
|
||||
return xerrors.Errorf("unable to get metadata: %w", err)
|
||||
}
|
||||
|
||||
if err = c.metadata.Store(metadata); err != nil {
|
||||
if err = c.dbc.StoreMetadata(metadata, filepath.Join(cacheDir, "db")); err != nil {
|
||||
return xerrors.Errorf("failed to store metadata: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
type Metadata struct { // TODO: Move all Metadata things to trivy-db repo
|
||||
fs afero.Fs
|
||||
filePath string
|
||||
}
|
||||
@@ -210,20 +211,6 @@ func MetadataPath(cacheDir string) string {
|
||||
return filepath.Join(dbDir, metadataFile)
|
||||
}
|
||||
|
||||
// StoreMetadata stores database metadata as a file
|
||||
func (m Metadata) Store(metadata db.Metadata) error {
|
||||
f, err := m.fs.Create(m.filePath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to create a metadata file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err = json.NewEncoder(f).Encode(metadata); err != nil {
|
||||
return xerrors.Errorf("unable to encode metadata: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMetadata deletes the file of database metadata
|
||||
func (m Metadata) Delete() error {
|
||||
if err := m.fs.Remove(m.filePath); err != nil {
|
||||
|
||||
@@ -2,6 +2,8 @@ package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -142,11 +144,13 @@ func TestClient_NeedsUpdate(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
metadata := NewMetadata(fs, "/cache")
|
||||
if tc.metadata != (db.Metadata{}) {
|
||||
metadata.Store(tc.metadata)
|
||||
b, err := json.Marshal(tc.metadata)
|
||||
require.NoError(t, err)
|
||||
err = afero.WriteFile(fs, metadata.filePath, b, 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
client := Client{
|
||||
//dbc: mockConfig,
|
||||
clock: tc.clock,
|
||||
metadata: metadata,
|
||||
}
|
||||
@@ -161,22 +165,15 @@ func TestClient_NeedsUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expected, needsUpdate)
|
||||
//mockConfig.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Download(t *testing.T) {
|
||||
type getMetadataOutput struct {
|
||||
metadata db.Metadata
|
||||
err error
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
light bool
|
||||
downloadDB []github.DownloadDBExpectation
|
||||
getMetadata dbOperationGetMetadataExpectation
|
||||
expectedContent []byte
|
||||
expectedError error
|
||||
}{
|
||||
@@ -191,15 +188,6 @@ func TestClient_Download(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
getMetadata: dbOperationGetMetadataExpectation{
|
||||
Returns: dbOperationGetMetadataReturns{
|
||||
Metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: db.TypeFull,
|
||||
NextUpdate: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DownloadDB returns an error",
|
||||
@@ -235,7 +223,6 @@ func TestClient_Download(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockConfig := new(mockDbOperation)
|
||||
mockConfig.ApplyGetMetadataExpectation(tc.getMetadata)
|
||||
|
||||
mockGitHubClient, err := github.NewMockClient(tc.downloadDB)
|
||||
require.NoError(t, err, tc.name)
|
||||
@@ -263,3 +250,99 @@ func TestClient_Download(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_UpdateMetadata(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
getMetadataExpectation dbOperationGetMetadataExpectation
|
||||
storeMetadataExpectation dbOperationStoreMetadataExpectation
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
getMetadataExpectation: dbOperationGetMetadataExpectation{
|
||||
Returns: dbOperationGetMetadataReturns{
|
||||
Metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: 1,
|
||||
NextUpdate: time.Date(2020, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
},
|
||||
storeMetadataExpectation: dbOperationStoreMetadataExpectation{
|
||||
Metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: 1,
|
||||
NextUpdate: time.Date(2020, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad path, get metadata fails",
|
||||
getMetadataExpectation: dbOperationGetMetadataExpectation{
|
||||
Returns: dbOperationGetMetadataReturns{
|
||||
Err: errors.New("get metadata failed"),
|
||||
},
|
||||
},
|
||||
expectedError: errors.New("unable to get metadata: get metadata failed"),
|
||||
},
|
||||
{
|
||||
name: "sad path, store metadata fails",
|
||||
getMetadataExpectation: dbOperationGetMetadataExpectation{
|
||||
Returns: dbOperationGetMetadataReturns{
|
||||
Metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: 1,
|
||||
NextUpdate: time.Date(2020, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
},
|
||||
storeMetadataExpectation: dbOperationStoreMetadataExpectation{
|
||||
Metadata: db.Metadata{
|
||||
Version: 1,
|
||||
Type: 1,
|
||||
NextUpdate: time.Date(2020, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
Returns: dbOperationStoreMetadataReturns{
|
||||
Err: errors.New("store metadata failed"),
|
||||
},
|
||||
},
|
||||
expectedError: errors.New("failed to store metadata: store metadata failed"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockConfig := new(mockDbOperation)
|
||||
err := log.InitLogger(false, true)
|
||||
require.NoError(t, err, "failed to init logger")
|
||||
|
||||
mockConfig.ApplyGetMetadataExpectation(tc.getMetadataExpectation)
|
||||
mockConfig.ApplyStoreMetadataExpectation(tc.storeMetadataExpectation)
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
metadata := NewMetadata(fs, "/cache")
|
||||
|
||||
dir, err := ioutil.TempDir("", "db")
|
||||
require.NoError(t, err, tc.name)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pb := indicator.NewProgressBar(true)
|
||||
client := NewClient(mockConfig, nil, pb, nil, metadata)
|
||||
|
||||
err = client.UpdateMetadata(dir)
|
||||
switch {
|
||||
case tc.expectedError != nil:
|
||||
assert.EqualError(t, err, tc.expectedError.Error(), tc.name)
|
||||
default:
|
||||
assert.NoError(t, err, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
pkgdb "github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ type mockDbOperation struct {
|
||||
}
|
||||
|
||||
type dbOperationGetMetadataReturns struct {
|
||||
Metadata pkgdb.Metadata
|
||||
Metadata db.Metadata
|
||||
Err error
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ func (_m *mockDbOperation) ApplyGetMetadataExpectations(expectations []dbOperati
|
||||
}
|
||||
|
||||
// GetMetadata provides a mock function with given fields:
|
||||
func (_m *mockDbOperation) GetMetadata() (pkgdb.Metadata, error) {
|
||||
func (_m *mockDbOperation) GetMetadata() (db.Metadata, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 pkgdb.Metadata
|
||||
if rf, ok := ret.Get(0).(func() pkgdb.Metadata); ok {
|
||||
var r0 db.Metadata
|
||||
if rf, ok := ret.Get(0).(func() db.Metadata); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(pkgdb.Metadata)
|
||||
r0 = ret.Get(0).(db.Metadata)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
@@ -52,3 +52,37 @@ func (_m *mockDbOperation) GetMetadata() (pkgdb.Metadata, error) {
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
type dbOperationStoreMetadataReturns struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
type dbOperationStoreMetadataExpectation struct {
|
||||
Metadata db.Metadata
|
||||
Dir string
|
||||
Returns dbOperationStoreMetadataReturns
|
||||
}
|
||||
|
||||
func (_m *mockDbOperation) ApplyStoreMetadataExpectation(e dbOperationStoreMetadataExpectation) {
|
||||
_m.On("StoreMetadata", e.Metadata, mock.Anything).Return(e.Returns.Err)
|
||||
}
|
||||
|
||||
func (_m *mockDbOperation) ApplyStoreMetadataExpectations(expectations []dbOperationStoreMetadataExpectation) {
|
||||
for _, e := range expectations {
|
||||
_m.ApplyStoreMetadataExpectation(e)
|
||||
}
|
||||
}
|
||||
|
||||
// StoreMetadata provides a mock function with given fields: metadata, dir
|
||||
func (_m *mockDbOperation) StoreMetadata(metadata db.Metadata, dir string) error {
|
||||
ret := _m.Called(metadata, dir)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(db.Metadata, string) error); ok {
|
||||
r0 = rf(metadata, dir)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
version "github.com/knqyf263/go-deb-version"
|
||||
version "github.com/knqyf263/go-apk-version"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
@@ -37,6 +37,7 @@ var (
|
||||
"3.9": time.Date(2020, 11, 1, 23, 59, 59, 0, time.UTC),
|
||||
"3.10": time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC),
|
||||
"3.11": time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC),
|
||||
"3.12": time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -153,6 +153,54 @@ func TestScanner_Detect(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "contain pre",
|
||||
args: args{
|
||||
osVer: "3.12",
|
||||
pkgs: []ftypes.Package{
|
||||
{
|
||||
Name: "test",
|
||||
Version: "0.1.0_alpha",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mocks: mocks{
|
||||
get: []get{
|
||||
{
|
||||
input: getInput{
|
||||
osVer: "3.12",
|
||||
pkgName: "test",
|
||||
},
|
||||
output: getOutput{
|
||||
advisories: []dbTypes.Advisory{
|
||||
{
|
||||
VulnerabilityID: "CVE-2030-0001",
|
||||
FixedVersion: "0.1.0_alpha_pre2",
|
||||
},
|
||||
{
|
||||
VulnerabilityID: "CVE-2030-0002",
|
||||
FixedVersion: "0.1.0_alpha2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2030-0002",
|
||||
PkgName: "test",
|
||||
InstalledVersion: "0.1.0_alpha",
|
||||
FixedVersion: "0.1.0_alpha2",
|
||||
Layer: ftypes.Layer{
|
||||
DiffID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get returns an error",
|
||||
args: args{
|
||||
|
||||
@@ -24,43 +24,56 @@ func InitLogger(debug, disable bool) (err error) {
|
||||
}
|
||||
|
||||
func NewLogger(debug, disable bool) (*zap.SugaredLogger, error) {
|
||||
level := zap.NewAtomicLevel()
|
||||
if debug {
|
||||
level.SetLevel(zapcore.DebugLevel)
|
||||
} else {
|
||||
level.SetLevel(zapcore.InfoLevel)
|
||||
// First, define our level-handling logic.
|
||||
errorPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.ErrorLevel
|
||||
})
|
||||
logPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
if debug {
|
||||
return lvl < zapcore.ErrorLevel
|
||||
}
|
||||
// Not enable debug level
|
||||
return zapcore.DebugLevel < lvl && lvl < zapcore.ErrorLevel
|
||||
})
|
||||
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "Time",
|
||||
LevelKey: "Level",
|
||||
NameKey: "Name",
|
||||
CallerKey: "Caller",
|
||||
MessageKey: "Msg",
|
||||
StacktraceKey: "St",
|
||||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeDuration: zapcore.StringDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
myConfig := zap.Config{
|
||||
Level: level,
|
||||
Encoding: "console",
|
||||
Development: debug,
|
||||
DisableStacktrace: true,
|
||||
DisableCaller: true,
|
||||
EncoderConfig: zapcore.EncoderConfig{
|
||||
TimeKey: "Time",
|
||||
LevelKey: "Level",
|
||||
NameKey: "Name",
|
||||
CallerKey: "Caller",
|
||||
MessageKey: "Msg",
|
||||
StacktraceKey: "St",
|
||||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeDuration: zapcore.StringDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
},
|
||||
OutputPaths: []string{"stdout"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
}
|
||||
consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
|
||||
|
||||
// High-priority output should also go to standard error, and low-priority
|
||||
// output should also go to standard out.
|
||||
consoleLogs := zapcore.Lock(os.Stdout)
|
||||
consoleErrors := zapcore.Lock(os.Stderr)
|
||||
if disable {
|
||||
myConfig.OutputPaths = []string{os.DevNull}
|
||||
myConfig.ErrorOutputPaths = []string{os.DevNull}
|
||||
devNull, err := os.Create(os.DevNull)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Discard low-priority output
|
||||
consoleLogs = zapcore.Lock(devNull)
|
||||
}
|
||||
|
||||
logger, err := myConfig.Build()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to build zap config: %w", err)
|
||||
core := zapcore.NewTee(
|
||||
zapcore.NewCore(consoleEncoder, consoleErrors, errorPriority),
|
||||
zapcore.NewCore(consoleEncoder, consoleLogs, logPriority),
|
||||
)
|
||||
|
||||
opts := []zap.Option{zap.ErrorOutput(zapcore.Lock(os.Stderr))}
|
||||
if debug {
|
||||
opts = append(opts, zap.Development())
|
||||
}
|
||||
logger := zap.New(core, opts...)
|
||||
|
||||
return logger.Sugar(), nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -26,14 +28,6 @@ type Result struct {
|
||||
}
|
||||
|
||||
func WriteResults(format string, output io.Writer, results Results, outputTemplate string, light bool) error {
|
||||
if strings.HasPrefix(outputTemplate, "@") {
|
||||
buf, err := ioutil.ReadFile(strings.TrimPrefix(outputTemplate, "@"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Error retrieving template from path: %w", err)
|
||||
}
|
||||
outputTemplate = string(buf)
|
||||
}
|
||||
|
||||
var writer Writer
|
||||
switch format {
|
||||
case "table":
|
||||
@@ -41,7 +35,23 @@ func WriteResults(format string, output io.Writer, results Results, outputTempla
|
||||
case "json":
|
||||
writer = &JsonWriter{Output: output}
|
||||
case "template":
|
||||
tmpl, err := template.New("output template").Parse(outputTemplate)
|
||||
if strings.HasPrefix(outputTemplate, "@") {
|
||||
buf, err := ioutil.ReadFile(strings.TrimPrefix(outputTemplate, "@"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Error retrieving template from path: %w", err)
|
||||
}
|
||||
outputTemplate = string(buf)
|
||||
}
|
||||
tmpl, err := template.New("output template").Funcs(template.FuncMap{
|
||||
"escapeXML": func(input string) string {
|
||||
escaped := &bytes.Buffer{}
|
||||
if err := xml.EscapeText(escaped, []byte(input)); err != nil {
|
||||
fmt.Printf("error while escapeString to XML: %v", err.Error())
|
||||
return input
|
||||
}
|
||||
return escaped.String()
|
||||
},
|
||||
}).Parse(outputTemplate)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error parsing template: %w", err)
|
||||
}
|
||||
|
||||
@@ -230,13 +230,59 @@ func TestReportWriter_Template(t *testing.T) {
|
||||
template: "{{ range . }}{{ range .Vulnerabilities}}{{ println .VulnerabilityID .Severity }}{{ end }}{{ end }}",
|
||||
expected: "CVE-2019-0000 HIGH\nCVE-2019-0000 HIGH\nCVE-2019-0001 CRITICAL\n",
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
detectedVulns: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "123",
|
||||
PkgName: "foo",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "3.4.5",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: `gcc: POWER9 "DARN" RNG intrinsic produces repeated output`,
|
||||
Description: `curl version curl 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0.`,
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
template: `<testsuites>
|
||||
{{- range . -}}
|
||||
{{- $failures := len .Vulnerabilities }}
|
||||
<testsuite tests="1" failures="{{ $failures }}" time="" name="{{ .Target }}">
|
||||
{{- if not (eq .Type "") }}
|
||||
<properties>
|
||||
<property name="type" value="{{ .Type }}"></property>
|
||||
</properties>
|
||||
{{- end -}}
|
||||
{{ range .Vulnerabilities }}
|
||||
<testcase classname="{{ .PkgName }}-{{ .InstalledVersion }}" name="[{{ .Vulnerability.Severity }}] {{ .VulnerabilityID }}" time="">
|
||||
<failure message={{escapeXML .Title | printf "%q" }} type="description">{{escapeXML .Description | printf "%q" }}</failure>
|
||||
</testcase>
|
||||
{{- end }}
|
||||
</testsuite>
|
||||
{{- end }}
|
||||
</testsuites>`,
|
||||
|
||||
expected: `<testsuites>
|
||||
<testsuite tests="1" failures="1" time="" name="foojunit">
|
||||
<properties>
|
||||
<property name="type" value="test"></property>
|
||||
</properties>
|
||||
<testcase classname="foo-1.2.3" name="[HIGH] 123" time="">
|
||||
<failure message="gcc: POWER9 "DARN" RNG intrinsic produces repeated output" type="description">"curl version curl 7.20.0 to and including curl 7.59.0 contains a CWE-126: Buffer Over-read vulnerability in denial of service that can result in curl can be tricked into reading data beyond the end of a heap based buffer used to store downloaded RTSP content.. This vulnerability appears to have been fixed in curl < 7.20.0 and curl >= 7.60.0."</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>`,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tmplWritten := bytes.Buffer{}
|
||||
inputResults := report.Results{
|
||||
{
|
||||
Target: "foojson",
|
||||
Target: "foojunit",
|
||||
Type: "test",
|
||||
Vulnerabilities: tc.detectedVulns,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
@@ -78,17 +77,9 @@ func (s *CacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*
|
||||
}
|
||||
|
||||
func (s *CacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsRequest) (*rpcCache.MissingBlobsResponse, error) {
|
||||
var layerIDs []string
|
||||
for _, blobID := range in.BlobIds {
|
||||
l, err := s.cache.GetBlob(blobID)
|
||||
if err != nil || l.SchemaVersion != ftypes.BlobJSONSchemaVersion {
|
||||
layerIDs = append(layerIDs, blobID)
|
||||
}
|
||||
missingArtifact, blobIDs, err := s.cache.MissingBlobs(in.ArtifactId, in.BlobIds)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get missing blobs: %w", err)
|
||||
}
|
||||
var missingImage bool
|
||||
img, err := s.cache.GetArtifact(in.ArtifactId)
|
||||
if err != nil || img.SchemaVersion != ftypes.ArtifactJSONSchemaVersion {
|
||||
missingImage = true
|
||||
}
|
||||
return &rpcCache.MissingBlobsResponse{MissingArtifact: missingImage, MissingBlobIds: layerIDs}, nil
|
||||
return &rpcCache.MissingBlobsResponse{MissingArtifact: missingArtifact, MissingBlobIds: blobIDs}, nil
|
||||
}
|
||||
|
||||
@@ -473,12 +473,11 @@ func TestCacheServer_MissingBlobs(t *testing.T) {
|
||||
in *rpcCache.MissingBlobsRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
getLayerExpectations []cache.LocalArtifactCacheGetBlobExpectation
|
||||
getImageExpectations []cache.LocalArtifactCacheGetArtifactExpectation
|
||||
want *rpcCache.MissingBlobsResponse
|
||||
wantErr string
|
||||
name string
|
||||
args args
|
||||
getArtifactCacheMissingBlobsExpectations []cache.ArtifactCacheMissingBlobsExpectation
|
||||
want *rpcCache.MissingBlobsResponse
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
@@ -491,100 +490,24 @@ func TestCacheServer_MissingBlobs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{
|
||||
getArtifactCacheMissingBlobsExpectations: []cache.ArtifactCacheMissingBlobsExpectation{
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetBlobArgs{
|
||||
BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetBlobReturns{
|
||||
BlobInfo: ftypes.BlobInfo{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetBlobArgs{
|
||||
BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetBlobReturns{
|
||||
BlobInfo: ftypes.BlobInfo{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getImageExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetArtifactArgs{
|
||||
ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetArtifactReturns{
|
||||
ArtifactInfo: ftypes.ArtifactInfo{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
},
|
||||
Args: cache.ArtifactCacheMissingBlobsArgs{ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
BlobIDs: []string{"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5"}},
|
||||
Returns: cache.ArtifactCacheMissingBlobsReturns{
|
||||
MissingArtifact: false, MissingBlobIDs: []string{"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5"}, Err: nil},
|
||||
},
|
||||
},
|
||||
want: &rpcCache.MissingBlobsResponse{
|
||||
MissingArtifact: false,
|
||||
MissingBlobIds: []string{"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "schema version doesn't match",
|
||||
args: args{
|
||||
in: &rpcCache.MissingBlobsRequest{
|
||||
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
BlobIds: []string{
|
||||
"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5",
|
||||
},
|
||||
},
|
||||
},
|
||||
getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetBlobArgs{
|
||||
BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetBlobReturns{
|
||||
BlobInfo: ftypes.BlobInfo{
|
||||
SchemaVersion: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetBlobArgs{
|
||||
BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetBlobReturns{
|
||||
BlobInfo: ftypes.BlobInfo{
|
||||
SchemaVersion: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
getImageExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{
|
||||
{
|
||||
Args: cache.LocalArtifactCacheGetArtifactArgs{
|
||||
ArtifactID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
},
|
||||
Returns: cache.LocalArtifactCacheGetArtifactReturns{
|
||||
ArtifactInfo: ftypes.ArtifactInfo{},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &rpcCache.MissingBlobsResponse{
|
||||
MissingArtifact: true,
|
||||
MissingBlobIds: []string{
|
||||
"sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
|
||||
"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5",
|
||||
},
|
||||
MissingBlobIds: []string{"sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCache := new(mockCache)
|
||||
mockCache.ApplyGetBlobExpectations(tt.getLayerExpectations)
|
||||
mockCache.ApplyGetArtifactExpectations(tt.getImageExpectations)
|
||||
mockCache.ApplyMissingBlobsExpectations(tt.getArtifactCacheMissingBlobsExpectations)
|
||||
|
||||
s := NewCacheServer(mockCache)
|
||||
got, err := s.MissingBlobs(tt.args.ctx, tt.args.in)
|
||||
@@ -597,7 +520,7 @@ func TestCacheServer_MissingBlobs(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
mockCache.MockLocalArtifactCache.AssertExpectations(t)
|
||||
mockCache.MockArtifactCache.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,74 @@ func TestClient_FillInfo(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and vendor vectors",
|
||||
name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and CVSS info",
|
||||
getVulnerability: []db.GetVulnerabilityExpectation{
|
||||
{
|
||||
Args: db.GetVulnerabilityArgs{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
},
|
||||
Returns: db.GetVulnerabilityReturns{
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityMedium.String(),
|
||||
VendorSeverity: dbTypes.VendorSeverity{
|
||||
vulnerability.RedHat: dbTypes.SeverityLow, // CentOS uses RedHat
|
||||
},
|
||||
CVSS: map[string]dbTypes.CVSS{
|
||||
vulnerability.Nvd: {
|
||||
V2Vector: "(AV:N/AC:L/Au:N/C:P/I:P/A:P)",
|
||||
V2Score: 4.5,
|
||||
V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 5.6,
|
||||
},
|
||||
vulnerability.RedHat: {
|
||||
V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N",
|
||||
V2Score: 7.8,
|
||||
V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 9.8,
|
||||
},
|
||||
},
|
||||
References: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
vulns: []types.DetectedVulnerability{
|
||||
{VulnerabilityID: "CVE-2019-0001"},
|
||||
},
|
||||
reportType: vulnerability.CentOS,
|
||||
},
|
||||
expectedVulnerabilities: []types.DetectedVulnerability{
|
||||
{
|
||||
VulnerabilityID: "CVE-2019-0001",
|
||||
Vulnerability: dbTypes.Vulnerability{
|
||||
Title: "dos",
|
||||
Description: "dos vulnerability",
|
||||
Severity: dbTypes.SeverityLow.String(),
|
||||
References: []string{"http://example.com"},
|
||||
CVSS: map[string]dbTypes.CVSS{
|
||||
vulnerability.Nvd: {
|
||||
V2Vector: "(AV:N/AC:L/Au:N/C:P/I:P/A:P)",
|
||||
V2Score: 4.5,
|
||||
V3Vector: "CVSS:3.0/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 5.6,
|
||||
},
|
||||
vulnerability.RedHat: {
|
||||
V2Vector: "AV:N/AC:M/Au:N/C:N/I:P/A:N",
|
||||
V2Score: 7.8,
|
||||
V3Vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
V3Score: 9.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
SeveritySource: vulnerability.RedHat,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path, with only OS vulnerability, yes vendor severity, with both NVD and deprecated vendor vectors",
|
||||
getVulnerability: []db.GetVulnerabilityExpectation{
|
||||
{
|
||||
Args: db.GetVulnerabilityArgs{
|
||||
|
||||
Reference in New Issue
Block a user