mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-18 02:09:32 -08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53ad8c2f35 | ||
|
|
34ba0ca8d7 | ||
|
|
6d82700032 | ||
|
|
a0a991ca16 | ||
|
|
989b893bf2 | ||
|
|
d270edea75 | ||
|
|
7aa407099c | ||
|
|
abeeb37e75 |
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
|
$ 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
|
## Debian/Ubuntu
|
||||||
|
|
||||||
Replace `[CODE_NAME]` with your code name
|
Replace `[CODE_NAME]` with your code name
|
||||||
@@ -43,6 +49,14 @@ $ sudo apt-get update
|
|||||||
$ sudo apt-get install trivy
|
$ 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
|
## Mac OS X / Homebrew
|
||||||
You can use homebrew on OS X.
|
You can use homebrew on OS X.
|
||||||
```
|
```
|
||||||
@@ -60,6 +74,69 @@ $ go get -u github.com/knqyf263/trivy
|
|||||||
```
|
```
|
||||||
|
|
||||||
# Examples
|
# 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
|
# Usage
|
||||||
|
|
||||||
@@ -70,17 +147,20 @@ NAME:
|
|||||||
USAGE:
|
USAGE:
|
||||||
main [options] image_name
|
main [options] image_name
|
||||||
VERSION:
|
VERSION:
|
||||||
0.0.1
|
0.0.3
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
--format value, -f value format (table, json) (default: "table")
|
--format value, -f value format (table, json) (default: "table")
|
||||||
--input value, -i value input file path instead of image name
|
--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
|
--output value, -o value output file name
|
||||||
|
--exit-code value Exit code when vulnerabilities were found (default: 0)
|
||||||
--skip-update skip db update
|
--skip-update skip db update
|
||||||
--clean, -c clean all cache
|
--clean, -c clean all cache
|
||||||
|
--quiet, -q suppress progress bar
|
||||||
--debug, -d debug mode
|
--debug, -d debug mode
|
||||||
--help, -h show help
|
--help, -h show help
|
||||||
--version, -v print the version
|
--version, -v print the version
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Q&A
|
# Q&A
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
l "log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -59,6 +60,11 @@ OPTIONS:
|
|||||||
Name: "output, o",
|
Name: "output, o",
|
||||||
Usage: "output file name",
|
Usage: "output file name",
|
||||||
},
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "exit-code",
|
||||||
|
Usage: "Exit code when vulnerabilities were found",
|
||||||
|
Value: 0,
|
||||||
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "skip-update",
|
Name: "skip-update",
|
||||||
Usage: "skip db update",
|
Usage: "skip db update",
|
||||||
@@ -67,6 +73,14 @@ OPTIONS:
|
|||||||
Name: "clean, c",
|
Name: "clean, c",
|
||||||
Usage: "clean all cache",
|
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{
|
cli.BoolFlag{
|
||||||
Name: "debug, d",
|
Name: "debug, d",
|
||||||
Usage: "debug mode",
|
Usage: "debug mode",
|
||||||
@@ -79,6 +93,9 @@ OPTIONS:
|
|||||||
|
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if log.Logger != nil {
|
||||||
log.Logger.Fatal(err)
|
log.Logger.Fatal(err)
|
||||||
}
|
}
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/fatih/color v1.7.0
|
github.com/fatih/color v1.7.0
|
||||||
github.com/gliderlabs/ssh v0.1.3 // indirect
|
github.com/gliderlabs/ssh v0.1.3 // indirect
|
||||||
github.com/golang/protobuf v1.3.1 // indirect
|
github.com/golang/protobuf v1.3.1 // indirect
|
||||||
github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6
|
github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70
|
||||||
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b
|
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b
|
||||||
github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790
|
github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790
|
||||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -117,6 +117,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/knqyf263/berkeleydb v0.0.0-20190501065933-fafe01fb9662/go.mod h1:bu1CcN4tUtoRcI/B/RFHhxMNKFHVq/c3SV+UTyduoXg=
|
github.com/knqyf263/berkeleydb v0.0.0-20190501065933-fafe01fb9662/go.mod h1:bu1CcN4tUtoRcI/B/RFHhxMNKFHVq/c3SV+UTyduoXg=
|
||||||
github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6 h1:iSztZNfwEPMN2CvUX1SxNEclRZn+rwRMdsnAegxRJk4=
|
github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6 h1:iSztZNfwEPMN2CvUX1SxNEclRZn+rwRMdsnAegxRJk4=
|
||||||
github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6/go.mod h1:OiuWIClssf5WzbMcR8lfspdBVaP+vRQndY4kHeFgrDw=
|
github.com/knqyf263/fanal v0.0.0-20190506110705-2b5cb3000ff6/go.mod h1:OiuWIClssf5WzbMcR8lfspdBVaP+vRQndY4kHeFgrDw=
|
||||||
|
github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70 h1:L27WBZxk7N70WilG91kgvs0EnV+JVCoOTsNQa8tMBJs=
|
||||||
|
github.com/knqyf263/fanal v0.0.0-20190507123206-ceab60083e70/go.mod h1:OiuWIClssf5WzbMcR8lfspdBVaP+vRQndY4kHeFgrDw=
|
||||||
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b h1:DiDMmSwuY27PJxA2Gs0+uI/bQ/ehKARaGXRdlp+wFis=
|
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b h1:DiDMmSwuY27PJxA2Gs0+uI/bQ/ehKARaGXRdlp+wFis=
|
||||||
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
|
github.com/knqyf263/go-deb-version v0.0.0-20170509080151-9865fe14d09b/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
|
||||||
github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790 h1:c02gG0yRNr25lcLOH+678SuuxxMUq36i48PQnmAweWk=
|
github.com/knqyf263/go-dep-parser v0.0.0-20190429154931-c377a5391790 h1:c02gG0yRNr25lcLOH+678SuuxxMUq36i48PQnmAweWk=
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/briandowns/spinner"
|
|
||||||
"github.com/knqyf263/trivy/pkg/log"
|
"github.com/knqyf263/trivy/pkg/log"
|
||||||
"github.com/knqyf263/trivy/pkg/utils"
|
"github.com/knqyf263/trivy/pkg/utils"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
@@ -33,10 +31,13 @@ func CloneOrPull(url, repoPath string) (map[string]struct{}, error) {
|
|||||||
updatedFiles[strings.TrimSpace(filename)] = struct{}{}
|
updatedFiles[strings.TrimSpace(filename)] = struct{}{}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !utils.IsCommandAvailable("git") {
|
||||||
|
log.Logger.Warn("Recommend installing git (if not, DB update is very slow)")
|
||||||
|
}
|
||||||
log.Logger.Debug("remove an existed directory")
|
log.Logger.Debug("remove an existed directory")
|
||||||
|
|
||||||
s := spinner.New(spinner.CharSets[36], 100*time.Millisecond)
|
suffix := " The first time will take a while..."
|
||||||
s.Suffix = " The first time will take a while..."
|
s := utils.NewSpinner(suffix)
|
||||||
s.Start()
|
s.Start()
|
||||||
defer s.Stop()
|
defer s.Stop()
|
||||||
|
|
||||||
@@ -74,7 +75,6 @@ func clone(url, repoPath string) error {
|
|||||||
if utils.IsCommandAvailable("git") {
|
if utils.IsCommandAvailable("git") {
|
||||||
return cloneByOSCommand(url, repoPath)
|
return cloneByOSCommand(url, repoPath)
|
||||||
}
|
}
|
||||||
log.Logger.Warn("Recommend installing git (if not, DB update is very slow)")
|
|
||||||
|
|
||||||
_, err := git.PlainClone(repoPath, false, &git.CloneOptions{
|
_, err := git.PlainClone(repoPath, false, &git.CloneOptions{
|
||||||
URL: url,
|
URL: url,
|
||||||
|
|||||||
@@ -42,8 +42,17 @@ func (tw TableWriter) write(result Result) {
|
|||||||
severityCount := map[string]int{}
|
severityCount := map[string]int{}
|
||||||
for _, v := range result.Vulnerabilities {
|
for _, v := range result.Vulnerabilities {
|
||||||
severityCount[v.Severity]++
|
severityCount[v.Severity]++
|
||||||
|
|
||||||
|
title := v.Title
|
||||||
|
if title == "" {
|
||||||
|
title = v.Description
|
||||||
|
}
|
||||||
|
splittedTitle := strings.Split(title, " ")
|
||||||
|
if len(splittedTitle) >= 12 {
|
||||||
|
title = strings.Join(splittedTitle[:12], " ") + "..."
|
||||||
|
}
|
||||||
table.Append([]string{v.PkgName, v.VulnerabilityID, vulnerability.ColorizeSeverity(v.Severity),
|
table.Append([]string{v.PkgName, v.VulnerabilityID, vulnerability.ColorizeSeverity(v.Severity),
|
||||||
v.InstalledVersion, v.FixedVersion, v.Title})
|
v.InstalledVersion, v.FixedVersion, title})
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []string
|
var results []string
|
||||||
|
|||||||
38
pkg/run.go
38
pkg/run.go
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/knqyf263/fanal/cache"
|
||||||
|
|
||||||
"github.com/knqyf263/trivy/pkg/utils"
|
"github.com/knqyf263/trivy/pkg/utils"
|
||||||
|
|
||||||
"github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability"
|
"github.com/knqyf263/trivy/pkg/vulnsrc/vulnerability"
|
||||||
@@ -27,6 +29,18 @@ func Run(c *cli.Context) (err error) {
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||||
|
|
||||||
|
clean := c.Bool("clean")
|
||||||
|
if clean {
|
||||||
|
log.Logger.Info("Cleaning caches...")
|
||||||
|
if err = cache.Clear(); err != nil {
|
||||||
|
return xerrors.New("failed to remove image layer cache")
|
||||||
|
}
|
||||||
|
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
||||||
|
return xerrors.New("failed to remove cache")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
filePath := c.String("input")
|
filePath := c.String("input")
|
||||||
if filePath == "" && len(args) == 0 {
|
if filePath == "" && len(args) == 0 {
|
||||||
@@ -34,13 +48,8 @@ func Run(c *cli.Context) (err error) {
|
|||||||
cli.ShowAppHelpAndExit(c, 1)
|
cli.ShowAppHelpAndExit(c, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
clean := c.Bool("clean")
|
utils.Quiet = c.Bool("quiet")
|
||||||
if clean {
|
|
||||||
log.Logger.Info("Cleaning caches...")
|
|
||||||
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
|
||||||
return xerrors.New("failed to remove cache")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o := c.String("output")
|
o := c.String("output")
|
||||||
output := os.Stdout
|
output := os.Stdout
|
||||||
if o != "" {
|
if o != "" {
|
||||||
@@ -53,7 +62,8 @@ func Run(c *cli.Context) (err error) {
|
|||||||
for _, s := range strings.Split(c.String("severity"), ",") {
|
for _, s := range strings.Split(c.String("severity"), ",") {
|
||||||
severity, err := vulnerability.NewSeverity(s)
|
severity, err := vulnerability.NewSeverity(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("error in severity option: %w", err)
|
log.Logger.Infof("error in severity option: %s", err)
|
||||||
|
cli.ShowAppHelpAndExit(c, 1)
|
||||||
}
|
}
|
||||||
severities = append(severities, severity)
|
severities = append(severities, severity)
|
||||||
}
|
}
|
||||||
@@ -68,11 +78,13 @@ func Run(c *cli.Context) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoreUnfixed := c.Bool("ignore-unfixed")
|
||||||
|
|
||||||
var imageName string
|
var imageName string
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
imageName = args[0]
|
imageName = args[0]
|
||||||
}
|
}
|
||||||
results, err := scanner.ScanImage(imageName, filePath, severities)
|
results, err := scanner.ScanImage(imageName, filePath, severities, ignoreUnfixed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("error in image scan: %w", err)
|
return xerrors.Errorf("error in image scan: %w", err)
|
||||||
}
|
}
|
||||||
@@ -88,7 +100,13 @@ func Run(c *cli.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err = writer.Write(results); err != nil {
|
if err = writer.Write(results); err != nil {
|
||||||
return err
|
return xerrors.Errorf("failed to write results: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
if len(result.Vulnerabilities) > 0 {
|
||||||
|
os.Exit(c.Int("exit-code"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/knqyf263/trivy/pkg/log"
|
"github.com/knqyf263/trivy/pkg/log"
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ var (
|
|||||||
vulnerability.NodejsSecurityWg, vulnerability.PythonSafetyDB}
|
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 results report.Results
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -67,7 +68,7 @@ func ScanImage(imageName, filePath string, severities []vulnerability.Severity)
|
|||||||
|
|
||||||
results = append(results, report.Result{
|
results = append(results, report.Result{
|
||||||
FileName: fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion),
|
FileName: fmt.Sprintf("%s (%s %s)", target, osFamily, osVersion),
|
||||||
Vulnerabilities: processVulnerabilties(osVulns, severities),
|
Vulnerabilities: processVulnerabilties(osVulns, severities, ignoreUnfixed),
|
||||||
})
|
})
|
||||||
|
|
||||||
libVulns, err := library.Scan(files)
|
libVulns, err := library.Scan(files)
|
||||||
@@ -77,7 +78,7 @@ func ScanImage(imageName, filePath string, severities []vulnerability.Severity)
|
|||||||
for path, vulns := range libVulns {
|
for path, vulns := range libVulns {
|
||||||
results = append(results, report.Result{
|
results = append(results, report.Result{
|
||||||
FileName: path,
|
FileName: path,
|
||||||
Vulnerabilities: processVulnerabilties(vulns, severities),
|
Vulnerabilities: processVulnerabilties(vulns, severities, ignoreUnfixed),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,26 +92,39 @@ func ScanFile(f *os.File, severities []vulnerability.Severity) (report.Result, e
|
|||||||
}
|
}
|
||||||
result := report.Result{
|
result := report.Result{
|
||||||
FileName: f.Name(),
|
FileName: f.Name(),
|
||||||
Vulnerabilities: processVulnerabilties(vulns, severities),
|
Vulnerabilities: processVulnerabilties(vulns, severities, false),
|
||||||
}
|
}
|
||||||
return result, nil
|
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
|
var vulnerabilities []types.Vulnerability
|
||||||
for _, vuln := range vulns {
|
for _, vuln := range vulns {
|
||||||
sev, title := getDetail(vuln.VulnerabilityID)
|
sev, title, description, references := getDetail(vuln.VulnerabilityID)
|
||||||
|
|
||||||
// Filter vulnerabilities by severity
|
// Filter vulnerabilities by severity
|
||||||
for _, s := range severities {
|
for _, s := range severities {
|
||||||
if s == sev {
|
if s == sev {
|
||||||
vuln.Severity = fmt.Sprint(sev)
|
vuln.Severity = fmt.Sprint(sev)
|
||||||
vuln.Title = title
|
vuln.Title = title
|
||||||
|
vuln.Description = description
|
||||||
|
vuln.References = references
|
||||||
|
|
||||||
|
// Ignore unfixed vulnerabilities
|
||||||
|
if ignoreUnfixed && vuln.FixedVersion == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
vulnerabilities = append(vulnerabilities, vuln)
|
vulnerabilities = append(vulnerabilities, vuln)
|
||||||
break
|
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
|
return vulnerabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,17 +140,15 @@ func openStream(path string) (*os.File, error) {
|
|||||||
return os.Open(path)
|
return os.Open(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDetail(vulnID string) (vulnerability.Severity, string) {
|
func getDetail(vulnID string) (vulnerability.Severity, string, string, []string) {
|
||||||
details, err := vulnerability.Get(vulnID)
|
details, err := vulnerability.Get(vulnID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Logger.Debug(err)
|
log.Logger.Debug(err)
|
||||||
return vulnerability.SeverityUnknown, ""
|
return vulnerability.SeverityUnknown, "", "", nil
|
||||||
} else if len(details) == 0 {
|
} else if len(details) == 0 {
|
||||||
return vulnerability.SeverityUnknown, ""
|
return vulnerability.SeverityUnknown, "", "", nil
|
||||||
}
|
}
|
||||||
severity := getSeverity(details)
|
return getSeverity(details), getTitle(details), getDescription(details), getReferences(details)
|
||||||
title := getTitle(details)
|
|
||||||
return severity, title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSeverity(details map[string]vulnerability.Vulnerability) vulnerability.Severity {
|
func getSeverity(details map[string]vulnerability.Vulnerability) vulnerability.Severity {
|
||||||
@@ -171,6 +183,37 @@ func getTitle(details map[string]vulnerability.Vulnerability) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDescription(details map[string]vulnerability.Vulnerability) string {
|
||||||
|
for _, source := range sources {
|
||||||
|
d, ok := details[source]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d.Description != "" {
|
||||||
|
return d.Description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReferences(details map[string]vulnerability.Vulnerability) []string {
|
||||||
|
references := map[string]struct{}{}
|
||||||
|
for _, source := range sources {
|
||||||
|
d, ok := details[source]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, ref := range d.References {
|
||||||
|
references[ref] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var refs []string
|
||||||
|
for ref := range references {
|
||||||
|
refs = append(refs, ref)
|
||||||
|
}
|
||||||
|
return refs
|
||||||
|
}
|
||||||
|
|
||||||
func scoreToSeverity(score float64) vulnerability.Severity {
|
func scoreToSeverity(score float64) vulnerability.Severity {
|
||||||
if score >= 9.0 {
|
if score >= 9.0 {
|
||||||
return vulnerability.SeverityCritical
|
return vulnerability.SeverityCritical
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ type Vulnerability struct {
|
|||||||
FixedVersion string
|
FixedVersion string
|
||||||
|
|
||||||
Title string
|
Title string
|
||||||
|
Description string
|
||||||
Severity string
|
Severity string
|
||||||
|
References []string
|
||||||
}
|
}
|
||||||
|
|||||||
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("Alpine updated files: %d", len(targets))
|
log.Logger.Debugf("Alpine updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
|
|
||||||
var cves []AlpineCVE
|
var cves []AlpineCVE
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package debianoval
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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))
|
log.Logger.Debugf("Debian OVAL updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
|
|
||||||
var cves []DebianOVAL
|
var cves []DebianOVAL
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package debian
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,7 +44,7 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("Debian updated files: %d", len(targets))
|
log.Logger.Debugf("Debian updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
|
|
||||||
var cves []DebianCVE
|
var cves []DebianCVE
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package nvd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -20,8 +19,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
nvdDir = "nvd"
|
nvdDir = "nvd"
|
||||||
rootBucket = "NVD"
|
|
||||||
nestedBucket = "dummy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Update(dir string, updatedFiles map[string]struct{}) error {
|
func Update(dir string, updatedFiles map[string]struct{}) error {
|
||||||
@@ -35,11 +32,11 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("NVD updated files: %d", len(targets))
|
log.Logger.Debugf("NVD updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
var items []vulnerability.Item
|
var items []Item
|
||||||
err = utils.FileWalk(rootDir, targets, func(r io.Reader, _ string) error {
|
err = utils.FileWalk(rootDir, targets, func(r io.Reader, _ string) error {
|
||||||
item := vulnerability.Item{}
|
item := Item{}
|
||||||
if err := json.NewDecoder(r).Decode(&item); err != nil {
|
if err := json.NewDecoder(r).Decode(&item); err != nil {
|
||||||
return xerrors.Errorf("failed to decode NVD JSON: %w", err)
|
return xerrors.Errorf("failed to decode NVD JSON: %w", err)
|
||||||
}
|
}
|
||||||
@@ -58,20 +55,33 @@ func Update(dir string, updatedFiles map[string]struct{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(items []vulnerability.Item) error {
|
func save(items []Item) error {
|
||||||
log.Logger.Debug("NVD batch update")
|
log.Logger.Debug("NVD batch update")
|
||||||
err := vulnerability.BatchUpdate(func(b *bolt.Bucket) error {
|
err := vulnerability.BatchUpdate(func(b *bolt.Bucket) error {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
cveID := item.Cve.Meta.ID
|
cveID := item.Cve.Meta.ID
|
||||||
severity, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV2.Severity)
|
severity, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV2.Severity)
|
||||||
severityV3, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV3.CvssV3.BaseSeverity)
|
severityV3, _ := vulnerability.NewSeverity(item.Impact.BaseMetricV3.CvssV3.BaseSeverity)
|
||||||
|
|
||||||
|
var references []string
|
||||||
|
for _, ref := range item.Cve.References.ReferenceDataList {
|
||||||
|
references = append(references, ref.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var description string
|
||||||
|
for _, d := range item.Cve.Description.DescriptionDataList {
|
||||||
|
if d.Value != "" {
|
||||||
|
description = d.Value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vuln := vulnerability.Vulnerability{
|
vuln := vulnerability.Vulnerability{
|
||||||
Severity: severity,
|
Severity: severity,
|
||||||
SeverityV3: severityV3,
|
SeverityV3: severityV3,
|
||||||
// TODO
|
References: references,
|
||||||
References: []string{},
|
|
||||||
Title: "",
|
Title: "",
|
||||||
Description: "",
|
Description: description,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Put(b, cveID, vulnerability.Nvd, vuln); err != nil {
|
if err := db.Put(b, cveID, vulnerability.Nvd, vuln); err != nil {
|
||||||
|
|||||||
55
pkg/vulnsrc/nvd/types.go
Normal file
55
pkg/vulnsrc/nvd/types.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package nvd
|
||||||
|
|
||||||
|
type NVD struct {
|
||||||
|
CVEItems []Item `json:"CVE_Items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
Cve Cve
|
||||||
|
Impact Impact
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cve struct {
|
||||||
|
Meta Meta `json:"CVE_data_meta"`
|
||||||
|
References References
|
||||||
|
Description Description
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Impact struct {
|
||||||
|
BaseMetricV2 BaseMetricV2
|
||||||
|
BaseMetricV3 BaseMetricV3
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseMetricV2 struct {
|
||||||
|
Severity string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseMetricV3 struct {
|
||||||
|
CvssV3 CvssV3
|
||||||
|
}
|
||||||
|
|
||||||
|
type CvssV3 struct {
|
||||||
|
BaseSeverity string
|
||||||
|
}
|
||||||
|
|
||||||
|
type References struct {
|
||||||
|
ReferenceDataList []ReferenceData `json:"reference_data"`
|
||||||
|
}
|
||||||
|
type ReferenceData struct {
|
||||||
|
Name string
|
||||||
|
Refsource string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Description struct {
|
||||||
|
DescriptionDataList []DescriptionData `json:"description_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DescriptionData struct {
|
||||||
|
Lang string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package redhat
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"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))
|
log.Logger.Debugf("Red Hat updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
|
|
||||||
var cves []RedhatCVE
|
var cves []RedhatCVE
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package ubuntu
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/cheggaaa/pb.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"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))
|
log.Logger.Debugf("Ubuntu OVAL updated files: %d", len(targets))
|
||||||
|
|
||||||
bar := pb.StartNew(len(targets))
|
bar := utils.PbStartNew(len(targets))
|
||||||
defer bar.Finish()
|
defer bar.Finish()
|
||||||
|
|
||||||
var cves []UbuntuCVE
|
var cves []UbuntuCVE
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ func NewSeverity(severity string) (Severity, error) {
|
|||||||
return SeverityUnknown, fmt.Errorf("unknown severity: %s", severity)
|
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 {
|
func ColorizeSeverity(severity string) string {
|
||||||
for i, name := range SeverityNames {
|
for i, name := range SeverityNames {
|
||||||
if severity == name {
|
if severity == name {
|
||||||
@@ -59,37 +65,3 @@ func (s Severity) String() string {
|
|||||||
type LastUpdated struct {
|
type LastUpdated struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type NVD struct {
|
|
||||||
CVEItems []Item `json:"CVE_Items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Cve Cve
|
|
||||||
Impact Impact
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cve struct {
|
|
||||||
Meta Meta `json:"CVE_data_meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Meta struct {
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Impact struct {
|
|
||||||
BaseMetricV2 BaseMetricV2
|
|
||||||
BaseMetricV3 BaseMetricV3
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseMetricV2 struct {
|
|
||||||
Severity string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseMetricV3 struct {
|
|
||||||
CvssV3 CvssV3
|
|
||||||
}
|
|
||||||
|
|
||||||
type CvssV3 struct {
|
|
||||||
BaseSeverity string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package vulnsrc
|
package vulnsrc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/knqyf263/trivy/pkg/git"
|
"github.com/knqyf263/trivy/pkg/git"
|
||||||
"github.com/knqyf263/trivy/pkg/log"
|
"github.com/knqyf263/trivy/pkg/log"
|
||||||
"github.com/knqyf263/trivy/pkg/utils"
|
"github.com/knqyf263/trivy/pkg/utils"
|
||||||
@@ -11,7 +13,6 @@ import (
|
|||||||
"github.com/knqyf263/trivy/pkg/vulnsrc/redhat"
|
"github.com/knqyf263/trivy/pkg/vulnsrc/redhat"
|
||||||
"github.com/knqyf263/trivy/pkg/vulnsrc/ubuntu"
|
"github.com/knqyf263/trivy/pkg/vulnsrc/ubuntu"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
Reference in New Issue
Block a user