Skip to content

Commit 25097ff

Browse files
authored
feat: add ztsd layer support for cve scans (#4258)
* feat: add ztsd layer support for cve scans Fixes issue #4255 Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test(cve): cover zstd-compressed layers in scannability check Add a case exercising ispec.MediaTypeImageLayerZstd alongside the existing gzip case in TestImageScannable, per Copilot review feedback that the new accepted media type had no test coverage. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test(cve): clarify zstd scannability test description Reword the Convey description to state that the test validates acceptance of the zstd layer media type, not decompression of actual zstd-compressed content, per Copilot review feedback that the test data (an empty JSON blob) doesn't exercise decompression. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(cve): guard against empty Manifests in isManifestScanable Real ingestion paths (OnUpdateManifest, ParseStorage) always populate ImageMeta.Manifests with exactly one entry for an ImageManifest, via convert.GetImageManifestMeta. But isManifestScanable reads independently from metaDB and indexes Manifests[0] unconditionally, so a stale or malformed metadb entry would panic the scan instead of surfacing as an error, per Copilot review feedback. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(cve): correct typo in isManifestScannable and error message spacing Addresses Copilot review nits on scanner.go: rename isManifestScanable to isManifestScannable to match sibling helpers (IsImageFormatScannable, isIndexScannable, isManifestDataScannable), and remove a double space in the "0 manifests" error message. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> --------- Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
1 parent 21d5902 commit 25097ff

2 files changed

Lines changed: 48 additions & 25 deletions

File tree

pkg/extensions/search/cve/trivy/scanner.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ func (scanner Scanner) IsImageMediaScannable(repo, digestStr, mediaType string)
446446

447447
if mediaType == ispec.MediaTypeImageManifest || //nolint:gocritic // not converting to switch-case
448448
compat.IsCompatibleManifestMediaType(mediaType) {
449-
ok, err := scanner.isManifestScanable(digestStr)
449+
ok, err := scanner.isManifestScannable(digestStr)
450450
if err != nil {
451451
return ok, fmt.Errorf("image '%s' %w", image, err)
452452
}
@@ -465,7 +465,26 @@ func (scanner Scanner) IsImageMediaScannable(repo, digestStr, mediaType string)
465465
}
466466
}
467467

