mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
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:
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}{}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ type ScanOptions struct {
|
||||
VulnType []string
|
||||
ScanRemovedPackages bool
|
||||
ListAllPackages bool
|
||||
SkipDirectories []string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user