0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-23 22:27:35 -05:00
zot/pkg/extensions/search/resolver.go
Alex Stan d325c8b5f4 Fix problems signaled by new linter version v1.45.2
PR (linter: upgrade linter version #405) triggered lint job which failed
with many errors generated by various linters. Configurations were added to
golangcilint.yaml and several refactorings were made in order to improve the
results of the linter.

maintidx linter disabled

Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
2022-04-27 09:55:44 -07:00

552 lines
14 KiB
Go

package search
//go:generate go run github.com/99designs/gqlgen
import (
"context"
"fmt"
"strconv"
"strings"
godigest "github.com/opencontainers/go-digest"
"zotregistry.io/zot/pkg/log" // nolint: gci
"zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
digestinfo "zotregistry.io/zot/pkg/extensions/search/digest"
"zotregistry.io/zot/pkg/storage"
) // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
// Resolver ...
type Resolver struct {
cveInfo *cveinfo.CveInfo
storeController storage.StoreController
digestInfo *digestinfo.DigestInfo
log log.Logger
}
// Query ...
func (r *Resolver) Query() QueryResolver {
return &queryResolver{r}
}
type queryResolver struct{ *Resolver }
type cveDetail struct {
Title string
Description string
Severity string
PackageList []*PackageInfo
}
// GetResolverConfig ...
func GetResolverConfig(log log.Logger, storeController storage.StoreController, enableCVE bool) Config {
var cveInfo *cveinfo.CveInfo
var err error
if enableCVE {
cveInfo, err = cveinfo.GetCVEInfo(storeController, log)
if err != nil {
panic(err)
}
}
digestInfo := digestinfo.NewDigestInfo(storeController, log)
resConfig := &Resolver{cveInfo: cveInfo, storeController: storeController, digestInfo: digestInfo, log: log}
return Config{
Resolvers: resConfig, Directives: DirectiveRoot{},
Complexity: ComplexityRoot{},
}
}
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, name string) (*RepoInfo, error) {
olu := common.NewOciLayoutUtils(r.storeController, r.log)
repo, err := olu.GetExpandedRepoInfo(name)
if err != nil {
r.log.Error().Err(err).Msg("error getting repos")
return &RepoInfo{}, err
}
// repos type is of common deep copy this to search
repoInfo := &RepoInfo{}
manifests := make([]*ManifestInfo, 0)
for _, manifest := range repo.Manifests {
tag := manifest.Tag
digest := manifest.Digest
isSigned := manifest.IsSigned
manifestInfo := &ManifestInfo{Tag: &tag, Digest: &digest, IsSigned: &isSigned}
layers := make([]*LayerInfo, 0)
for _, l := range manifest.Layers {
size := l.Size
digest := l.Digest
layerInfo := &LayerInfo{Digest: &digest, Size: &size}
layers = append(layers, layerInfo)
}
manifestInfo.Layers = layers
manifests = append(manifests, manifestInfo)
}
repoInfo.Manifests = manifests
return repoInfo, nil
}
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error) {
trivyCtx := r.cveInfo.GetTrivyContext(image)
r.log.Info().Str("image", image).Msg("scanning image")
isValidImage, err := r.cveInfo.LayoutUtils.IsValidImageFormat(image)
if !isValidImage {
r.log.Debug().Str("image", image).Msg("image media type not supported for scanning")
return &CVEResultForImage{}, err
}
report, err := cveinfo.ScanImage(trivyCtx.Ctx)
if err != nil {
r.log.Error().Err(err).Msg("unable to scan image repository")
return &CVEResultForImage{}, err
}
var copyImgTag string
if strings.Contains(image, ":") {
copyImgTag = strings.Split(image, ":")[1]
}
cveidMap := make(map[string]cveDetail)
for _, result := range report.Results {
for _, vulnerability := range result.Vulnerabilities {
pkgName := vulnerability.PkgName
installedVersion := vulnerability.InstalledVersion
var fixedVersion string
if vulnerability.FixedVersion != "" {
fixedVersion = vulnerability.FixedVersion
} else {
fixedVersion = "Not Specified"
}
_, ok := cveidMap[vulnerability.VulnerabilityID]
if ok {
cveDetailStruct := cveidMap[vulnerability.VulnerabilityID]
pkgList := cveDetailStruct.PackageList
pkgList = append(pkgList,
&PackageInfo{Name: &pkgName, InstalledVersion: &installedVersion, FixedVersion: &fixedVersion})
cveDetailStruct.PackageList = pkgList
cveidMap[vulnerability.VulnerabilityID] = cveDetailStruct
} else {
newPkgList := make([]*PackageInfo, 0)
newPkgList = append(newPkgList,
&PackageInfo{Name: &pkgName, InstalledVersion: &installedVersion, FixedVersion: &fixedVersion})
cveidMap[vulnerability.VulnerabilityID] = cveDetail{
Title: vulnerability.Title,
Description: vulnerability.Description, Severity: vulnerability.Severity, PackageList: newPkgList,
}
}
}
}
cveids := []*Cve{}
for id, cveDetail := range cveidMap {
vulID := id
desc := cveDetail.Description
title := cveDetail.Title
severity := cveDetail.Severity
pkgList := cveDetail.PackageList
cveids = append(cveids,
&Cve{ID: &vulID, Title: &title, Description: &desc, Severity: &severity, PackageList: pkgList})
}
return &CVEResultForImage{Tag: &copyImgTag, CVEList: cveids}, nil
}
func (r *queryResolver) ImageListForCve(ctx context.Context, cvid string) ([]*ImgResultForCve, error) {
finalCveResult := []*ImgResultForCve{}
r.log.Info().Msg("extracting repositories")
defaultStore := r.storeController.DefaultStore
defaultTrivyCtx := r.cveInfo.CveTrivyController.DefaultCveConfig
repoList, err := defaultStore.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return finalCveResult, err
}
r.cveInfo.Log.Info().Msg("scanning each global repository")
cveResult, err := r.getImageListForCVE(repoList, cvid, defaultStore, defaultTrivyCtx)
if err != nil {
r.log.Error().Err(err).Msg("error getting cve list for global repositories")
return finalCveResult, err
}
finalCveResult = append(finalCveResult, cveResult...)
subStore := r.storeController.SubStore
for route, store := range subStore {
subRepoList, err := store.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return cveResult, err
}
subTrivyCtx := r.cveInfo.CveTrivyController.SubCveConfig[route]
subCveResult, err := r.getImageListForCVE(subRepoList, cvid, store, subTrivyCtx)
if err != nil {
r.log.Error().Err(err).Msg("unable to get cve result for sub repositories")
return finalCveResult, err
}
finalCveResult = append(finalCveResult, subCveResult...)
}
return finalCveResult, nil
}
func (r *queryResolver) getImageListForCVE(repoList []string, cvid string, imgStore storage.ImageStore,
trivyCtx *cveinfo.TrivyCtx,
) ([]*ImgResultForCve, error) {
cveResult := []*ImgResultForCve{}
for _, repo := range repoList {
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
name := repo
tags, err := r.cveInfo.GetImageListForCVE(repo, cvid, imgStore, trivyCtx)
if err != nil {
r.log.Error().Err(err).Msg("error getting tag")
return cveResult, err
}
if len(tags) != 0 {
cveResult = append(cveResult, &ImgResultForCve{Name: &name, Tags: tags})
}
}
return cveResult, nil
}
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, cvid, image string) (*ImgResultForFixedCve, error) { // nolint: lll
imgResultForFixedCVE := &ImgResultForFixedCve{}
r.log.Info().Str("image", image).Msg("extracting list of tags available in image")
tagsInfo, err := r.cveInfo.LayoutUtils.GetImageTagsWithTimestamp(image)
if err != nil {
r.log.Error().Err(err).Msg("unable to read image tags")
return imgResultForFixedCVE, err
}
infectedTags := make([]common.TagInfo, 0)
var hasCVE bool
for _, tag := range tagsInfo {
image := fmt.Sprintf("%s:%s", image, tag.Name)
isValidImage, _ := r.cveInfo.LayoutUtils.IsValidImageFormat(image)
if !isValidImage {
r.log.Debug().Str("image",
fmt.Sprintf("%s:%s", image, tag.Name)).
Msg("image media type not supported for scanning, adding as an infected image")
infectedTags = append(infectedTags, common.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp})
continue
}
trivyCtx := r.cveInfo.GetTrivyContext(image)
r.cveInfo.Log.Info().Str("image", fmt.Sprintf("%s:%s", image, tag.Name)).Msg("scanning image")
report, err := cveinfo.ScanImage(trivyCtx.Ctx)
if err != nil {
r.log.Error().Err(err).
Str("image", fmt.Sprintf("%s:%s", image, tag.Name)).Msg("unable to scan image")
continue
}
hasCVE = false
for _, result := range report.Results {
for _, vulnerability := range result.Vulnerabilities {
if vulnerability.VulnerabilityID == cvid {
hasCVE = true
break
}
}
}
if hasCVE {
infectedTags = append(infectedTags, common.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp, Digest: tag.Digest})
}
}
var finalTagList []*TagInfo
if len(infectedTags) != 0 {
r.log.Info().Msg("comparing fixed tags timestamp")
fixedTags := common.GetFixedTags(tagsInfo, infectedTags)
finalTagList = getGraphqlCompatibleTags(fixedTags)
} else {
r.log.Info().Str("image", image).Str("cve-id", cvid).Msg("image does not contain any tag that have given cve")
finalTagList = getGraphqlCompatibleTags(tagsInfo)
}
imgResultForFixedCVE = &ImgResultForFixedCve{Tags: finalTagList}
return imgResultForFixedCVE, nil
}
func (r *queryResolver) ImageListForDigest(ctx context.Context, digestID string) ([]*ImgResultForDigest, error) {
imgResultForDigest := []*ImgResultForDigest{}
r.log.Info().Msg("extracting repositories")
defaultStore := r.storeController.DefaultStore
repoList, err := defaultStore.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return imgResultForDigest, err
}
r.log.Info().Msg("scanning each global repository")
partialImgResultForDigest, err := r.getImageListForDigest(repoList, digestID)
if err != nil {
r.log.Error().Err(err).Msg("unable to get image and tag list for global repositories")
return imgResultForDigest, err
}
imgResultForDigest = append(imgResultForDigest, partialImgResultForDigest...)
subStore := r.storeController.SubStore
for _, store := range subStore {
subRepoList, err := store.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search sub-repositories")
return imgResultForDigest, err
}
partialImgResultForDigest, err = r.getImageListForDigest(subRepoList, digestID)
if err != nil {
r.log.Error().Err(err).Msg("unable to get image and tag list for sub-repositories")
return imgResultForDigest, err
}
imgResultForDigest = append(imgResultForDigest, partialImgResultForDigest...)
}
return imgResultForDigest, nil
}
func (r *queryResolver) getImageListForDigest(repoList []string,
digest string,
) ([]*ImgResultForDigest, error) {
imgResultForDigest := []*ImgResultForDigest{}
var errResult error
for _, repo := range repoList {
r.log.Info().Str("repo", repo).Msg("filtering list of tags in image repo by digest")
tags, err := r.digestInfo.GetImageTagsByDigest(repo, digest)
if err != nil {
r.log.Error().Err(err).Msg("unable to get filtered list of image tags")
errResult = err
continue
}
if len(tags) != 0 {
name := repo
imgResultForDigest = append(imgResultForDigest, &ImgResultForDigest{Name: &name, Tags: tags})
}
}
return imgResultForDigest, errResult
}
func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*ImageInfo, error) {
r.log.Info().Msg("extension api: finding image list")
imageList := make([]*ImageInfo, 0)
defaultStore := r.storeController.DefaultStore
dsImageList, err := r.getImageListWithLatestTag(defaultStore)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
return imageList, err
}
if len(dsImageList) != 0 {
imageList = append(imageList, dsImageList...)
}
subStore := r.storeController.SubStore
for _, store := range subStore {
ssImageList, err := r.getImageListWithLatestTag(store)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
return imageList, err
}
if len(ssImageList) != 0 {
imageList = append(imageList, ssImageList...)
}
}
return imageList, nil
}
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*ImageInfo, error) {
results := make([]*ImageInfo, 0)
repoList, err := store.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting repositories list")
return results, err
}
if len(repoList) == 0 {
r.log.Info().Msg("no repositories found")
}
layoutUtils := common.NewOciLayoutUtils(r.storeController, r.log)
for _, repo := range repoList {
tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error getting tag timestamp info")
return results, err
}
if len(tagsInfo) == 0 {
r.log.Info().Str("no tagsinfo found for repo", repo).Msg(" continuing traversing")
continue
}
latestTag := common.GetLatestTag(tagsInfo)
digest := godigest.Digest(latestTag.Digest)
manifest, err := layoutUtils.GetImageBlobManifest(repo, digest)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error reading manifest")
return results, err
}
size := strconv.FormatInt(manifest.Config.Size, 10)
name := repo
imageConfig, err := layoutUtils.GetImageInfo(repo, manifest.Config.Digest)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error reading image config")
return results, err
}
labels := imageConfig.Config.Labels
// Read Description
desc := common.GetDescription(labels)
// Read licenses
license := common.GetLicense(labels)
// Read vendor
vendor := common.GetVendor(labels)
// Read categories
categories := common.GetCategories(labels)
results = append(results, &ImageInfo{
Name: &name, Latest: &latestTag.Name,
Description: &desc, Licenses: &license, Vendor: &vendor,
Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp,
})
}
return results, nil
}
func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*TagInfo {
finalTagList := make([]*TagInfo, 0)
for _, tag := range fixedTags {
fixTag := tag
finalTagList = append(finalTagList,
&TagInfo{Name: &fixTag.Name, Digest: &fixTag.Digest, Timestamp: &fixTag.Timestamp})
}
return finalTagList
}