Skip to content

Commit

Permalink
feat(image): Add image-src flag to specify which runtime(s) to use (#…
Browse files Browse the repository at this point in the history
…4047)

Signed-off-by: Peter Engelbert <pmengelbert@gmail.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
  • Loading branch information
pmengelbert and knqyf263 authored May 15, 2023
1 parent 50c8b41 commit 6a0e152
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 114 deletions.
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ trivy image [flags] IMAGE_NAME
--ignored-licenses strings specify a list of license to ignore
--ignorefile string specify .trivyignore file (default ".trivyignore")
--image-config-scanners string comma-separated list of what security issues to detect on container image configurations (config,secret)
--image-src strings image source(s) to use, in priority order (docker,containerd,podman,remote) (default [docker,containerd,podman,remote])
--include-non-failures include successes and exceptions, available with '--scanners config'
--input string input file path instead of image name
--java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db")
Expand Down
17 changes: 17 additions & 0 deletions docs/docs/target/container_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,23 @@ GitHub Personal Access Token
You can see environment variables with `docker inspect`.

## Supported

Trivy will look for the specified image in a series of locations. By default, it
will first look in the local Docker Engine, then Containerd, Podman, and
finally container registry.

This behavior can be modified with the `--image-src` flag. For example, the
command

```bash
trivy image --image-src podman,containerd alpine:3.7.3
```

Will first search in Podman. If the image is found there, it will be scanned
and the results returned. If the image is not found in Podman, then Trivy will
search in Containerd. If the image is not found there either, the scan will
fail and no more image sources will be searched.

### Docker Engine
Trivy tries to looks for the specified image in your local Docker Engine.
It will be skipped if Docker Engine is not running locally.
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
DockerOptions: ftypes.DockerOptions{
Host: opts.DockerHost,
},
ImageSources: opts.ImageSources,
},

