feat(filesystem): scan in client/server mode (#1829)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
afdesk
2022-03-21 19:51:18 +06:00
committed by GitHub
parent 12d0317a67
commit d6418cf0de
49 changed files with 763 additions and 1014 deletions

View File

@@ -31,5 +31,9 @@ OPTIONS:
--config-policy value specify paths to the Rego policy files directory, applying config files [$TRIVY_CONFIG_POLICY]
--config-data value specify paths from which data for the Rego policies will be recursively loaded [$TRIVY_CONFIG_DATA]
--policy-namespaces value, --namespaces value Rego namespaces (default: "users") [$TRIVY_POLICY_NAMESPACES]
--server value server address [$TRIVY_SERVER]
--token value for authentication [$TRIVY_TOKEN]
--token-header value specify a header name for token (default: "Trivy-Token") [$TRIVY_TOKEN_HEADER]
--custom-headers value custom headers [$TRIVY_CUSTOM_HEADERS]
--help, -h show help (default: false)
```

View File

@@ -6,7 +6,8 @@ Scan a local project including language-specific files.
$ trivy fs /path/to/project
```
## Local Project
## Standalone mode
### Local Project
Trivy will look for vulnerabilities based on lock files such as Gemfile.lock and package-lock.json.
```
@@ -54,3 +55,49 @@ It's also possible to scan a single file.
```
$ trivy fs ~/src/github.com/aquasecurity/trivy-ci-test/Pipfile.lock
```
## Client/Server mode
You must launch Trivy server in advance.
```sh
$ trivy server
```
Then, Trivy works as a client if you specify the `--server` option.
```sh
$ trivy fs --server http://localhost:4954 --severity CRITICAL ./integration/testdata/fixtures/fs/pom/
```
<details>
<summary>Result</summary>
```
pom.xml (pom)
=============
Total: 4 (CRITICAL: 4)
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
| com.fasterxml.jackson.core:jackson-databind | CVE-2017-17485 | CRITICAL | 2.9.1 | 2.8.11, 2.9.4 | jackson-databind: Unsafe |
| | | | | | deserialization due to |
| | | | | | incomplete black list (incomplete |
| | | | | | fix for CVE-2017-15095)... |
| | | | | | -->avd.aquasec.com/nvd/cve-2017-17485 |
+ +------------------+ + +--------------------------------+---------------------------------------+
| | CVE-2020-9546 | | | 2.7.9.7, 2.8.11.6, 2.9.10.4 | jackson-databind: Serialization |
| | | | | | gadgets in shaded-hikari-config |
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9546 |
+ +------------------+ + + +---------------------------------------+
| | CVE-2020-9547 | | | | jackson-databind: Serialization |
| | | | | | gadgets in ibatis-sqlmap |
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9547 |
+ +------------------+ + + +---------------------------------------+
| | CVE-2020-9548 | | | | jackson-databind: Serialization |
| | | | | | gadgets in anteros-core |
| | | | | | -->avd.aquasec.com/nvd/cve-2020-9548 |
+---------------------------------------------+------------------+----------+-------------------+--------------------------------+---------------------------------------+
```
</details>

View File

@@ -26,6 +26,8 @@ import (
)
type csArgs struct {
Command string
RemoteAddrOption string
Format string
TemplatePath string
IgnoreUnfixed bool
@@ -35,6 +37,7 @@ type csArgs struct {
ClientToken string
ClientTokenHeader string
ListAllPackages bool
Target string
}
func TestClientServer(t *testing.T) {
@@ -220,6 +223,15 @@ func TestClientServer(t *testing.T) {
},
golden: "testdata/busybox-with-lockfile.json.golden",
},
{
name: "scan pox.xml with fs command in client/server mode",
args: csArgs{
Command: "fs",
RemoteAddrOption: "--server",
Target: "testdata/fixtures/fs/pom/",
},
golden: "testdata/pom.json.golden",
},
}
app, addr, cacheDir := setup(t, setupOptions{})
@@ -525,8 +537,14 @@ func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []stri
}
func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden string) ([]string, string) {
if c.Command == "" {
c.Command = "client"
}
if c.RemoteAddrOption == "" {
c.RemoteAddrOption = "--remote"
}
t.Helper()
osArgs := []string{"trivy", "--cache-dir", cacheDir, "client", "--remote", "http://" + addr}
osArgs := []string{"trivy", "--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
if c.Format != "" {
osArgs = append(osArgs, "--format", c.Format)
@@ -567,6 +585,10 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden st
osArgs = append(osArgs, "--output", outputFile)
if c.Target != "" {
osArgs = append(osArgs, c.Target)
}
return osArgs, outputFile
}

16
pkg/cache/nop.go vendored Normal file
View 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
View File

@@ -31,7 +31,6 @@ func NewRemoteCache(url string, customHeaders http.Header, insecure bool) cache.
},
},
}
c := rpcCache.NewCacheProtobufClient(url, httpClient)
return &RemoteCache{ctx: ctx, client: c}
}

View File

@@ -13,7 +13,6 @@ import (
"github.com/aquasecurity/trivy-db/pkg/metadata"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/commands/client"
"github.com/aquasecurity/trivy/pkg/commands/plugin"
"github.com/aquasecurity/trivy/pkg/commands/server"
"github.com/aquasecurity/trivy/pkg/result"
@@ -210,14 +209,14 @@ var (
token = cli.StringFlag{
Name: "token",
Usage: "for authentication",
Usage: "for authentication in client/server mode",
EnvVars: []string{"TRIVY_TOKEN"},
}
tokenHeader = cli.StringFlag{
Name: "token-header",
Value: "Trivy-Token",
Usage: "specify a header name for token",
Usage: "specify a header name for token in client/server mode",
EnvVars: []string{"TRIVY_TOKEN_HEADER"},
}
@@ -313,6 +312,18 @@ var (
EnvVars: []string{"TRIVY_INSECURE"},
}
remoteServer = cli.StringFlag{
Name: "server",
Usage: "server address",
EnvVars: []string{"TRIVY_SERVER"},
}
customHeaders = cli.StringSliceFlag{
Name: "custom-headers",
Usage: "custom headers in client/server mode",
EnvVars: []string{"TRIVY_CUSTOM_HEADERS"},
}
// Global flags
globalFlags = []cli.Flag{
&quietFlag,
@@ -473,9 +484,17 @@ func NewFilesystemCommand() *cli.Command {
&offlineScan,
stringSliceFlag(skipFiles),
stringSliceFlag(skipDirs),
// for misconfiguration
stringSliceFlag(configPolicy),
stringSliceFlag(configData),
stringSliceFlag(policyNamespaces),
// for client/server
&remoteServer,
&token,
&tokenHeader,
&customHeaders,
},
}
}
@@ -565,7 +584,7 @@ func NewClientCommand() *cli.Command {
Aliases: []string{"c"},
ArgsUsage: "image_name",
Usage: "client mode",
Action: client.Run,
Action: artifact.ImageRun,
Flags: []cli.Flag{
&templateFlag,
&formatFlag,
@@ -589,20 +608,17 @@ func NewClientCommand() *cli.Command {
&offlineScan,
&insecureFlag,
// original flags
&token,
&tokenHeader,
&customHeaders,
// original flags
&cli.StringFlag{
Name: "remote",
Value: "http://localhost:4954",
Usage: "server address",
EnvVars: []string{"TRIVY_REMOTE"},
},
&cli.StringSliceFlag{
Name: "custom-headers",
Usage: "custom headers",
EnvVars: []string{"TRIVY_CUSTOM_HEADERS"},
},
},
}
}

View File

@@ -26,5 +26,5 @@ func ConfigRun(ctx *cli.Context) error {
opt.SkipDBUpdate = true
// Run filesystem command internally
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
}

View File

@@ -7,15 +7,23 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/analyzer/config"
"github.com/aquasecurity/fanal/artifact"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/pkg/scanner"
)
func filesystemScanner(ctx context.Context, path string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, path, ac, lac, artifactOpt, scannerOpt)
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
func filesystemStandaloneScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
func filesystemRemoteScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
@@ -32,7 +40,13 @@ func FilesystemRun(ctx *cli.Context) error {
// Disable the individual package scanning
opt.DisabledAnalyzers = analyzer.TypeIndividualPkgs
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
// client/server mode
if opt.RemoteAddr != "" {
return Run(ctx.Context, opt, filesystemRemoteScanner, initCache)
}
// standalone mode
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
}
// RootfsRun runs scan on rootfs.
@@ -45,5 +59,11 @@ func RootfsRun(ctx *cli.Context) error {
// Disable the lock file scanning
opt.DisabledAnalyzers = analyzer.TypeLockfiles
return Run(ctx.Context, opt, filesystemScanner, initFSCache)
// client/server mode
if opt.RemoteAddr != "" {
return Run(ctx.Context, opt, filesystemRemoteScanner, initCache)
}
// standalone mode
return Run(ctx.Context, opt, filesystemStandaloneScanner, initCache)
}

View File

@@ -7,36 +7,67 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/analyzer/config"
"github.com/aquasecurity/fanal/artifact"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/types"
)
func archiveScanner(ctx context.Context, input string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
s, err := initializeArchiveScanner(ctx, input, ac, lac, artifactOpt, scannerOpt)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
}
return s, func() {}, nil
}
func dockerScanner(ctx context.Context, imageName string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
insecure bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
dockerOpt, err := types.GetDockerOption(insecure)
// imageScanner initializes a container image scanner in standalone mode
// $ trivy image alpine:3.15
func imageScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
if err != nil {
return scanner.Scanner{}, nil, err
}
s, cleanup, err := initializeDockerScanner(ctx, imageName, ac, lac, dockerOpt, artifactOpt, scannerOpt)
s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err)
}
return s, cleanup, nil
}
// ImageRun runs scan on docker image
// archiveScanner initializes an image archive scanner in standalone mode
// $ trivy image --input alpine.tar
func archiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
}
return s, func() {}, nil
}
// remoteImageScanner initializes a container image scanner in client/server mode
// $ trivy image --server localhost:4954 alpine:3.15
func remoteImageScanner(ctx context.Context, conf scannerConfig) (
scanner.Scanner, func(), error) {
// Scan an image in Docker Engine, Docker Registry, etc.
dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS)
if err != nil {
return scanner.Scanner{}, nil, err
}
s, cleanup, err := initializeRemoteDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
dockerOpt, conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the docker scanner: %w", err)
}
return s, cleanup, nil
}
// remoteArchiveScanner initializes an image archive scanner in client/server mode
// $ trivy image --server localhost:4954 --input alpine.tar
func remoteArchiveScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
// Scan tar file
s, err := initializeRemoteArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption,
conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, nil, xerrors.Errorf("unable to initialize the archive scanner: %w", err)
}
return s, func() {}, nil
}
// ImageRun runs scan on container image
func ImageRun(ctx *cli.Context) error {
opt, err := initOption(ctx)
if err != nil {
@@ -47,9 +78,34 @@ func ImageRun(ctx *cli.Context) error {
opt.DisabledAnalyzers = analyzer.TypeLockfiles
if opt.Input != "" {
// scan tar file
return Run(ctx.Context, opt, archiveScanner, initFSCache)
return archiveImageRun(ctx.Context, opt)
}
return Run(ctx.Context, opt, dockerScanner, initFSCache)
return imageRun(ctx.Context, opt)
}
func archiveImageRun(ctx context.Context, opt Option) error {
// standalone mode
scanner := archiveScanner
if opt.RemoteAddr != "" {
// client/server mode
scanner = remoteArchiveScanner
}
// scan tar file
return Run(ctx, opt, scanner, initCache)
}
func imageRun(ctx context.Context, opt Option) error {
// standalone mode
scanner := imageScanner
if opt.RemoteAddr != "" {
// client/server mode
scanner = remoteImageScanner
}
// scan container image
return Run(ctx, opt, scanner, initCache)
}

View File

@@ -13,9 +13,16 @@ import (
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/rpc/client"
"github.com/aquasecurity/trivy/pkg/scanner"
)
//////////////
// Standalone
//////////////
// initializeDockerScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc.
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option,
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
@@ -23,6 +30,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
return scanner.Scanner{}, nil, nil
}
// initializeArchiveScanner is for container image archive scanning in standalone mode
// e.g. docker save -o alpine.tar alpine:3.15
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
configScannerOption config.ScannerOption) (scanner.Scanner, error) {
@@ -30,6 +39,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
return scanner.Scanner{}, nil
}
// initializeFilesystemScanner is for filesystem scanning in standalone mode
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option,
configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
@@ -48,3 +58,39 @@ func initializeResultClient() result.Client {
wire.Build(result.SuperSet)
return result.Client{}
}
/////////////////
// Client/Server
/////////////////
// initializeRemoteDockerScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc.
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache,
remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option,
configScannerOption config.ScannerOption) (
scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteDockerSet)
return scanner.Scanner{}, nil, nil
}
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
// e.g. docker save -o alpine.tar alpine:3.15
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
scanner.Scanner, error) {
wire.Build(scanner.RemoteArchiveSet)
return scanner.Scanner{}, nil
}
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache,
remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (
scanner.Scanner, func(), error) {
wire.Build(scanner.RemoteFilesystemSet)
return scanner.Scanner{}, nil, nil
}
func initializeRemoteResultClient() result.Client {
wire.Build(result.SuperSet)
return result.Client{}
}

View File

@@ -17,6 +17,7 @@ type Option struct {
option.ReportOption
option.CacheOption
option.ConfigOption
option.RemoteOption
// We don't want to allow disabled analyzers to be passed by users,
// but it differs depending on scanning modes.
@@ -38,6 +39,7 @@ func NewOption(c *cli.Context) (Option, error) {
ReportOption: option.NewReportOption(c),
CacheOption: option.NewCacheOption(c),
ConfigOption: option.NewConfigOption(c),
RemoteOption: option.NewRemoteOption(c),
}, nil
}
@@ -55,7 +57,6 @@ func (c *Option) Init() error {
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
return err
}
return nil
}
@@ -69,6 +70,7 @@ func (c *Option) initPreScanOptions() error {
if err := c.CacheOption.Init(); err != nil {
return err
}
c.RemoteOption.Init(c.Logger)
return nil
}

View File

@@ -2,6 +2,7 @@ package artifact
import (
"flag"
"net/http"
"os"
"testing"
@@ -60,6 +61,81 @@ func TestOption_Init(t *testing.T) {
},
},
},
{
name: "happy path with token and token header",
args: []string{"--server", "http://localhost:8080", "--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{
RemoteAddr: "http://localhost:8080",
CustomHeaders: http.Header{
"X-Trivy-Token": []string{"secret"},
},
},
},
},
{
name: "invalid option combination: token and token header without server",
args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
logs: []string{
"'--token', '--token-header' and 'custom-header' can be used only with '--server'",
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
},
},
{
name: "happy path with good custom headers",
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foo:bar", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{
RemoteAddr: "http://localhost:8080",
CustomHeaders: http.Header{
"Foo": []string{"bar"},
}},
},
},
{
name: "happy path with bad custom headers",
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foobaz", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{RemoteAddr: "http://localhost:8080", CustomHeaders: http.Header{}},
},
},
{
name: "happy path: reset",
args: []string{"--reset"},
@@ -182,6 +258,10 @@ func TestOption_Init(t *testing.T) {
set.String("security-checks", "vuln", "")
set.String("template", "", "")
set.String("format", "", "")
set.String("server", "", "")
set.String("token", "", "")
set.String("token-header", "", "")
set.Var(&cli.StringSlice{}, "custom-headers", "")
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)

View File

@@ -7,16 +7,14 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/fanal/analyzer"
"github.com/aquasecurity/fanal/analyzer/config"
"github.com/aquasecurity/fanal/artifact"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/types"
)
func repositoryScanner(ctx context.Context, dir string, ac cache.ArtifactCache, lac cache.LocalArtifactCache,
_ bool, artifactOpt artifact.Option, scannerOpt config.ScannerOption) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, dir, ac, lac, artifactOpt, scannerOpt)
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
func repositoryScanner(ctx context.Context, conf scannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache,
conf.ArtifactOption, conf.MisconfOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
@@ -36,5 +34,5 @@ func RepositoryRun(ctx *cli.Context) error {
// Disable the OS analyzers and individual package analyzers
opt.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
return Run(ctx.Context, opt, repositoryScanner, initFSCache)
return Run(ctx.Context, opt, repositoryScanner, initCache)
}

View File

@@ -13,9 +13,11 @@ import (
"github.com/aquasecurity/fanal/artifact"
"github.com/aquasecurity/fanal/cache"
"github.com/aquasecurity/trivy-db/pkg/db"
tcache "github.com/aquasecurity/trivy/pkg/cache"
"github.com/aquasecurity/trivy/pkg/commands/operation"
"github.com/aquasecurity/trivy/pkg/log"
pkgReport "github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/rpc/client"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
@@ -25,9 +27,26 @@ const defaultPolicyNamespace = "appshield"
var errSkipScan = errors.New("skip subsequent processes")
type scannerConfig struct {
// e.g. image name and file path
Target string
// Cache
ArtifactCache cache.ArtifactCache
LocalArtifactCache cache.LocalArtifactCache
// Client/Server options
RemoteOption client.ScannerOption
// Artifact options
ArtifactOption artifact.Option
// Misconfiguration scanning options
MisconfOption config.ScannerOption
}
// InitializeScanner defines the initialize function signature of scanner
type InitializeScanner func(context.Context, string, cache.ArtifactCache, cache.LocalArtifactCache, bool,
artifact.Option, config.ScannerOption) (scanner.Scanner, func(), error)
type InitializeScanner func(context.Context, scannerConfig) (scanner.Scanner, func(), error)
// InitCache defines cache initializer
type InitCache func(c Option) (cache.Cache, error)
@@ -37,7 +56,11 @@ func Run(ctx context.Context, opt Option, initializeScanner InitializeScanner, i
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
defer cancel()
return runWithTimeout(ctx, opt, initializeScanner, initCache)
err := runWithTimeout(ctx, opt, initializeScanner, initCache)
if xerrors.Is(err, context.DeadlineExceeded) {
log.Logger.Warn("Increase --timeout value")
}
return err
}
func runWithTimeout(ctx context.Context, opt Option, initializeScanner InitializeScanner, initCache InitCache) error {
@@ -54,8 +77,8 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
}
defer cacheClient.Close()
// When scanning config files, it doesn't need to download the vulnerability database.
if utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
if opt.RemoteAddr == "" && utils.StringInSlice(types.SecurityCheckVulnerability, opt.SecurityChecks) {
if err = initDB(opt); err != nil {
if errors.Is(err, errSkipScan) {
return nil
@@ -92,7 +115,14 @@ func runWithTimeout(ctx context.Context, opt Option, initializeScanner Initializ
return nil
}
func initFSCache(c Option) (cache.Cache, error) {
func initCache(c Option) (cache.Cache, error) {
// client/server mode
if c.RemoteAddr != "" {
remoteCache := tcache.NewRemoteCache(c.RemoteAddr, c.CustomHeaders, c.Insecure)
return tcache.NopCache(remoteCache), nil
}
// standalone mode
utils.SetCacheDir(c.CacheDir)
cache, err := operation.NewCache(c.CacheOption)
if err != nil {
@@ -181,7 +211,7 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
}
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
// ScannerOptions is filled only when config scanning is enabled.
// ScannerOption is filled only when config scanning is enabled.
var configScannerOptions config.ScannerOption
if utils.StringInSlice(types.SecurityCheckConfig, opt.SecurityChecks) {
noProgress := opt.Quiet || opt.NoProgress
@@ -199,16 +229,25 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
}
}
artifactOpt := artifact.Option{
s, cleanup, err := initializeScanner(ctx, scannerConfig{
Target: target,
ArtifactCache: cacheClient,
LocalArtifactCache: cacheClient,
RemoteOption: client.ScannerOption{
RemoteURL: opt.RemoteAddr,
CustomHeaders: opt.CustomHeaders,
Insecure: opt.Insecure,
},
ArtifactOption: artifact.Option{
DisabledAnalyzers: disabledAnalyzers(opt),
SkipFiles: opt.SkipFiles,
SkipDirs: opt.SkipDirs,
InsecureSkipTLS: opt.Insecure,
Offline: opt.OfflineScan,
NoProgress: opt.NoProgress || opt.Quiet,
}
s, cleanup, err := initializeScanner(ctx, target, cacheClient, cacheClient, opt.Insecure, artifactOpt, configScannerOptions)
},
MisconfOption: configScannerOptions,
})
if err != nil {
return types.Report{}, xerrors.Errorf("unable to initialize a scanner: %w", err)
}
@@ -225,7 +264,10 @@ func filter(ctx context.Context, opt Option, report types.Report) (types.Report,
resultClient := initializeResultClient()
results := report.Results
for i := range results {
// Fill vulnerability info only in standalone mode
if opt.RemoteAddr == "" {
resultClient.FillVulnerabilityInfo(results[i].Vulnerabilities, results[i].Type)
}
vulns, misconfSummary, misconfs, err := resultClient.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations,
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
if err != nil {

View File

@@ -20,12 +20,15 @@ import (
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/detector/ospkg"
"github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/rpc/client"
"github.com/aquasecurity/trivy/pkg/scanner"
"github.com/aquasecurity/trivy/pkg/scanner/local"
)
// Injectors from inject.go:
// initializeDockerScanner is for container image scanning in standalone mode
// e.g. dockerd, container registry, podman, etc.
func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
@@ -45,6 +48,8 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach
}, nil
}
// initializeArchiveScanner is for container image archive scanning in standalone mode
// e.g. docker save -o alpine.tar alpine:3.15
func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
@@ -61,6 +66,7 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach
return scannerScanner, nil
}
// initializeFilesystemScanner is for filesystem scanning in standalone mode
func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
applierApplier := applier.NewApplier(localArtifactCache)
detector := ospkg.Detector{}
@@ -93,3 +99,56 @@ func initializeResultClient() result.Client {
client := result.NewClient(dbConfig)
return client
}
// initializeRemoteDockerScanner is for container image scanning in client/server mode
// e.g. dockerd, container registry, podman, etc.
func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
clientScanner := client.NewScanner(remoteScanOptions)
typesImage, cleanup, err := image.NewDockerImage(ctx, imageName, dockerOpt)
if err != nil {
return scanner.Scanner{}, nil, err
}
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
if err != nil {
cleanup()
return scanner.Scanner{}, nil, err
}
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
return scannerScanner, func() {
cleanup()
}, nil
}
// initializeRemoteArchiveScanner is for container image archive scanning in client/server mode
// e.g. docker save -o alpine.tar alpine:3.15
func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, error) {
clientScanner := client.NewScanner(remoteScanOptions)
typesImage, err := image.NewArchiveImage(filePath)
if err != nil {
return scanner.Scanner{}, err
}
artifactArtifact, err := image2.NewArtifact(typesImage, artifactCache, artifactOption, configScannerOption)
if err != nil {
return scanner.Scanner{}, err
}
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
return scannerScanner, nil
}
// initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode
func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option, configScannerOption config.ScannerOption) (scanner.Scanner, func(), error) {
clientScanner := client.NewScanner(remoteScanOptions)
artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption, configScannerOption)
if err != nil {
return scanner.Scanner{}, nil, err
}
scannerScanner := scanner.NewScanner(clientScanner, artifactArtifact)
return scannerScanner, func() {
}, nil
}
func initializeRemoteResultClient() result.Client {
dbConfig := db.Config{}
resultClient := result.NewClient(dbConfig)
return resultClient
}

View File

@@ -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{}
}

View File

@@ -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
}

View File

@@ -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)
})
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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
}

View File

@@ -56,6 +56,8 @@ func NewCache(c option.CacheOption) (Cache, error) {
redisCache := cache.NewRedisCache(options)
return Cache{Cache: redisCache}, nil
}
// standalone mode
fsCache, err := cache.NewFSCache(utils.CacheDir())
if err != nil {
return Cache{}, xerrors.Errorf("unable to initialize fs cache: %w", err)

View 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
}

View 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)
})
}
}

View File

@@ -5,58 +5,63 @@ import (
"crypto/tls"
"net/http"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/google/wire"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/fanal/types"
r "github.com/aquasecurity/trivy/pkg/rpc"
"github.com/aquasecurity/trivy/pkg/types"
rpc "github.com/aquasecurity/trivy/rpc/scanner"
)
// SuperSet binds the dependencies for RPC client
var SuperSet = wire.NewSet(
NewProtobufClient,
NewScanner,
)
// RemoteURL for RPC remote host
type RemoteURL string
// Insecure for RPC remote host
type Insecure bool
// NewProtobufClient is the factory method to return RPC scanner
func NewProtobufClient(remoteURL RemoteURL, insecure Insecure) rpc.Scanner {
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: bool(insecure),
},
},
type options struct {
rpcClient rpc.Scanner
}
return rpc.NewScannerProtobufClient(string(remoteURL), httpClient)
type option func(*options)
// WithRPCClient takes rpc client for testability
func WithRPCClient(c rpc.Scanner) option {
return func(opts *options) {
opts.rpcClient = c
}
}
// CustomHeaders for holding HTTP headers
type CustomHeaders http.Header
// ScannerOption holds options for RPC client
type ScannerOption struct {
RemoteURL string
Insecure bool
CustomHeaders http.Header
}
// Scanner implements the RPC scanner
type Scanner struct {
customHeaders CustomHeaders
customHeaders http.Header
client rpc.Scanner
}
// NewScanner is the factory method to return RPC Scanner
func NewScanner(customHeaders CustomHeaders, s rpc.Scanner) Scanner {
return Scanner{customHeaders: customHeaders, client: s}
func NewScanner(scannerOptions ScannerOption, opts ...option) Scanner {
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: scannerOptions.Insecure,
},
},
}
c := rpc.NewScannerProtobufClient(scannerOptions.RemoteURL, httpClient)
o := &options{rpcClient: c}
for _, opt := range opts {
opt(o)
}
return Scanner{customHeaders: scannerOptions.CustomHeaders, client: o.rpcClient}
}
// Scan scans the image
func (s Scanner) Scan(target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, *ftypes.OS, error) {
ctx := WithCustomHeaders(context.Background(), http.Header(s.customHeaders))
ctx := WithCustomHeaders(context.Background(), s.customHeaders)
var res *rpc.ScanResponse
err := r.Retry(func() error {

View File

@@ -1,94 +1,27 @@
package client
import (
"context"
"errors"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protojson"
ftypes "github.com/aquasecurity/fanal/types"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/utils"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/rpc/common"
"github.com/aquasecurity/trivy/rpc/scanner"
rpc "github.com/aquasecurity/trivy/rpc/scanner"
)
type mockScanner struct {
mock.Mock
}
type scanArgs struct {
Ctx context.Context
CtxAnything bool
Request *scanner.ScanRequest
RequestAnything bool
}
type scanReturns struct {
Res *scanner.ScanResponse
Err error
}
type scanExpectation struct {
Args scanArgs
Returns scanReturns
}
func (_m *mockScanner) ApplyScanExpectation(e scanExpectation) {
var args []interface{}
if e.Args.CtxAnything {
args = append(args, mock.Anything)
} else {
args = append(args, e.Args.Ctx)
}
if e.Args.RequestAnything {
args = append(args, mock.Anything)
} else {
args = append(args, e.Args.Request)
}
_m.On("Scan", args...).Return(e.Returns.Res, e.Returns.Err)
}
func (_m *mockScanner) ApplyScanExpectations(expectations []scanExpectation) {
for _, e := range expectations {
_m.ApplyScanExpectation(e)
}
}
// Scan provides a mock function with given fields: Ctx, Request
func (_m *mockScanner) Scan(Ctx context.Context, Request *scanner.ScanRequest) (*scanner.ScanResponse, error) {
ret := _m.Called(Ctx, Request)
var r0 *scanner.ScanResponse
if rf, ok := ret.Get(0).(func(context.Context, *scanner.ScanRequest) *scanner.ScanResponse); ok {
r0 = rf(Ctx, Request)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*scanner.ScanResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *scanner.ScanRequest) error); ok {
r1 = rf(Ctx, Request)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func TestScanner_Scan(t *testing.T) {
type fields struct {
customHeaders CustomHeaders
}
type args struct {
target string
imageID string
@@ -97,9 +30,9 @@ func TestScanner_Scan(t *testing.T) {
}
tests := []struct {
name string
fields fields
customHeaders http.Header
args args
scanExpectation scanExpectation
expectation *rpc.ScanResponse
wantResults types.Results
wantOS *ftypes.OS
wantEosl bool
@@ -107,11 +40,9 @@ func TestScanner_Scan(t *testing.T) {
}{
{
name: "happy path",
fields: fields{
customHeaders: CustomHeaders{
customHeaders: http.Header{
"Trivy-Token": []string{"foo"},
},
},
args: args{
target: "alpine:3.11",
imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
@@ -120,26 +51,13 @@ func TestScanner_Scan(t *testing.T) {
VulnType: []string{"os"},
},
},
scanExpectation: scanExpectation{
Args: scanArgs{
CtxAnything: true,
Request: &scanner.ScanRequest{
Target: "alpine:3.11",
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
Options: &scanner.ScanOptions{
VulnType: []string{"os"},
},
},
},
Returns: scanReturns{
Res: &scanner.ScanResponse{
expectation: &rpc.ScanResponse{
Os: &common.OS{
Family: "alpine",
Name: "3.11",
Eosl: true,
},
Results: []*scanner.Result{
Results: []*rpc.Result{
{
Target: "alpine:3.11",
Vulnerabilities: []*common.Vulnerability{
@@ -182,8 +100,6 @@ func TestScanner_Scan(t *testing.T) {
},
},
},
},
},
wantResults: types.Results{
{
Target: "alpine:3.11",
@@ -232,11 +148,9 @@ func TestScanner_Scan(t *testing.T) {
},
{
name: "sad path: Scan returns an error",
fields: fields{
customHeaders: CustomHeaders{
customHeaders: http.Header{
"Trivy-Token": []string{"foo"},
},
},
args: args{
target: "alpine:3.11",
imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
@@ -245,41 +159,44 @@ func TestScanner_Scan(t *testing.T) {
VulnType: []string{"os"},
},
},
scanExpectation: scanExpectation{
Args: scanArgs{
CtxAnything: true,
Request: &scanner.ScanRequest{
Target: "alpine:3.11",
ArtifactId: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a",
BlobIds: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"},
Options: &scanner.ScanOptions{
VulnType: []string{"os"},
},
},
},
Returns: scanReturns{
Err: errors.New("error"),
},
},
wantErr: "failed to detect vulnerabilities via RPC",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := new(mockScanner)
mockClient.ApplyScanExpectation(tt.scanExpectation)
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tt.expectation == nil {
e := map[string]interface{}{
"code": "not_found",
"msg": "expectation is empty",
}
b, _ := json.Marshal(e)
w.WriteHeader(http.StatusBadGateway)
w.Write(b)
return
}
b, err := protojson.Marshal(tt.expectation)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "json marshalling error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}))
client := rpc.NewScannerJSONClient(ts.URL, ts.Client())
s := NewScanner(ScannerOption{CustomHeaders: tt.customHeaders}, WithRPCClient(client))
s := NewScanner(tt.fields.customHeaders, mockClient)
gotResults, gotOS, err := s.Scan(tt.args.target, tt.args.imageID, tt.args.layerIDs, tt.args.options)
if tt.wantErr != "" {
require.NotNil(t, err, tt.name)
require.Contains(t, err.Error(), tt.wantErr, tt.name)
return
} else {
require.NoError(t, err, tt.name)
}
require.NoError(t, err, tt.name)
assert.Equal(t, tt.wantResults, gotResults)
assert.Equal(t, tt.wantOS, gotOS)
})
@@ -290,36 +207,32 @@ func TestScanner_ScanServerInsecure(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer ts.Close()
type args struct {
request *scanner.ScanRequest
insecure bool
}
tests := []struct {
name string
args args
insecure bool
wantErr string
}{
{
name: "happy path",
args: args{
request: &scanner.ScanRequest{},
insecure: true,
},
},
{
name: "sad path",
args: args{
request: &scanner.ScanRequest{},
insecure: false,
},
wantErr: "certificate signed by unknown authority",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := NewProtobufClient(RemoteURL(ts.URL), Insecure(tt.args.insecure))
_, err := s.Scan(context.Background(), tt.args.request)
c := rpc.NewScannerProtobufClient(ts.URL, &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: tt.insecure,
},
},
})
s := NewScanner(ScannerOption{Insecure: tt.insecure}, WithRPCClient(c))
_, _, err := s.Scan("dummy", "", nil, types.ScanOptions{})
if tt.wantErr != "" {
require.Error(t, err)

View File

@@ -19,6 +19,10 @@ import (
"github.com/aquasecurity/trivy/pkg/types"
)
///////////////
// Standalone
///////////////
// StandaloneSuperSet is used in the standalone mode
var StandaloneSuperSet = wire.NewSet(
local.SuperSet,
@@ -52,22 +56,33 @@ var StandaloneRepositorySet = wire.NewSet(
StandaloneSuperSet,
)
/////////////////
// Client/Server
/////////////////
// RemoteSuperSet is used in the client mode
var RemoteSuperSet = wire.NewSet(
aimage.NewArtifact,
client.SuperSet,
client.NewScanner,
wire.Bind(new(Driver), new(client.Scanner)),
NewScanner,
)
// RemoteFilesystemSet binds filesystem dependencies for client/server mode
var RemoteFilesystemSet = wire.NewSet(
flocal.NewArtifact,
RemoteSuperSet,
)
// RemoteDockerSet binds remote docker dependencies
var RemoteDockerSet = wire.NewSet(
aimage.NewArtifact,
image.NewDockerImage,
RemoteSuperSet,
)
// RemoteArchiveSet binds remote archive dependencies
var RemoteArchiveSet = wire.NewSet(
aimage.NewArtifact,
image.NewArchiveImage,
RemoteSuperSet,
)