0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-01-06 22:40:28 -05:00
zot/pkg/extensions/search/cve/cve.go
Jan-Otto Kröpke f618b1d4ef
ci(deps): upgrade golangci-lint (#2556)
* ci(deps): upgrade golangci-lint

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>

* build(deps): removed disabled linters

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>

* build(deps): go run github.com/daixiang0/gci@latest write .

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): go run golang.org/x/tools/cmd/goimports@latest -l -w .

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): go run github.com/bombsimon/wsl/v4/cmd...@latest -strict-append -test=true -fix ./...

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): go run github.com/catenacyber/perfsprint@latest -fix ./...

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): replace gomnd by mnd

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): make gqlgen

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build: Revert "build(deps): go run github.com/daixiang0/gci@latest write ."

This reverts commit 5bf8c42e1f.

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): go run github.com/daixiang0/gci@latest write -s 'standard' -s default -s 'prefix(zotregistry.dev/zot)' .

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* build(deps): make gqlgen

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: wsl issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: check-log issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: gci issues

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

* fix: tests

Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>

---------

Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
Signed-off-by: Jan-Otto Kröpke <joe@cloudeteer.de>
2024-07-29 10:32:51 -07:00

655 lines
19 KiB
Go

package cveinfo
import (
"context"
"sort"
"strings"
"time"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
zerr "zotregistry.dev/zot/errors"
zcommon "zotregistry.dev/zot/pkg/common"
cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
"zotregistry.dev/zot/pkg/extensions/search/cve/trivy"
"zotregistry.dev/zot/pkg/log"
mTypes "zotregistry.dev/zot/pkg/meta/types"
"zotregistry.dev/zot/pkg/storage"
)
type CveInfo interface {
GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE, excludedCVE string, severity string,
pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE, excludedCVE string,
pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error)
}
type Scanner interface {
ScanImage(ctx context.Context, image string) (map[string]cvemodel.CVE, error)
IsImageFormatScannable(repo, ref string) (bool, error)
IsImageMediaScannable(repo, digestStr, mediaType string) (bool, error)
IsResultCached(digestStr string) bool
GetCachedResult(digestStr string) map[string]cvemodel.CVE
UpdateDB(ctx context.Context) error
}
type BaseCveInfo struct {
Log log.Logger
Scanner Scanner
MetaDB mTypes.MetaDB
}
func NewScanner(storeController storage.StoreController, metaDB mTypes.MetaDB,
dbRepository, javaDBRepository string, log log.Logger,
) Scanner {
return trivy.NewScanner(storeController, metaDB, dbRepository, javaDBRepository, log)
}
func NewCVEInfo(scanner Scanner, metaDB mTypes.MetaDB, log log.Logger) *BaseCveInfo {
return &BaseCveInfo{
Log: log,
Scanner: scanner,
MetaDB: metaDB,
}
}
func (cveinfo BaseCveInfo) GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {
imgList := make([]cvemodel.TagInfo, 0)
repoMeta, err := cveinfo.MetaDB.GetRepoMeta(ctx, repo)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("cve-id", cveID).
Msg("failed to get list of tags from repo")
return imgList, err
}
for tag, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest, ispec.MediaTypeImageIndex:
manifestDigestStr := descriptor.Digest
manifestDigest := godigest.Digest(manifestDigestStr)
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, manifestDigestStr)
if !isScanableImage || err != nil {
cveinfo.Log.Debug().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
continue
}
cveMap, err := cveinfo.Scanner.ScanImage(ctx, zcommon.GetFullImageName(repo, tag))
if err != nil {
if zcommon.IsContextDone(ctx) {
return imgList, err
}
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
continue
}
if _, hasCVE := cveMap[cveID]; hasCVE {
imgList = append(imgList, cvemodel.TagInfo{
Tag: tag,
Descriptor: cvemodel.Descriptor{
Digest: manifestDigest,
MediaType: descriptor.MediaType,
},
})
}
default:
cveinfo.Log.Debug().Str("image", repo+":"+tag).Str("mediaType", descriptor.MediaType).
Msg("image media type not supported for scanning")
}
}
return imgList, nil
}
func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(ctx context.Context, repo, cveID string,
) ([]cvemodel.TagInfo, error) {
repoMeta, err := cveinfo.MetaDB.GetRepoMeta(ctx, repo)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("cve-id", cveID).
Msg("failed to get list of tags from repo")
return []cvemodel.TagInfo{}, err
}
vulnerableTags := make([]cvemodel.TagInfo, 0)
allTags := make([]cvemodel.TagInfo, 0)
for tag, descriptor := range repoMeta.Tags {
if zcommon.IsContextDone(ctx) {
return []cvemodel.TagInfo{}, ctx.Err()
}
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigestStr := descriptor.Digest
tagInfo, err := getTagInfoForManifest(tag, manifestDigestStr, cveinfo.MetaDB)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
Str("cve-id", cveID).Msg("failed to retrieve manifest and config")
continue
}
allTags = append(allTags, tagInfo)
if cveinfo.isManifestVulnerable(ctx, repo, tag, manifestDigestStr, cveID) {
vulnerableTags = append(vulnerableTags, tagInfo)
}
case ispec.MediaTypeImageIndex:
indexDigestStr := descriptor.Digest
indexContent, err := getIndexContent(cveinfo.MetaDB, indexDigestStr)
if err != nil {
continue
}
vulnerableManifests := []cvemodel.DescriptorInfo{}
allManifests := []cvemodel.DescriptorInfo{}
for _, manifest := range indexContent.Manifests {
tagInfo, err := getTagInfoForManifest(tag, manifest.Digest.String(), cveinfo.MetaDB)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
Str("cve-id", cveID).Msg("failed to retrieve manifest and config")
continue
}
manifestDescriptorInfo := cvemodel.DescriptorInfo{
Descriptor: tagInfo.Descriptor,
Timestamp: tagInfo.Timestamp,
}
allManifests = append(allManifests, manifestDescriptorInfo)
if cveinfo.isManifestVulnerable(ctx, repo, tag, manifest.Digest.String(), cveID) {
vulnerableManifests = append(vulnerableManifests, manifestDescriptorInfo)
}
}
if len(allManifests) > 0 {
allTags = append(allTags, cvemodel.TagInfo{
Tag: tag,
Descriptor: cvemodel.Descriptor{
Digest: godigest.Digest(indexDigestStr),
MediaType: ispec.MediaTypeImageIndex,
},
Manifests: allManifests,
Timestamp: mostRecentUpdate(allManifests),
})
}
if len(vulnerableManifests) > 0 {
vulnerableTags = append(vulnerableTags, cvemodel.TagInfo{
Tag: tag,
Descriptor: cvemodel.Descriptor{
Digest: godigest.Digest(indexDigestStr),
MediaType: ispec.MediaTypeImageIndex,
},
Manifests: vulnerableManifests,
Timestamp: mostRecentUpdate(vulnerableManifests),
})
}
default:
cveinfo.Log.Debug().Str("mediaType", descriptor.MediaType).
Msg("image media type not supported for scanning")
}
}
var fixedTags []cvemodel.TagInfo
if len(vulnerableTags) != 0 {
cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
Interface("tags", vulnerableTags).Msg("vulnerable tags")
fixedTags = GetFixedTags(allTags, vulnerableTags)
cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
Interface("tags", fixedTags).Msg("fixed tags")
} else {
cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
Msg("image does not contain any tag that have given cve")
fixedTags = allTags
}
return fixedTags, nil
}
func mostRecentUpdate(allManifests []cvemodel.DescriptorInfo) time.Time {
if len(allManifests) == 0 {
return time.Time{}
}
timeStamp := allManifests[0].Timestamp
for i := range allManifests {
if timeStamp.Before(allManifests[i].Timestamp) {
timeStamp = allManifests[i].Timestamp
}
}
return timeStamp
}
func getTagInfoForManifest(tag, manifestDigestStr string, metaDB mTypes.MetaDB) (cvemodel.TagInfo, error) {
configContent, manifestDigest, err := getConfigAndDigest(metaDB, manifestDigestStr)
if err != nil {
return cvemodel.TagInfo{}, err
}
lastUpdated := zcommon.GetImageLastUpdated(configContent)
return cvemodel.TagInfo{
Tag: tag,
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
Manifests: []cvemodel.DescriptorInfo{
{
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
Timestamp: lastUpdated,
},
},
Timestamp: lastUpdated,
}, nil
}
func (cveinfo *BaseCveInfo) isManifestVulnerable(ctx context.Context, repo, tag, manifestDigestStr, cveID string,
) bool {
image := zcommon.GetFullImageName(repo, tag)
isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, manifestDigestStr, ispec.MediaTypeImageManifest)
if !isValidImage || err != nil {
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).Err(err).
Msg("image media type not supported for scanning, adding as a vulnerable image")
return true
}
cveMap, err := cveinfo.Scanner.ScanImage(ctx, zcommon.GetFullImageName(repo, manifestDigestStr))
if err != nil {
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
Msg("scanning failed, adding as a vulnerable image")
return true
}
hasCVE := false
for id := range cveMap {
if id == cveID {
hasCVE = true
break
}
}
return hasCVE
}
func getIndexContent(metaDB mTypes.MetaDB, indexDigestStr string) (ispec.Index, error) {
indexDigest, err := godigest.Parse(indexDigestStr)
if err != nil {
return ispec.Index{}, err
}
indexData, err := metaDB.GetImageMeta(indexDigest)
if err != nil {
return ispec.Index{}, err
}
if indexData.Index == nil {
return ispec.Index{}, zerr.ErrUnexpectedMediaType
}
return *indexData.Index, nil
}
func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.Image, godigest.Digest, error) {
manifestDigest, err := godigest.Parse(manifestDigestStr)
if err != nil {
return ispec.Image{}, "", err
}
manifestData, err := metaDB.GetImageMeta(manifestDigest)
if err != nil {
return ispec.Image{}, "", err
}
// we'll fail the execution if the config is not compatible with ispec.Image because we can't scan this type of images.
if manifestData.Manifests[0].Manifest.Config.MediaType != ispec.MediaTypeImageConfig {
return ispec.Image{}, "", zerr.ErrUnexpectedMediaType
}
return manifestData.Manifests[0].Config, manifestDigest, err
}
func filterCVEMap(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE, severity string,
pageFinder *CvePageFinder,
) {
searchedCVE = strings.ToUpper(searchedCVE)
for _, cve := range cveMap {
if severity != "" && (cvemodel.CompareSeverities(cve.Severity, severity) != 0) {
continue
}
if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
continue
}
if cve.ContainsStr(searchedCVE) {
pageFinder.Add(cve)
}
}
}
func filterCVEList(cveList []cvemodel.CVE, searchedCVE, excludedCVE, severity string, pageFinder *CvePageFinder) {
searchedCVE = strings.ToUpper(searchedCVE)
for _, cve := range cveList {
if severity != "" && (cvemodel.CompareSeverities(cve.Severity, severity) != 0) {
continue
}
if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
continue
}
if cve.ContainsStr(searchedCVE) {
pageFinder.Add(cve)
}
}
}
func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref string, searchedCVE string,
excludedCVE, severity string, pageInput cvemodel.PageInput,
) (
[]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error,
) {
imageCVESummary := cvemodel.ImageCVESummary{
MaxSeverity: cvemodel.SeverityNotScanned,
}
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
if !isValidImage {
cveinfo.Log.Debug().Str("image", repo+":"+ref).Err(err).Msg("image is not scanable")
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
}
image := zcommon.GetFullImageName(repo, ref)
cveMap, err := cveinfo.Scanner.ScanImage(ctx, image)
if err != nil {
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
}
imageCVESummary = initCVESummaryFromCVEMap(cveMap)
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
if err != nil {
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
}
filterCVEMap(cveMap, searchedCVE, excludedCVE, severity, pageFinder)
cveList, pageInfo := pageFinder.Page()
return cveList, imageCVESummary, pageInfo, nil
}
func (cveinfo BaseCveInfo) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string,
excludedCVE string, pageInput cvemodel.PageInput,
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) {
minuendRepo, minuendRef, _ := zcommon.GetImageDirAndReference(minuend)
subtrahendRepo, subtrahendRef, _ := zcommon.GetImageDirAndReference(subtrahend)
// get the CVEs of image and comparedImage
minuendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, minuendRepo, minuendRef, searchedCVE, excludedCVE,
"", cvemodel.PageInput{})
if err != nil {
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
}
subtrahendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, subtrahendRepo, subtrahendRef,
searchedCVE, excludedCVE, "", cvemodel.PageInput{})
if err != nil {
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
}
subtrahendCVEMap := map[string]cvemodel.CVE{}
for _, cve := range subtrahendCVEList {
cve := cve
subtrahendCVEMap[cve.ID] = cve
}
var (
count int
unknownCount int
lowCount int
mediumCount int
highCount int
criticalCount int
maxSeverity string
diffCVEs = []cvemodel.CVE{}
)
for i := range minuendCVEList {
if _, ok := subtrahendCVEMap[minuendCVEList[i].ID]; !ok {
diffCVEs = append(diffCVEs, minuendCVEList[i])
switch minuendCVEList[i].Severity {
case cvemodel.SeverityUnknown:
unknownCount++
case cvemodel.SeverityLow:
lowCount++
case cvemodel.SeverityMedium:
mediumCount++
case cvemodel.SeverityHigh:
highCount++
case cvemodel.SeverityCritical:
criticalCount++
}
if cvemodel.CompareSeverities(maxSeverity, minuendCVEList[i].Severity) > 0 {
maxSeverity = minuendCVEList[i].Severity
}
}
}
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
if err != nil {
return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
}
filterCVEList(diffCVEs, "", "", "", pageFinder)
cveList, pageInfo := pageFinder.Page()
count = unknownCount + lowCount + mediumCount + highCount + criticalCount
diffCVESummary := cvemodel.ImageCVESummary{
Count: count,
UnknownCount: unknownCount,
LowCount: lowCount,
MediumCount: mediumCount,
HighCount: highCount,
CriticalCount: criticalCount,
MaxSeverity: maxSeverity,
}
return cveList, diffCVESummary, pageInfo, nil
}
func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string,
) (cvemodel.ImageCVESummary, error) {
// There are several cases, expected returned values below:
// not scanned yet - max severity "" - cve count 0 - no Errors
// not scannable - max severity "" - cve count 0 - has Errors
// scannable no issues found - max severity "NONE" - cve count 0 - no Errors
// scannable issues found - max severity from Scanner - cve count >0 - no Errors
// For this call we only look at the scanner cache, we skip the actual scanning to save time
if !cveinfo.Scanner.IsResultCached(digestStr) {
isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, digestStr, mediaType)
if !isValidImage {
cveinfo.Log.Debug().Str("digest", digestStr).Str("mediaType", mediaType).
Err(err).Msg("image is not scannable")
}
// Counters are initialized with 0 by default
imageCVESummary := cvemodel.ImageCVESummary{
MaxSeverity: cvemodel.SeverityNotScanned,
}
return imageCVESummary, err
}
// We will make due with cached results
cveMap := cveinfo.Scanner.GetCachedResult(digestStr)
return initCVESummaryFromCVEMap(cveMap), nil
}
func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
earliestVulnerable := vulnerableTags[0]
vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
for _, tag := range vulnerableTags {
vulnerableTagMap[tag.Tag] = tag
switch tag.Descriptor.MediaType {
case ispec.MediaTypeImageManifest:
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
earliestVulnerable = tag
}
case ispec.MediaTypeImageIndex:
for _, manifestDesc := range tag.Manifests {
if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
earliestVulnerable = tag
}
}
default:
continue
}
}
var fixedTags []cvemodel.TagInfo
// There are some downsides to this logic
// We assume there can't be multiple "branches" of the same
// image built at different times containing different fixes
// There may be older images which have a fix or
// newer images which don't
for _, tag := range allTags {
switch tag.Descriptor.MediaType {
case ispec.MediaTypeImageManifest:
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
// The vulnerability did not exist at the time this
// image was built
continue
}
// If the image is old enough for the vulnerability to
// exist, but it was not detected, it means it contains
// the fix
if _, ok := vulnerableTagMap[tag.Tag]; !ok {
fixedTags = append(fixedTags, tag)
}
case ispec.MediaTypeImageIndex:
fixedManifests := []cvemodel.DescriptorInfo{}
// If the latest update inside the index is before the earliest vulnerability found then
// the index can't contain a fix
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
continue
}
vulnTagInfo, indexHasVulnerableManifest := vulnerableTagMap[tag.Tag]
for _, manifestDesc := range tag.Manifests {
if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
// The vulnerability did not exist at the time this image was built
continue
}
// check if the current manifest doesn't have the vulnerability
if !indexHasVulnerableManifest || !containsDescriptorInfo(vulnTagInfo.Manifests, manifestDesc) {
fixedManifests = append(fixedManifests, manifestDesc)
}
}
if len(fixedManifests) > 0 {
fixedTag := tag
fixedTag.Manifests = fixedManifests
fixedTags = append(fixedTags, fixedTag)
}
default:
continue
}
}
return fixedTags
}
func containsDescriptorInfo(slice []cvemodel.DescriptorInfo, descriptorInfo cvemodel.DescriptorInfo) bool {
for _, di := range slice {
if di.Digest == descriptorInfo.Digest {
return true
}
}
return false
}
func initCVESummaryFromCVEMap(cveMap map[string]cvemodel.CVE) cvemodel.ImageCVESummary {
// Counters are initialized with 0 by default
imageCVESummary := cvemodel.ImageCVESummary{
MaxSeverity: cvemodel.SeverityNotScanned,
}
imageCVESummary.Count = len(cveMap)
if imageCVESummary.Count == 0 {
imageCVESummary.MaxSeverity = cvemodel.SeverityNone
return imageCVESummary
}
imageCVESummary.MaxSeverity = cvemodel.SeverityUnknown
for _, cve := range cveMap {
switch cve.Severity {
case cvemodel.SeverityUnknown:
imageCVESummary.UnknownCount += 1
case cvemodel.SeverityLow:
imageCVESummary.LowCount += 1
case cvemodel.SeverityMedium:
imageCVESummary.MediumCount += 1
case cvemodel.SeverityHigh:
imageCVESummary.HighCount += 1
case cvemodel.SeverityCritical:
imageCVESummary.CriticalCount += 1
}
if cvemodel.CompareSeverities(imageCVESummary.MaxSeverity, cve.Severity) > 0 {
imageCVESummary.MaxSeverity = cve.Severity
}
}
return imageCVESummary
}