mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-06 04:41:18 -08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d82700032 | ||
|
|
a0a991ca16 | ||
|
|
989b893bf2 | ||
|
|
d270edea75 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
imgs
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM golang:1.12-alpine AS builder
|
||||
ADD go.mod go.sum /app/
|
||||
WORKDIR /app/
|
||||
RUN apk --no-cache add git
|
||||
RUN go mod download
|
||||
ADD . /app/
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /trivy cmd/trivy/main.go
|
||||
|
||||
FROM alpine:3.9
|
||||
RUN apk --no-cache add ca-certificates git
|
||||
COPY --from=builder /trivy /usr/local/bin/trivy
|
||||
RUN chmod +x /usr/local/bin/trivy
|
||||
|
||||
CMD ["trivy"]
|
||||
84
README.md
84
README.md
@@ -29,6 +29,12 @@ $ sudo yum -y update
|
||||
$ sudo yum -y install trivy
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
$ rpm -ivh https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.rpm
|
||||
```
|
||||
|
||||
## Debian/Ubuntu
|
||||
|
||||
Replace `[CODE_NAME]` with your code name
|
||||
@@ -43,6 +49,14 @@ $ sudo apt-get update
|
||||
$ sudo apt-get install trivy
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
$ sudo apt-get install rpm
|
||||
$ wget https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.deb
|
||||
$ sudo dpkg -i trivy_0.0.3_Linux-64bit.deb
|
||||
```
|
||||
|
||||
## Mac OS X / Homebrew
|
||||
You can use homebrew on OS X.
|
||||
```
|
||||
@@ -60,6 +74,69 @@ $ go get -u github.com/knqyf263/trivy
|
||||
```
|
||||
|
||||
# Examples
|
||||
## 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` .
|
||||
|
||||
**Note**: The first time take a while (faster by cache after the second time)
|
||||
### Travis CI
|
||||
|
||||
```
|
||||
$ cat .travis.yml
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- docker build -t trivy-ci-test:latest .
|
||||
- wget https://github.com/knqyf263/trivy/releases/download/v0.0.3/trivy_0.0.3_Linux-64bit.tar.gz
|
||||
- tar zxvf trivy_0.0.3_Linux-64bit.tar.gz
|
||||
script:
|
||||
- ./trivy --exit-code 1 --quiet trivy-ci-test:latest
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/trivy
|
||||
```
|
||||
|
||||
example: https://travis-ci.org/knqyf263/trivy-ci-test
|
||||
repository: https://github.com/knqyf263/trivy-ci-test
|
||||
|
||||
### Circle CI
|
||||
|
||||
```
|
||||
$ cat .circleci/config.yml
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: docker:18.09-git
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- restore_cache:
|
||||
key: vulnerability-db
|
||||
- run:
|
||||
name: Build image
|
||||
command: docker build -t trivy-ci-test:latest .
|
||||
- run:
|
||||
name: Install trivy
|
||||
command: |
|
||||
wget https://github.com/knqyf263/trivy/releases/download/v0.0.4/trivy_0.0.4_Linux-64bit.tar.gz
|
||||
tar zxvf trivy_0.0.4_Linux-64bit.tar.gz
|
||||
mv trivy /usr/local/bin
|
||||
- run:
|
||||
name: Scan the local image with trivy
|
||||
command: trivy --exit-code 1 --quiet trivy-ci-test:latest
|
||||
- save_cache:
|
||||
key: vulnerability-db
|
||||
paths:
|
||||
- $HOME/.cache/trivy
|
||||
workflows:
|
||||
version: 2
|
||||
release:
|
||||
jobs:
|
||||
- build
|
||||
```
|
||||
|
||||
example: https://circleci.com/gh/knqyf263/trivy-ci-test
|
||||
repository: https://github.com/knqyf263/trivy-ci-test
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -70,17 +147,20 @@ NAME:
|
||||
USAGE:
|
||||
main [options] image_name
|
||||
VERSION:
|
||||
0.0.1
|
||||
0.0.3
|
||||
OPTIONS:
|
||||
--format value, -f value format (table, json) (default: "table")
|
||||
--input value, -i value input file path instead of image name
|
||||
--severity value, -s value severities of vulnerabilities to be displayed (comma separated) (default: "CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN")
|
||||
--severity value, -s value severities of vulnerabilities to be displayed (comma separated) (default: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL")
|
||||
--output value, -o value output file name
|
||||
--exit-code value Exit code when vulnerabilities were found (default: 0)
|
||||
--skip-update skip db update
|
||||
--clean, -c clean all cache
|
||||
--quiet, -q suppress progress bar
|
||||
--debug, -d debug mode
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
|
||||
```
|
||||
|
||||
# Q&A
|
||||
|
||||
@@ -73,6 +73,14 @@ OPTIONS:
|
||||
Name: "clean, c",
|
||||
Usage: "clean all cache",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "suppress progress bar",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "ignore-unfixed",
|
||||
Usage: "display only fixed vulnerabilities",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "debug mode",
|
||||
|
||||
@@ -4,9 +4,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/knqyf263/trivy/pkg/log"
|
||||
"github.com/knqyf263/trivy/pkg/utils"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -38,8 +36,8 @@ func CloneOrPull(url, repoPath string) (map[string]struct{}, error) {
|
||||
}
|
||||
log.Logger.Debug("remove an existed directory")
|
||||
|
||||
s := spinner.New(spinner.CharSets[36], 100*time.Millisecond)
|
||||
s.Suffix = " The first time will take a while..."
|
||||
suffix := " The first time will take a while..."
|
||||
s := utils.NewSpinner(suffix)
|
||||
s.Start()
|
||||
defer s.Stop()
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ func Run(c *cli.Context) (err error) {
|
||||
cli.ShowAppHelpAndExit(c, 1)
|
||||
}
|
||||
|
||||
utils.Quiet = c.Bool("quiet")
|
||||
|
||||
clean := c.Bool("clean")
|
||||
if clean {
|
||||
log.Logger.Info("Cleaning caches...")
|
||||
@@ -74,11 +76,13 @@ func Run(c *cli.Context) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
ignoreUnfixed := c.Bool("ignore-unfixed")
|
||||
|
||||
var imageName string
|
||||
if filePath == "" {
|
||||
imageName = args[0]
|
||||
}
|
||||
results, err := scanner.ScanImage(imageName, filePath, severities)
|
||||
results, err := scanner.ScanImage(imageName, filePath, severities, ignoreUnfixed)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/knqyf263/trivy/pkg/log"
|
||||
|
||||
@@ -31,7 +32,7 @@ var (
|
||||
vulnerability.NodejsSecurityWg, vulnerability.PythonSafetyDB}
|
||||
)
|
||||
|
||||
func ScanImage(imageName, filePath string, severities []vulnerability.Severity) (report.Results, error) {
|
||||
func ScanImage(imageName, filePath string, severities []vulnerability.Severity, ignoreUnfixed bool) (report.Results, error) {
|
||||
var results report.Results
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
@@ -67,7 +68,7 @@ func ScanImage(imageName, filePath string, severities []vulnerability.Severity)
|
||||
|
||||
results = append(results, report.Result{
|
||||
FileName: fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion),
|
||||
Vulnerabilities: processVulnerabilties(osVulns, severities),
|
||||
Vulnerabilities: processVulnerabilties(osVulns, severities, ignoreUnfixed),
|
||||
})
|
||||
|
||||
libVulns, err := library.Scan(files)
|
||||
@@ -77,7 +78,7 @@ func ScanImage(imageName, filePath string, severities []vulnerability.Severity)
|
||||
for path, vulns := range libVulns {
|
||||
results = append(results, report.Result{
|
||||
FileName: path,
|
||||
Vulnerabilities: processVulnerabilties(vulns, severities),
|
||||
Vulnerabilities: processVulnerabilties(vulns, severities, ignoreUnfixed),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -91,12 +92,12 @@ func ScanFile(f *os.File, severities []vulnerability.Severity) (report.Result, e
|
||||
}
|
||||
result := report.Result{
|
||||
FileName: f.Name(),
|
||||
Vulnerabilities: processVulnerabilties(vulns, severities),
|
||||
Vulnerabilities: processVulnerabilties(vulns, severities, false),
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func processVulnerabilties(vulns []types.Vulnerability, severities []vulnerability.Severity) []types.Vulnerability {
|
||||
func processVulnerabilties(vulns []types.Vulnerability, severities []vulnerability.Severity, ignoreUnfixed bool) []types.Vulnerability {
|
||||
var vulnerabilities []types.Vulnerability
|
||||
for _, vuln := range vulns {
|
||||
sev, title := getDetail(vuln.VulnerabilityID)
|
||||
@@ -106,11 +107,22 @@ func processVulnerabilties(vulns []types.Vulnerability, severities []vulnerabili
|
||||
if s == sev {
|
||||
vuln.Severity = fmt.Sprint(sev)
|
||||
vuln.Title = title
|
||||
|
||||
// Ignore unfixed vulnerabilities
|
||||
if ignoreUnfixed && vuln.FixedVersion == "" {
|
||||
continue
|
||||
}
|
||||
vulnerabilities = append(vulnerabilities, vuln)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(vulnerabilities, func(i, j int) bool {
|
||||
if vulnerabilities[i].PkgName != vulnerabilities[j].PkgName {
|
||||
return vulnerabilities[i].PkgName < vulnerabilities[j].PkgName
|
||||
}
|
||||
return vulnerability.CompareSeverityString(vulnerabilities[j].Severity, vulnerabilities[i].Severity)
|
||||
})
|
||||
return vulnerabilities
|
||||
}
|
||||
|
||||
|
||||
63
pkg/utils/progress.go
Normal file
63
pkg/utils/progress.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
pb "gopkg.in/cheggaaa/pb.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
Quiet = false
|
||||
)
|
||||
|
||||
type Spinner struct {
|
||||
client *spinner.Spinner
|
||||
}
|
||||
|
||||
func NewSpinner(suffix string) *Spinner {
|
||||
if Quiet {
|
||||
return &Spinner{}
|
||||
}
|
||||
s := spinner.New(spinner.CharSets[36], 100*time.Millisecond)
|
||||
s.Suffix = suffix
|
||||
return &Spinner{client: s}
|
||||
}
|
||||
|
||||
func (s *Spinner) Start() {
|
||||
if s.client == nil {
|
||||
return
|
||||
}
|
||||
s.client.Start()
|
||||
}
|
||||
func (s *Spinner) Stop() {
|
||||
if s.client == nil {
|
||||
return
|
||||
}
|
||||
s.client.Stop()
|
||||
}
|
||||
|
||||
type ProgressBar struct {
|
||||
client *pb.ProgressBar
|
||||
}
|
||||
|
||||
func PbStartNew(total int) *ProgressBar {
|
||||
if Quiet {
|
||||
return &ProgressBar{}
|
||||
}
|
||||
bar := pb.StartNew(total)
|
||||
return &ProgressBar{client: bar}
|
||||
}
|
||||
|
||||
func (p *ProgressBar) Increment() {
|
||||
if p.client == nil {
|
||||
return
|
||||
}
|
||||
p.client.Increment()
|
||||
}
|
||||
func (p *ProgressBar) Finish() {
|
||||
if p.client == nil {
|
||||
return
|
||||
}
|
||||
p.client.Finish()
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package alpine
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
@@ -37,7 +36,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("Alpine updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
|
||||
var cves []AlpineCVE
|
||||
|
||||
@@ -3,7 +3,6 @@ package debianoval
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -39,7 +38,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("Debian OVAL updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
|
||||
var cves []DebianOVAL
|
||||
|
||||
@@ -3,7 +3,6 @@ package debian
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -45,7 +44,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("Debian updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
|
||||
var cves []DebianCVE
|
||||
|
||||
@@ -2,7 +2,6 @@ package nvd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
@@ -35,7 +34,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("NVD updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
var items []vulnerability.Item
|
||||
err = utils.FileWalk(rootDir, targets, func(r io.Reader, _ string) error {
|
||||
|
||||
@@ -3,7 +3,6 @@ package redhat
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@@ -41,7 +40,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("Red Hat updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
|
||||
var cves []RedhatCVE
|
||||
|
||||
@@ -3,7 +3,6 @@ package ubuntu
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gopkg.in/cheggaaa/pb.v1"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
@@ -56,7 +55,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||
}
|
||||
log.Logger.Debugf("Ubuntu OVAL updated files: %d", len(targets))
|
||||
|
||||
bar := pb.StartNew(len(targets))
|
||||
bar := utils.PbStartNew(len(targets))
|
||||
defer bar.Finish()
|
||||
|
||||
var cves []UbuntuCVE
|
||||
|
||||
@@ -43,6 +43,12 @@ func NewSeverity(severity string) (Severity, error) {
|
||||
return SeverityUnknown, fmt.Errorf("unknown severity: %s", severity)
|
||||
}
|
||||
|
||||
func CompareSeverityString(sev1, sev2 string) bool {
|
||||
s1, _ := NewSeverity(sev1)
|
||||
s2, _ := NewSeverity(sev2)
|
||||
return s1 < s2
|
||||
}
|
||||
|
||||
func ColorizeSeverity(severity string) string {
|
||||
for i, name := range SeverityNames {
|
||||
if severity == name {
|
||||
|
||||
Reference in New Issue
Block a user