From 9d2057a7c2029d259f8ee40e84a37a90b6ba7136 Mon Sep 17 00:00:00 2001 From: Parvez Date: Mon, 11 Mar 2024 09:57:57 +0530 Subject: [PATCH] feat(image): customer podman host or socket option (#6256) Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen --- .../configuration/cli/trivy_image.md | 1 + .../references/configuration/config-file.md | 5 +++ docs/docs/target/container_image.md | 7 ++++ pkg/commands/artifact/run.go | 3 ++ pkg/fanal/image/daemon.go | 4 +- pkg/fanal/image/daemon/image_test.go | 40 +++++++++++++++++++ pkg/fanal/image/daemon/podman.go | 9 +++-- pkg/fanal/image/daemon/podman_test.go | 2 +- pkg/fanal/types/image.go | 2 +- pkg/flag/image_flags.go | 11 +++++ 10 files changed, 77 insertions(+), 7 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index e5484111bc86..ebf36a4140d3 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -78,6 +78,7 @@ trivy image [flags] IMAGE_NAME --parallel int number of goroutines enabled for parallel scanning, set 0 to auto-detect parallelism (default 5) --password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons. --platform string set platform in the form os/arch if image is multi-platform capable + --podman-host string unix podman socket path to use for podman scanning --policy-bundle-repository string OCI registry URL to retrieve policy bundle from (default "ghcr.io/aquasecurity/trivy-policies:0") --policy-namespaces strings Rego namespaces --redis-ca string redis ca file location, if using redis as cache backend diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 23b5a3778345..1c94f2eb8db6 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -203,6 +203,11 @@ image: # Same as '--docker-host' # Default is empty host: + + podman: + # Same as '--podman-host' + # Default is empty + host: ``` ## Vulnerability Options diff --git a/docs/docs/target/container_image.md b/docs/docs/target/container_image.md index 5e86c76b3121..2ce008922614 100644 --- a/docs/docs/target/container_image.md +++ b/docs/docs/target/container_image.md @@ -500,3 +500,10 @@ You can configure Docker daemon socket with `DOCKER_HOST` or `--docker-host`. ```shell $ trivy image --docker-host tcp://127.0.0.1:2375 YOUR_IMAGE ``` + +### Configure Podman daemon socket to connect to. +You can configure Podman daemon socket with `--podman-host`. + +```shell +$ trivy image --podman-host /run/user/1000/podman/podman.sock YOUR_IMAGE +``` \ No newline at end of file diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 0e1158e31586..4f8be6e1d911 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -670,6 +670,9 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi DockerOptions: ftypes.DockerOptions{ Host: opts.DockerHost, }, + PodmanOptions: ftypes.PodmanOptions{ + Host: opts.PodmanHost, + }, ImageSources: opts.ImageSources, }, diff --git a/pkg/fanal/image/daemon.go b/pkg/fanal/image/daemon.go index f6eb303e4125..6b9be09c4437 100644 --- a/pkg/fanal/image/daemon.go +++ b/pkg/fanal/image/daemon.go @@ -21,8 +21,8 @@ func tryDockerDaemon(_ context.Context, imageName string, ref name.Reference, op } -func tryPodmanDaemon(_ context.Context, imageName string, _ name.Reference, _ types.ImageOptions) (types.Image, func(), error) { - img, cleanup, err := daemon.PodmanImage(imageName) +func tryPodmanDaemon(_ context.Context, imageName string, _ name.Reference, opts types.ImageOptions) (types.Image, func(), error) { + img, cleanup, err := daemon.PodmanImage(imageName, opts.PodmanOptions.Host) if err != nil { return nil, nil, err } diff --git a/pkg/fanal/image/daemon/image_test.go b/pkg/fanal/image/daemon/image_test.go index be6ddd86e525..a8462200e479 100644 --- a/pkg/fanal/image/daemon/image_test.go +++ b/pkg/fanal/image/daemon/image_test.go @@ -115,6 +115,46 @@ func Test_image_ConfigNameWithCustomDockerHost(t *testing.T) { assert.Nil(t, err) } +func Test_image_ConfigNameWithCustomPodmanHost(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("podman.sock is not available for Windows CI") + } + + ref, err := name.ParseReference("alpine:3.11") + require.NoError(t, err) + + eo := engine.Option{ + APIVersion: opt.APIVersion, + ImagePaths: map[string]string{ + "index.docker.io/library/alpine:3.11": "../../test/testdata/alpine-311.tar.gz", + }, + } + + runtimeDir, err := os.MkdirTemp("", "daemon") + require.NoError(t, err) + + dir := filepath.Join(runtimeDir, "image") + err = os.MkdirAll(dir, os.ModePerm) + require.NoError(t, err) + + podmanSocket := filepath.Join(dir, "image-test-podman-socket.sock") + eo.UnixDomainSocket = podmanSocket + + te := engine.NewDockerEngine(eo) + defer te.Close() + + img, cleanup, err := PodmanImage(ref.Name(), podmanSocket) + 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 diff --git a/pkg/fanal/image/daemon/podman.go b/pkg/fanal/image/daemon/podman.go index e5c24aec7faf..51766ae01822 100644 --- a/pkg/fanal/image/daemon/podman.go +++ b/pkg/fanal/image/daemon/podman.go @@ -25,10 +25,13 @@ type podmanClient struct { c http.Client } -func newPodmanClient() (podmanClient, error) { +func newPodmanClient(host string) (podmanClient, error) { // Get Podman socket location sockDir := os.Getenv("XDG_RUNTIME_DIR") socket := filepath.Join(sockDir, "podman", "podman.sock") + if host != "" { + socket = host + } if _, err := os.Stat(socket); err != nil { return podmanClient{}, xerrors.Errorf("no podman socket found: %w", err) @@ -109,10 +112,10 @@ func (p podmanClient) imageSave(_ context.Context, imageNames []string) (io.Read // PodmanImage implements v1.Image by extending daemon.Image. // The caller must call cleanup() to remove a temporary file. -func PodmanImage(ref string) (Image, func(), error) { +func PodmanImage(ref, host string) (Image, func(), error) { cleanup := func() {} - c, err := newPodmanClient() + c, err := newPodmanClient(host) if err != nil { return nil, cleanup, xerrors.Errorf("unable to initialize Podman client: %w", err) } diff --git a/pkg/fanal/image/daemon/podman_test.go b/pkg/fanal/image/daemon/podman_test.go index 821d10017ffe..d408def16e87 100644 --- a/pkg/fanal/image/daemon/podman_test.go +++ b/pkg/fanal/image/daemon/podman_test.go @@ -84,7 +84,7 @@ func TestPodmanImage(t *testing.T) { ref, err := name.ParseReference(tt.imageName) require.NoError(t, err) - img, cleanup, err := PodmanImage(ref.Name()) + img, cleanup, err := PodmanImage(ref.Name(), "") defer cleanup() if tt.wantErr { diff --git a/pkg/fanal/types/image.go b/pkg/fanal/types/image.go index 7726169ce149..91cdfb44b60c 100644 --- a/pkg/fanal/types/image.go +++ b/pkg/fanal/types/image.go @@ -60,7 +60,7 @@ type DockerOptions struct { } type PodmanOptions struct { - // Add Podman-specific options + Host string } type ContainerdOptions struct { diff --git a/pkg/flag/image_flags.go b/pkg/flag/image_flags.go index 93caccd48834..aaa3fc65b930 100644 --- a/pkg/flag/image_flags.go +++ b/pkg/flag/image_flags.go @@ -45,6 +45,12 @@ var ( Default: "", Usage: "unix domain socket path to use for docker scanning", } + PodmanHostFlag = Flag[string]{ + Name: "podman-host", + ConfigName: "image.podman.host", + Default: "", + Usage: "unix podman socket path to use for podman scanning", + } SourceFlag = Flag[[]string]{ Name: "image-src", ConfigName: "image.source", @@ -60,6 +66,7 @@ type ImageFlagGroup struct { ScanRemovedPkgs *Flag[bool] Platform *Flag[string] DockerHost *Flag[string] + PodmanHost *Flag[string] ImageSources *Flag[[]string] } @@ -69,6 +76,7 @@ type ImageOptions struct { ScanRemovedPkgs bool Platform ftypes.Platform DockerHost string + PodmanHost string ImageSources ftypes.ImageSources } @@ -79,6 +87,7 @@ func NewImageFlagGroup() *ImageFlagGroup { ScanRemovedPkgs: ScanRemovedPkgsFlag.Clone(), Platform: PlatformFlag.Clone(), DockerHost: DockerHostFlag.Clone(), + PodmanHost: PodmanHostFlag.Clone(), ImageSources: SourceFlag.Clone(), } } @@ -94,6 +103,7 @@ func (f *ImageFlagGroup) Flags() []Flagger { f.ScanRemovedPkgs, f.Platform, f.DockerHost, + f.PodmanHost, f.ImageSources, } } @@ -121,6 +131,7 @@ func (f *ImageFlagGroup) ToOptions() (ImageOptions, error) { ScanRemovedPkgs: f.ScanRemovedPkgs.Value(), Platform: platform, DockerHost: f.DockerHost.Value(), + PodmanHost: f.PodmanHost.Value(), ImageSources: xstrings.ToTSlice[ftypes.ImageSource](f.ImageSources.Value()), }, nil }