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-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]
|
--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]
|
--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)
|
--help, -h show help (default: false)
|
||||||
```
|
```
|
||||||
@@ -6,7 +6,8 @@ Scan a local project including language-specific files.
|
|||||||
$ trivy fs /path/to/project
|
$ 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.
|
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
|
$ 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 {
|
type csArgs struct {
|
||||||
|
Command string
|
||||||
|
RemoteAddrOption string
|
||||||
Format string
|
Format string
|
||||||
TemplatePath string
|
TemplatePath string
|
||||||
IgnoreUnfixed bool
|
IgnoreUnfixed bool
|
||||||
@@ -35,6 +37,7 @@ type csArgs struct {
|
|||||||
ClientToken string
|
ClientToken string
|
||||||
ClientTokenHeader string
|
ClientTokenHeader string
|
||||||
ListAllPackages bool
|
ListAllPackages bool
|
||||||
|
Target string
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientServer(t *testing.T) {
|
func TestClientServer(t *testing.T) {
|
||||||
@@ -220,6 +223,15 @@ func TestClientServer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
golden: "testdata/busybox-with-lockfile.json.golden",
|
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{})
|
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) {
|
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()
|
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 != "" {
|
if c.Format != "" {
|
||||||
osArgs = append(osArgs, "--format", 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)
|
osArgs = append(osArgs, "--output", outputFile)
|
||||||
|
|
||||||
|
if c.Target != "" {
|
||||||
|
osArgs = append(osArgs, c.Target)
|
||||||
|
}
|
||||||
|
|
||||||
return osArgs, outputFile
|
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)
|
c := rpcCache.NewCacheProtobufClient(url, httpClient)
|
||||||
return &RemoteCache{ctx: ctx, client: c}
|
return &RemoteCache{ctx: ctx, client: c}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
"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/plugin"
|
||||||
"github.com/aquasecurity/trivy/pkg/commands/server"
|
"github.com/aquasecurity/trivy/pkg/commands/server"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"github.com/aquasecurity/trivy/pkg/result"
|
||||||
@@ -210,14 +209,14 @@ var (
|
|||||||
|
|
||||||
token = cli.StringFlag{
|
token = cli.StringFlag{
|
||||||
Name: "token",
|
Name: "token",
|
||||||
Usage: "for authentication",
|
Usage: "for authentication in client/server mode",
|
||||||
EnvVars: []string{"TRIVY_TOKEN"},
|
EnvVars: []string{"TRIVY_TOKEN"},
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenHeader = cli.StringFlag{
|
tokenHeader = cli.StringFlag{
|
||||||
Name: "token-header",
|
Name: "token-header",
|
||||||
Value: "Trivy-Token",
|
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"},
|
EnvVars: []string{"TRIVY_TOKEN_HEADER"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,6 +312,18 @@ var (
|
|||||||
EnvVars: []string{"TRIVY_INSECURE"},
|
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
|
// Global flags
|
||||||
globalFlags = []cli.Flag{
|
globalFlags = []cli.Flag{
|
||||||
&quietFlag,
|
&quietFlag,
|
||||||
@@ -473,9 +484,17 @@ func NewFilesystemCommand() *cli.Command {
|
|||||||
&offlineScan,
|
&offlineScan,
|
||||||
stringSliceFlag(skipFiles),
|
stringSliceFlag(skipFiles),
|
||||||
stringSliceFlag(skipDirs),
|
stringSliceFlag(skipDirs),
|
||||||
|
|
||||||
|
// for misconfiguration
|
||||||
stringSliceFlag(configPolicy),
|
stringSliceFlag(configPolicy),
|
||||||
stringSliceFlag(configData),
|
stringSliceFlag(configData),
|
||||||
stringSliceFlag(policyNamespaces),
|
stringSliceFlag(policyNamespaces),
|
||||||
|
|
||||||
|
// for client/server
|
||||||
|
&remoteServer,
|
||||||
|
&token,
|
||||||
|
&tokenHeader,
|
||||||
|
&customHeaders,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,7 +584,7 @@ func NewClientCommand() *cli.Command {
|
|||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
ArgsUsage: "image_name",
|
ArgsUsage: "image_name",
|
||||||
Usage: "client mode",
|
Usage: "client mode",
|
||||||
Action: client.Run,
|
Action: artifact.ImageRun,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&templateFlag,
|
&templateFlag,
|
||||||
&formatFlag,
|
&formatFlag,
|
||||||
@@ -589,20 +608,17 @@ func NewClientCommand() *cli.Command {
|
|||||||
&offlineScan,
|
&offlineScan,
|
||||||
&insecureFlag,
|
&insecureFlag,
|
||||||
|
|
||||||
// original flags
|
|
||||||
&token,
|
&token,
|
||||||
&tokenHeader,
|
&tokenHeader,
|
||||||
|
&customHeaders,
|
||||||
|
|
||||||
|
// original flags
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "remote",
|
Name: "remote",
|
||||||
Value: "http://localhost:4954",
|
Value: "http://localhost:4954",
|
||||||
Usage: "server address",
|
Usage: "server address",
|
||||||
EnvVars: []string{"TRIVY_REMOTE"},
|
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
|
opt.SkipDBUpdate = true
|
||||||
|
|
||||||
// Run filesystem command internally
|
// 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"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/fanal/analyzer"
|
"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/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func filesystemScanner(ctx context.Context, path string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
|
||||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
func filesystemStandaloneScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||||
s, cleanup, err := initializeFilesystemScanner(ctx, path, ac, lac, artifactOpt, scannerOpt)
|
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 {
|
if err != nil {
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
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
|
// Disable the individual package scanning
|
||||||
opt.DisabledAnalyzers = analyzer.TypeIndividualPkgs
|
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.
|
// RootfsRun runs scan on rootfs.
|
||||||
@@ -45,5 +59,11 @@ func RootfsRun(ctx *cli.Context) error {
|
|||||||
// Disable the lock file scanning
|
// Disable the lock file scanning
|
||||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
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"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/fanal/analyzer"
|
"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/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
// imageScanner initializes a container image scanner in standalone mode
|
||||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
// $ trivy image alpine:3.15
|
||||||
s, err := initializeArchiveScanner(ctx, input, ac, lac, artifactOpt, scannerOpt)
|
func imageScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||||
if err != nil {
|
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return scanner.Scanner{}, nil, err
|
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 {
|
if err != nil {
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
|
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
|
||||||
}
|
}
|
||||||
return s, cleanup, nil
|
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 {
|
func ImageRun(ctx *cli.Context) error {
|
||||||
opt, err := initOption(ctx)
|
opt, err := initOption(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,9 +78,34 @@ func ImageRun(ctx *cli.Context) error {
|
|||||||
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
opt.DisabledAnalyzers = analyzer.TypeLockfiles
|
||||||
|
|
||||||
if opt.Input != "" {
|
if opt.Input != "" {
|
||||||
// scan tar file
|
return archiveImageRun(ctx.Context, opt)
|
||||||
return Run(ctx.Context, opt, archiveScanner, initFSCache)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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/cache"
|
||||||
"github.com/aquasecurity/fanal/types"
|
"github.com/aquasecurity/fanal/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// 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,
|
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
|
||||||
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option,
|
||||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||||
@@ -23,6 +30,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
|||||||
return scanner.Scanner{}, nil, nil
|
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,
|
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
|
||||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||||
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
|
||||||
@@ -30,6 +39,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
|||||||
return scanner.Scanner{}, nil
|
return scanner.Scanner{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initializeFilesystemScanner is for filesystem scanning in standalone mode
|
||||||
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
|
||||||
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
|
||||||
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
|
||||||
@@ -48,3 +58,39 @@ func initializeResultClient() result.Client {
|
|||||||
wire.Build(result.SuperSet)
|
wire.Build(result.SuperSet)
|
||||||
return result.Client{}
|
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.ReportOption
|
||||||
option.CacheOption
|
option.CacheOption
|
||||||
option.ConfigOption
|
option.ConfigOption
|
||||||
|
option.RemoteOption
|
||||||
|
|
||||||
// We don't want to allow disabled analyzers to be passed by users,
|
// We don't want to allow disabled analyzers to be passed by users,
|
||||||
// but it differs depending on scanning modes.
|
// but it differs depending on scanning modes.
|
||||||
@@ -38,6 +39,7 @@ func NewOption(c *cli.Context) (Option, error) {
|
|||||||
ReportOption: option.NewReportOption(c),
|
ReportOption: option.NewReportOption(c),
|
||||||
CacheOption: option.NewCacheOption(c),
|
CacheOption: option.NewCacheOption(c),
|
||||||
ConfigOption: option.NewConfigOption(c),
|
ConfigOption: option.NewConfigOption(c),
|
||||||
|
RemoteOption: option.NewRemoteOption(c),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +57,6 @@ func (c *Option) Init() error {
|
|||||||
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
|
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ func (c *Option) initPreScanOptions() error {
|
|||||||
if err := c.CacheOption.Init(); err != nil {
|
if err := c.CacheOption.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
c.RemoteOption.Init(c.Logger)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package artifact
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"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",
|
name: "happy path: reset",
|
||||||
args: []string{"--reset"},
|
args: []string{"--reset"},
|
||||||
@@ -182,6 +258,10 @@ func TestOption_Init(t *testing.T) {
|
|||||||
set.String("security-checks", "vuln", "")
|
set.String("security-checks", "vuln", "")
|
||||||
set.String("template", "", "")
|
set.String("template", "", "")
|
||||||
set.String("format", "", "")
|
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)
|
ctx := cli.NewContext(app, set, nil)
|
||||||
_ = set.Parse(tt.args)
|
_ = set.Parse(tt.args)
|
||||||
|
|||||||
@@ -7,16 +7,14 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/aquasecurity/fanal/analyzer"
|
"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/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
|
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
|
||||||
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
|
func repositoryScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
|
||||||
s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac, artifactOpt, scannerOpt)
|
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
|
||||||
|
conf.ArtifactOption, conf.MisconfOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
|
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
|
// Disable the OS analyzers and individual package analyzers
|
||||||
opt.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
|
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/artifact"
|
||||||
"github.com/aquasecurity/fanal/cache"
|
"github.com/aquasecurity/fanal/cache"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"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/commands/operation"
|
||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
pkgReport "github.com/aquasecurity/trivy/pkg/report"
|
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/scanner"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/utils"
|
"github.com/aquasecurity/trivy/pkg/utils"
|
||||||
@@ -25,9 +27,26 @@ const defaultPolicyNamespace = "appshield"
|
|||||||
|
|
||||||
var errSkipScan = errors.New("skip subsequent processes")
|
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
|
// InitializeScanner defines the initialize function signature of scanner
|
||||||
type InitializeScanner func(context.Context, string, cache.ArtifactCache, cache.LocalArtifactCache, bool,
|
type InitializeScanner func(context.Context, scannerConfig) (scanner.Scanner, func(), error)
|
||||||
artifact.Option, config.ScannerOption) (scanner.Scanner, func(), error)
|
|
||||||
|
|
||||||
// InitCache defines cache initializer
|
// InitCache defines cache initializer
|
||||||
type InitCache func(c Option) (cache.Cache, error)
|
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)
|
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
|
||||||
defer cancel()
|
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 {
|
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()
|
defer cacheClient.Close()
|
||||||
|
|
||||||
// When scanning config files, it doesn't need to download the vulnerability database.
|
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
|
||||||
if utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
|
if opt.RemoteAddr == "" && utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
|
||||||
if err = initDB(opt); err != nil {
|
if err = initDB(opt); err != nil {
|
||||||
if errors.Is(err, errSkipScan) {
|
if errors.Is(err, errSkipScan) {
|
||||||
return nil
|
return nil
|
||||||
@@ -92,7 +115,14 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
|
|||||||
return nil
|
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)
|
utils.SetCacheDir(c.CacheDir)
|
||||||
cache, err := operation.NewCache(c.CacheOption)
|
cache, err := operation.NewCache(c.CacheOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -181,7 +211,7 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
|||||||
}
|
}
|
||||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
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
|
var configScannerOptions config.ScannerOption
|
||||||
if utils.StringInSlice(types.SecurityCheckConfig, opt.SecurityChecks) {
|
if utils.StringInSlice(types.SecurityCheckConfig, opt.SecurityChecks) {
|
||||||
noProgress := opt.Quiet || opt.NoProgress
|
noProgress := opt.Quiet || opt.NoProgress
|
||||||
@@ -199,16 +229,25 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
artifactOpt := artifact.Option{
|
s, cleanup, err := initializeScanner(ctx, scannerConfig{
|
||||||
DisabledAnalyzers: disabledAnalyzers(opt),
|
Target: target,
|
||||||
SkipFiles: opt.SkipFiles,
|
ArtifactCache: cacheClient,
|
||||||
SkipDirs: opt.SkipDirs,
|
LocalArtifactCache: cacheClient,
|
||||||
InsecureSkipTLS: opt.Insecure,
|
RemoteOption: client.ScannerOption{
|
||||||
Offline: opt.OfflineScan,
|
RemoteURL: opt.RemoteAddr,
|
||||||
NoProgress: opt.NoProgress || opt.Quiet,
|
CustomHeaders: opt.CustomHeaders,
|
||||||
}
|
Insecure: opt.Insecure,
|
||||||
|
},
|
||||||
s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Insecure, artifactOpt, configScannerOptions)
|
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 {
|
if err != nil {
|
||||||
return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
|
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()
|
resultClient := initializeResultClient()
|
||||||
results := report.Results
|
results := report.Results
|
||||||
for i := range 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,
|
vulns, misconfSummary, misconfs, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations,
|
||||||
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,12 +20,15 @@ import (
|
|||||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||||
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
|
||||||
"github.com/aquasecurity/trivy/pkg/result"
|
"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"
|
||||||
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
"github.com/aquasecurity/trivy/pkg/scanner/local"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Injectors from inject.go:
|
// 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) {
|
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)
|
applierApplier := applier.NewApplier(localArtifactCache)
|
||||||
detector := ospkg.Detector{}
|
detector := ospkg.Detector{}
|
||||||
@@ -45,6 +48,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
|
|||||||
}, 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) {
|
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)
|
applierApplier := applier.NewApplier(localArtifactCache)
|
||||||
detector := ospkg.Detector{}
|
detector := ospkg.Detector{}
|
||||||
@@ -61,6 +66,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
|
|||||||
return scannerScanner, nil
|
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) {
|
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)
|
applierApplier := applier.NewApplier(localArtifactCache)
|
||||||
detector := ospkg.Detector{}
|
detector := ospkg.Detector{}
|
||||||
@@ -93,3 +99,56 @@ func initializeResultClient() result.Client {
|
|||||||
client := result.NewClient(dbConfig)
|
client := result.NewClient(dbConfig)
|
||||||
return client
|
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)
|
redisCache := cache.NewRedisCache(options)
|
||||||
return Cache{Cache: redisCache}, nil
|
return Cache{Cache: redisCache}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// standalone mode
|
||||||
fsCache, err := cache.NewFSCache(utils.CacheDir())
|
fsCache, err := cache.NewFSCache(utils.CacheDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Cache{}, xerrors.Errorf("unable to initialize fs cache: %w", err)
|
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"
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
|
||||||
|
|
||||||
"github.com/google/wire"
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
ftypes "github.com/aquasecurity/fanal/types"
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
r "github.com/aquasecurity/trivy/pkg/rpc"
|
r "github.com/aquasecurity/trivy/pkg/rpc"
|
||||||
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
rpc "github.com/aquasecurity/trivy/rpc/scanner"
|
rpc "github.com/aquasecurity/trivy/rpc/scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SuperSet binds the dependencies for RPC client
|
type options struct {
|
||||||
var SuperSet = wire.NewSet(
|
rpcClient rpc.Scanner
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomHeaders for holding HTTP headers
|
type option func(*options)
|
||||||
type CustomHeaders http.Header
|
|
||||||
|
// 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
|
// Scanner implements the RPC scanner
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
customHeaders CustomHeaders
|
customHeaders http.Header
|
||||||
client rpc.Scanner
|
client rpc.Scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScanner is the factory method to return RPC Scanner
|
// NewScanner is the factory method to return RPC Scanner
|
||||||
func NewScanner(customHeaders CustomHeaders, s rpc.Scanner) Scanner {
|
func NewScanner(scannerOptions ScannerOption, opts ...option) Scanner {
|
||||||
return Scanner{customHeaders: customHeaders, client: s}
|
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
|
// Scan scans the image
|
||||||
func (s Scanner) Scan(target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
|
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
|
var res *rpc.ScanResponse
|
||||||
err := r.Retry(func() error {
|
err := r.Retry(func() error {
|
||||||
|
|||||||
@@ -1,94 +1,27 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"crypto/tls"
|
||||||
"errors"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/protobuf/ptypes/timestamp"
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
|
||||||
ftypes "github.com/aquasecurity/fanal/types"
|
ftypes "github.com/aquasecurity/fanal/types"
|
||||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||||
"github.com/aquasecurity/trivy-db/pkg/utils"
|
"github.com/aquasecurity/trivy-db/pkg/utils"
|
||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
"github.com/aquasecurity/trivy/rpc/common"
|
"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) {
|
func TestScanner_Scan(t *testing.T) {
|
||||||
type fields struct {
|
|
||||||
customHeaders CustomHeaders
|
|
||||||
}
|
|
||||||
type args struct {
|
type args struct {
|
||||||
target string
|
target string
|
||||||
imageID string
|
imageID string
|
||||||
@@ -96,21 +29,19 @@ func TestScanner_Scan(t *testing.T) {
|
|||||||
options types.ScanOptions
|
options types.ScanOptions
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
customHeaders http.Header
|
||||||
args args
|
args args
|
||||||
scanExpectation scanExpectation
|
expectation *rpc.ScanResponse
|
||||||
wantResults types.Results
|
wantResults types.Results
|
||||||
wantOS *ftypes.OS
|
wantOS *ftypes.OS
|
||||||
wantEosl bool
|
wantEosl bool
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy path",
|
name: "happy path",
|
||||||
fields: fields{
|
customHeaders: http.Header{
|
||||||
customHeaders: CustomHeaders{
|
"Trivy-Token": []string{"foo"},
|
||||||
"Trivy-Token": []string{"foo"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
target: "alpine:3.11",
|
target: "alpine:3.11",
|
||||||
@@ -120,64 +51,49 @@ func TestScanner_Scan(t *testing.T) {
|
|||||||
VulnType: []string{"os"},
|
VulnType: []string{"os"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scanExpectation: scanExpectation{
|
expectation: &rpc.ScanResponse{
|
||||||
Args: scanArgs{
|
Os: &common.OS{
|
||||||
CtxAnything: true,
|
Family: "alpine",
|
||||||
Request: &scanner.ScanRequest{
|
Name: "3.11",
|
||||||
Target: "alpine:3.11",
|
Eosl: true,
|
||||||
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
|
|
||||||
BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
|
|
||||||
Options: &scanner.ScanOptions{
|
|
||||||
VulnType: []string{"os"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Returns: scanReturns{
|
Results: []*rpc.Result{
|
||||||
Res: &scanner.ScanResponse{
|
{
|
||||||
Os: &common.OS{
|
Target: "alpine:3.11",
|
||||||
Family: "alpine",
|
Vulnerabilities: []*common.Vulnerability{
|
||||||
Name: "3.11",
|
|
||||||
Eosl: true,
|
|
||||||
},
|
|
||||||
Results: []*scanner.Result{
|
|
||||||
{
|
{
|
||||||
Target: "alpine:3.11",
|
VulnerabilityId: "CVE-2020-0001",
|
||||||
Vulnerabilities: []*common.Vulnerability{
|
PkgName: "musl",
|
||||||
{
|
InstalledVersion: "1.2.3",
|
||||||
VulnerabilityId: "CVE-2020-0001",
|
FixedVersion: "1.2.4",
|
||||||
PkgName: "musl",
|
Title: "DoS",
|
||||||
InstalledVersion: "1.2.3",
|
Description: "Denial os Service",
|
||||||
FixedVersion: "1.2.4",
|
Severity: common.Severity_CRITICAL,
|
||||||
Title: "DoS",
|
References: []string{"http://exammple.com"},
|
||||||
Description: "Denial os Service",
|
SeveritySource: "nvd",
|
||||||
Severity: common.Severity_CRITICAL,
|
Cvss: map[string]*common.CVSS{
|
||||||
References: []string{"http://exammple.com"},
|
"nvd": {
|
||||||
SeveritySource: "nvd",
|
V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C",
|
||||||
Cvss: map[string]*common.CVSS{
|
V3Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
|
||||||
"nvd": {
|
V2Score: 7.2,
|
||||||
V2Vector: "AV:L/AC:L/Au:N/C:C/I:C/A:C",
|
V3Score: 7.8,
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
"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",
|
name: "sad path: Scan returns an error",
|
||||||
fields: fields{
|
customHeaders: http.Header{
|
||||||
customHeaders: CustomHeaders{
|
"Trivy-Token": []string{"foo"},
|
||||||
"Trivy-Token": []string{"foo"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
target: "alpine:3.11",
|
target: "alpine:3.11",
|
||||||
@@ -245,41 +159,44 @@ func TestScanner_Scan(t *testing.T) {
|
|||||||
VulnType: []string{"os"},
|
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",
|
wantErr: "failed to detect vulnerabilities via RPC",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
mockClient := new(mockScanner)
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
mockClient.ApplyScanExpectation(tt.scanExpectation)
|
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)
|
gotResults, gotOS, err := s.Scan(tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options)
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.NotNil(t, err, tt.name)
|
require.NotNil(t, err, tt.name)
|
||||||
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
require.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
require.NoError(t, err, tt.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err, tt.name)
|
||||||
assert.Equal(t, tt.wantResults, gotResults)
|
assert.Equal(t, tt.wantResults, gotResults)
|
||||||
assert.Equal(t, tt.wantOS, gotOS)
|
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) {}))
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
type args struct {
|
|
||||||
request *scanner.ScanRequest
|
|
||||||
insecure bool
|
|
||||||
}
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
insecure bool
|
||||||
wantErr string
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "happy path",
|
name: "happy path",
|
||||||
args: args{
|
insecure: true,
|
||||||
request: &scanner.ScanRequest{},
|
|
||||||
insecure: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sad path",
|
name: "sad path",
|
||||||
args: args{
|
insecure: false,
|
||||||
request: &scanner.ScanRequest{},
|
wantErr: "certificate signed by unknown authority",
|
||||||
insecure: false,
|
|
||||||
},
|
|
||||||
wantErr: "certificate signed by unknown authority",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := rpc.NewScannerProtobufClient(ts.URL, &http.Client{
|
||||||
s := NewProtobufClient(RemoteURL(ts.URL), Insecure(tt.args.insecure))
|
Transport: &http.Transport{
|
||||||
_, err := s.Scan(context.Background(), tt.args.request)
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: tt.insecure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
s := NewScanner(ScannerOption{Insecure: tt.insecure}, WithRPCClient(c))
|
||||||
|
_, _, err := s.Scan("dummy", "", nil, types.ScanOptions{})
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/types"
|
"github.com/aquasecurity/trivy/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Standalone
|
||||||
|
///////////////
|
||||||
|
|
||||||
// StandaloneSuperSet is used in the standalone mode
|
// StandaloneSuperSet is used in the standalone mode
|
||||||
var StandaloneSuperSet = wire.NewSet(
|
var StandaloneSuperSet = wire.NewSet(
|
||||||
local.SuperSet,
|
local.SuperSet,
|
||||||
@@ -52,22 +56,33 @@ var StandaloneRepositorySet = wire.NewSet(
|
|||||||
StandaloneSuperSet,
|
StandaloneSuperSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// Client/Server
|
||||||
|
/////////////////
|
||||||
|
|
||||||
// RemoteSuperSet is used in the client mode
|
// RemoteSuperSet is used in the client mode
|
||||||
var RemoteSuperSet = wire.NewSet(
|
var RemoteSuperSet = wire.NewSet(
|
||||||
aimage.NewArtifact,
|
client.NewScanner,
|
||||||
client.SuperSet,
|
|
||||||
wire.Bind(new(Driver), new(client.Scanner)),
|
wire.Bind(new(Driver), new(client.Scanner)),
|
||||||
NewScanner,
|
NewScanner,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RemoteFilesystemSet binds filesystem dependencies for client/server mode
|
||||||
|
var RemoteFilesystemSet = wire.NewSet(
|
||||||
|
flocal.NewArtifact,
|
||||||
|
RemoteSuperSet,
|
||||||
|
)
|
||||||
|
|
||||||
// RemoteDockerSet binds remote docker dependencies
|
// RemoteDockerSet binds remote docker dependencies
|
||||||
var RemoteDockerSet = wire.NewSet(
|
var RemoteDockerSet = wire.NewSet(
|
||||||
|
aimage.NewArtifact,
|
||||||
image.NewDockerImage,
|
image.NewDockerImage,
|
||||||
RemoteSuperSet,
|
RemoteSuperSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoteArchiveSet binds remote archive dependencies
|
// RemoteArchiveSet binds remote archive dependencies
|
||||||
var RemoteArchiveSet = wire.NewSet(
|
var RemoteArchiveSet = wire.NewSet(
|
||||||
|
aimage.NewArtifact,
|
||||||
image.NewArchiveImage,
|
image.NewArchiveImage,
|
||||||
RemoteSuperSet,
|
RemoteSuperSet,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user