Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(image): custom docker host option #3599

Merged
merged 14 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,

Expand Down
18 changes: 14 additions & 4 deletions pkg/commands/artifact/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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, "")
aswath-s-tw marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return scanner.Scanner{}, nil, err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Option struct {
SBOMSources []string
RekorURL string
Platform string
DockerHost string
Slow bool // Lower CPU and memory
AWSRegion string

Expand Down
4 changes: 2 additions & 2 deletions pkg/fanal/image/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, opt types.DockerOption) (types.Image, func(), error) {
img, cleanup, err := daemon.DockerImage(ref, opt.DockerHost)
if err != nil {
return nil, nil, err
}
Expand Down
10 changes: 8 additions & 2 deletions pkg/fanal/image/daemon/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ 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())
opts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}
if host != "" {
// adding host parameter to the last assuming it will pickup more preference
opts = append(opts, client.WithHost(host))
}
c, err := client.NewClientWithOpts(opts...)

if err != nil {
return nil, cleanup, xerrors.Errorf("failed to initialize a docker client: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/fanal/image/daemon/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
77 changes: 62 additions & 15 deletions pkg/fanal/image/daemon/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"testing"
"time"

Expand All @@ -17,18 +19,19 @@ import (
"github.com/aquasecurity/testdocker/engine"
)

func TestMain(m *testing.M) {
imagePaths := map[string]string{
"alpine:3.10": "../../test/testdata/alpine-310.tar.gz",
"alpine:3.11": "../../test/testdata/alpine-311.tar.gz",
"gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz",
}
var imagePaths = map[string]string{
"alpine:3.10": "../../test/testdata/alpine-310.tar.gz",
"alpine:3.11": "../../test/testdata/alpine-311.tar.gz",
"gcr.io/distroless/base": "../../test/testdata/distroless.tar.gz",
}

// for Docker
opt := engine.Option{
APIVersion: "1.38",
ImagePaths: imagePaths,
}
// for Docker
var opt = engine.Option{
APIVersion: "1.38",
ImagePaths: imagePaths,
}

func TestMain(m *testing.M) {
te := engine.NewDockerEngine(opt)
defer te.Close()

Expand Down Expand Up @@ -59,7 +62,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()

Expand All @@ -70,6 +73,50 @@ func Test_image_ConfigName(t *testing.T) {
}
}

func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) {

ref, err := name.ParseReference("alpine:3.11")
require.NoError(t, err)

eo := engine.Option{
APIVersion: opt.APIVersion,
ImagePaths: opt.ImagePaths,
}

var dockerHostParam string

if runtime.GOOS != "windows" {
runtimeDir, err := ioutil.TempDir("", "daemon")
require.NoError(t, err)

dir := filepath.Join(runtimeDir, "image")
err = os.MkdirAll(dir, os.ModePerm)
require.NoError(t, err)

customDockerHost := filepath.Join(dir, "image-test-unix-socket.sock")
eo.UnixDomainSocket = customDockerHost
dockerHostParam = "unix://" + customDockerHost
}

te := engine.NewDockerEngine(eo)
defer te.Close()

if runtime.GOOS == "windows" {
dockerHostParam = te.Listener.Addr().Network() + "://" + te.Listener.Addr().String()
}

img, cleanup, err := DockerImage(ref, dockerHostParam)
require.NoError(t, err)
defer cleanup()

conf, err := img.ConfigName()
assert.Equal(t, v1.Hash{
Algorithm: "sha256",
Hex: "a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
}, conf)
assert.Nil(t, err)
}

func Test_image_ConfigFile(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -156,7 +203,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()

Expand Down Expand Up @@ -201,7 +248,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()

Expand Down Expand Up @@ -230,7 +277,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()

Expand Down
2 changes: 1 addition & 1 deletion pkg/fanal/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
if err == nil {
// Return v1.Image if the image is found in Docker Engine
return img, cleanup, nil
Expand Down
3 changes: 3 additions & 0 deletions pkg/fanal/types/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ type DockerOption struct {

// Architecture
Platform string

// Unix domain socket path
DockerHost string
}
12 changes: 11 additions & 1 deletion pkg/flag/image_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,28 @@ 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 {
Input *Flag // local image archive
ImageConfigScanners *Flag
ScanRemovedPkgs *Flag
Platform *Flag
DockerHost *Flag
}

type ImageOptions struct {
Input string
ImageConfigScanners types.Scanners
ScanRemovedPkgs bool
Platform string
DockerHost string
}

func NewImageFlagGroup() *ImageFlagGroup {
Expand All @@ -58,6 +66,7 @@ func NewImageFlagGroup() *ImageFlagGroup {
ImageConfigScanners: &ImageConfigScannersFlag,
ScanRemovedPkgs: &ScanRemovedPkgsFlag,
Platform: &PlatformFlag,
DockerHost: &DockerHostFlag,
}
}

Expand All @@ -66,7 +75,7 @@ func (f *ImageFlagGroup) Name() string {
}

func (f *ImageFlagGroup) Flags() []*Flag {
return []*Flag{f.Input, f.ImageConfigScanners, f.ScanRemovedPkgs, f.Platform}
return []*Flag{f.Input, f.ImageConfigScanners, f.ScanRemovedPkgs, f.Platform, f.DockerHost}
}

func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) {
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion pkg/types/docker_conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -29,5 +29,6 @@ func GetDockerOption(insecureTlsSkip bool, Platform string) (types.DockerOption,
InsecureSkipTLSVerify: insecureTlsSkip,
NonSSL: cfg.NonSSL,
Platform: Platform,
DockerHost: DockerHost,
}, nil
}
1 change: 1 addition & 0 deletions pkg/types/scanoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ type ScanOptions struct {
ListAllPackages bool
LicenseCategories map[types.LicenseCategory][]string
FilePatterns []string
DockerHost string
}