diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index fea6e33666d1..dfcc6f67b7d7 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -531,6 +531,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand Platform: opts.Platform, // this is valid only for 'image' subcommand + DockerHost: opts.DockerHost, // this is valid only for 'image' subcommand ListAllPackages: opts.ListAllPkgs, LicenseCategories: opts.LicenseCategories, FilePatterns: opts.FilePatterns, @@ -617,6 +618,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi SBOMSources: opts.SBOMSources, RekorURL: opts.RekorURL, Platform: opts.Platform, + DockerHost: opts.DockerHost, Slow: opts.Slow, AWSRegion: opts.Region, diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go index 74df2f6f1270..dbd80669879d 100644 --- a/pkg/commands/artifact/scanner.go +++ b/pkg/commands/artifact/scanner.go @@ -12,12 +12,22 @@ import ( // imageStandaloneScanner initializes a container image scanner in standalone mode // $ trivy image alpine:3.15 func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS, conf.ArtifactOption.Platform) + dockerOpt, err := types.GetDockerOption( + conf.ArtifactOption.InsecureSkipTLS, + conf.ArtifactOption.Platform, + conf.ArtifactOption.DockerHost, + ) if err != nil { return scanner.Scanner{}, nil, err } - s, cleanup, err := initializeDockerScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, - dockerOpt, conf.ArtifactOption) + s, cleanup, err := initializeDockerScanner( + ctx, + conf.Target, + conf.ArtifactCache, + conf.LocalArtifactCache, + dockerOpt, + conf.ArtifactOption, + ) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a docker scanner: %w", err) } @@ -39,7 +49,7 @@ func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner. func imageRemoteScanner(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, conf.ArtifactOption.Platform) + dockerOpt, err := types.GetDockerOption(conf.ArtifactOption.InsecureSkipTLS, conf.ArtifactOption.Platform, "") if err != nil { return scanner.Scanner{}, nil, err } diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index 89aa9cbb60d8..2b4f6bb7df82 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -26,6 +26,7 @@ type Option struct { SBOMSources []string RekorURL string Platform string + DockerHost string Slow bool // Lower CPU and memory AWSRegion string diff --git a/pkg/fanal/image/daemon.go b/pkg/fanal/image/daemon.go index 4be9c361d604..a2d7a0c26b8e 100644 --- a/pkg/fanal/image/daemon.go +++ b/pkg/fanal/image/daemon.go @@ -9,8 +9,8 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func tryDockerDaemon(imageName string, ref name.Reference) (types.Image, func(), error) { - img, cleanup, err := daemon.DockerImage(ref) +func tryDockerDaemon(imageName string, ref name.Reference, host string) (types.Image, func(), error) { + img, cleanup, err := daemon.DockerImage(ref, host) if err != nil { return nil, nil, err } diff --git a/pkg/fanal/image/daemon/docker.go b/pkg/fanal/image/daemon/docker.go index 26a707390cac..c10065f92173 100644 --- a/pkg/fanal/image/daemon/docker.go +++ b/pkg/fanal/image/daemon/docker.go @@ -11,10 +11,17 @@ import ( // DockerImage implements v1.Image by extending daemon.Image. // The caller must call cleanup() to remove a temporary file. -func DockerImage(ref name.Reference) (Image, func(), error) { +func DockerImage(ref name.Reference, host string) (Image, func(), error) { cleanup := func() {} - c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + var c *client.Client + var err error + if host == "" { + c, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + } else { + // adding host parameter to the last assuming it will pickup more preference + c, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithHost(host)) + } if err != nil { return nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err) } diff --git a/pkg/fanal/image/daemon/docker_test.go b/pkg/fanal/image/daemon/docker_test.go index 25c6612c66a9..a68c935289a3 100644 --- a/pkg/fanal/image/daemon/docker_test.go +++ b/pkg/fanal/image/daemon/docker_test.go @@ -40,7 +40,7 @@ func TestDockerImage(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - _, cleanup, err := DockerImage(ref) + _, cleanup, err := DockerImage(ref, "") assert.Equal(t, tt.wantErr, err != nil, err) defer func() { if cleanup != nil { diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go index 543e5ee3b444..b124cc8abf7a 100644 --- a/pkg/fanal/image/daemon/image_test.go +++ b/pkg/fanal/image/daemon/image_test.go @@ -59,7 +59,7 @@ func Test_image_ConfigName(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - img, cleanup, err := DockerImage(ref) + img, cleanup, err := DockerImage(ref, "") require.NoError(t, err) defer cleanup() @@ -156,7 +156,7 @@ func Test_image_ConfigFile(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - img, cleanup, err := DockerImage(ref) + img, cleanup, err := DockerImage(ref, "") require.NoError(t, err) defer cleanup() @@ -201,7 +201,7 @@ func Test_image_LayerByDiffID(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - img, cleanup, err := DockerImage(ref) + img, cleanup, err := DockerImage(ref, "") require.NoError(t, err) defer cleanup() @@ -230,7 +230,7 @@ func Test_image_RawConfigFile(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - img, cleanup, err := DockerImage(ref) + img, cleanup, err := DockerImage(ref, "") require.NoError(t, err) defer cleanup() diff --git a/pkg/fanal/image/image.go b/pkg/fanal/image/image.go index 7ac9e0af80cb..e7b2c0579ac1 100644 --- a/pkg/fanal/image/image.go +++ b/pkg/fanal/image/image.go @@ -67,7 +67,7 @@ func NewContainerImage(ctx context.Context, imageName string, option types.Docke // Try accessing Docker Daemon if o.dockerd { - img, cleanup, err := tryDockerDaemon(imageName, ref) + img, cleanup, err := tryDockerDaemon(imageName, ref, option.DockerHost) if err == nil { // Return v1.Image if the image is found in Docker Engine return img, cleanup, nil diff --git a/pkg/fanal/types/docker.go b/pkg/fanal/types/docker.go index 511c140d91fa..8186d8520a5b 100644 --- a/pkg/fanal/types/docker.go +++ b/pkg/fanal/types/docker.go @@ -23,4 +23,7 @@ type DockerOption struct { // Architecture Platform string + + // Unix domain socket path + DockerHost string } diff --git a/pkg/flag/image_flags.go b/pkg/flag/image_flags.go index e7f337e99f8c..202ddbf73c29 100644 --- a/pkg/flag/image_flags.go +++ b/pkg/flag/image_flags.go @@ -36,6 +36,12 @@ var ( Value: "", Usage: "set platform in the form os/arch if image is multi-platform capable", } + DockerHostFlag = Flag{ + Name: "docker-host", + ConfigName: "image.docker-host", + Value: "", + Usage: "unix domain socket path to use for docker standalone scanning", + } ) type ImageFlagGroup struct { @@ -43,6 +49,7 @@ type ImageFlagGroup struct { ImageConfigScanners *Flag ScanRemovedPkgs *Flag Platform *Flag + DockerHost *Flag } type ImageOptions struct { @@ -50,6 +57,7 @@ type ImageOptions struct { ImageConfigScanners types.Scanners ScanRemovedPkgs bool Platform string + DockerHost string } func NewImageFlagGroup() *ImageFlagGroup { @@ -58,6 +66,7 @@ func NewImageFlagGroup() *ImageFlagGroup { ImageConfigScanners: &ImageConfigScannersFlag, ScanRemovedPkgs: &ScanRemovedPkgsFlag, Platform: &PlatformFlag, + DockerHost: &DockerHostFlag, } } @@ -79,5 +88,6 @@ func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { ImageConfigScanners: scanners, ScanRemovedPkgs: getBool(f.ScanRemovedPkgs), Platform: getString(f.Platform), + DockerHost: getString(f.DockerHost), }, nil } diff --git a/pkg/types/docker_conf.go b/pkg/types/docker_conf.go index 345dbeff3470..719b9f475474 100644 --- a/pkg/types/docker_conf.go +++ b/pkg/types/docker_conf.go @@ -16,7 +16,7 @@ type DockerConfig struct { } // GetDockerOption returns the Docker scanning options using DockerConfig -func GetDockerOption(insecureTlsSkip bool, Platform string) (types.DockerOption, error) { +func GetDockerOption(insecureTlsSkip bool, Platform string, DockerHost string) (types.DockerOption, error) { cfg := DockerConfig{} if err := env.Parse(&cfg); err != nil { return types.DockerOption{}, xerrors.Errorf("unable to parse environment variables: %w", err) @@ -29,5 +29,6 @@ func GetDockerOption(insecureTlsSkip bool, Platform string) (types.DockerOption, InsecureSkipTLSVerify: insecureTlsSkip, NonSSL: cfg.NonSSL, Platform: Platform, + DockerHost: DockerHost, }, nil } diff --git a/pkg/types/scanoptions.go b/pkg/types/scanoptions.go index de42889a8ca3..5c1dfa3898ad 100644 --- a/pkg/types/scanoptions.go +++ b/pkg/types/scanoptions.go @@ -14,4 +14,5 @@ type ScanOptions struct { ListAllPackages bool LicenseCategories map[types.LicenseCategory][]string FilePatterns []string + DockerHost string }