feat: support repository and filesystem scan (#503)

* refactor: embed config

* refactor: replace image and layer with artifact and blob

* feat(config): add ArtifactConfig

* fix(scanner): use Artifact

* test(scanner): update mocks

* feat: add repo and fs subcommands

* chore(mod): update

* refactor: fix warn message

* feat(cli): add --no-progress to repo and fs

* mod: Update fanal dependency

Signed-off-by: Simarpreet Singh <simar@linux.com>

Co-authored-by: Simarpreet Singh <simar@linux.com>
This commit is contained in:
Teppei Fukuda
2020-05-30 19:46:12 +03:00
committed by GitHub
parent 03ad8a3cd0
commit 2f2d1a908b
47 changed files with 1482 additions and 1025 deletions

View File

@@ -12,9 +12,9 @@ import (
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/internal/artifact"
"github.com/aquasecurity/trivy/internal/client"
"github.com/aquasecurity/trivy/internal/server"
"github.com/aquasecurity/trivy/internal/standalone"
tdb "github.com/aquasecurity/trivy/pkg/db"
"github.com/aquasecurity/trivy/pkg/utils"
"github.com/aquasecurity/trivy/pkg/vulnerability"
@@ -240,10 +240,12 @@ func NewApp(version string) *cli.App {
app.Flags = flags
app.Commands = []*cli.Command{
NewImageCommand(),
NewFilesystemCommand(),
NewRepositoryCommand(),
NewClientCommand(),
NewServerCommand(),
}
app.Action = standalone.Run
app.Action = artifact.ImageRun
return app
}
@@ -320,11 +322,65 @@ func NewImageCommand() *cli.Command {
Name: "image",
Aliases: []string{"i"},
Usage: "scan an image",
Action: standalone.Run,
Action: artifact.ImageRun,
Flags: imageFlags,
}
}
func NewFilesystemCommand() *cli.Command {
return &cli.Command{
Name: "filesystem",
Aliases: []string{"fs"},
Usage: "scan local filesystem",
Action: artifact.FilesystemRun,
Flags: []cli.Flag{
&templateFlag,
&formatFlag,
&inputFlag,
&severityFlag,
&outputFlag,
&exitCodeFlag,
&clearCacheFlag,
&quietFlag,
&ignoreUnfixedFlag,
&debugFlag,
&removedPkgsFlag,
&vulnTypeFlag,
&ignoreFileFlag,
&cacheDirFlag,
&timeoutFlag,
&noProgressFlag,
},
}
}
func NewRepositoryCommand() *cli.Command {
return &cli.Command{
Name: "repository",
Aliases: []string{"repo"},
Usage: "scan remote repository",
Action: artifact.RepositoryRun,
Flags: []cli.Flag{
&templateFlag,
&formatFlag,
&inputFlag,
&severityFlag,
&outputFlag,
&exitCodeFlag,
&clearCacheFlag,
&quietFlag,
&ignoreUnfixedFlag,
&debugFlag,
&removedPkgsFlag,
&vulnTypeFlag,
&ignoreFileFlag,
&cacheDirFlag,
&timeoutFlag,
&noProgressFlag,
},
}
}
func NewClientCommand() *cli.Command {
return &cli.Command{
Name: "client",

View File

@@ -9,12 +9,11 @@ import (
type Config struct {
config.GlobalConfig
config.ArtifactConfig
config.DBConfig
config.ImageConfig
config.ReportConfig
NoProgress bool
// deprecated
onlyUpdate string
// deprecated
@@ -30,12 +29,11 @@ func New(c *cli.Context) (Config, error) {
}
return Config{
GlobalConfig: gc,
DBConfig: config.NewDBConfig(c),
ImageConfig: config.NewImageConfig(c),
ReportConfig: config.NewReportConfig(c),
NoProgress: c.Bool("no-progress"),
GlobalConfig: gc,
ArtifactConfig: config.NewArtifactConfig(c),
DBConfig: config.NewDBConfig(c),
ImageConfig: config.NewImageConfig(c),
ReportConfig: config.NewReportConfig(c),
onlyUpdate: c.String("only-update"),
refresh: c.Bool("refresh"),
@@ -43,7 +41,7 @@ func New(c *cli.Context) (Config, error) {
}, nil
}
func (c *Config) Init() error {
func (c *Config) Init(image bool) error {
if err := c.ReportConfig.Init(c.Logger); err != nil {
return err
}
@@ -59,10 +57,16 @@ func (c *Config) Init() error {
return nil
}
if err := c.ImageConfig.Init(c.Context.Args(), c.Logger); err != nil {
if err := c.ArtifactConfig.Init(c.Context.Args(), c.Logger); err != nil {
cli.ShowAppHelp(c.Context)
return err
}
if image {
if err := c.ImageConfig.Init(c.Context.Args(), c.Logger); err != nil {
return err
}
}
return nil
}

View File

@@ -38,8 +38,8 @@ func TestConfig_Init(t *testing.T) {
GlobalConfig: config.GlobalConfig{
Quiet: true,
},
ImageConfig: config.ImageConfig{
ImageName: "alpine:3.10",
ArtifactConfig: config.ArtifactConfig{
Target: "alpine:3.10",
},
ReportConfig: config.ReportConfig{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
@@ -74,8 +74,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "centos:7",
ArtifactConfig: config.ArtifactConfig{
Target: "centos:7",
},
},
},
@@ -91,8 +91,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "debian:buster",
ArtifactConfig: config.ArtifactConfig{
Target: "debian:buster",
},
onlyUpdate: "alpine",
},
@@ -110,8 +110,8 @@ func TestConfig_Init(t *testing.T) {
VulnType: []string{"os", "library"},
Template: "@contrib/gitlab.tpl",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
@@ -129,8 +129,8 @@ func TestConfig_Init(t *testing.T) {
Template: "@contrib/gitlab.tpl",
Format: "json",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
@@ -147,8 +147,8 @@ func TestConfig_Init(t *testing.T) {
VulnType: []string{"os", "library"},
Format: "template",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
@@ -165,8 +165,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "gcr.io/distroless/base",
ArtifactConfig: config.ArtifactConfig{
Target: "gcr.io/distroless/base",
},
autoRefresh: true,
},
@@ -180,7 +180,7 @@ func TestConfig_Init(t *testing.T) {
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple images cannot be specified",
"multiple targets cannot be specified",
},
wantErr: "arguments error",
},
@@ -223,7 +223,7 @@ func TestConfig_Init(t *testing.T) {
require.NoError(t, err, err)
c.GlobalConfig.Logger = logger.Sugar()
err = c.Init()
err = c.Init(true)
// tests log messages
var gotMessages []string

36
internal/artifact/fs.go Normal file
View File

@@ -0,0 +1,36 @@
package artifact
import (
"context"
"time"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/internal/artifact/config"
"github.com/aquasecurity/trivy/pkg/scanner"
)
func filesystemScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, timeout time.Duration) (
scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, dir, ac, lac)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
func FilesystemRun(cliCtx *cli.Context) error {
c, err := config.New(cliCtx)
if err != nil {
return err
}
// initialize config
if err = c.Init(false); err != nil {
return xerrors.Errorf("failed to initialize options: %w", err)
}
return run(c, filesystemScanner)
}

View File

@@ -0,0 +1,50 @@
package artifact
import (
"context"
"time"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/internal/artifact/config"
"github.com/aquasecurity/trivy/pkg/scanner"
)
func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, timeout time.Duration) (
scanner.Scanner, func(), error) {
s, err := initializeArchiveScanner(ctx, input, ac, lac, timeout)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
}
return s, func() {}, nil
}
func dockerScanner(ctx context.Context, imageName string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, timeout time.Duration) (
scanner.Scanner, func(), error) {
s, cleanup, err := initializeDockerScanner(ctx, imageName, ac, lac, timeout)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
}
return s, cleanup, nil
}
func ImageRun(cliCtx *cli.Context) error {
c, err := config.New(cliCtx)
if err != nil {
return err
}
// initialize config
if err = c.Init(true); err != nil {
return xerrors.Errorf("failed to initialize options: %w", err)
}
if c.Input != "" {
// scan tar file
return run(c, archiveScanner)
}
return run(c, dockerScanner)
}

View File

@@ -1,6 +1,6 @@
// +build wireinject
package standalone
package artifact
import (
"context"
@@ -12,18 +12,28 @@ import (
"github.com/google/wire"
)
func initializeDockerScanner(ctx context.Context, imageName string, layerCache cache.ImageCache, localImageCache cache.LocalImageCache,
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache,
timeout time.Duration) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneDockerSet)
return scanner.Scanner{}, nil, nil
}
func initializeArchiveScanner(ctx context.Context, filePath string, layerCache cache.ImageCache, localImageCache cache.LocalImageCache,
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache,
timeout time.Duration) (scanner.Scanner, error) {
wire.Build(scanner.StandaloneArchiveSet)
return scanner.Scanner{}, nil
}
func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneFilesystemSet)
return scanner.Scanner{}, nil, nil
}
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) {
wire.Build(scanner.StandaloneRepositorySet)
return scanner.Scanner{}, nil, nil
}
func initializeVulnerabilityClient() vulnerability.Client {
wire.Build(vulnerability.SuperSet)
return vulnerability.Client{}

View File

@@ -0,0 +1,36 @@
package artifact
import (
"context"
"time"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/internal/artifact/config"
"github.com/aquasecurity/trivy/pkg/scanner"
)
func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache, timeout time.Duration) (
scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
func RepositoryRun(cliCtx *cli.Context) error {
c, err := config.New(cliCtx)
if err != nil {
return err
}
// initialize config
if err = c.Init(false); err != nil {
return xerrors.Errorf("failed to initialize options: %w", err)
}
return run(c, repositoryScanner)
}

View File

@@ -1,17 +1,17 @@
package standalone
package artifact
import (
"context"
l "log"
"os"
"time"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/internal/artifact/config"
"github.com/aquasecurity/trivy/internal/operation"
"github.com/aquasecurity/trivy/internal/standalone/config"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/scanner"
@@ -19,24 +19,14 @@ import (
"github.com/aquasecurity/trivy/pkg/utils"
)
func Run(cliCtx *cli.Context) error {
c, err := config.New(cliCtx)
if err != nil {
return err
}
return run(c)
}
type InitializeScanner func(context.Context, string, cache.ArtifactCache, cache.LocalArtifactCache, time.Duration) (
scanner.Scanner, func(), error)
func run(c config.Config) (err error) {
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
func run(c config.Config, initializeScanner InitializeScanner) error {
if err := log.InitLogger(c.Debug, c.Quiet); err != nil {
l.Fatal(err)
}
// initialize config
if err = c.Init(); err != nil {
return xerrors.Errorf("failed to initialize options: %w", err)
}
// configure cache dir
utils.SetCacheDir(c.CacheDir)
cacheClient, err := cache.NewFSCache(c.CacheDir)
@@ -70,32 +60,25 @@ func run(c config.Config) (err error) {
}
defer db.Close()
var scanner scanner.Scanner
ctx := context.Background()
cleanup := func() {}
target := c.Target
if c.Input != "" {
// scan tar file
scanner, err = initializeArchiveScanner(ctx, c.Input, cacheClient, cacheClient, c.Timeout)
if err != nil {
return xerrors.Errorf("unable to initialize the archive scanner: %w", err)
}
} else {
// scan an image in Docker Engine or Docker Registry
scanner, cleanup, err = initializeDockerScanner(ctx, c.ImageName, cacheClient, cacheClient, c.Timeout)
if err != nil {
return xerrors.Errorf("unable to initialize the docker scanner: %w", err)
}
target = c.Input
}
ctx := context.Background()
scanner, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, c.Timeout)
if err != nil {
return xerrors.Errorf("unable to initialize a scanner: %w", err)
}
defer cleanup()
scanOptions := types.ScanOptions{
VulnType: c.VulnType,
ScanRemovedPackages: c.ScanRemovedPkgs,
ScanRemovedPackages: c.ScanRemovedPkgs, // this is valid only for image subcommand
}
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
results, err := scanner.ScanImage(scanOptions)
results, err := scanner.ScanArtifact(ctx, scanOptions)
if err != nil {
return xerrors.Errorf("error in image scan: %w", err)
}

View File

@@ -0,0 +1,96 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package artifact
import (
"context"
"github.com/aquasecurity/fanal/applier"
image2 "github.com/aquasecurity/fanal/artifact/image"
local2 "github.com/aquasecurity/fanal/artifact/local"
"github.com/aquasecurity/fanal/artifact/remote"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/fanal/image"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/detector/library"
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/scanner/local"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/vulnerability"
"time"
)
// Injectors from inject.go:
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applierApplier, detector, libraryDetector)
dockerOption, err := types.GetDockerOption(timeout)
if err != nil {
return scanner.Scanner{}, nil, err
}
imageImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOption)
if err != nil {
return scanner.Scanner{}, nil, err
}
artifact := image2.NewArtifact(imageImage, artifactCache)
scannerScanner := scanner.NewScanner(localScanner, artifact)
return scannerScanner, func() {
cleanup()
}, nil
}
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, timeout time.Duration) (scanner.Scanner, error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applierApplier, detector, libraryDetector)
imageImage, err := image.NewArchiveImage(filePath)
if err != nil {
return scanner.Scanner{}, err
}
artifact := image2.NewArtifact(imageImage, artifactCache)
scannerScanner := scanner.NewScanner(localScanner, artifact)
return scannerScanner, nil
}
func initializeFilesystemScanner(ctx context.Context, dir string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applierApplier, detector, libraryDetector)
artifact := local2.NewArtifact(dir, artifactCache)
scannerScanner := scanner.NewScanner(localScanner, artifact)
return scannerScanner, func() {
}, nil
}
func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applierApplier, detector, libraryDetector)
artifact, cleanup, err := remote.NewArtifact(url, artifactCache)
if err != nil {
return scanner.Scanner{}, nil, err
}
scannerScanner := scanner.NewScanner(localScanner, artifact)
return scannerScanner, func() {
cleanup()
}, nil
}
func initializeVulnerabilityClient() vulnerability.Client {
config := db.Config{}
client := vulnerability.NewClient(config)
return client
}