// For misconfiguration scanning
Expand Down
25 changes: 9 additions & 16 deletions pkg/commands/artifact/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions pkg/compliance/spec/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import (
"os"
"strings"

defsecTypes "github.com/aquasecurity/defsec/pkg/types"

"golang.org/x/exp/maps"
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"

sp "github.com/aquasecurity/defsec/pkg/spec"
defsecTypes "github.com/aquasecurity/defsec/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand Down
12 changes: 6 additions & 6 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, opt types.DockerOptions) (types.Image, func(), error) {
img, cleanup, err := daemon.DockerImage(ref, opt.Host)
func tryDockerDaemon(_ context.Context, imageName string, ref name.Reference, opt types.ImageOptions) (types.Image, func(), error) {
img, cleanup, err := daemon.DockerImage(ref, opt.DockerOptions.Host)
if err != nil {
return nil, nil, err
}
Expand All @@ -21,18 +21,18 @@ func tryDockerDaemon(imageName string, ref name.Reference, opt types.DockerOptio

}

func tryPodmanDaemon(ref string) (types.Image, func(), error) {
img, cleanup, err := daemon.PodmanImage(ref)
func tryPodmanDaemon(_ context.Context, imageName string, _ name.Reference, _ types.ImageOptions) (types.Image, func(), error) {
img, cleanup, err := daemon.PodmanImage(imageName)
if err != nil {
return nil, nil, err
}
return daemonImage{
Image: img,
name: ref,
name: imageName,
}, cleanup, nil
}

func tryContainerdDaemon(ctx context.Context, imageName string) (types.Image, func(), error) {
func tryContainerdDaemon(ctx context.Context, imageName string, _ name.Reference, _ types.ImageOptions) (types.Image, func(), error) {
img, cleanup, err := daemon.ContainerdImage(ctx, imageName)
if err != nil {
return nil, cleanup, err
Expand Down
89 changes: 18 additions & 71 deletions pkg/fanal/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,102 +9,49 @@ import (
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
)

type options struct {
dockerd bool
podman bool
containerd bool
remote bool
}

type Option func(*options)

func DisableDockerd() Option {
return func(opts *options) {
opts.dockerd = false
}
}
type imageSourceFunc func(ctx context.Context, imageName string, ref name.Reference, option types.ImageOptions) (types.Image, func(), error)

func DisablePodman() Option {
return func(opts *options) {
opts.podman = false
}
var imageSourceFuncs = map[types.ImageSource]imageSourceFunc{
types.ContainerdImageSource: tryContainerdDaemon,
types.PodmanImageSource: tryPodmanDaemon,
types.DockerImageSource: tryDockerDaemon,
types.RemoteImageSource: tryRemote,
}

func DisableContainerd() Option {
return func(opts *options) {
opts.containerd = false
}
}

func DisableRemote() Option {
return func(opts *options) {
opts.remote = false
}
}

func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOptions, opts ...Option) (types.Image, func(), error) {
o := &options{
dockerd: true,
podman: true,
containerd: true,
remote: true,
}
for _, opt := range opts {
opt(o)
func NewContainerImage(ctx context.Context, imageName string, opt types.ImageOptions) (types.Image, func(), error) {
if len(opt.ImageSources) == 0 {
return nil, func() {}, xerrors.New("no image sources supplied")
}

var errs error
var nameOpts []name.Option
if opt.RegistryOptions.Insecure {
nameOpts = append(nameOpts, name.Insecure)
}

ref, err := name.ParseReference(imageName, nameOpts...)
if err != nil {
return nil, func() {}, xerrors.Errorf("failed to parse the image name: %w", err)
}

// Try accessing Docker Daemon
if o.dockerd {
img, cleanup, err := tryDockerDaemon(imageName, ref, opt.DockerOptions)
if err == nil {
// Return v1.Image if the image is found in Docker Engine
return img, cleanup, nil
}
errs = multierror.Append(errs, err)
}

// Try accessing Podman
if o.podman {
img, cleanup, err := tryPodmanDaemon(imageName)
if err == nil {
// Return v1.Image if the image is found in Podman
return img, cleanup, nil
for _, src := range opt.ImageSources {
trySrc, ok := imageSourceFuncs[src]
if !ok {
log.Logger.Warnf("Unknown image source: '%s'", src)
continue
}
errs = multierror.Append(errs, err)
}

// Try containerd
if o.containerd {
img, cleanup, err := tryContainerdDaemon(ctx, imageName)
img, cleanup, err := trySrc(ctx, imageName, ref, opt)
if err == nil {
// Return v1.Image if the image is found in containerd
// Return v1.Image if the image is found
return img, cleanup, nil
}
errs = multierror.Append(errs, err)
}

// Try accessing Docker Registry
if o.remote {
img, err := tryRemote(ctx, imageName, ref, opt.RegistryOptions)
if err == nil {
// Return v1.Image if the image is found in a remote registry
return img, func() {}, nil
}
errs = multierror.Append(errs, err)
}

return nil, func() {}, errs
}

Expand Down
4 changes: 3 additions & 1 deletion pkg/fanal/image/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ func TestNewDockerImage(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.option.ImageSources = types.AllImageSources
img, cleanup, err := NewContainerImage(context.Background(), tt.args.imageName, tt.args.option)
defer cleanup()

Expand Down Expand Up @@ -393,6 +394,7 @@ func TestNewDockerImageWithPrivateRegistry(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.option.ImageSources = types.AllImageSources
_, cleanup, err := NewContainerImage(context.Background(), tt.args.imageName, tt.args.option)
defer cleanup()

Expand Down Expand Up @@ -543,7 +545,7 @@ func TestDockerPlatformArguments(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
imageName := fmt.Sprintf("%s/library/alpine:3.10", serverAddr)

tt.args.option.ImageSources = types.AllImageSources
_, cleanup, err := NewContainerImage(context.Background(), imageName, tt.args.option)
defer cleanup()

Expand Down
13 changes: 8 additions & 5 deletions pkg/fanal/image/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import (
"github.com/aquasecurity/trivy/pkg/remote"
)

func tryRemote(ctx context.Context, imageName string, ref name.Reference, option types.RegistryOptions) (types.Image, error) {
desc, err := remote.Get(ctx, ref, option)
func tryRemote(ctx context.Context, imageName string, ref name.Reference, option types.ImageOptions) (types.Image, func(), error) {
// This function doesn't need cleanup
cleanup := func() {}

desc, err := remote.Get(ctx, ref, option.RegistryOptions)
if err != nil {
return nil, err
return nil, cleanup, err
}
img, err := desc.Image()
if err != nil {
return nil, err
return nil, cleanup, err
}

// Return v1.Image if the image is found in Docker Registry
Expand All @@ -28,7 +31,7 @@ func tryRemote(ctx context.Context, imageName string, ref name.Reference, option
Image: img,
ref: implicitReference{ref: ref},
descriptor: desc,
}, nil
}, cleanup, nil

}

Expand Down
15 changes: 9 additions & 6 deletions pkg/fanal/test/integration/containerd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ func TestContainerd_SearchLocalStoreByNameOrDigest(t *testing.T) {
}
})

img, cleanup, err := image.NewContainerImage(ctx, tt.searchName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
// enable only containerd
img, cleanup, err := image.NewContainerImage(ctx, tt.searchName,
types.ImageOptions{ImageSources: types.ImageSources{types.ContainerdImageSource}})
defer cleanup()
if tt.expectErr {
require.Error(t, err)
Expand Down Expand Up @@ -679,8 +680,9 @@ func localImageTestWithNamespace(t *testing.T, namespace string) {
require.NoError(t, err)

// Enable only containerd
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{
ImageSources: types.ImageSources{types.ContainerdImageSource},
})
require.NoError(t, err)
defer cleanup()

Expand Down Expand Up @@ -814,8 +816,9 @@ func TestContainerd_PullImage(t *testing.T) {
require.NoError(t, err)

// Enable only containerd
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{},
image.DisableDockerd(), image.DisablePodman(), image.DisableRemote())
img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{
ImageSources: types.ImageSources{types.ContainerdImageSource},
})
require.NoError(t, err)
defer cleanup()

Expand Down
Loading

0 comments on commit 6a0e152

Please sign in to comment.