Skip to content

Commit

Permalink
pkg/util/trivy: move containerd trivy code to separate file (#32193)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulcacheux authored Dec 16, 2024
1 parent a0a4d16 commit fde3c5b
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 96 deletions.
96 changes: 0 additions & 96 deletions pkg/util/trivy/trivy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ import (
"time"

"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces"

"github.com/DataDog/datadog-agent/comp/core/config"
workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def"
"github.com/DataDog/datadog-agent/pkg/config/env"
"github.com/DataDog/datadog-agent/pkg/sbom"
cutil "github.com/DataDog/datadog-agent/pkg/util/containerd"
containersimage "github.com/DataDog/datadog-agent/pkg/util/containers/image"
"github.com/DataDog/datadog-agent/pkg/util/crio"
"github.com/DataDog/datadog-agent/pkg/util/log"
Expand All @@ -48,9 +46,6 @@ import (
"github.com/aquasecurity/trivy/pkg/scanner/ospkg"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/vulnerability"
"github.com/containerd/containerd"
"github.com/containerd/containerd/leases"
"github.com/containerd/errdefs"
"github.com/docker/docker/client"

// This is required to load sqlite based RPM databases
Expand All @@ -70,9 +65,6 @@ const (
TypeImageConfigSecret = "image-config-secret" // TypeImageConfigSecret defines a history-dockerfile analyzer
)

// ContainerdAccessor is a function that should return a containerd client
type ContainerdAccessor func() (cutil.ContainerdItf, error)

// collectorConfig allows to pass configuration
type collectorConfig struct {
clearCacheOnClose bool
Expand Down Expand Up @@ -317,94 +309,6 @@ func (c *Collector) scanOverlayFS(ctx context.Context, layers []string, imgMeta
return report, nil
}

// ScanContainerdImageFromSnapshotter scans containerd image directly from the snapshotter
func (c *Collector) ScanContainerdImageFromSnapshotter(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
// Computing duration of containerd lease
deadline, _ := ctx.Deadline()
expiration := deadline.Sub(time.Now().Add(cleanupTimeout))
clClient := client.RawClient()
imageID := imgMeta.ID

mounts, err := client.Mounts(ctx, expiration, imgMeta.Namespace, img)
if err != nil {
return nil, fmt.Errorf("unable to get mounts for image %s, err: %w", imgMeta.ID, err)
}

layers := extractLayersFromOverlayFSMounts(mounts)
if len(layers) == 0 {
return nil, fmt.Errorf("unable to extract layers from overlayfs mounts %+v for image %s", mounts, imgMeta.ID)
}

ctx = namespaces.WithNamespace(ctx, imgMeta.Namespace)
// Adding a lease to cleanup dandling snaphots at expiration
ctx, done, err := clClient.WithLease(ctx,
leases.WithID(imageID),
leases.WithExpiration(expiration),
leases.WithLabels(map[string]string{
"containerd.io/gc.ref.snapshot." + containerd.DefaultSnapshotter: imageID,
}),
)
if err != nil && !errdefs.IsAlreadyExists(err) {
return nil, fmt.Errorf("unable to get a lease, err: %w", err)
}

report, err := c.scanOverlayFS(ctx, layers, imgMeta, scanOptions)

if err := done(ctx); err != nil {
log.Warnf("Unable to cancel containerd lease with id: %s, err: %v", imageID, err)
}

return report, err
}

// ScanContainerdImage scans containerd image by exporting it and scanning the tarball
func (c *Collector) ScanContainerdImage(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
fanalImage, cleanup, err := convertContainerdImage(ctx, client.RawClient(), imgMeta, img)
if cleanup != nil {
defer cleanup()
}
if err != nil {
return nil, fmt.Errorf("unable to convert containerd image, err: %w", err)
}

return c.scanImage(ctx, fanalImage, imgMeta, scanOptions)
}

// ScanContainerdImageFromFilesystem scans containerd image from file-system
func (c *Collector) ScanContainerdImageFromFilesystem(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
//nolint:gosimple // TODO(CINT) Fix go simple linte
imagePath, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("containerd-image-*"))
if err != nil {
return nil, fmt.Errorf("unable to create temp dir, err: %w", err)
}
defer func() {
err := os.RemoveAll(imagePath)
if err != nil {
log.Errorf("Unable to remove temp dir: %s, err: %v", imagePath, err)
}
}()

// Computing duration of containerd lease
deadline, _ := ctx.Deadline()
expiration := deadline.Sub(time.Now().Add(cleanupTimeout))

cleanUp, err := client.MountImage(ctx, expiration, imgMeta.Namespace, img, imagePath)
if err != nil {
return nil, fmt.Errorf("unable to mount containerd image, err: %w", err)
}

defer func() {
cleanUpContext, cleanUpContextCancel := context.WithTimeout(context.Background(), cleanupTimeout)
err := cleanUp(cleanUpContext)
cleanUpContextCancel()
if err != nil {
log.Errorf("Unable to clean up mounted image, err: %v", err)
}
}()

return c.scanFilesystem(ctx, os.DirFS("/"), imagePath, imgMeta, scanOptions)
}

// ScanCRIOImageFromOverlayFS scans the CRI-O image layers using OverlayFS.
func (c *Collector) ScanCRIOImageFromOverlayFS(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, client crio.Client, scanOptions sbom.ScanOptions) (sbom.Report, error) {
lowerDirs, err := client.GetCRIOImageLayers(imgMeta)
Expand Down
116 changes: 116 additions & 0 deletions pkg/util/trivy/trivy_containerd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build trivy && containerd

// Package trivy holds the scan components
package trivy

import (
"context"
"fmt"
"os"
"time"

workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def"
"github.com/DataDog/datadog-agent/pkg/sbom"
cutil "github.com/DataDog/datadog-agent/pkg/util/containerd"
"github.com/DataDog/datadog-agent/pkg/util/log"
"github.com/containerd/containerd"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/errdefs"
)

// ContainerdAccessor is a function that should return a containerd client
type ContainerdAccessor func() (cutil.ContainerdItf, error)

// ScanContainerdImageFromSnapshotter scans containerd image directly from the snapshotter
func (c *Collector) ScanContainerdImageFromSnapshotter(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
// Computing duration of containerd lease
deadline, _ := ctx.Deadline()
expiration := deadline.Sub(time.Now().Add(cleanupTimeout))
clClient := client.RawClient()
imageID := imgMeta.ID

mounts, err := client.Mounts(ctx, expiration, imgMeta.Namespace, img)
if err != nil {
return nil, fmt.Errorf("unable to get mounts for image %s, err: %w", imgMeta.ID, err)
}

layers := extractLayersFromOverlayFSMounts(mounts)
if len(layers) == 0 {
return nil, fmt.Errorf("unable to extract layers from overlayfs mounts %+v for image %s", mounts, imgMeta.ID)
}

ctx = namespaces.WithNamespace(ctx, imgMeta.Namespace)
// Adding a lease to cleanup dandling snaphots at expiration
ctx, done, err := clClient.WithLease(ctx,
leases.WithID(imageID),
leases.WithExpiration(expiration),
leases.WithLabels(map[string]string{
"containerd.io/gc.ref.snapshot." + containerd.DefaultSnapshotter: imageID,
}),
)
if err != nil && !errdefs.IsAlreadyExists(err) {
return nil, fmt.Errorf("unable to get a lease, err: %w", err)
}

report, err := c.scanOverlayFS(ctx, layers, imgMeta, scanOptions)

if err := done(ctx); err != nil {
log.Warnf("Unable to cancel containerd lease with id: %s, err: %v", imageID, err)
}

return report, err
}

// ScanContainerdImage scans containerd image by exporting it and scanning the tarball
func (c *Collector) ScanContainerdImage(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
fanalImage, cleanup, err := convertContainerdImage(ctx, client.RawClient(), imgMeta, img)
if cleanup != nil {
defer cleanup()
}
if err != nil {
return nil, fmt.Errorf("unable to convert containerd image, err: %w", err)
}

return c.scanImage(ctx, fanalImage, imgMeta, scanOptions)
}

// ScanContainerdImageFromFilesystem scans containerd image from file-system
func (c *Collector) ScanContainerdImageFromFilesystem(ctx context.Context, imgMeta *workloadmeta.ContainerImageMetadata, img containerd.Image, client cutil.ContainerdItf, scanOptions sbom.ScanOptions) (sbom.Report, error) {
//nolint:gosimple // TODO(CINT) Fix go simple linte
imagePath, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("containerd-image-*"))
if err != nil {
return nil, fmt.Errorf("unable to create temp dir, err: %w", err)
}
defer func() {
err := os.RemoveAll(imagePath)
if err != nil {
log.Errorf("Unable to remove temp dir: %s, err: %v", imagePath, err)
}
}()

// Computing duration of containerd lease
deadline, _ := ctx.Deadline()
expiration := deadline.Sub(time.Now().Add(cleanupTimeout))

cleanUp, err := client.MountImage(ctx, expiration, imgMeta.Namespace, img, imagePath)
if err != nil {
return nil, fmt.Errorf("unable to mount containerd image, err: %w", err)
}

defer func() {
cleanUpContext, cleanUpContextCancel := context.WithTimeout(context.Background(), cleanupTimeout)
err := cleanUp(cleanUpContext)
cleanUpContextCancel()
if err != nil {
log.Errorf("Unable to clean up mounted image, err: %v", err)
}
}()

return c.scanFilesystem(ctx, os.DirFS("/"), imagePath, imgMeta, scanOptions)
}

0 comments on commit fde3c5b

Please sign in to comment.