2020-06-24 14:38:42 -05:00
|
|
|
package cveinfo
|
|
|
|
|
|
|
|
import (
|
2023-01-09 15:37:44 -05:00
|
|
|
"encoding/json"
|
2021-04-05 19:40:33 -05:00
|
|
|
"fmt"
|
2022-09-28 13:39:54 -05:00
|
|
|
|
2022-10-22 15:46:13 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
2022-01-19 10:57:10 -05:00
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
"zotregistry.io/zot/errors"
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
2022-09-28 13:39:54 -05:00
|
|
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
|
|
|
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/log"
|
2023-01-09 15:37:44 -05:00
|
|
|
"zotregistry.io/zot/pkg/meta/repodb"
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/storage"
|
2020-06-24 14:38:42 -05:00
|
|
|
)
|
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
type CveInfo interface {
|
2023-01-20 15:09:40 -05:00
|
|
|
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
|
2022-09-28 13:39:54 -05:00
|
|
|
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
|
2023-02-27 14:23:18 -05:00
|
|
|
GetCVEListForImage(repo, tag string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
|
|
|
|
GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
|
2023-01-24 18:03:10 -05:00
|
|
|
CompareSeverities(severity1, severity2 string) int
|
2022-09-28 13:39:54 -05:00
|
|
|
UpdateDB() error
|
2020-06-24 14:38:42 -05:00
|
|
|
}
|
2020-06-26 14:09:10 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
type Scanner interface {
|
|
|
|
ScanImage(image string) (map[string]cvemodel.CVE, error)
|
2023-02-27 14:23:18 -05:00
|
|
|
IsImageFormatScannable(repo, tag string) (bool, error)
|
2022-09-28 13:39:54 -05:00
|
|
|
CompareSeverities(severity1, severity2 string) int
|
|
|
|
UpdateDB() error
|
2021-10-04 16:27:26 -05:00
|
|
|
}
|
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
type ImageCVESummary struct {
|
|
|
|
Count int
|
|
|
|
MaxSeverity string
|
|
|
|
}
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
type BaseCveInfo struct {
|
2023-01-09 15:37:44 -05:00
|
|
|
Log log.Logger
|
|
|
|
Scanner Scanner
|
|
|
|
RepoDB repodb.RepoDB
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func NewCVEInfo(storeController storage.StoreController, repoDB repodb.RepoDB,
|
2023-01-18 17:18:03 -05:00
|
|
|
dbRepository string, log log.Logger,
|
2023-01-09 15:37:44 -05:00
|
|
|
) *BaseCveInfo {
|
2023-01-18 17:18:03 -05:00
|
|
|
scanner := trivy.NewScanner(storeController, repoDB, dbRepository, log)
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return &BaseCveInfo{
|
|
|
|
Log: log,
|
|
|
|
Scanner: scanner,
|
|
|
|
RepoDB: repoDB,
|
|
|
|
}
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2023-01-20 15:09:40 -05:00
|
|
|
func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) {
|
|
|
|
imgList := make([]common.TagInfo, 0)
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
repoMeta, err := cveinfo.RepoDB.GetRepoMeta(repo)
|
2022-09-28 13:39:54 -05:00
|
|
|
if err != nil {
|
2023-01-09 15:37:44 -05:00
|
|
|
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("cve-id", cveID).
|
|
|
|
Msg("unable to get list of tags from repo")
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
return imgList, err
|
|
|
|
}
|
2021-10-04 16:27:26 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for tag, descriptor := range repoMeta.Tags {
|
2023-02-27 14:23:18 -05:00
|
|
|
switch descriptor.MediaType {
|
|
|
|
case ispec.MediaTypeImageManifest:
|
|
|
|
manifestDigestStr := descriptor.Digest
|
|
|
|
|
|
|
|
manifestDigest := godigest.Digest(manifestDigestStr)
|
|
|
|
|
|
|
|
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
|
|
|
if !isScanableImage || err != nil {
|
|
|
|
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
|
|
|
|
if err != nil {
|
|
|
|
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, hasCVE := cveMap[cveID]; hasCVE {
|
|
|
|
imgList = append(imgList, common.TagInfo{
|
|
|
|
Name: tag,
|
|
|
|
Descriptor: common.Descriptor{
|
|
|
|
Digest: manifestDigest,
|
|
|
|
MediaType: descriptor.MediaType,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
cveinfo.Log.Error().Msgf("type '%s' not supported for scanning", descriptor.MediaType)
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
|
|
|
}
|
2020-06-26 14:09:10 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
return imgList, nil
|
2020-06-26 14:09:10 -05:00
|
|
|
}
|
2020-08-19 01:03:16 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error) {
|
2023-01-09 15:37:44 -05:00
|
|
|
repoMeta, err := cveinfo.RepoDB.GetRepoMeta(repo)
|
2022-09-28 13:39:54 -05:00
|
|
|
if err != nil {
|
2023-01-09 15:37:44 -05:00
|
|
|
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("cve-id", cveID).
|
|
|
|
Msg("unable to get list of tags from repo")
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
return []common.TagInfo{}, err
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
vulnerableTags := make([]common.TagInfo, 0)
|
2023-01-09 15:37:44 -05:00
|
|
|
allTags := make([]common.TagInfo, 0)
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
var hasCVE bool
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for tag, descriptor := range repoMeta.Tags {
|
|
|
|
manifestDigestStr := descriptor.Digest
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
switch descriptor.MediaType {
|
|
|
|
case ispec.MediaTypeImageManifest:
|
|
|
|
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
|
|
|
if err != nil {
|
|
|
|
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
|
|
|
|
Str("cve-id", cveID).Str("digest", manifestDigestStr).Msg("unable to parse digest")
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
continue
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
manifestMeta, err := cveinfo.RepoDB.GetManifestMeta(repo, manifestDigest)
|
|
|
|
if err != nil {
|
|
|
|
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
|
|
|
|
Str("cve-id", cveID).Msg("unable to obtain manifest meta")
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
continue
|
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
var configContent ispec.Image
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
|
|
|
if err != nil {
|
|
|
|
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
|
|
|
|
Str("cve-id", cveID).Msg("unable to unmashal manifest blob")
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
continue
|
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
tagInfo := common.TagInfo{
|
|
|
|
Name: tag,
|
|
|
|
Timestamp: common.GetImageLastUpdated(configContent),
|
|
|
|
Descriptor: common.Descriptor{Digest: manifestDigest, MediaType: descriptor.MediaType},
|
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
allTags = append(allTags, tagInfo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
image := fmt.Sprintf("%s:%s", repo, tag)
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
|
|
|
if !isValidImage || err != nil {
|
|
|
|
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
|
|
|
Msg("image media type not supported for scanning, adding as a vulnerable image")
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
vulnerableTags = append(vulnerableTags, tagInfo)
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
continue
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
|
|
|
|
if err != nil {
|
|
|
|
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
|
|
|
Msg("scanning failed, adding as a vulnerable image")
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
vulnerableTags = append(vulnerableTags, tagInfo)
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
hasCVE = false
|
|
|
|
|
|
|
|
for id := range cveMap {
|
|
|
|
if id == cveID {
|
|
|
|
hasCVE = true
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if hasCVE {
|
|
|
|
vulnerableTags = append(vulnerableTags, tagInfo)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
cveinfo.Log.Error().Msgf("media type not supported '%s'", descriptor.MediaType)
|
|
|
|
|
|
|
|
return []common.TagInfo{},
|
|
|
|
fmt.Errorf("media type '%s' is not supported: %w", descriptor.MediaType, errors.ErrNotImplemented)
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
var fixedTags []common.TagInfo
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if len(vulnerableTags) != 0 {
|
|
|
|
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).Msgf("Vulnerable tags: %v", vulnerableTags)
|
|
|
|
fixedTags = common.GetFixedTags(allTags, vulnerableTags)
|
|
|
|
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).Msgf("Fixed tags: %v", fixedTags)
|
2022-09-28 13:39:54 -05:00
|
|
|
} else {
|
|
|
|
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).
|
|
|
|
Msg("image does not contain any tag that have given cve")
|
2023-01-09 15:37:44 -05:00
|
|
|
fixedTags = allTags
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return fixedTags, nil
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, pageInput PageInput) (
|
2023-01-24 18:03:10 -05:00
|
|
|
[]cvemodel.CVE,
|
|
|
|
PageInfo,
|
|
|
|
error,
|
|
|
|
) {
|
2023-02-27 14:23:18 -05:00
|
|
|
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
2022-09-28 13:39:54 -05:00
|
|
|
if !isValidImage {
|
2023-01-24 18:03:10 -05:00
|
|
|
return []cvemodel.CVE{}, PageInfo{}, err
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
image := getImageString(repo, tag)
|
|
|
|
|
2023-01-24 18:03:10 -05:00
|
|
|
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
|
|
|
if err != nil {
|
|
|
|
return []cvemodel.CVE{}, PageInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy, cveinfo)
|
|
|
|
if err != nil {
|
|
|
|
return []cvemodel.CVE{}, PageInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, cve := range cveMap {
|
|
|
|
pageFinder.Add(cve)
|
|
|
|
}
|
|
|
|
|
|
|
|
cveList, pageInfo := pageFinder.Page()
|
|
|
|
|
|
|
|
return cveList, pageInfo, nil
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
|
|
|
|
) (ImageCVESummary, error) {
|
2022-10-24 14:27:26 -05:00
|
|
|
// There are several cases, expected returned values below:
|
|
|
|
// not scannable / error during scan - max severity "" - cve count 0 - 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
|
2022-09-28 13:39:54 -05:00
|
|
|
imageCVESummary := ImageCVESummary{
|
|
|
|
Count: 0,
|
2022-10-24 14:27:26 -05:00
|
|
|
MaxSeverity: "",
|
2022-09-28 13:39:54 -05:00
|
|
|
}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
2022-09-28 13:39:54 -05:00
|
|
|
if !isValidImage {
|
|
|
|
return imageCVESummary, err
|
|
|
|
}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
image := getImageString(repo, tag)
|
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
|
|
|
if err != nil {
|
|
|
|
return imageCVESummary, err
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
2022-10-24 14:27:26 -05:00
|
|
|
imageCVESummary.Count = len(cveMap)
|
|
|
|
|
|
|
|
if imageCVESummary.Count == 0 {
|
|
|
|
imageCVESummary.MaxSeverity = "NONE"
|
|
|
|
|
|
|
|
return imageCVESummary, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
imageCVESummary.MaxSeverity = "UNKNOWN"
|
2022-09-28 13:39:54 -05:00
|
|
|
for _, cve := range cveMap {
|
|
|
|
if cveinfo.Scanner.CompareSeverities(imageCVESummary.MaxSeverity, cve.Severity) > 0 {
|
|
|
|
imageCVESummary.MaxSeverity = cve.Severity
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
return imageCVESummary, nil
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
func getImageString(repo, reference string) string {
|
|
|
|
image := repo + ":" + reference
|
|
|
|
|
|
|
|
if common.ReferenceIsDigest(reference) {
|
|
|
|
image = repo + "@" + reference
|
|
|
|
}
|
|
|
|
|
|
|
|
return image
|
|
|
|
}
|
|
|
|
|
2022-09-28 13:39:54 -05:00
|
|
|
func (cveinfo BaseCveInfo) UpdateDB() error {
|
|
|
|
return cveinfo.Scanner.UpdateDB()
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
2023-01-24 18:03:10 -05:00
|
|
|
|
|
|
|
func (cveinfo BaseCveInfo) CompareSeverities(severity1, severity2 string) int {
|
|
|
|
return cveinfo.Scanner.CompareSeverities(severity1, severity2)
|
|
|
|
}
|