feat: add --skip-directories option (#595)

* feat: add --skip-directories option

* chore(README): update

* refactor: rename skip-directories to skip-dirs

* Update internal/app.go

Co-authored-by: Daniel Pacak <pacak.daniel@gmail.com>

* refactor: add some context in the warning message

* chore(README): update

Co-authored-by: Daniel Pacak <pacak.daniel@gmail.com>
This commit is contained in:
Teppei Fukuda
2020-08-12 14:03:12 +03:00
committed by GitHub
parent 675e1b4118
commit 96af6dc499
7 changed files with 236 additions and 6 deletions

View File

@@ -46,6 +46,7 @@ A Simple and Comprehensive Vulnerability Scanner for Containers and other Artifa
+ [Filter the vulnerabilities by severities](#filter-the-vulnerabilities-by-severities)
+ [Filter the vulnerabilities by type](#filter-the-vulnerabilities-by-type)
+ [Filter the vulnerabilities by Open Policy Agent](#filter-the-vulnerabilities-by-open-policy-agent-policy)
+ [Skip traversal in the specific directory](#skip-traversal-in-the-specific-directory)
+ [Skip update of vulnerability DB](#skip-update-of-vulnerability-db)
+ [Only download vulnerability database](#only-download-vulnerability-database)
+ [Ignore unfixed vulnerabilities](#ignore-unfixed-vulnerabilities)
@@ -1140,6 +1141,13 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 0)
</details>
### Skip traversal in the specific directory
Trivy traversals directories and look for all lock files by default. If your image contains lock files which are not maintained by you, you can skip traversal in the specific directory.
```
$ trivy image --skip-dirs "/usr/lib/ruby/gems,/etc" fluent/fluentd:edge
```
### Skip update of vulnerability DB
@@ -1739,6 +1747,7 @@ OPTIONS:
--timeout value docker timeout (default: 2m0s) [$TRIVY_TIMEOUT]
--light light mode: it's faster, but vulnerability descriptions and references are not displayed (default: false) [$TRIVY_LIGHT]
--list-all-pkgs enabling the option will output all packages regardless of vulnerability [$TRIVY_LIST_ALL_PKGS]
--skip-dirs value specify the directory where the traversal is skipped [$TRIVY_SKIP_DIRS]
--help, -h show help (default: false)
```

View File

@@ -188,6 +188,12 @@ var (
EnvVars: []string{"TRIVY_LIST_ALL_PKGS"},
}
skipDirectories = cli.StringFlag{
Name: "skip-dirs",
Usage: "specify the directory where the traversal is skipped",
EnvVars: []string{"TRIVY_SKIP_DIRS"},
}
globalFlags = []cli.Flag{
&quietFlag,
&debugFlag,
@@ -214,6 +220,7 @@ var (
&lightFlag,
&ignorePolicy,
&listAllPackages,
&skipDirectories,
}
// deprecated options
@@ -368,6 +375,7 @@ func NewFilesystemCommand() *cli.Command {
&noProgressFlag,
&ignorePolicy,
&listAllPackages,
&skipDirectories,
},
}
}
@@ -398,6 +406,7 @@ func NewRepositoryCommand() *cli.Command {
&noProgressFlag,
&ignorePolicy,
&listAllPackages,
&skipDirectories,
},
}
}

View File

@@ -77,6 +77,7 @@ func run(c config.Config, initializeScanner InitializeScanner) error {
VulnType: c.VulnType,
ScanRemovedPackages: c.ScanRemovedPkgs, // this is valid only for image subcommand
ListAllPackages: c.ListAllPkgs,
SkipDirectories: c.SkipDirectories,
}
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)

View File

@@ -1,6 +1,7 @@
package config
import (
"strings"
"time"
"github.com/urfave/cli/v2"
@@ -13,15 +14,19 @@ type ArtifactConfig struct {
Timeout time.Duration
ClearCache bool
skipDirectories string
SkipDirectories []string
// this field is populated in Init()
Target string
}
func NewArtifactConfig(c *cli.Context) ArtifactConfig {
return ArtifactConfig{
Input: c.String("input"),
Timeout: c.Duration("timeout"),
ClearCache: c.Bool("clear-cache"),
Input: c.String("input"),
Timeout: c.Duration("timeout"),
ClearCache: c.Bool("clear-cache"),
skipDirectories: c.String("skip-dirs"),
}
}
@@ -40,5 +45,9 @@ func (c *ArtifactConfig) Init(args cli.Args, logger *zap.SugaredLogger) (err err
c.Target = args.First()
}
if c.skipDirectories != "" {
c.SkipDirectories = strings.Split(c.skipDirectories, ",")
}
return nil
}

View File

@@ -2,6 +2,8 @@ package local
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -110,7 +112,7 @@ func (s Scanner) Scan(target string, imageID string, layerIDs []string, options
}
if utils.StringInSlice("library", options.VulnType) {
libResults, err := s.scanLibrary(imageDetail.Applications, options.ListAllPackages)
libResults, err := s.scanLibrary(imageDetail.Applications, options)
if err != nil {
return nil, nil, false, xerrors.Errorf("failed to scan application libraries: %w", err)
}
@@ -140,19 +142,24 @@ func (s Scanner) scanOSPkg(target, osFamily, osName string, pkgs []ftypes.Packag
return result, eosl, nil
}
func (s Scanner) scanLibrary(apps []ftypes.Application, listAllPackages bool) (report.Results, error) {
func (s Scanner) scanLibrary(apps []ftypes.Application, options types.ScanOptions) (report.Results, error) {
var results report.Results
for _, app := range apps {
vulns, err := s.libDetector.Detect("", app.FilePath, time.Time{}, app.Libraries)
if err != nil {
return nil, xerrors.Errorf("failed vulnerability detection of libraries: %w", err)
}
if skipped(app.FilePath, options.SkipDirectories) {
continue
}
libReport := report.Result{
Target: app.FilePath,
Vulnerabilities: vulns,
Type: app.Type,
}
if listAllPackages {
if options.ListAllPackages {
var pkgs []ftypes.Package
for _, lib := range app.Libraries {
pkgs = append(pkgs, ftypes.Package{
@@ -174,6 +181,21 @@ func (s Scanner) scanLibrary(apps []ftypes.Application, listAllPackages bool) (r
return results, nil
}
func skipped(filePath string, skipDirectories []string) bool {
for _, skipDir := range skipDirectories {
skipDir = strings.TrimLeft(filepath.Clean(skipDir), string(os.PathSeparator))
rel, err := filepath.Rel(skipDir, filePath)
if err != nil {
log.Logger.Warnf("Unexpected error while skipping directories: %s", err)
return false
}
if !strings.HasPrefix(rel, "..") {
return true
}
}
return false
}
func mergePkgs(pkgs, pkgsFromCommands []ftypes.Package) []ftypes.Package {
// pkg has priority over pkgsFromCommands
uniqPkgs := map[string]struct{}{}

View File

@@ -785,6 +785,134 @@ func TestScanner_Scan(t *testing.T) {
Name: "3.11",
},
},
{
name: "happy path with skip directories",
args: args{
target: "alpine:latest",
layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
options: types.ScanOptions{
VulnType: []string{"library"},
SkipDirectories: []string{"/usr/lib/ruby/gems"},
},
},
applyLayersExpectation: ApplierApplyLayersExpectation{
Args: ApplierApplyLayersArgs{
BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
},
Returns: ApplierApplyLayersReturns{
Detail: ftypes.ArtifactDetail{
OS: &ftypes.OS{
Family: "alpine",
Name: "3.11",
},
Packages: []ftypes.Package{
{Name: "musl", Version: "1.2.3"},
},
Applications: []ftypes.Application{
{
Type: "bundler",
FilePath: "usr/lib/ruby/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock",
Libraries: []ftypes.LibraryInfo{
{
Library: dtypes.Library{Name: "rails", Version: "5.1"},
Layer: ftypes.Layer{
DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0",
},
},
},
},
{
Type: "composer",
FilePath: "app/composer-lock.json",
Libraries: []ftypes.LibraryInfo{
{
Library: dtypes.Library{Name: "laravel", Version: "6.0.0"},
Layer: ftypes.Layer{
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
},
},
},
},
},
},
},
},
libDetectExpectations: []LibraryDetectorDetectExpectation{
{
Args: LibraryDetectorDetectArgs{
FilePath: "usr/lib/ruby/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock",
Pkgs: []ftypes.LibraryInfo{
{
Library: dtypes.Library{Name: "rails", Version: "5.1"},
Layer: ftypes.Layer{
DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0",
},
},
},
},
Returns: LibraryDetectorDetectReturns{
DetectedVulns: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-11111",
PkgName: "rails",
InstalledVersion: "5.1",
FixedVersion: "5.2",
Layer: ftypes.Layer{
DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0",
},
},
},
},
},
{
Args: LibraryDetectorDetectArgs{
FilePath: "app/composer-lock.json",
Pkgs: []ftypes.LibraryInfo{
{
Library: dtypes.Library{Name: "laravel", Version: "6.0.0"},
Layer: ftypes.Layer{
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
},
},
},
},
Returns: LibraryDetectorDetectReturns{
DetectedVulns: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-22222",
PkgName: "laravel",
InstalledVersion: "6.0.0",
FixedVersion: "6.1.0",
Layer: ftypes.Layer{
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
},
},
},
},
},
},
wantResults: report.Results{
{
Target: "app/composer-lock.json",
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-22222",
PkgName: "laravel",
InstalledVersion: "6.0.0",
FixedVersion: "6.1.0",
Layer: ftypes.Layer{
DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303",
},
},
},
Type: "composer",
},
},
wantOS: &ftypes.OS{
Family: "alpine",
Name: "3.11",
},
},
{
name: "sad path: ApplyLayers returns an error",
args: args{
@@ -950,3 +1078,54 @@ func TestScanner_Scan(t *testing.T) {
})
}
}
func Test_skipped(t *testing.T) {
type args struct {
filePath string
skipDirectories []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "no skip directory",
args: args{
filePath: "app/Gemfile.lock",
skipDirectories: []string{},
},
want: false,
},
{
name: "skip directory with the leading slash",
args: args{
filePath: "app/Gemfile.lock",
skipDirectories: []string{"/app"},
},
want: true,
},
{
name: "skip directory without a slash",
args: args{
filePath: "usr/lib/ruby/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock",
skipDirectories: []string{"/usr/lib/ruby"},
},
want: true,
},
{
name: "not skipped",
args: args{
filePath: "usr/lib/ruby/gems/2.5.0/gems/http_parser.rb-0.6.0/Gemfile.lock",
skipDirectories: []string{"lib/ruby"},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := skipped(tt.args.filePath, tt.args.skipDirectories)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -4,4 +4,5 @@ type ScanOptions struct {
VulnType []string
ScanRemovedPackages bool
ListAllPackages bool
SkipDirectories []string
}