Compare commits

...

4 Commits

Author SHA1 Message Date
knqyf263
6d82700032 Add --ignore-unfixed option and sort vulnerabilities by a severity 2019-05-08 13:31:51 +09:00
knqyf263
a0a991ca16 Update README 2019-05-08 07:47:47 +09:00
knqyf263
989b893bf2 Add Dockerfile 2019-05-07 23:07:21 +09:00
knqyf263
d270edea75 Support --quiet option 2019-05-07 23:06:57 +09:00
15 changed files with 204 additions and 24 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
imgs

14
Dockerfile Normal file
View 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"]

View File

@@ -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

View File

@@ -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",

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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
View 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()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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 {