mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 07:10:41 -08:00
feat(filesystem): scan in client/server mode (#1829)
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
@@ -31,5 +31,9 @@ OPTIONS:
|
||||
--config-policy value specify paths to the Rego policy files directory, applying config files [$TRIVY_CONFIG_POLICY]
|
||||
--config-data value specify paths from which data for the Rego policies will be recursively loaded [$TRIVY_CONFIG_DATA]
|
||||
--policy-namespaces value, --namespaces value Rego namespaces (default: "users") [$TRIVY_POLICY_NAMESPACES]
|
||||
--server value server address [$TRIVY_SERVER]
|
||||
--token value for authentication [$TRIVY_TOKEN]
|
||||
--token-header value specify a header name for token (default: "Trivy-Token") [$TRIVY_TOKEN_HEADER]
|
||||
--custom-headers value custom headers [$TRIVY_CUSTOM_HEADERS]
|
||||
--help, -h show help (default: false)
|
||||
```
|
||||
@@ -6,7 +6,8 @@ Scan a local project including language-specific files.
|
||||
$ trivy fs /path/to/project
|
||||
```
|
||||
|
||||
## Local Project
|
||||
## Standalone mode
|
||||
### Local Project
|
||||
Trivy will look for vulnerabilities based on lock files such as Gemfile.lock and package-lock.json.
|
||||
|
||||
```
|
||||
@@ -54,3 +55,49 @@ It's also possible to scan a single file.
|
||||
```
|
||||
$ trivy fs ~/src/github.com/aquasecurity/trivy-ci-test/Pipfile.lock
|
||||
```
|
||||
|
||||
## Client/Server mode
|
||||
You must launch Trivy server in advance.
|
||||
|
||||
```sh
|
||||
$ trivy server
|
||||
```
|
||||
|
||||
Then, Trivy works as a client if you specify the `--server` option.
|
||||
|
||||
```sh
|
||||
$ trivy fs --server http://localhost:4954 --severity CRITICAL ./integration/testdata/fixtures/fs/pom/
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Result</summary>
|
||||
|
||||
```
|
||||
pom.xml (pom)
|
||||
=============
|
||||
Total: 4 (CRITICAL: 4)
|
||||
|
||||
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
|
||||
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
|
||||
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
|
||||
| com.fasterxml.jackson.core:jackson-databind | CVE-2017-17485 | CRITICAL | 2.9.1 | 2.8.11, 2.9.4 | jackson-databind: Unsafe |
|
||||
| | | | | | deserialization due to |
|
||||
| | | | | | incomplete black list (incomplete |
|
||||
| | | | | | fix for CVE-2017-15095)... |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2017-17485 |
|
||||
+ +------------------+ + +--------------------------------+---------------------------------------+
|
||||
| | CVE-2020-9546 | | | 2.7.9.7, 2.8.11.6, 2.9.10.4 | jackson-databind: Serialization |
|
||||
| | | | | | gadgets in shaded-hikari-config |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9546 |
|
||||
+ +------------------+ + + +---------------------------------------+
|
||||
| | CVE-2020-9547 | | | | jackson-databind: Serialization |
|
||||
| | | | | | gadgets in ibatis-sqlmap |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9547 |
|
||||
+ +------------------+ + + +---------------------------------------+
|
||||
| | CVE-2020-9548 | | | | jackson-databind: Serialization |
|
||||
| | | | | | gadgets in anteros-core |
|
||||
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9548 |
|
||||
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
)
|
||||
|
||||
type csArgs struct {
|
||||
Command string
|
||||
RemoteAddrOption string
|
||||
Format string
|
||||
TemplatePath string
|
||||
IgnoreUnfixed bool
|
||||
@@ -35,6 +37,7 @@ type csArgs struct {
|
||||
ClientToken string
|
||||
ClientTokenHeader string
|
||||
ListAllPackages bool
|
||||
Target string
|
||||
}
|
||||
|
||||
func TestClientServer(t *testing.T) {
|
||||
@@ -220,6 +223,15 @@ func TestClientServer(t *testing.T) {
|
||||
},
|
||||
golden: "testdata/busybox-with-lockfile.json.golden",
|
||||
},
|
||||
{
|
||||
name: "scan pox.xml with fs command in client/server mode",
|
||||
args: csArgs{
|
||||
Command: "fs",
|
||||
RemoteAddrOption: "--server",
|
||||
Target: "testdata/fixtures/fs/pom/",
|
||||
},
|
||||
golden: "testdata/pom.json.golden",
|
||||
},
|
||||
}
|
||||
|
||||
app, addr, cacheDir := setup(t, setupOptions{})
|
||||
@@ -525,8 +537,14 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri
|
||||
}
|
||||
|
||||
func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) ([]string, string) {
|
||||
if c.Command == "" {
|
||||
c.Command = "client"
|
||||
}
|
||||
if c.RemoteAddrOption == "" {
|
||||
c.RemoteAddrOption = "--remote"
|
||||
}
|
||||
t.Helper()
|
||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "client", "--remote", "http://" + addr}
|
||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
|
||||
|
||||
if c.Format != "" {
|
||||
osArgs = append(osArgs, "--format", c.Format)
|
||||
@@ -567,6 +585,10 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden st
|
||||
|
||||
osArgs = append(osArgs, "--output", outputFile)
|
||||
|
||||
if c.Target != "" {
|
||||
osArgs = append(osArgs, c.Target)
|
||||
}
|
||||
|
||||
return osArgs, outputFile
|
||||
}
|
||||
|
||||
|
||||
16
pkg/cache/nop.go
vendored
Normal file
16
pkg/cache/nop.go
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package cache
|
||||
|
||||
import "github.com/aquasecurity/fanal/cache"
|
||||
|
||||
func NopCache(ac cache.ArtifactCache) cache.Cache {
|
||||
return nopCache{ArtifactCache: ac}
|
||||
}
|
||||
|
||||
type nopCache struct {
|
||||
cache.ArtifactCache
|
||||
cache.LocalArtifactCache
|
||||
}
|
||||
|
||||
func (nopCache) Close() error {
|
||||
return nil
|
||||
}
|
||||
1
pkg/cache/remote.go
vendored
1
pkg/cache/remote.go
vendored
@@ -31,7 +31,6 @@ func NewRemoteCache(url string, customHeaders http.Header, insecure bool) cache.
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := rpcCache.NewCacheProtobufClient(url, httpClient)
|
||||
return &RemoteCache{ctx: ctx, client: c}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/client"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/plugin"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
@@ -210,14 +209,14 @@ var (
|
||||
|
||||
token = cli.StringFlag{
|
||||
Name: "token",
|
||||
Usage: "for authentication",
|
||||
Usage: "for authentication in client/server mode",
|
||||
EnvVars: []string{"TRIVY_TOKEN"},
|
||||
}
|
||||
|
||||
tokenHeader = cli.StringFlag{
|
||||
Name: "token-header",
|
||||
Value: "Trivy-Token",
|
||||
Usage: "specify a header name for token",
|
||||
Usage: "specify a header name for token in client/server mode",
|
||||
EnvVars: []string{"TRIVY_TOKEN_HEADER"},
|
||||
}
|
||||
|
||||
@@ -313,6 +312,18 @@ var (
|
||||
EnvVars: []string{"TRIVY_INSECURE"},
|
||||
}
|
||||
|
||||
remoteServer = cli.StringFlag{
|
||||
Name: "server",
|
||||
Usage: "server address",
|
||||
EnvVars: []string{"TRIVY_SERVER"},
|
||||
}
|
||||
|
||||
customHeaders = cli.StringSliceFlag{
|
||||
Name: "custom-headers",
|
||||
Usage: "custom headers in client/server mode",
|
||||
EnvVars: []string{"TRIVY_CUSTOM_HEADERS"},
|
||||
}
|
||||
|
||||
// Global flags
|
||||
globalFlags = []cli.Flag{
|
||||
&quietFlag,
|
||||
@@ -473,9 +484,17 @@ func NewFilesystemCommand() *cli.Command {
|
||||
&offlineScan,
|
||||
stringSliceFlag(skipFiles),
|
||||
stringSliceFlag(skipDirs),
|
||||
|
||||
// for misconfiguration
|
||||
stringSliceFlag(configPolicy),
|
||||
stringSliceFlag(configData),
|
||||
stringSliceFlag(policyNamespaces),
|
||||
|
||||
// for client/server
|
||||
&remoteServer,
|
||||
&token,
|
||||
&tokenHeader,
|
||||
&customHeaders,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -565,7 +584,7 @@ func NewClientCommand() *cli.Command {
|
||||
Aliases: []string{"c"},
|
||||
ArgsUsage: "image_name",
|
||||
Usage: "client mode",
|
||||
Action: client.Run,
|
||||
Action: artifact.ImageRun,
|
||||
Flags: []cli.Flag{
|
||||
&templateFlag,
|
||||
&formatFlag,
|
||||
@@ -589,20 +608,17 @@ func NewClientCommand() *cli.Command {
|
||||
&offlineScan,
|
||||
&insecureFlag,
|
||||
|
||||
// original flags
|
||||
&token,
|
||||
&tokenHeader,
|
||||
&customHeaders,
|
||||
|
||||
// original flags
|
||||
&cli.StringFlag{
|
||||
Name: "remote",
|
||||
Value: "http://localhost:4954",
|
||||
Usage: "server address",
|
||||
EnvVars: []string{"TRIVY_REMOTE"},
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "custom-headers",
|
||||
Usage: "custom headers",
|
||||
EnvVars: []string{"TRIVY_CUSTOM_HEADERS"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ func ConfigRun(ctx *cli.Context) error {
|
||||
opt.SkipDBUpdate = true
|
||||
|
||||
// Run filesystem command internally
|
||||
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
|
||||
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
|
||||
}
|
||||
|
||||
@@ -7,15 +7,23 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
)
|
||||
|
||||
func filesystemScanner(ctx context.Context, path string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeFilesystemScanner(ctx, path, ac, lac, artifactOpt, scannerOpt)
|
||||
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
|
||||
func filesystemStandaloneScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
return s, cleanup, nil
|
||||
}
|
||||
|
||||
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
|
||||
func filesystemRemoteScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
@@ -32,7 +40,13 @@ func FilesystemRun(ctx *cli.Context) error {
|
||||
// Disable the individual package scanning
|
||||
opt.DisabledAnalyzers = analyzer.TypeIndividualPkgs
|
||||
|
||||
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
|
||||
// client/server mode
|
||||
if opt.RemoteAddr != "" {
|
||||
return Run(ctx.Context, opt, filesystemRemoteScanner, initCache)
|
||||
}
|
||||
|
||||
// standalone mode
|
||||
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
|
||||
}
|
||||
|
||||
// RootfsRun runs scan on rootfs.
|
||||
@@ -45,5 +59,11 @@ func RootfsRun(ctx *cli.Context) error {
|
||||
// Disable the lock file scanning
|
||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
||||
|
||||
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
|
||||
// client/server mode
|
||||
if opt.RemoteAddr != "" {
|
||||
return Run(ctx.Context, opt, filesystemRemoteScanner, initCache)
|
||||
}
|
||||
|
||||
// standalone mode
|
||||
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
|
||||
}
|
||||
|
||||
@@ -7,36 +7,67 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
s, err := initializeArchiveScanner(ctx, input, ac, lac, artifactOpt, scannerOpt)
|
||||
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,
|
||||
insecure bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
dockerOpt, err := types.GetDockerOption(insecure)
|
||||
// imageScanner initializes a container image scanner in standalone mode
|
||||
// $ trivy image alpine:3.15
|
||||
func imageScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
s, cleanup, err := initializeDockerScanner(ctx, imageName, ac, lac, dockerOpt, artifactOpt, scannerOpt)
|
||||
s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
|
||||
}
|
||||
return s, cleanup, nil
|
||||
}
|
||||
|
||||
// ImageRun runs scan on docker image
|
||||
// archiveScanner initializes an image archive scanner in standalone mode
|
||||
// $ trivy image --input alpine.tar
|
||||
func archiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
|
||||
}
|
||||
return s, func() {}, nil
|
||||
}
|
||||
|
||||
// remoteImageScanner initializes a container image scanner in client/server mode
|
||||
// $ trivy image --server localhost:4954 alpine:3.15
|
||||
func remoteImageScanner(ctx context.Context, conf scannerConfig) (
|
||||
scanner.Scanner, func(), error) {
|
||||
// Scan an image in Docker Engine, Docker Registry, etc.
|
||||
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
|
||||
s, cleanup, err := initializeRemoteDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the docker scanner: %w", err)
|
||||
}
|
||||
return s, cleanup, nil
|
||||
}
|
||||
|
||||
// remoteArchiveScanner initializes an image archive scanner in client/server mode
|
||||
// $ trivy image --server localhost:4954 --input alpine.tar
|
||||
func remoteArchiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
// Scan tar file
|
||||
s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
|
||||
}
|
||||
return s, func() {}, nil
|
||||
}
|
||||
|
||||
// ImageRun runs scan on container image
|
||||
func ImageRun(ctx *cli.Context) error {
|
||||
opt, err := initOption(ctx)
|
||||
if err != nil {
|
||||
@@ -47,9 +78,34 @@ func ImageRun(ctx *cli.Context) error {
|
||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
||||
|
||||
if opt.Input != "" {
|
||||
// scan tar file
|
||||
return Run(ctx.Context, opt, archiveScanner, initFSCache)
|
||||
return archiveImageRun(ctx.Context, opt)
|
||||
}
|
||||
|
||||
return Run(ctx.Context, opt, dockerScanner, initFSCache)
|
||||
return imageRun(ctx.Context, opt)
|
||||
}
|
||||
|
||||
func archiveImageRun(ctx context.Context, opt Option) error {
|
||||
// standalone mode
|
||||
scanner := archiveScanner
|
||||
|
||||
if opt.RemoteAddr != "" {
|
||||
// client/server mode
|
||||
scanner = remoteArchiveScanner
|
||||
}
|
||||
|
||||
// scan tar file
|
||||
return Run(ctx, opt, scanner, initCache)
|
||||
}
|
||||
|
||||
func imageRun(ctx context.Context, opt Option) error {
|
||||
// standalone mode
|
||||
scanner := imageScanner
|
||||
|
||||
if opt.RemoteAddr != "" {
|
||||
// client/server mode
|
||||
scanner = remoteImageScanner
|
||||
}
|
||||
|
||||
// scan container image
|
||||
return Run(ctx, opt, scanner, initCache)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,16 @@ import (
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
)
|
||||
|
||||
//////////////
|
||||
// Standalone
|
||||
//////////////
|
||||
|
||||
// initializeDockerScanner is for container image scanning in standalone mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
@@ -23,6 +30,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
// initializeArchiveScanner is for container image archive scanning in standalone mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
@@ -30,6 +39,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
||||
return scanner.Scanner{}, nil
|
||||
}
|
||||
|
||||
// initializeFilesystemScanner is for filesystem scanning in standalone mode
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
@@ -48,3 +58,39 @@ func initializeResultClient() result.Client {
|
||||
wire.Build(result.SuperSet)
|
||||
return result.Client{}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Client/Server
|
||||
/////////////////
|
||||
|
||||
// initializeRemoteDockerScanner is for container image scanning in client/server mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.RemoteDockerSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, error) {
|
||||
wire.Build(scanner.RemoteArchiveSet)
|
||||
return scanner.Scanner{}, nil
|
||||
}
|
||||
|
||||
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
|
||||
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
||||
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.RemoteFilesystemSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
func initializeRemoteResultClient() result.Client {
|
||||
wire.Build(result.SuperSet)
|
||||
return result.Client{}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Option struct {
|
||||
option.ReportOption
|
||||
option.CacheOption
|
||||
option.ConfigOption
|
||||
option.RemoteOption
|
||||
|
||||
// We don't want to allow disabled analyzers to be passed by users,
|
||||
// but it differs depending on scanning modes.
|
||||
@@ -38,6 +39,7 @@ func NewOption(c *cli.Context) (Option, error) {
|
||||
ReportOption: option.NewReportOption(c),
|
||||
CacheOption: option.NewCacheOption(c),
|
||||
ConfigOption: option.NewConfigOption(c),
|
||||
RemoteOption: option.NewRemoteOption(c),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -55,7 +57,6 @@ func (c *Option) Init() error {
|
||||
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,6 +70,7 @@ func (c *Option) initPreScanOptions() error {
|
||||
if err := c.CacheOption.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.RemoteOption.Init(c.Logger)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package artifact
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -60,6 +61,81 @@ func TestOption_Init(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with token and token header",
|
||||
args: []string{"--server", "http://localhost:8080", "--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
RemoteOption: option.RemoteOption{
|
||||
RemoteAddr: "http://localhost:8080",
|
||||
CustomHeaders: http.Header{
|
||||
"X-Trivy-Token": []string{"secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid option combination: token and token header without server",
|
||||
args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
|
||||
logs: []string{
|
||||
"'--token', '--token-header' and 'custom-header' can be used only with '--server'",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with good custom headers",
|
||||
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foo:bar", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
RemoteOption: option.RemoteOption{
|
||||
RemoteAddr: "http://localhost:8080",
|
||||
CustomHeaders: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with bad custom headers",
|
||||
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foobaz", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
RemoteOption: option.RemoteOption{RemoteAddr: "http://localhost:8080", CustomHeaders: http.Header{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path: reset",
|
||||
args: []string{"--reset"},
|
||||
@@ -182,6 +258,10 @@ func TestOption_Init(t *testing.T) {
|
||||
set.String("security-checks", "vuln", "")
|
||||
set.String("template", "", "")
|
||||
set.String("format", "", "")
|
||||
set.String("server", "", "")
|
||||
set.String("token", "", "")
|
||||
set.String("token-header", "", "")
|
||||
set.Var(&cli.StringSlice{}, "custom-headers", "")
|
||||
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
@@ -7,16 +7,14 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac, artifactOpt, scannerOpt)
|
||||
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
|
||||
func repositoryScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||
conf.ArtifactOption, conf.MisconfOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
||||
}
|
||||
@@ -36,5 +34,5 @@ func RepositoryRun(ctx *cli.Context) error {
|
||||
// Disable the OS analyzers and individual package analyzers
|
||||
opt.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
|
||||
|
||||
return Run(ctx.Context, opt, repositoryScanner, initFSCache)
|
||||
return Run(ctx.Context, opt, repositoryScanner, initCache)
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ import (
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
tcache "github.com/aquasecurity/trivy/pkg/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
@@ -25,9 +27,26 @@ const defaultPolicyNamespace = "appshield"
|
||||
|
||||
var errSkipScan = errors.New("skip subsequent processes")
|
||||
|
||||
type scannerConfig struct {
|
||||
// e.g. image name and file path
|
||||
Target string
|
||||
|
||||
// Cache
|
||||
ArtifactCache cache.ArtifactCache
|
||||
LocalArtifactCache cache.LocalArtifactCache
|
||||
|
||||
// Client/Server options
|
||||
RemoteOption client.ScannerOption
|
||||
|
||||
// Artifact options
|
||||
ArtifactOption artifact.Option
|
||||
|
||||
// Misconfiguration scanning options
|
||||
MisconfOption config.ScannerOption
|
||||
}
|
||||
|
||||
// InitializeScanner defines the initialize function signature of scanner
|
||||
type InitializeScanner func(context.Context, string, cache.ArtifactCache, cache.LocalArtifactCache, bool,
|
||||
artifact.Option, config.ScannerOption) (scanner.Scanner, func(), error)
|
||||
type InitializeScanner func(context.Context, scannerConfig) (scanner.Scanner, func(), error)
|
||||
|
||||
// InitCache defines cache initializer
|
||||
type InitCache func(c Option) (cache.Cache, error)
|
||||
@@ -37,7 +56,11 @@ func Run(ctx context.Context, opt Option, initializeScanner InitializeScanner, i
|
||||
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
|
||||
defer cancel()
|
||||
|
||||
return runWithTimeout(ctx, opt, initializeScanner, initCache)
|
||||
err := runWithTimeout(ctx, opt, initializeScanner, initCache)
|
||||
if xerrors.Is(err, context.DeadlineExceeded) {
|
||||
log.Logger.Warn("Increase --timeout value")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func runWithTimeout(ctx context.Context, opt Option, initializeScanner InitializeScanner, initCache InitCache) error {
|
||||
@@ -54,8 +77,8 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
|
||||
}
|
||||
defer cacheClient.Close()
|
||||
|
||||
// When scanning config files, it doesn't need to download the vulnerability database.
|
||||
if utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
|
||||
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
|
||||
if opt.RemoteAddr == "" && utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
|
||||
if err = initDB(opt); err != nil {
|
||||
if errors.Is(err, errSkipScan) {
|
||||
return nil
|
||||
@@ -92,7 +115,14 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
|
||||
return nil
|
||||
}
|
||||
|
||||
func initFSCache(c Option) (cache.Cache, error) {
|
||||
func initCache(c Option) (cache.Cache, error) {
|
||||
// client/server mode
|
||||
if c.RemoteAddr != "" {
|
||||
remoteCache := tcache.NewRemoteCache(c.RemoteAddr, c.CustomHeaders, c.Insecure)
|
||||
return tcache.NopCache(remoteCache), nil
|
||||
}
|
||||
|
||||
// standalone mode
|
||||
utils.SetCacheDir(c.CacheDir)
|
||||
cache, err := operation.NewCache(c.CacheOption)
|
||||
if err != nil {
|
||||
@@ -181,7 +211,7 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
||||
}
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
// ScannerOptions is filled only when config scanning is enabled.
|
||||
// ScannerOption is filled only when config scanning is enabled.
|
||||
var configScannerOptions config.ScannerOption
|
||||
if utils.StringInSlice(types.SecurityCheckConfig, opt.SecurityChecks) {
|
||||
noProgress := opt.Quiet || opt.NoProgress
|
||||
@@ -199,16 +229,25 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
||||
}
|
||||
}
|
||||
|
||||
artifactOpt := artifact.Option{
|
||||
DisabledAnalyzers: disabledAnalyzers(opt),
|
||||
SkipFiles: opt.SkipFiles,
|
||||
SkipDirs: opt.SkipDirs,
|
||||
InsecureSkipTLS: opt.Insecure,
|
||||
Offline: opt.OfflineScan,
|
||||
NoProgress: opt.NoProgress || opt.Quiet,
|
||||
}
|
||||
|
||||
s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Insecure, artifactOpt, configScannerOptions)
|
||||
s, cleanup, err := initializeScanner(ctx, scannerConfig{
|
||||
Target: target,
|
||||
ArtifactCache: cacheClient,
|
||||
LocalArtifactCache: cacheClient,
|
||||
RemoteOption: client.ScannerOption{
|
||||
RemoteURL: opt.RemoteAddr,
|
||||
CustomHeaders: opt.CustomHeaders,
|
||||
Insecure: opt.Insecure,
|
||||
},
|
||||
ArtifactOption: artifact.Option{
|
||||
DisabledAnalyzers: disabledAnalyzers(opt),
|
||||
SkipFiles: opt.SkipFiles,
|
||||
SkipDirs: opt.SkipDirs,
|
||||
InsecureSkipTLS: opt.Insecure,
|
||||
Offline: opt.OfflineScan,
|
||||
NoProgress: opt.NoProgress || opt.Quiet,
|
||||
},
|
||||
MisconfOption: configScannerOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
|
||||
}
|
||||
@@ -225,7 +264,10 @@ func filter(ctx context.Context, opt Option, report types.Report) (types.Report,
|
||||
resultClient := initializeResultClient()
|
||||
results := report.Results
|
||||
for i := range results {
|
||||
resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
|
||||
// Fill vulnerability info only in standalone mode
|
||||
if opt.RemoteAddr == "" {
|
||||
resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
|
||||
}
|
||||
vulns, misconfSummary, misconfs, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations,
|
||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,12 +20,15 @@ import (
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
// initializeDockerScanner is for container image scanning in standalone mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
@@ -45,6 +48,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
||||
}, nil
|
||||
}
|
||||
|
||||
// initializeArchiveScanner is for container image archive scanning in standalone mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
@@ -61,6 +66,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
||||
return scannerScanner, nil
|
||||
}
|
||||
|
||||
// initializeFilesystemScanner is for filesystem scanning in standalone mode
|
||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
applierApplier := applier.NewApplier(localArtifactCache)
|
||||
detector := ospkg.Detector{}
|
||||
@@ -93,3 +99,56 @@ func initializeResultClient() result.Client {
|
||||
client := result.NewClient(dbConfig)
|
||||
return client
|
||||
}
|
||||
|
||||
// initializeRemoteDockerScanner is for container image scanning in client/server mode
|
||||
// e.g. dockerd, container registry, podman, etc.
|
||||
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
typesImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOpt)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
|
||||
return scannerScanner, func() {
|
||||
cleanup()
|
||||
}, nil
|
||||
}
|
||||
|
||||
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
|
||||
// e.g. docker save -o alpine.tar alpine:3.15
|
||||
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
typesImage, err := image.NewArchiveImage(filePath)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
|
||||
return scannerScanner, nil
|
||||
}
|
||||
|
||||
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
|
||||
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
clientScanner := client.NewScanner(remoteScanOptions)
|
||||
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption, configScannerOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
|
||||
return scannerScanner, func() {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initializeRemoteResultClient() result.Client {
|
||||
dbConfig := db.Config{}
|
||||
resultClient := result.NewClient(dbConfig)
|
||||
return resultClient
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
)
|
||||
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders,
|
||||
url client.RemoteURL, insecure client.Insecure, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
|
||||
scanner.Scanner, func(), error) {
|
||||
wire.Build(scanner.RemoteDockerSet)
|
||||
return scanner.Scanner{}, nil, nil
|
||||
}
|
||||
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||
customHeaders client.CustomHeaders, url client.RemoteURL, insecure client.Insecure, artifactOption artifact.Option,
|
||||
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
wire.Build(scanner.RemoteArchiveSet)
|
||||
return scanner.Scanner{}, nil
|
||||
}
|
||||
|
||||
func initializeResultClient() result.Client {
|
||||
wire.Build(result.SuperSet)
|
||||
return result.Client{}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||
)
|
||||
|
||||
// Option holds the Trivy client options
|
||||
type Option struct {
|
||||
option.GlobalOption
|
||||
option.ArtifactOption
|
||||
option.ImageOption
|
||||
option.ReportOption
|
||||
option.ConfigOption
|
||||
|
||||
// For policy downloading
|
||||
NoProgress bool
|
||||
|
||||
// We don't want to allow disabled analyzers to be passed by users,
|
||||
// but it differs depending on scanning modes.
|
||||
DisabledAnalyzers []analyzer.Type
|
||||
|
||||
RemoteAddr string
|
||||
token string
|
||||
tokenHeader string
|
||||
customHeaders []string
|
||||
// this field is populated in Init()
|
||||
CustomHeaders http.Header
|
||||
}
|
||||
|
||||
// NewOption is the factory method for Option
|
||||
func NewOption(c *cli.Context) (Option, error) {
|
||||
gc, err := option.NewGlobalOption(c)
|
||||
if err != nil {
|
||||
return Option{}, xerrors.Errorf("failed to initialize global options: %w", err)
|
||||
}
|
||||
|
||||
return Option{
|
||||
GlobalOption: gc,
|
||||
ArtifactOption: option.NewArtifactOption(c),
|
||||
ImageOption: option.NewImageOption(c),
|
||||
ReportOption: option.NewReportOption(c),
|
||||
ConfigOption: option.NewConfigOption(c),
|
||||
NoProgress: c.Bool("no-progress"),
|
||||
RemoteAddr: c.String("remote"),
|
||||
token: c.String("token"),
|
||||
tokenHeader: c.String("token-header"),
|
||||
customHeaders: c.StringSlice("custom-headers"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Init initializes the options
|
||||
func (c *Option) Init() (err error) {
|
||||
// --clear-cache doesn't conduct the scan
|
||||
if c.ClearCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.CustomHeaders = splitCustomHeaders(c.customHeaders)
|
||||
|
||||
// add token to custom headers
|
||||
if c.token != "" {
|
||||
c.CustomHeaders.Set(c.tokenHeader, c.token)
|
||||
}
|
||||
|
||||
if err = c.ReportOption.Init(c.Context.App.Writer, c.Logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitCustomHeaders(headers []string) http.Header {
|
||||
result := make(http.Header)
|
||||
for _, header := range headers {
|
||||
// e.g. x-api-token:XXX
|
||||
s := strings.SplitN(header, ":", 2)
|
||||
if len(s) != 2 {
|
||||
continue
|
||||
}
|
||||
result.Set(s[0], s[1])
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/option"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
func TestConfig_Init(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
logs []string
|
||||
want Option
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: []string{"--severity", "CRITICAL", "--vuln-type", "os", "--quiet", "alpine:3.10"},
|
||||
want: Option{
|
||||
GlobalOption: option.GlobalOption{
|
||||
Quiet: true,
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.10",
|
||||
},
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
VulnType: []string{types.VulnTypeOS},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
Output: os.Stdout,
|
||||
},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config scanning",
|
||||
args: []string{"--severity", "CRITICAL", "--security-checks", "config", "--quiet", "alpine:3.10"},
|
||||
want: Option{
|
||||
GlobalOption: option.GlobalOption{
|
||||
Quiet: true,
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.10",
|
||||
},
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckConfig},
|
||||
Output: os.Stdout,
|
||||
},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with token and token header",
|
||||
args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
token: "secret",
|
||||
tokenHeader: "X-Trivy-Token",
|
||||
CustomHeaders: http.Header{
|
||||
"X-Trivy-Token": []string{"secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with good custom headers",
|
||||
args: []string{"--custom-headers", "foo:bar", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
customHeaders: []string{"foo:bar"},
|
||||
CustomHeaders: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with bad custom headers",
|
||||
args: []string{"--custom-headers", "foobaz", "alpine:3.11"},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "alpine:3.11",
|
||||
},
|
||||
customHeaders: []string{"foobaz"},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with an unknown severity",
|
||||
args: []string{"--severity", "CRITICAL,INVALID", "centos:7"},
|
||||
logs: []string{
|
||||
"unknown severity option: unknown severity: INVALID",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "centos:7",
|
||||
},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid option combination: --template enabled without --format",
|
||||
args: []string{"--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
||||
logs: []string{
|
||||
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
Template: "@contrib/gitlab.tpl",
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
||||
},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid option combination: --template and --format json",
|
||||
args: []string{"--format", "json", "--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
|
||||
logs: []string{
|
||||
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
Template: "@contrib/gitlab.tpl",
|
||||
Format: "json",
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
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' is not specified. Specify '--template' option when you use '--format template'.",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityMedium},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
Format: "template",
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
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' is not specified. Specify '--template' option when you use '--format template'.",
|
||||
},
|
||||
want: Option{
|
||||
ReportOption: option.ReportOption{
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityMedium},
|
||||
Output: os.Stdout,
|
||||
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
|
||||
SecurityChecks: []string{types.SecurityCheckVulnerability},
|
||||
Format: "template",
|
||||
},
|
||||
ArtifactOption: option.ArtifactOption{
|
||||
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
|
||||
},
|
||||
CustomHeaders: http.Header{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad: multiple image names",
|
||||
args: []string{"centos:7", "alpine:3.10"},
|
||||
logs: []string{
|
||||
"multiple targets cannot be specified",
|
||||
},
|
||||
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)
|
||||
set.Bool("quiet", false, "")
|
||||
set.Bool("no-progress", false, "")
|
||||
set.Bool("clear-cache", false, "")
|
||||
set.String("severity", "CRITICAL", "")
|
||||
set.String("vuln-type", "os,library", "")
|
||||
set.String("security-checks", "vuln", "")
|
||||
set.String("template", "", "")
|
||||
set.String("format", "", "")
|
||||
set.String("token", "", "")
|
||||
set.String("token-header", "", "")
|
||||
set.Var(&cli.StringSlice{}, "custom-headers", "")
|
||||
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
c, err := NewOption(ctx)
|
||||
require.NoError(t, err, err)
|
||||
|
||||
c.GlobalOption.Logger = logger.Sugar()
|
||||
err = c.Init()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
tt.want.GlobalOption.Context = ctx
|
||||
tt.want.GlobalOption.Logger = logger.Sugar()
|
||||
assert.Equal(t, tt.want, c, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_splitCustomHeaders(t *testing.T) {
|
||||
type args struct {
|
||||
headers []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want http.Header
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
headers: []string{"x-api-token:foo bar", "Authorization:user:password"},
|
||||
},
|
||||
want: http.Header{
|
||||
"X-Api-Token": []string{"foo bar"},
|
||||
"Authorization": []string{"user:password"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := splitCustomHeaders(tt.args.headers)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/fanal/analyzer"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/cache"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/operation"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
const defaultPolicyNamespace = "appshield"
|
||||
|
||||
// Run runs the scan
|
||||
func Run(cliCtx *cli.Context) error {
|
||||
opt, err := NewOption(cliCtx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("option error: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(cliCtx.Context, opt.Timeout)
|
||||
defer cancel()
|
||||
|
||||
// Disable the lock file scanning
|
||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
||||
|
||||
err = runWithTimeout(ctx, opt)
|
||||
if xerrors.Is(err, context.DeadlineExceeded) {
|
||||
log.Logger.Warn("Increase --timeout value")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func runWithTimeout(ctx context.Context, opt Option) error {
|
||||
if err := initialize(&opt); err != nil {
|
||||
return xerrors.Errorf("initialize error: %w", err)
|
||||
}
|
||||
|
||||
if opt.ClearCache {
|
||||
log.Logger.Warn("A client doesn't have image cache")
|
||||
return nil
|
||||
}
|
||||
|
||||
s, cleanup, err := initializeScanner(ctx, opt)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("scanner initialize error: %w", err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
scanOptions := types.ScanOptions{
|
||||
VulnType: opt.VulnType,
|
||||
SecurityChecks: opt.SecurityChecks,
|
||||
ScanRemovedPackages: opt.ScanRemovedPkgs,
|
||||
ListAllPackages: opt.ListAllPkgs,
|
||||
}
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
report, err := s.ScanArtifact(ctx, scanOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
resultClient := initializeResultClient()
|
||||
results := report.Results
|
||||
for i := range results {
|
||||
vulns, misconfSummary, misconfs, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations,
|
||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("filter error: %w", err)
|
||||
}
|
||||
results[i].Vulnerabilities = vulns
|
||||
results[i].Misconfigurations = misconfs
|
||||
results[i].MisconfSummary = misconfSummary
|
||||
}
|
||||
|
||||
if err = pkgReport.Write(report, pkgReport.Option{
|
||||
AppVersion: opt.GlobalOption.AppVersion,
|
||||
Format: opt.Format,
|
||||
Output: opt.Output,
|
||||
Severities: opt.Severities,
|
||||
OutputTemplate: opt.Template,
|
||||
IncludeNonFailures: opt.IncludeNonFailures,
|
||||
Trace: opt.Trace,
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
exit(opt, results)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initialize(opt *Option) error {
|
||||
// Initialize logger
|
||||
if err := log.InitLogger(opt.Debug, opt.Quiet); err != nil {
|
||||
return xerrors.Errorf("failed to initialize a logger: %w", err)
|
||||
}
|
||||
|
||||
// Initialize options
|
||||
if err := opt.Init(); err != nil {
|
||||
return xerrors.Errorf("failed to initialize options: %w", err)
|
||||
}
|
||||
|
||||
// configure cache dir
|
||||
utils.SetCacheDir(opt.CacheDir)
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func disabledAnalyzers(opt Option) []analyzer.Type {
|
||||
// Specified analyzers to be disabled depending on scanning modes
|
||||
// e.g. The 'image' subcommand should disable the lock file scanning.
|
||||
analyzers := opt.DisabledAnalyzers
|
||||
|
||||
// It doesn't analyze apk commands by default.
|
||||
if !opt.ScanRemovedPkgs {
|
||||
analyzers = append(analyzers, analyzer.TypeApkCommand)
|
||||
}
|
||||
|
||||
// Don't analyze programming language packages when not running in 'library' mode
|
||||
if !utils.StringInSlice(types.VulnTypeLibrary, opt.VulnType) {
|
||||
analyzers = append(analyzers, analyzer.TypeLanguages...)
|
||||
}
|
||||
|
||||
return analyzers
|
||||
}
|
||||
|
||||
func initializeScanner(ctx context.Context, opt Option) (scanner.Scanner, func(), error) {
|
||||
remoteCache := cache.NewRemoteCache(opt.RemoteAddr, opt.CustomHeaders, opt.Insecure)
|
||||
|
||||
// ScannerOptions is filled only when config scanning is enabled.
|
||||
var configScannerOptions config.ScannerOption
|
||||
if utils.StringInSlice(types.SecurityCheckConfig, opt.SecurityChecks) {
|
||||
noProgress := opt.Quiet || opt.NoProgress
|
||||
builtinPolicyPaths, err := operation.InitBuiltinPolicies(ctx, opt.CacheDir, noProgress, opt.SkipPolicyUpdate)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("failed to initialize default policies: %w", err)
|
||||
}
|
||||
|
||||
configScannerOptions = config.ScannerOption{
|
||||
Trace: opt.Trace,
|
||||
Namespaces: append(opt.PolicyNamespaces, defaultPolicyNamespace),
|
||||
PolicyPaths: append(opt.PolicyPaths, builtinPolicyPaths...),
|
||||
DataPaths: opt.DataPaths,
|
||||
FilePatterns: opt.FilePatterns,
|
||||
}
|
||||
}
|
||||
|
||||
artifactOpt := artifact.Option{
|
||||
DisabledAnalyzers: disabledAnalyzers(opt),
|
||||
SkipFiles: opt.SkipFiles,
|
||||
SkipDirs: opt.SkipDirs,
|
||||
Offline: opt.OfflineScan,
|
||||
}
|
||||
|
||||
if opt.Input != "" {
|
||||
// Scan tar file
|
||||
s, err := initializeArchiveScanner(ctx, opt.Input, remoteCache, client.CustomHeaders(opt.CustomHeaders),
|
||||
client.RemoteURL(opt.RemoteAddr), client.Insecure(opt.Insecure), artifactOpt, configScannerOptions)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
|
||||
}
|
||||
return s, func() {}, nil
|
||||
}
|
||||
|
||||
// Scan an image in Docker Engine or Docker Registry
|
||||
dockerOpt, err := types.GetDockerOption(opt.Insecure)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
|
||||
s, cleanup, err := initializeDockerScanner(ctx, opt.Target, remoteCache, client.CustomHeaders(opt.CustomHeaders),
|
||||
client.RemoteURL(opt.RemoteAddr), client.Insecure(opt.Insecure), dockerOpt, artifactOpt, configScannerOptions)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the docker scanner: %w", err)
|
||||
}
|
||||
|
||||
return s, cleanup, nil
|
||||
}
|
||||
|
||||
func exit(c Option, results types.Results) {
|
||||
if c.ExitCode != 0 {
|
||||
for _, result := range results {
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
os.Exit(c.ExitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/aquasecurity/fanal/analyzer/config"
|
||||
"github.com/aquasecurity/fanal/artifact"
|
||||
image2 "github.com/aquasecurity/fanal/artifact/image"
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/fanal/image"
|
||||
"github.com/aquasecurity/fanal/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/rpc/client"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
)
|
||||
|
||||
// Injectors from inject.go:
|
||||
|
||||
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, insecure client.Insecure, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||
scannerScanner := client.NewProtobufClient(url, insecure)
|
||||
clientScanner := client.NewScanner(customHeaders, scannerScanner)
|
||||
typesImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOpt)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return scanner.Scanner{}, nil, err
|
||||
}
|
||||
scanner2 := scanner.NewScanner(clientScanner, artifactArtifact)
|
||||
return scanner2, func() {
|
||||
cleanup()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, customHeaders client.CustomHeaders, url client.RemoteURL, insecure client.Insecure, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||
scannerScanner := client.NewProtobufClient(url, insecure)
|
||||
clientScanner := client.NewScanner(customHeaders, scannerScanner)
|
||||
typesImage, err := image.NewArchiveImage(filePath)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
|
||||
if err != nil {
|
||||
return scanner.Scanner{}, err
|
||||
}
|
||||
scanner2 := scanner.NewScanner(clientScanner, artifactArtifact)
|
||||
return scanner2, nil
|
||||
}
|
||||
|
||||
func initializeResultClient() result.Client {
|
||||
dbConfig := db.Config{}
|
||||
resultClient := result.NewClient(dbConfig)
|
||||
return resultClient
|
||||
}
|
||||
@@ -56,6 +56,8 @@ func NewCache(c option.CacheOption) (Cache, error) {
|
||||
redisCache := cache.NewRedisCache(options)
|
||||
return Cache{Cache: redisCache}, nil
|
||||
}
|
||||
|
||||
// standalone mode
|
||||
fsCache, err := cache.NewFSCache(utils.CacheDir())
|
||||
if err != nil {
|
||||
return Cache{}, xerrors.Errorf("unable to initialize fs cache: %w", err)
|
||||
|
||||
74
pkg/commands/option/remote.go
Normal file
74
pkg/commands/option/remote.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// RemoteOption holds options for client/server
|
||||
type RemoteOption struct {
|
||||
RemoteAddr string
|
||||
customHeaders []string
|
||||
token string
|
||||
tokenHeader string
|
||||
remote string // deprecated
|
||||
|
||||
// this field is populated in Init()
|
||||
CustomHeaders http.Header
|
||||
}
|
||||
|
||||
func NewRemoteOption(c *cli.Context) RemoteOption {
|
||||
r := RemoteOption{
|
||||
RemoteAddr: c.String("server"),
|
||||
customHeaders: c.StringSlice("custom-headers"),
|
||||
token: c.String("token"),
|
||||
tokenHeader: c.String("token-header"),
|
||||
remote: c.String("remote"), // deprecated
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Init initialize the options for client/server mode
|
||||
func (c *RemoteOption) Init(logger *zap.SugaredLogger) {
|
||||
// for testability
|
||||
defer func() {
|
||||
c.token = ""
|
||||
c.tokenHeader = ""
|
||||
c.remote = ""
|
||||
c.customHeaders = nil
|
||||
}()
|
||||
|
||||
// for backward compatibility, should be removed in the future
|
||||
if c.remote != "" {
|
||||
c.RemoteAddr = c.remote
|
||||
}
|
||||
|
||||
if c.RemoteAddr == "" {
|
||||
if len(c.customHeaders) > 0 || c.token != "" || c.tokenHeader != "" {
|
||||
logger.Warn(`'--token', '--token-header' and 'custom-header' can be used only with '--server'`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.CustomHeaders = splitCustomHeaders(c.customHeaders)
|
||||
if c.token != "" {
|
||||
c.CustomHeaders.Set(c.tokenHeader, c.token)
|
||||
}
|
||||
}
|
||||
|
||||
func splitCustomHeaders(headers []string) http.Header {
|
||||
result := make(http.Header)
|
||||
for _, header := range headers {
|
||||
// e.g. x-api-token:XXX
|
||||
s := strings.SplitN(header, ":", 2)
|
||||
if len(s) != 2 {
|
||||
continue
|
||||
}
|
||||
result.Set(s[0], s[1])
|
||||
}
|
||||
return result
|
||||
}
|
||||
36
pkg/commands/option/remote_test.go
Normal file
36
pkg/commands/option/remote_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_splitCustomHeaders(t *testing.T) {
|
||||
type args struct {
|
||||
headers []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want http.Header
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
headers: []string{"x-api-token:foo bar", "Authorization:user:password"},
|
||||
},
|
||||
want: http.Header{
|
||||
"X-Api-Token": []string{"foo bar"},
|
||||
"Authorization": []string{"user:password"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := splitCustomHeaders(tt.args.headers)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,58 +5,63 @@ import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
|
||||
"github.com/google/wire"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
r "github.com/aquasecurity/trivy/pkg/rpc"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/scanner"
|
||||
)
|
||||
|
||||
// SuperSet binds the dependencies for RPC client
|
||||
var SuperSet = wire.NewSet(
|
||||
NewProtobufClient,
|
||||
NewScanner,
|
||||
)
|
||||
|
||||
// RemoteURL for RPC remote host
|
||||
type RemoteURL string
|
||||
|
||||
// Insecure for RPC remote host
|
||||
type Insecure bool
|
||||
|
||||
// NewProtobufClient is the factory method to return RPC scanner
|
||||
func NewProtobufClient(remoteURL RemoteURL, insecure Insecure) rpc.Scanner {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: bool(insecure),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return rpc.NewScannerProtobufClient(string(remoteURL), httpClient)
|
||||
type options struct {
|
||||
rpcClient rpc.Scanner
|
||||
}
|
||||
|
||||
// CustomHeaders for holding HTTP headers
|
||||
type CustomHeaders http.Header
|
||||
type option func(*options)
|
||||
|
||||
// WithRPCClient takes rpc client for testability
|
||||
func WithRPCClient(c rpc.Scanner) option {
|
||||
return func(opts *options) {
|
||||
opts.rpcClient = c
|
||||
}
|
||||
}
|
||||
|
||||
// ScannerOption holds options for RPC client
|
||||
type ScannerOption struct {
|
||||
RemoteURL string
|
||||
Insecure bool
|
||||
CustomHeaders http.Header
|
||||
}
|
||||
|
||||
// Scanner implements the RPC scanner
|
||||
type Scanner struct {
|
||||
customHeaders CustomHeaders
|
||||
customHeaders http.Header
|
||||
client rpc.Scanner
|
||||
}
|
||||
|
||||
// NewScanner is the factory method to return RPC Scanner
|
||||
func NewScanner(customHeaders CustomHeaders, s rpc.Scanner) Scanner {
|
||||
return Scanner{customHeaders: customHeaders, client: s}
|
||||
func NewScanner(scannerOptions ScannerOption, opts ...option) Scanner {
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: scannerOptions.Insecure,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient)
|
||||
|
||||
o := &options{rpcClient: c}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
return Scanner{customHeaders: scannerOptions.CustomHeaders, client: o.rpcClient}
|
||||
}
|
||||
|
||||
// Scan scans the image
|
||||
func (s Scanner) Scan(target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
||||
ctx := WithCustomHeaders(context.Background(), http.Header(s.customHeaders))
|
||||
ctx := WithCustomHeaders(context.Background(), s.customHeaders)
|
||||
|
||||
var res *rpc.ScanResponse
|
||||
err := r.Retry(func() error {
|
||||
|
||||
@@ -1,94 +1,27 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
ftypes "github.com/aquasecurity/fanal/types"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy-db/pkg/utils"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/rpc/common"
|
||||
"github.com/aquasecurity/trivy/rpc/scanner"
|
||||
rpc "github.com/aquasecurity/trivy/rpc/scanner"
|
||||
)
|
||||
|
||||
type mockScanner struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type scanArgs struct {
|
||||
Ctx context.Context
|
||||
CtxAnything bool
|
||||
Request *scanner.ScanRequest
|
||||
RequestAnything bool
|
||||
}
|
||||
|
||||
type scanReturns struct {
|
||||
Res *scanner.ScanResponse
|
||||
Err error
|
||||
}
|
||||
|
||||
type scanExpectation struct {
|
||||
Args scanArgs
|
||||
Returns scanReturns
|
||||
}
|
||||
|
||||
func (_m *mockScanner) ApplyScanExpectation(e scanExpectation) {
|
||||
var args []interface{}
|
||||
if e.Args.CtxAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Ctx)
|
||||
}
|
||||
if e.Args.RequestAnything {
|
||||
args = append(args, mock.Anything)
|
||||
} else {
|
||||
args = append(args, e.Args.Request)
|
||||
}
|
||||
_m.On("Scan", args...).Return(e.Returns.Res, e.Returns.Err)
|
||||
}
|
||||
|
||||
func (_m *mockScanner) ApplyScanExpectations(expectations []scanExpectation) {
|
||||
for _, e := range expectations {
|
||||
_m.ApplyScanExpectation(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Scan provides a mock function with given fields: Ctx, Request
|
||||
func (_m *mockScanner) Scan(Ctx context.Context, Request *scanner.ScanRequest) (*scanner.ScanResponse, error) {
|
||||
ret := _m.Called(Ctx, Request)
|
||||
|
||||
var r0 *scanner.ScanResponse
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *scanner.ScanRequest) *scanner.ScanResponse); ok {
|
||||
r0 = rf(Ctx, Request)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*scanner.ScanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *scanner.ScanRequest) error); ok {
|
||||
r1 = rf(Ctx, Request)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
type fields struct {
|
||||
customHeaders CustomHeaders
|
||||
}
|
||||
type args struct {
|
||||
target string
|
||||
imageID string
|
||||
@@ -96,21 +29,19 @@ func TestScanner_Scan(t *testing.T) {
|
||||
options types.ScanOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
scanExpectation scanExpectation
|
||||
wantResults types.Results
|
||||
wantOS *ftypes.OS
|
||||
wantEosl bool
|
||||
wantErr string
|
||||
name string
|
||||
customHeaders http.Header
|
||||
args args
|
||||
expectation *rpc.ScanResponse
|
||||
wantResults types.Results
|
||||
wantOS *ftypes.OS
|
||||
wantEosl bool
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
customHeaders: CustomHeaders{
|
||||
"Trivy-Token": []string{"foo"},
|
||||
},
|
||||
customHeaders: http.Header{
|
||||
"Trivy-Token": []string{"foo"},
|
||||
},
|
||||
args: args{
|
||||
target: "alpine:3.11",
|
||||
@@ -120,64 +51,49 @@ func TestScanner_Scan(t *testing.T) {
|
||||
VulnType: []string{"os"},
|
||||
},
|
||||
},
|
||||
scanExpectation: scanExpectation{
|
||||
Args: scanArgs{
|
||||
CtxAnything: true,
|
||||
Request: &scanner.ScanRequest{
|
||||
Target: "alpine:3.11",
|
||||
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
Options: &scanner.ScanOptions{
|
||||
VulnType: []string{"os"},
|
||||
},
|
||||
},
|
||||
expectation: &rpc.ScanResponse{
|
||||
Os: &common.OS{
|
||||
Family: "alpine",
|
||||
Name: "3.11",
|
||||
Eosl: true,
|
||||
},
|
||||
Returns: scanReturns{
|
||||
Res: &scanner.ScanResponse{
|
||||
Os: &common.OS{
|
||||
Family: "alpine",
|
||||
Name: "3.11",
|
||||
Eosl: true,
|
||||
},
|
||||
Results: []*scanner.Result{
|
||||
Results: []*rpc.Result{
|
||||
{
|
||||
Target: "alpine:3.11",
|
||||
Vulnerabilities: []*common.Vulnerability{
|
||||
{
|
||||
Target: "alpine:3.11",
|
||||
Vulnerabilities: []*common.Vulnerability{
|
||||
{
|
||||
VulnerabilityId: "CVE-2020-0001",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "DoS",
|
||||
Description: "Denial os Service",
|
||||
Severity: common.Severity_CRITICAL,
|
||||
References: []string{"http://exammple.com"},
|
||||
SeveritySource: "nvd",
|
||||
Cvss: map[string]*common.CVSS{
|
||||
"nvd": {
|
||||
V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C",
|
||||
V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
||||
V2Score: 7.2,
|
||||
V3Score: 7.8,
|
||||
},
|
||||
"redhat": {
|
||||
V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C",
|
||||
V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
||||
V2Score: 4.2,
|
||||
V3Score: 2.8,
|
||||
},
|
||||
},
|
||||
CweIds: []string{"CWE-78"},
|
||||
Layer: &common.Layer{
|
||||
DiffId: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10",
|
||||
},
|
||||
LastModifiedDate: ×tamp.Timestamp{
|
||||
Seconds: 1577840460,
|
||||
},
|
||||
PublishedDate: ×tamp.Timestamp{
|
||||
Seconds: 978310860,
|
||||
},
|
||||
VulnerabilityId: "CVE-2020-0001",
|
||||
PkgName: "musl",
|
||||
InstalledVersion: "1.2.3",
|
||||
FixedVersion: "1.2.4",
|
||||
Title: "DoS",
|
||||
Description: "Denial os Service",
|
||||
Severity: common.Severity_CRITICAL,
|
||||
References: []string{"http://exammple.com"},
|
||||
SeveritySource: "nvd",
|
||||
Cvss: map[string]*common.CVSS{
|
||||
"nvd": {
|
||||
V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C",
|
||||
V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
||||
V2Score: 7.2,
|
||||
V3Score: 7.8,
|
||||
},
|
||||
"redhat": {
|
||||
V2Vector: "AV:H/AC:L/Au:N/C:C/I:C/A:C",
|
||||
V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
||||
V2Score: 4.2,
|
||||
V3Score: 2.8,
|
||||
},
|
||||
},
|
||||
CweIds: []string{"CWE-78"},
|
||||
Layer: &common.Layer{
|
||||
DiffId: "sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10",
|
||||
},
|
||||
LastModifiedDate: ×tamp.Timestamp{
|
||||
Seconds: 1577840460,
|
||||
},
|
||||
PublishedDate: ×tamp.Timestamp{
|
||||
Seconds: 978310860,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -232,10 +148,8 @@ func TestScanner_Scan(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "sad path: Scan returns an error",
|
||||
fields: fields{
|
||||
customHeaders: CustomHeaders{
|
||||
"Trivy-Token": []string{"foo"},
|
||||
},
|
||||
customHeaders: http.Header{
|
||||
"Trivy-Token": []string{"foo"},
|
||||
},
|
||||
args: args{
|
||||
target: "alpine:3.11",
|
||||
@@ -245,41 +159,44 @@ func TestScanner_Scan(t *testing.T) {
|
||||
VulnType: []string{"os"},
|
||||
},
|
||||
},
|
||||
scanExpectation: scanExpectation{
|
||||
Args: scanArgs{
|
||||
CtxAnything: true,
|
||||
Request: &scanner.ScanRequest{
|
||||
Target: "alpine:3.11",
|
||||
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
||||
BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
||||
Options: &scanner.ScanOptions{
|
||||
VulnType: []string{"os"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Returns: scanReturns{
|
||||
Err: errors.New("error"),
|
||||
},
|
||||
},
|
||||
wantErr: "failed to detect vulnerabilities via RPC",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := new(mockScanner)
|
||||
mockClient.ApplyScanExpectation(tt.scanExpectation)
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if tt.expectation == nil {
|
||||
e := map[string]interface{}{
|
||||
"code": "not_found",
|
||||
"msg": "expectation is empty",
|
||||
}
|
||||
b, _ := json.Marshal(e)
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
w.Write(b)
|
||||
return
|
||||
}
|
||||
b, err := protojson.Marshal(tt.expectation)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "json marshalling error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
}))
|
||||
client := rpc.NewScannerJSONClient(ts.URL, ts.Client())
|
||||
|
||||
s := NewScanner(ScannerOption{CustomHeaders: tt.customHeaders}, WithRPCClient(client))
|
||||
|
||||
s := NewScanner(tt.fields.customHeaders, mockClient)
|
||||
gotResults, gotOS, err := s.Scan(tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.NotNil(t, err, tt.name)
|
||||
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
require.NoError(t, err, tt.name)
|
||||
assert.Equal(t, tt.wantResults, gotResults)
|
||||
assert.Equal(t, tt.wantOS, gotOS)
|
||||
})
|
||||
@@ -290,36 +207,32 @@ func TestScanner_ScanServerInsecure(t *testing.T) {
|
||||
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
defer ts.Close()
|
||||
|
||||
type args struct {
|
||||
request *scanner.ScanRequest
|
||||
insecure bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr string
|
||||
name string
|
||||
insecure bool
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: args{
|
||||
request: &scanner.ScanRequest{},
|
||||
insecure: true,
|
||||
},
|
||||
name: "happy path",
|
||||
insecure: true,
|
||||
},
|
||||
{
|
||||
name: "sad path",
|
||||
args: args{
|
||||
request: &scanner.ScanRequest{},
|
||||
insecure: false,
|
||||
},
|
||||
wantErr: "certificate signed by unknown authority",
|
||||
name: "sad path",
|
||||
insecure: false,
|
||||
wantErr: "certificate signed by unknown authority",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
s := NewProtobufClient(RemoteURL(ts.URL), Insecure(tt.args.insecure))
|
||||
_, err := s.Scan(context.Background(), tt.args.request)
|
||||
c := rpc.NewScannerProtobufClient(ts.URL, &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: tt.insecure,
|
||||
},
|
||||
},
|
||||
})
|
||||
s := NewScanner(ScannerOption{Insecure: tt.insecure}, WithRPCClient(c))
|
||||
_, _, err := s.Scan("dummy", "", nil, types.ScanOptions{})
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -19,6 +19,10 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
)
|
||||
|
||||
///////////////
|
||||
// Standalone
|
||||
///////////////
|
||||
|
||||
// StandaloneSuperSet is used in the standalone mode
|
||||
var StandaloneSuperSet = wire.NewSet(
|
||||
local.SuperSet,
|
||||
@@ -52,22 +56,33 @@ var StandaloneRepositorySet = wire.NewSet(
|
||||
StandaloneSuperSet,
|
||||
)
|
||||
|
||||
/////////////////
|
||||
// Client/Server
|
||||
/////////////////
|
||||
|
||||
// RemoteSuperSet is used in the client mode
|
||||
var RemoteSuperSet = wire.NewSet(
|
||||
aimage.NewArtifact,
|
||||
client.SuperSet,
|
||||
client.NewScanner,
|
||||
wire.Bind(new(Driver), new(client.Scanner)),
|
||||
NewScanner,
|
||||
)
|
||||
|
||||
// RemoteFilesystemSet binds filesystem dependencies for client/server mode
|
||||
var RemoteFilesystemSet = wire.NewSet(
|
||||
flocal.NewArtifact,
|
||||
RemoteSuperSet,
|
||||
)
|
||||
|
||||
// RemoteDockerSet binds remote docker dependencies
|
||||
var RemoteDockerSet = wire.NewSet(
|
||||
aimage.NewArtifact,
|
||||
image.NewDockerImage,
|
||||
RemoteSuperSet,
|
||||
)
|
||||
|
||||
// RemoteArchiveSet binds remote archive dependencies
|
||||
var RemoteArchiveSet = wire.NewSet(
|
||||
aimage.NewArtifact,
|
||||
image.NewArchiveImage,
|
||||
RemoteSuperSet,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user