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 }