View File

@@ -12,6 +12,7 @@ import (
type Config struct {
config.GlobalConfig
config.ArtifactConfig
config.ImageConfig
config.ReportConfig
@@ -31,14 +32,14 @@ func New(c *cli.Context) (Config, error) {
}
return Config{
GlobalConfig: gc,
ImageConfig: config.NewImageConfig(c),
ReportConfig: config.NewReportConfig(c),
RemoteAddr: c.String("remote"),
token: c.String("token"),
tokenHeader: c.String("token-header"),
customHeaders: c.StringSlice("custom-headers"),
GlobalConfig: gc,
ArtifactConfig: config.NewArtifactConfig(c),
ImageConfig: config.NewImageConfig(c),
ReportConfig: config.NewReportConfig(c),
RemoteAddr: c.String("remote"),
token: c.String("token"),
tokenHeader: c.String("token-header"),
customHeaders: c.StringSlice("custom-headers"),
}, nil
}
@@ -59,6 +60,10 @@ func (c *Config) Init() (err error) {
return err
}
if err := c.ArtifactConfig.Init(c.Context.Args(), c.Logger); err != nil {
return err
}
if err := c.ImageConfig.Init(c.Context.Args(), c.Logger); err != nil {
cli.ShowAppHelp(c.Context)
return err

View File

@@ -39,8 +39,8 @@ func TestConfig_Init(t *testing.T) {
GlobalConfig: config.GlobalConfig{
Quiet: true,
},
ImageConfig: config.ImageConfig{
ImageName: "alpine:3.10",
ArtifactConfig: config.ArtifactConfig{
Target: "alpine:3.10",
},
ReportConfig: config.ReportConfig{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
@@ -59,8 +59,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "alpine:3.11",
ArtifactConfig: config.ArtifactConfig{
Target: "alpine:3.11",
},
token: "secret",
tokenHeader: "X-Trivy-Token",
@@ -78,8 +78,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "alpine:3.11",
ArtifactConfig: config.ArtifactConfig{
Target: "alpine:3.11",
},
customHeaders: []string{"foo:bar"},
CustomHeaders: http.Header{
@@ -96,8 +96,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "alpine:3.11",
ArtifactConfig: config.ArtifactConfig{
Target: "alpine:3.11",
},
customHeaders: []string{"foobaz"},
CustomHeaders: http.Header{},
@@ -115,8 +115,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "centos:7",
ArtifactConfig: config.ArtifactConfig{
Target: "centos:7",
},
CustomHeaders: http.Header{},
},
@@ -134,8 +134,8 @@ func TestConfig_Init(t *testing.T) {
VulnType: []string{"os", "library"},
Template: "@contrib/gitlab.tpl",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
CustomHeaders: http.Header{},
},
@@ -154,8 +154,8 @@ func TestConfig_Init(t *testing.T) {
Template: "@contrib/gitlab.tpl",
Format: "json",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
CustomHeaders: http.Header{},
},
@@ -173,8 +173,27 @@ func TestConfig_Init(t *testing.T) {
VulnType: []string{"os", "library"},
Format: "template",
},
ImageConfig: config.ImageConfig{
ImageName: "gitlab/gitlab-ce:12.7.2-ce.0",
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
CustomHeaders: http.Header{},
},
},
{
name: "invalid option combination: --format template without --template",
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"--format template is ignored because --template not is specified. Specify --template option when you use --format template.",
},
want: Config{
ReportConfig: config.ReportConfig{
Severities: []dbTypes.Severity{dbTypes.SeverityMedium},
Output: os.Stdout,
VulnType: []string{"os", "library"},
Format: "template",
},
ArtifactConfig: config.ArtifactConfig{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
CustomHeaders: http.Header{},
},
@@ -191,8 +210,8 @@ func TestConfig_Init(t *testing.T) {
Output: os.Stdout,
VulnType: []string{"os", "library"},
},
ImageConfig: config.ImageConfig{
ImageName: "gcr.io/distroless/base",
ArtifactConfig: config.ArtifactConfig{
Target: "gcr.io/distroless/base",
},
CustomHeaders: http.Header{},
},
@@ -201,7 +220,7 @@ func TestConfig_Init(t *testing.T) {
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple images cannot be specified",
"multiple targets cannot be specified",
},
wantErr: "arguments error",
},
@@ -234,11 +253,7 @@ func TestConfig_Init(t *testing.T) {
set.String("format", "", "")
set.String("token", "", "")
set.String("token-header", "", "")
//set.String("custom-headers", "", "")
set.Var(&cli.StringSlice{}, "custom-headers", "")
//cli.StringSliceFlag{
// Name: "custom-headers",
//}
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)

View File

@@ -13,13 +13,13 @@ import (
"github.com/google/wire"
)
func initializeDockerScanner(ctx context.Context, imageName string, layerCache cache.ImageCache, customHeaders client.CustomHeaders,
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders,
url client.RemoteURL, timeout time.Duration) (scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteDockerSet)
return scanner.Scanner{}, nil, nil
}
func initializeArchiveScanner(ctx context.Context, filePath string, layerCache cache.ImageCache, customHeaders client.CustomHeaders,
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders,
url client.RemoteURL, timeout time.Duration) (scanner.Scanner, error) {
wire.Build(scanner.RemoteArchiveSet)
return scanner.Scanner{}, nil

View File

@@ -58,7 +58,7 @@ func run(c config.Config) (err error) {
}
} else {
// scan an image in Docker Engine or Docker Registry
scanner, cleanup, err = initializeDockerScanner(ctx, c.ImageName, remoteCache,
scanner, cleanup, err = initializeDockerScanner(ctx, c.Target, remoteCache,
client.CustomHeaders(c.CustomHeaders), client.RemoteURL(c.RemoteAddr), c.Timeout)
if err != nil {
return xerrors.Errorf("unable to initialize the docker scanner: %w", err)
@@ -72,7 +72,7 @@ func run(c config.Config) (err error) {
}
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
results, err := scanner.ScanImage(scanOptions)
results, err := scanner.ScanArtifact(ctx, scanOptions)
if err != nil {
return xerrors.Errorf("error in image scan: %w", err)
}

View File

@@ -7,9 +7,9 @@ package client
import (
"context"
"github.com/aquasecurity/fanal/analyzer"
image2 "github.com/aquasecurity/fanal/artifact/image"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/fanal/extractor/docker"
"github.com/aquasecurity/fanal/image"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/rpc/client"
"github.com/aquasecurity/trivy/pkg/scanner"
@@ -20,33 +20,33 @@ import (
// Injectors from inject.go:
func initializeDockerScanner(ctx context.Context, imageName string, layerCache cache.ImageCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration) (scanner.Scanner, func(), error) {
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration) (scanner.Scanner, func(), error) {
scannerScanner := client.NewProtobufClient(url)
clientScanner := client.NewScanner(customHeaders, scannerScanner)
dockerOption, err := types.GetDockerOption(timeout)
if err != nil {
return scanner.Scanner{}, nil, err
}
extractor, cleanup, err := docker.NewDockerExtractor(ctx, imageName, dockerOption)
imageImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOption)
if err != nil {
return scanner.Scanner{}, nil, err
}
config := analyzer.New(extractor, layerCache)
scanner2 := scanner.NewScanner(clientScanner, config)
artifact := image2.NewArtifact(imageImage, artifactCache)
scanner2 := scanner.NewScanner(clientScanner, artifact)
return scanner2, func() {
cleanup()
}, nil
}
func initializeArchiveScanner(ctx context.Context, filePath string, layerCache cache.ImageCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration) (scanner.Scanner, error) {
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, timeout time.Duration) (scanner.Scanner, error) {
scannerScanner := client.NewProtobufClient(url)
clientScanner := client.NewScanner(customHeaders, scannerScanner)
extractor, err := docker.NewArchiveImageExtractor(filePath)
imageImage, err := image.NewArchiveImage(filePath)
if err != nil {
return scanner.Scanner{}, err
}
config := analyzer.New(extractor, layerCache)
scanner2 := scanner.NewScanner(clientScanner, config)
artifact := image2.NewArtifact(imageImage, artifactCache)
scanner2 := scanner.NewScanner(clientScanner, artifact)
return scanner2, nil
}

View File

@@ -0,0 +1,42 @@
package config
import (
"time"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/xerrors"
)
type ArtifactConfig struct {
Input string
Timeout time.Duration
ClearCache bool
// 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"),
}
}
func (c *ArtifactConfig) Init(args cli.Args, logger *zap.SugaredLogger) (err error) {
if c.Input == "" && args.Len() == 0 {
logger.Error(`trivy requires at least 1 argument or --input option`)
return xerrors.New("arguments error")
} else if args.Len() > 1 {
logger.Error(`multiple targets cannot be specified`)
return xerrors.New("arguments error")
}
if c.Input == "" {
c.Target = args.First()
}
return nil
}

View File

@@ -0,0 +1,80 @@
package config_test
import (
"flag"
"testing"
"github.com/aquasecurity/trivy/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)
func TestArtifactConfig_Init(t *testing.T) {
tests := []struct {
name string
args []string
logs []string
want config.ArtifactConfig
wantErr string
}{
{
name: "happy path",
args: []string{"alpine:3.10"},
want: config.ArtifactConfig{
Target: "alpine:3.10",
},
},
{
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple targets cannot be specified",
},
wantErr: "arguments error",
},
{
name: "sad: no image name",
logs: []string{
"trivy requires at least 1 argument or --input option",
},
wantErr: "arguments error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
core, obs := observer.New(zap.InfoLevel)
logger := zap.New(core)
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
c := config.NewArtifactConfig(ctx)
err := c.Init(ctx.Args(), logger.Sugar())
// tests log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.logs, gotMessages, tt.name)
// test the error
switch {
case tt.wantErr != "":
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
default:
assert.NoError(t, err, tt.name)
}
assert.Equal(t, tt.want, c, tt.name)
})
}
}

View File

@@ -10,6 +10,7 @@ type DBConfig struct {
DownloadDBOnly bool
SkipUpdate bool
Light bool
NoProgress bool
}
func NewDBConfig(c *cli.Context) DBConfig {
@@ -18,6 +19,7 @@ func NewDBConfig(c *cli.Context) DBConfig {
DownloadDBOnly: c.Bool("download-db-only"),
SkipUpdate: c.Bool("skip-update"),
Light: c.Bool("light"),
NoProgress: c.Bool("no-progress"),
}
}

View File

@@ -1,8 +1,6 @@
package config
import (
"time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
@@ -10,40 +8,21 @@ import (
)
type ImageConfig struct {
Input string
ScanRemovedPkgs bool
Timeout time.Duration
ClearCache bool
// this field is populated in Init()
ImageName string
}
func NewImageConfig(c *cli.Context) ImageConfig {
return ImageConfig{
Input: c.String("input"),
ScanRemovedPkgs: c.Bool("removed-pkgs"),
Timeout: c.Duration("timeout"),
ClearCache: c.Bool("clear-cache"),
}
}
func (c *ImageConfig) Init(args cli.Args, logger *zap.SugaredLogger) (err error) {
if c.Input == "" && args.Len() == 0 {
logger.Error(`trivy requires at least 1 argument or --input option`)
return xerrors.New("arguments error")
} else if args.Len() > 1 {
logger.Error(`multiple images cannot be specified`)
return xerrors.New("arguments error")
}
if c.Input == "" {
c.ImageName = args.First()
}
imageName := args.First()
// Check whether 'latest' tag is used
if c.ImageName != "" {
ref, err := name.ParseReference(c.ImageName)
if imageName != "" {
ref, err := name.ParseReference(imageName)
if err != nil {
return xerrors.Errorf("invalid image: %w", err)
}

View File

@@ -13,51 +13,16 @@ import (
"github.com/aquasecurity/trivy/internal/config"
)
func TestNewImageConfig(t *testing.T) {
tests := []struct {
name string
args []string
want config.ImageConfig
}{
{
name: "happy path",
args: []string{"--clear-cache", "--input", "/tmp/alpine.tar"},
want: config.ImageConfig{
Input: "/tmp/alpine.tar",
ClearCache: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
set.Bool("clear-cache", false, "")
set.String("input", "", "")
c := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
got := config.NewImageConfig(c)
assert.Equal(t, tt.want, got, tt.name)
})
}
}
func TestImageConfig_Init(t *testing.T) {
tests := []struct {
name string
args []string
logs []string
want config.ImageConfig
wantErr string
}{
{
name: "happy path",
args: []string{"alpine:3.10"},
want: config.ImageConfig{
ImageName: "alpine:3.10",
},
},
{
name: "with latest tag",
@@ -65,24 +30,6 @@ func TestImageConfig_Init(t *testing.T) {
logs: []string{
"You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed",
},
want: config.ImageConfig{
ImageName: "gcr.io/distroless/base",
},
},
{
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple images cannot be specified",
},
wantErr: "arguments error",
},
{
name: "sad: no image name",
logs: []string{
"trivy requires at least 1 argument or --input option",
},
wantErr: "arguments error",
},
{
name: "sad: invalid image name",
@@ -100,7 +47,7 @@ func TestImageConfig_Init(t *testing.T) {
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
c := &config.ImageConfig{}
c := config.NewImageConfig(ctx)
err := c.Init(ctx.Args(), logger.Sugar())
@@ -120,8 +67,6 @@ func TestImageConfig_Init(t *testing.T) {
default:
assert.NoError(t, err, tt.name)
}
assert.Equal(t, &tt.want, c, tt.name)
})
}
}

View File

@@ -17,15 +17,15 @@ import (
var SuperSet = wire.NewSet(
cache.NewFSCache,
wire.Bind(new(cache.LocalImageCache), new(cache.FSCache)),
wire.Bind(new(cache.LocalArtifactCache), new(cache.FSCache)),
NewCache,
)
type Cache struct {
client cache.LocalImageCache
client cache.LocalArtifactCache
}
func NewCache(client cache.LocalImageCache) Cache {
func NewCache(client cache.LocalArtifactCache) Cache {
return Cache{client: client}
}

View File

@@ -28,6 +28,7 @@ func TestNew(t *testing.T) {
DBConfig: config.DBConfig{
Reset: true,
SkipUpdate: true,
NoProgress: true,
},
Listen: "localhost:8080",
},

View File

@@ -1,65 +0,0 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package standalone
import (
"context"
"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/fanal/extractor/docker"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/detector/library"
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/scanner/local"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/vulnerability"
"time"
)
// Injectors from inject.go:
func initializeDockerScanner(ctx context.Context, imageName string, layerCache cache.ImageCache, localImageCache cache.LocalImageCache, timeout time.Duration) (scanner.Scanner, func(), error) {
applier := analyzer.NewApplier(localImageCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applier, detector, libraryDetector)
dockerOption, err := types.GetDockerOption(timeout)
if err != nil {
return scanner.Scanner{}, nil, err
}
extractor, cleanup, err := docker.NewDockerExtractor(ctx, imageName, dockerOption)
if err != nil {
return scanner.Scanner{}, nil, err
}
config := analyzer.New(extractor, layerCache)
scannerScanner := scanner.NewScanner(localScanner, config)
return scannerScanner, func() {
cleanup()
}, nil
}
func initializeArchiveScanner(ctx context.Context, filePath string, layerCache cache.ImageCache, localImageCache cache.LocalImageCache, timeout time.Duration) (scanner.Scanner, error) {
applier := analyzer.NewApplier(localImageCache)
detector := ospkg.Detector{}
driverFactory := library.DriverFactory{}
libraryDetector := library.NewDetector(driverFactory)
localScanner := local.NewScanner(applier, detector, libraryDetector)
extractor, err := docker.NewArchiveImageExtractor(filePath)
if err != nil {
return scanner.Scanner{}, err
}
config := analyzer.New(extractor, layerCache)
scannerScanner := scanner.NewScanner(localScanner, config)
return scannerScanner, nil
}
func initializeVulnerabilityClient() vulnerability.Client {
config := db.Config{}
client := vulnerability.NewClient(config)
return client
}