468-
func (scanner Scanner) isManifestScanable(digestStr string) (bool, error) {
468+
// isManifestLayersScannable checks whether all layers of a manifest have
469+
// media types that Trivy is able to scan.
470+
func isManifestLayersScannable(layers []ispec.Descriptor) (bool, error) {
471+
for _, imageLayer := range layers {
472+
switch imageLayer.MediaType {
473+
case ispec.MediaTypeImageLayerGzip,
474+
ispec.MediaTypeImageLayerZstd,
475+
ispec.MediaTypeImageLayer,
476+
string(regTypes.DockerLayer):
477+
continue
478+
default:
479+
return false, fmt.Errorf("%w: layer media type '%s'",
480+
zerr.ErrScanNotSupported, imageLayer.MediaType)
481+
}
482+
}
483+
484+
return true, nil
485+
}
486+
487+
func (scanner Scanner) isManifestScannable(digestStr string) (bool, error) {
469488
if scanner.cache.Get(digestStr) != nil {
470489
return true, nil
471490
}
@@ -475,33 +494,19 @@ func (scanner Scanner) isManifestScanable(digestStr string) (bool, error) {
475494
return false, err
476495
}
477496

478-
for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
479-
switch imageLayer.MediaType {
480-
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
481-
continue
482-
default:
483-
return false, fmt.Errorf("%w: layer media type '%s'", zerr.ErrScanNotSupported, imageLayer.MediaType)
484-
}
497+
if len(manifestData.Manifests) == 0 {
498+
return false, fmt.Errorf("%w: manifest data has 0 manifests", zerr.ErrScanNotSupported)
485499
}
486500

487-
return true, nil
501+
return isManifestLayersScannable(manifestData.Manifests[0].Manifest.Layers)
488502
}
489503

490504
func (scanner Scanner) isManifestDataScannable(manifestData mTypes.ManifestMeta) (bool, error) {
491505
if scanner.cache.Get(manifestData.Digest.String()) != nil {
492506
return true, nil
493507
}
494508

495-
for _, imageLayer := range manifestData.Manifest.Layers {
496-
switch imageLayer.MediaType {
497-
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
498-
continue
499-
default:
500-
return false, fmt.Errorf("%w: layer media type '%s'", zerr.ErrScanNotSupported, imageLayer.MediaType)
501-
}
502-
}
503-
504-
return true, nil
509+
return isManifestLayersScannable(manifestData.Manifest.Layers)
505510
}
506511

507512
func (scanner Scanner) isIndexScannable(digestStr string) (bool, error) {
@@ -896,7 +901,7 @@ func (scanner Scanner) scanIndex(ctx context.Context, repo, digest string) (map[
896901
indexCveIDMap := map[string]cvemodel.CVE{}
897902

898903
for _, manifest := range indexData.Index.Manifests {
899-
if isScannable, err := scanner.isManifestScanable(manifest.Digest.String()); isScannable && err == nil {
904+
if isScannable, err := scanner.isManifestScannable(manifest.Digest.String()); isScannable && err == nil {
900905
manifestCveIDMap, err := scanner.scanManifest(ctx, repo, manifest.Digest.String())
901906
if err != nil {
902907
return nil, err

pkg/extensions/search/cve/trivy/scanner_internal_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,14 +414,14 @@ func TestImageScannable(t *testing.T) {
414414

415415
boltDriver, err := boltdb.GetBoltDriver(params)
416416
if err != nil {
417-
panic(err)
417+
t.Fatal(err)
418418
}
419419

420420
log := log.NewTestLogger()
421421

422422
metaDB, err := boltdb.New(boltDriver, log)
423423
if err != nil {
424-
panic(err)
424+
t.Fatal(err)
425425
}
426426

427427
// Create test data for the following cases
@@ -449,7 +449,19 @@ func TestImageScannable(t *testing.T) {
449449

450450
err = metaDB.SetRepoReference(context.Background(), "repo1", "valid", validImage.AsImageMeta())
451451
if err != nil {
452-
panic(err)
452+
t.Fatal(err)
453+
}
454+
455+
validZstdImage := CreateImageWith().
456+
Layers([]Layer{{
457+
MediaType: ispec.MediaTypeImageLayerZstd,
458+
Digest: ispec.DescriptorEmptyJSON.Digest,
459+
Blob: ispec.DescriptorEmptyJSON.Data,
460+
}}).ImageConfig(validConfig).Build()
461+
462+
err = metaDB.SetRepoReference(context.Background(), "repo1", "valid-zstd", validZstdImage.AsImageMeta())
463+
if err != nil {
464+
t.Fatal(err)
453465
}
454466

455467
// Create MetaDB data for manifest with unscannable layers
@@ -463,7 +475,7 @@ func TestImageScannable(t *testing.T) {
463475
err = metaDB.SetRepoReference(context.Background(), "repo1",
464476
"unscannable-layer", imageWithUnscannableLayer.AsImageMeta())
465477
if err != nil {
466-
panic(err)
478+
t.Fatal(err)
467479
}
468480

469481
// Continue with initializing the objects the scanner depends on
@@ -487,6 +499,12 @@ func TestImageScannable(t *testing.T) {
487499
So(result, ShouldBeTrue)
488500
})
489501

502+
Convey("Valid image with zstd layer media type should be scannable", t, func() {
503+
result, err := scanner.IsImageFormatScannable("repo1", "valid-zstd")
504+
So(err, ShouldBeNil)
505+
So(result, ShouldBeTrue)
506+
})
507+
490508
Convey("Image with layers of unsupported types should be unscannable", t, func() {
491509
result, err := scanner.IsImageFormatScannable("repo1", "unscannable-layer")
492510
So(err, ShouldNotBeNil)

0 commit comments

Comments
 (0)