zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/cve/cve.go (about)

     1  package cveinfo
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"strings"
     7  	"time"
     8  
     9  	godigest "github.com/opencontainers/go-digest"
    10  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    11  
    12  	zerr "zotregistry.dev/zot/errors"
    13  	zcommon "zotregistry.dev/zot/pkg/common"
    14  	cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model"
    15  	"zotregistry.dev/zot/pkg/extensions/search/cve/trivy"
    16  	"zotregistry.dev/zot/pkg/log"
    17  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    18  	"zotregistry.dev/zot/pkg/storage"
    19  )
    20  
    21  type CveInfo interface {
    22  	GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
    23  	GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
    24  	GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE, excludedCVE string, severity string,
    25  		pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
    26  	GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE, excludedCVE string,
    27  		pageInput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
    28  	GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error)
    29  }
    30  
    31  type Scanner interface {
    32  	ScanImage(ctx context.Context, image string) (map[string]cvemodel.CVE, error)
    33  	IsImageFormatScannable(repo, ref string) (bool, error)
    34  	IsImageMediaScannable(repo, digestStr, mediaType string) (bool, error)
    35  	IsResultCached(digestStr string) bool
    36  	GetCachedResult(digestStr string) map[string]cvemodel.CVE
    37  	UpdateDB(ctx context.Context) error
    38  }
    39  
    40  type BaseCveInfo struct {
    41  	Log     log.Logger
    42  	Scanner Scanner
    43  	MetaDB  mTypes.MetaDB
    44  }
    45  
    46  func NewScanner(storeController storage.StoreController, metaDB mTypes.MetaDB,
    47  	dbRepository, javaDBRepository string, log log.Logger,
    48  ) Scanner {
    49  	return trivy.NewScanner(storeController, metaDB, dbRepository, javaDBRepository, log)
    50  }
    51  
    52  func NewCVEInfo(scanner Scanner, metaDB mTypes.MetaDB, log log.Logger) *BaseCveInfo {
    53  	return &BaseCveInfo{
    54  		Log:     log,
    55  		Scanner: scanner,
    56  		MetaDB:  metaDB,
    57  	}
    58  }
    59  
    60  func (cveinfo BaseCveInfo) GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error) {
    61  	imgList := make([]cvemodel.TagInfo, 0)
    62  
    63  	repoMeta, err := cveinfo.MetaDB.GetRepoMeta(ctx, repo)
    64  	if err != nil {
    65  		cveinfo.Log.Error().Err(err).Str("repository", repo).Str("cve-id", cveID).
    66  			Msg("failed to get list of tags from repo")
    67  
    68  		return imgList, err
    69  	}
    70  
    71  	for tag, descriptor := range repoMeta.Tags {
    72  		switch descriptor.MediaType {
    73  		case ispec.MediaTypeImageManifest, ispec.MediaTypeImageIndex:
    74  			manifestDigestStr := descriptor.Digest
    75  
    76  			manifestDigest := godigest.Digest(manifestDigestStr)
    77  
    78  			isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, manifestDigestStr)
    79  			if !isScanableImage || err != nil {
    80  				cveinfo.Log.Debug().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
    81  
    82  				continue
    83  			}
    84  
    85  			cveMap, err := cveinfo.Scanner.ScanImage(ctx, zcommon.GetFullImageName(repo, tag))
    86  			if err != nil {
    87  				if zcommon.IsContextDone(ctx) {
    88  					return imgList, err
    89  				}
    90  
    91  				cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
    92  
    93  				continue
    94  			}
    95  
    96  			if _, hasCVE := cveMap[cveID]; hasCVE {
    97  				imgList = append(imgList, cvemodel.TagInfo{
    98  					Tag: tag,
    99  					Descriptor: cvemodel.Descriptor{
   100  						Digest:    manifestDigest,
   101  						MediaType: descriptor.MediaType,
   102  					},
   103  				})
   104  			}
   105  		default:
   106  			cveinfo.Log.Debug().Str("image", repo+":"+tag).Str("mediaType", descriptor.MediaType).
   107  				Msg("image media type not supported for scanning")
   108  		}
   109  	}
   110  
   111  	return imgList, nil
   112  }
   113  
   114  func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(ctx context.Context, repo, cveID string,
   115  ) ([]cvemodel.TagInfo, error) {
   116  	repoMeta, err := cveinfo.MetaDB.GetRepoMeta(ctx, repo)
   117  	if err != nil {
   118  		cveinfo.Log.Error().Err(err).Str("repository", repo).Str("cve-id", cveID).
   119  			Msg("failed to get list of tags from repo")
   120  
   121  		return []cvemodel.TagInfo{}, err
   122  	}
   123  
   124  	vulnerableTags := make([]cvemodel.TagInfo, 0)
   125  	allTags := make([]cvemodel.TagInfo, 0)
   126  
   127  	for tag, descriptor := range repoMeta.Tags {
   128  		if zcommon.IsContextDone(ctx) {
   129  			return []cvemodel.TagInfo{}, ctx.Err()
   130  		}
   131  
   132  		switch descriptor.MediaType {
   133  		case ispec.MediaTypeImageManifest:
   134  			manifestDigestStr := descriptor.Digest
   135  
   136  			tagInfo, err := getTagInfoForManifest(tag, manifestDigestStr, cveinfo.MetaDB)
   137  			if err != nil {
   138  				cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
   139  					Str("cve-id", cveID).Msg("failed to retrieve manifest and config")
   140  
   141  				continue
   142  			}
   143  
   144  			allTags = append(allTags, tagInfo)
   145  
   146  			if cveinfo.isManifestVulnerable(ctx, repo, tag, manifestDigestStr, cveID) {
   147  				vulnerableTags = append(vulnerableTags, tagInfo)
   148  			}
   149  		case ispec.MediaTypeImageIndex:
   150  			indexDigestStr := descriptor.Digest
   151  
   152  			indexContent, err := getIndexContent(cveinfo.MetaDB, indexDigestStr)
   153  			if err != nil {
   154  				continue
   155  			}
   156  
   157  			vulnerableManifests := []cvemodel.DescriptorInfo{}
   158  			allManifests := []cvemodel.DescriptorInfo{}
   159  
   160  			for _, manifest := range indexContent.Manifests {
   161  				tagInfo, err := getTagInfoForManifest(tag, manifest.Digest.String(), cveinfo.MetaDB)
   162  				if err != nil {
   163  					cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
   164  						Str("cve-id", cveID).Msg("failed to retrieve manifest and config")
   165  
   166  					continue
   167  				}
   168  
   169  				manifestDescriptorInfo := cvemodel.DescriptorInfo{
   170  					Descriptor: tagInfo.Descriptor,
   171  					Timestamp:  tagInfo.Timestamp,
   172  				}
   173  
   174  				allManifests = append(allManifests, manifestDescriptorInfo)
   175  
   176  				if cveinfo.isManifestVulnerable(ctx, repo, tag, manifest.Digest.String(), cveID) {
   177  					vulnerableManifests = append(vulnerableManifests, manifestDescriptorInfo)
   178  				}
   179  			}
   180  
   181  			if len(allManifests) > 0 {
   182  				allTags = append(allTags, cvemodel.TagInfo{
   183  					Tag: tag,
   184  					Descriptor: cvemodel.Descriptor{
   185  						Digest:    godigest.Digest(indexDigestStr),
   186  						MediaType: ispec.MediaTypeImageIndex,
   187  					},
   188  					Manifests: allManifests,
   189  					Timestamp: mostRecentUpdate(allManifests),
   190  				})
   191  			}
   192  
   193  			if len(vulnerableManifests) > 0 {
   194  				vulnerableTags = append(vulnerableTags, cvemodel.TagInfo{
   195  					Tag: tag,
   196  					Descriptor: cvemodel.Descriptor{
   197  						Digest:    godigest.Digest(indexDigestStr),
   198  						MediaType: ispec.MediaTypeImageIndex,
   199  					},
   200  					Manifests: vulnerableManifests,
   201  					Timestamp: mostRecentUpdate(vulnerableManifests),
   202  				})
   203  			}
   204  		default:
   205  			cveinfo.Log.Debug().Str("mediaType", descriptor.MediaType).
   206  				Msg("image media type not supported for scanning")
   207  		}
   208  	}
   209  
   210  	var fixedTags []cvemodel.TagInfo
   211  
   212  	if len(vulnerableTags) != 0 {
   213  		cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
   214  			Interface("tags", vulnerableTags).Msg("vulnerable tags")
   215  		fixedTags = GetFixedTags(allTags, vulnerableTags)
   216  		cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
   217  			Interface("tags", fixedTags).Msg("fixed tags")
   218  	} else {
   219  		cveinfo.Log.Info().Str("repository", repo).Str("cve-id", cveID).
   220  			Msg("image does not contain any tag that have given cve")
   221  		fixedTags = allTags
   222  	}
   223  
   224  	return fixedTags, nil
   225  }
   226  
   227  func mostRecentUpdate(allManifests []cvemodel.DescriptorInfo) time.Time {
   228  	if len(allManifests) == 0 {
   229  		return time.Time{}
   230  	}
   231  
   232  	timeStamp := allManifests[0].Timestamp
   233  
   234  	for i := range allManifests {
   235  		if timeStamp.Before(allManifests[i].Timestamp) {
   236  			timeStamp = allManifests[i].Timestamp
   237  		}
   238  	}
   239  
   240  	return timeStamp
   241  }
   242  
   243  func getTagInfoForManifest(tag, manifestDigestStr string, metaDB mTypes.MetaDB) (cvemodel.TagInfo, error) {
   244  	configContent, manifestDigest, err := getConfigAndDigest(metaDB, manifestDigestStr)
   245  	if err != nil {
   246  		return cvemodel.TagInfo{}, err
   247  	}
   248  
   249  	lastUpdated := zcommon.GetImageLastUpdated(configContent)
   250  
   251  	return cvemodel.TagInfo{
   252  		Tag:        tag,
   253  		Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
   254  		Manifests: []cvemodel.DescriptorInfo{
   255  			{
   256  				Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
   257  				Timestamp:  lastUpdated,
   258  			},
   259  		},
   260  		Timestamp: lastUpdated,
   261  	}, nil
   262  }
   263  
   264  func (cveinfo *BaseCveInfo) isManifestVulnerable(ctx context.Context, repo, tag, manifestDigestStr, cveID string,
   265  ) bool {
   266  	image := zcommon.GetFullImageName(repo, tag)
   267  
   268  	isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, manifestDigestStr, ispec.MediaTypeImageManifest)
   269  	if !isValidImage || err != nil {
   270  		cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).Err(err).
   271  			Msg("image media type not supported for scanning, adding as a vulnerable image")
   272  
   273  		return true
   274  	}
   275  
   276  	cveMap, err := cveinfo.Scanner.ScanImage(ctx, zcommon.GetFullImageName(repo, manifestDigestStr))
   277  	if err != nil {
   278  		cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
   279  			Msg("scanning failed, adding as a vulnerable image")
   280  
   281  		return true
   282  	}
   283  
   284  	hasCVE := false
   285  
   286  	for id := range cveMap {
   287  		if id == cveID {
   288  			hasCVE = true
   289  
   290  			break
   291  		}
   292  	}
   293  
   294  	return hasCVE
   295  }
   296  
   297  func getIndexContent(metaDB mTypes.MetaDB, indexDigestStr string) (ispec.Index, error) {
   298  	indexDigest, err := godigest.Parse(indexDigestStr)
   299  	if err != nil {
   300  		return ispec.Index{}, err
   301  	}
   302  
   303  	indexData, err := metaDB.GetImageMeta(indexDigest)
   304  	if err != nil {
   305  		return ispec.Index{}, err
   306  	}
   307  
   308  	if indexData.Index == nil {
   309  		return ispec.Index{}, zerr.ErrUnexpectedMediaType
   310  	}
   311  
   312  	return *indexData.Index, nil
   313  }
   314  
   315  func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.Image, godigest.Digest, error) {
   316  	manifestDigest, err := godigest.Parse(manifestDigestStr)
   317  	if err != nil {
   318  		return ispec.Image{}, "", err
   319  	}
   320  
   321  	manifestData, err := metaDB.GetImageMeta(manifestDigest)
   322  	if err != nil {
   323  		return ispec.Image{}, "", err
   324  	}
   325  
   326  	// we'll fail the execution if the config is not compatible with ispec.Image because we can't scan this type of images.
   327  	if manifestData.Manifests[0].Manifest.Config.MediaType != ispec.MediaTypeImageConfig {
   328  		return ispec.Image{}, "", zerr.ErrUnexpectedMediaType
   329  	}
   330  
   331  	return manifestData.Manifests[0].Config, manifestDigest, err
   332  }
   333  
   334  func filterCVEMap(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE, severity string,
   335  	pageFinder *CvePageFinder,
   336  ) {
   337  	searchedCVE = strings.ToUpper(searchedCVE)
   338  
   339  	for _, cve := range cveMap {
   340  		if severity != "" && (cvemodel.CompareSeverities(cve.Severity, severity) != 0) {
   341  			continue
   342  		}
   343  
   344  		if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
   345  			continue
   346  		}
   347  
   348  		if cve.ContainsStr(searchedCVE) {
   349  			pageFinder.Add(cve)
   350  		}
   351  	}
   352  }
   353  
   354  func filterCVEList(cveList []cvemodel.CVE, searchedCVE, excludedCVE, severity string, pageFinder *CvePageFinder) {
   355  	searchedCVE = strings.ToUpper(searchedCVE)
   356  
   357  	for _, cve := range cveList {
   358  		if severity != "" && (cvemodel.CompareSeverities(cve.Severity, severity) != 0) {
   359  			continue
   360  		}
   361  
   362  		if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
   363  			continue
   364  		}
   365  
   366  		if cve.ContainsStr(searchedCVE) {
   367  			pageFinder.Add(cve)
   368  		}
   369  	}
   370  }
   371  
   372  func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref string, searchedCVE string,
   373  	excludedCVE, severity string, pageInput cvemodel.PageInput,
   374  ) (
   375  	[]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error,
   376  ) {
   377  	imageCVESummary := cvemodel.ImageCVESummary{
   378  		MaxSeverity: cvemodel.SeverityNotScanned,
   379  	}
   380  
   381  	isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
   382  	if !isValidImage {
   383  		cveinfo.Log.Debug().Str("image", repo+":"+ref).Err(err).Msg("image is not scanable")
   384  
   385  		return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
   386  	}
   387  
   388  	image := zcommon.GetFullImageName(repo, ref)
   389  
   390  	cveMap, err := cveinfo.Scanner.ScanImage(ctx, image)
   391  	if err != nil {
   392  		return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
   393  	}
   394  
   395  	imageCVESummary = initCVESummaryFromCVEMap(cveMap)
   396  
   397  	pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
   398  	if err != nil {
   399  		return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
   400  	}
   401  
   402  	filterCVEMap(cveMap, searchedCVE, excludedCVE, severity, pageFinder)
   403  
   404  	cveList, pageInfo := pageFinder.Page()
   405  
   406  	return cveList, imageCVESummary, pageInfo, nil
   407  }
   408  
   409  func (cveinfo BaseCveInfo) GetCVEDiffListForImages(ctx context.Context, minuend, subtrahend, searchedCVE string,
   410  	excludedCVE string, pageInput cvemodel.PageInput,
   411  ) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error) {
   412  	minuendRepo, minuendRef, _ := zcommon.GetImageDirAndReference(minuend)
   413  	subtrahendRepo, subtrahendRef, _ := zcommon.GetImageDirAndReference(subtrahend)
   414  
   415  	// get the CVEs of image and comparedImage
   416  	minuendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, minuendRepo, minuendRef, searchedCVE, excludedCVE,
   417  		"", cvemodel.PageInput{})
   418  	if err != nil {
   419  		return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
   420  	}
   421  
   422  	subtrahendCVEList, _, _, err := cveinfo.GetCVEListForImage(ctx, subtrahendRepo, subtrahendRef,
   423  		searchedCVE, excludedCVE, "", cvemodel.PageInput{})
   424  	if err != nil {
   425  		return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
   426  	}
   427  
   428  	subtrahendCVEMap := map[string]cvemodel.CVE{}
   429  
   430  	for _, cve := range subtrahendCVEList {
   431  		cve := cve
   432  		subtrahendCVEMap[cve.ID] = cve
   433  	}
   434  
   435  	var (
   436  		count         int
   437  		unknownCount  int
   438  		lowCount      int
   439  		mediumCount   int
   440  		highCount     int
   441  		criticalCount int
   442  		maxSeverity   string
   443  
   444  		diffCVEs = []cvemodel.CVE{}
   445  	)
   446  
   447  	for i := range minuendCVEList {
   448  		if _, ok := subtrahendCVEMap[minuendCVEList[i].ID]; !ok {
   449  			diffCVEs = append(diffCVEs, minuendCVEList[i])
   450  
   451  			switch minuendCVEList[i].Severity {
   452  			case cvemodel.SeverityUnknown:
   453  				unknownCount++
   454  			case cvemodel.SeverityLow:
   455  				lowCount++
   456  			case cvemodel.SeverityMedium:
   457  				mediumCount++
   458  			case cvemodel.SeverityHigh:
   459  				highCount++
   460  			case cvemodel.SeverityCritical:
   461  				criticalCount++
   462  			}
   463  
   464  			if cvemodel.CompareSeverities(maxSeverity, minuendCVEList[i].Severity) > 0 {
   465  				maxSeverity = minuendCVEList[i].Severity
   466  			}
   467  		}
   468  	}
   469  
   470  	pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy)
   471  	if err != nil {
   472  		return nil, cvemodel.ImageCVESummary{}, zcommon.PageInfo{}, err
   473  	}
   474  
   475  	filterCVEList(diffCVEs, "", "", "", pageFinder)
   476  
   477  	cveList, pageInfo := pageFinder.Page()
   478  
   479  	count = unknownCount + lowCount + mediumCount + highCount + criticalCount
   480  
   481  	diffCVESummary := cvemodel.ImageCVESummary{
   482  		Count:         count,
   483  		UnknownCount:  unknownCount,
   484  		LowCount:      lowCount,
   485  		MediumCount:   mediumCount,
   486  		HighCount:     highCount,
   487  		CriticalCount: criticalCount,
   488  		MaxSeverity:   maxSeverity,
   489  	}
   490  
   491  	return cveList, diffCVESummary, pageInfo, nil
   492  }
   493  
   494  func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string,
   495  ) (cvemodel.ImageCVESummary, error) {
   496  	// There are several cases, expected returned values below:
   497  	// not scanned yet                     - max severity ""            - cve count 0   - no Errors
   498  	// not scannable                       - max severity ""            - cve count 0   - has Errors
   499  	// scannable no issues found           - max severity "NONE"        - cve count 0   - no Errors
   500  	// scannable issues found              - max severity from Scanner  - cve count >0  - no Errors
   501  	// For this call we only look at the scanner cache, we skip the actual scanning to save time
   502  	if !cveinfo.Scanner.IsResultCached(digestStr) {
   503  		isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, digestStr, mediaType)
   504  		if !isValidImage {
   505  			cveinfo.Log.Debug().Str("digest", digestStr).Str("mediaType", mediaType).
   506  				Err(err).Msg("image is not scannable")
   507  		}
   508  
   509  		// Counters are initialized with 0 by default
   510  		imageCVESummary := cvemodel.ImageCVESummary{
   511  			MaxSeverity: cvemodel.SeverityNotScanned,
   512  		}
   513  
   514  		return imageCVESummary, err
   515  	}
   516  
   517  	// We will make due with cached results
   518  	cveMap := cveinfo.Scanner.GetCachedResult(digestStr)
   519  
   520  	return initCVESummaryFromCVEMap(cveMap), nil
   521  }
   522  
   523  func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo {
   524  	sort.Slice(allTags, func(i, j int) bool {
   525  		return allTags[i].Timestamp.Before(allTags[j].Timestamp)
   526  	})
   527  
   528  	earliestVulnerable := vulnerableTags[0]
   529  	vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
   530  
   531  	for _, tag := range vulnerableTags {
   532  		vulnerableTagMap[tag.Tag] = tag
   533  
   534  		switch tag.Descriptor.MediaType {
   535  		case ispec.MediaTypeImageManifest:
   536  			if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
   537  				earliestVulnerable = tag
   538  			}
   539  		case ispec.MediaTypeImageIndex:
   540  			for _, manifestDesc := range tag.Manifests {
   541  				if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
   542  					earliestVulnerable = tag
   543  				}
   544  			}
   545  		default:
   546  			continue
   547  		}
   548  	}
   549  
   550  	var fixedTags []cvemodel.TagInfo
   551  
   552  	// There are some downsides to this logic
   553  	// We assume there can't be multiple "branches" of the same
   554  	// image built at different times containing different fixes
   555  	// There may be older images which have a fix or
   556  	// newer images which don't
   557  	for _, tag := range allTags {
   558  		switch tag.Descriptor.MediaType {
   559  		case ispec.MediaTypeImageManifest:
   560  			if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
   561  				// The vulnerability did not exist at the time this
   562  				// image was built
   563  				continue
   564  			}
   565  			// If the image is old enough for the vulnerability to
   566  			// exist, but it was not detected, it means it contains
   567  			// the fix
   568  			if _, ok := vulnerableTagMap[tag.Tag]; !ok {
   569  				fixedTags = append(fixedTags, tag)
   570  			}
   571  		case ispec.MediaTypeImageIndex:
   572  			fixedManifests := []cvemodel.DescriptorInfo{}
   573  
   574  			// If the latest update inside the index is before the earliest vulnerability found then
   575  			// the index can't contain a fix
   576  			if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
   577  				continue
   578  			}
   579  
   580  			vulnTagInfo, indexHasVulnerableManifest := vulnerableTagMap[tag.Tag]
   581  
   582  			for _, manifestDesc := range tag.Manifests {
   583  				if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
   584  					// The vulnerability did not exist at the time this image was built
   585  					continue
   586  				}
   587  
   588  				// check if the current manifest doesn't have the vulnerability
   589  				if !indexHasVulnerableManifest || !containsDescriptorInfo(vulnTagInfo.Manifests, manifestDesc) {
   590  					fixedManifests = append(fixedManifests, manifestDesc)
   591  				}
   592  			}
   593  
   594  			if len(fixedManifests) > 0 {
   595  				fixedTag := tag
   596  				fixedTag.Manifests = fixedManifests
   597  
   598  				fixedTags = append(fixedTags, fixedTag)
   599  			}
   600  		default:
   601  			continue
   602  		}
   603  	}
   604  
   605  	return fixedTags
   606  }
   607  
   608  func containsDescriptorInfo(slice []cvemodel.DescriptorInfo, descriptorInfo cvemodel.DescriptorInfo) bool {
   609  	for _, di := range slice {
   610  		if di.Digest == descriptorInfo.Digest {
   611  			return true
   612  		}
   613  	}
   614  
   615  	return false
   616  }
   617  
   618  func initCVESummaryFromCVEMap(cveMap map[string]cvemodel.CVE) cvemodel.ImageCVESummary {
   619  	// Counters are initialized with 0 by default
   620  	imageCVESummary := cvemodel.ImageCVESummary{
   621  		MaxSeverity: cvemodel.SeverityNotScanned,
   622  	}
   623  
   624  	imageCVESummary.Count = len(cveMap)
   625  	if imageCVESummary.Count == 0 {
   626  		imageCVESummary.MaxSeverity = cvemodel.SeverityNone
   627  
   628  		return imageCVESummary
   629  	}
   630  
   631  	imageCVESummary.MaxSeverity = cvemodel.SeverityUnknown
   632  
   633  	for _, cve := range cveMap {
   634  		switch cve.Severity {
   635  		case cvemodel.SeverityUnknown:
   636  			imageCVESummary.UnknownCount += 1
   637  		case cvemodel.SeverityLow:
   638  			imageCVESummary.LowCount += 1
   639  		case cvemodel.SeverityMedium:
   640  			imageCVESummary.MediumCount += 1
   641  		case cvemodel.SeverityHigh:
   642  			imageCVESummary.HighCount += 1
   643  		case cvemodel.SeverityCritical:
   644  			imageCVESummary.CriticalCount += 1
   645  		}
   646  
   647  		if cvemodel.CompareSeverities(imageCVESummary.MaxSeverity, cve.Severity) > 0 {
   648  			imageCVESummary.MaxSeverity = cve.Severity
   649  		}
   650  	}
   651  
   652  	return imageCVESummary
   653  }