mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
ac6c6a844c
- Digests were represented by different ways - We needed a uniform way to represent the digests and enforce a format - also replace usage of github.com/google/go-containerregistry/pkg/v1 with github.com/opencontainers/image-spec/specs-go/v1 Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com> (cherry picked from commit 96b2f29d6d57070a913ce419149cd481c0723815) (cherry picked from commit 3d41b583daea654c98378ce3dcb78937d71538e8) Co-authored-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
570 lines
16 KiB
Go
570 lines
16 KiB
Go
package search
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
|
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
|
)
|
|
|
|
// CVEListForImage is the resolver for the CVEListForImage field.
|
|
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*gql_generated.CVEResultForImage, error) {
|
|
cveidMap, err := r.cveInfo.GetCVEListForImage(image)
|
|
if err != nil {
|
|
return &gql_generated.CVEResultForImage{}, err
|
|
}
|
|
|
|
_, copyImgTag := common.GetImageDirAndTag(image)
|
|
|
|
cveids := []*gql_generated.Cve{}
|
|
|
|
for id, cveDetail := range cveidMap {
|
|
vulID := id
|
|
desc := cveDetail.Description
|
|
title := cveDetail.Title
|
|
severity := cveDetail.Severity
|
|
|
|
pkgList := make([]*gql_generated.PackageInfo, 0)
|
|
|
|
for _, pkg := range cveDetail.PackageList {
|
|
pkg := pkg
|
|
|
|
pkgList = append(pkgList,
|
|
&gql_generated.PackageInfo{
|
|
Name: &pkg.Name,
|
|
InstalledVersion: &pkg.InstalledVersion,
|
|
FixedVersion: &pkg.FixedVersion,
|
|
},
|
|
)
|
|
}
|
|
|
|
cveids = append(cveids,
|
|
&gql_generated.Cve{
|
|
ID: &vulID,
|
|
Title: &title,
|
|
Description: &desc,
|
|
Severity: &severity,
|
|
PackageList: pkgList,
|
|
},
|
|
)
|
|
}
|
|
|
|
return &gql_generated.CVEResultForImage{Tag: ©ImgTag, CVEList: cveids}, nil
|
|
}
|
|
|
|
// ImageListForCve is the resolver for the ImageListForCVE field.
|
|
func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
affectedImages := []*gql_generated.ImageSummary{}
|
|
|
|
r.log.Info().Msg("extracting repositories")
|
|
repoList, err := olu.GetRepositories()
|
|
if err != nil { //nolint: wsl
|
|
r.log.Error().Err(err).Msg("unable to search repositories")
|
|
|
|
return affectedImages, err
|
|
}
|
|
|
|
r.log.Info().Msg("scanning each repository")
|
|
|
|
for _, repo := range repoList {
|
|
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
|
|
|
|
imageListByCVE, err := r.cveInfo.GetImageListForCVE(repo, id)
|
|
if err != nil {
|
|
r.log.Error().Str("repo", repo).Str("CVE", id).Err(err).
|
|
Msg("error getting image list for CVE from repo")
|
|
|
|
return affectedImages, err
|
|
}
|
|
|
|
for _, imageByCVE := range imageListByCVE {
|
|
imageConfig, err := olu.GetImageConfigInfo(repo, imageByCVE.Digest)
|
|
if err != nil {
|
|
return affectedImages, err
|
|
}
|
|
|
|
isSigned := olu.CheckManifestSignature(repo, imageByCVE.Digest)
|
|
imageInfo := BuildImageInfo(repo, imageByCVE.Tag, imageByCVE.Digest, imageByCVE.Manifest, imageConfig, isSigned)
|
|
|
|
affectedImages = append(
|
|
affectedImages,
|
|
imageInfo,
|
|
)
|
|
}
|
|
}
|
|
|
|
return affectedImages, nil
|
|
}
|
|
|
|
// ImageListWithCVEFixed is the resolver for the ImageListWithCVEFixed field.
|
|
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) ([]*gql_generated.ImageSummary, error) {
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
unaffectedImages := []*gql_generated.ImageSummary{}
|
|
|
|
tagsInfo, err := r.cveInfo.GetImageListWithCVEFixed(image, id)
|
|
if err != nil {
|
|
return unaffectedImages, err
|
|
}
|
|
|
|
for _, tag := range tagsInfo {
|
|
digest := tag.Digest
|
|
|
|
manifest, err := olu.GetImageBlobManifest(image, digest)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Str("repo", image).Str("digest", tag.Digest.String()).
|
|
Msg("extension api: error reading manifest")
|
|
|
|
return unaffectedImages, err
|
|
}
|
|
|
|
imageConfig, err := olu.GetImageConfigInfo(image, digest)
|
|
if err != nil {
|
|
return []*gql_generated.ImageSummary{}, err
|
|
}
|
|
|
|
isSigned := olu.CheckManifestSignature(image, digest)
|
|
imageInfo := BuildImageInfo(image, tag.Name, digest, manifest, imageConfig, isSigned)
|
|
|
|
unaffectedImages = append(unaffectedImages, imageInfo)
|
|
}
|
|
|
|
return unaffectedImages, nil
|
|
}
|
|
|
|
// ImageListForDigest is the resolver for the ImageListForDigest field.
|
|
func (r *queryResolver) ImageListForDigest(ctx context.Context, id string) ([]*gql_generated.ImageSummary, error) {
|
|
imgResultForDigest := []*gql_generated.ImageSummary{}
|
|
|
|
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, id)
|
|
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, id)
|
|
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
|
|
}
|
|
|
|
// RepoListWithNewestImage is the resolver for the RepoListWithNewestImage field.
|
|
func (r *queryResolver) RepoListWithNewestImage(ctx context.Context) ([]*gql_generated.RepoSummary, error) {
|
|
r.log.Info().Msg("extension api: finding image list")
|
|
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
reposSummary := make([]*gql_generated.RepoSummary, 0)
|
|
|
|
repoList := []string{}
|
|
|
|
defaultRepoList, err := r.storeController.DefaultStore.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting default store repo list")
|
|
|
|
return reposSummary, err
|
|
}
|
|
|
|
if len(defaultRepoList) > 0 {
|
|
repoList = append(repoList, defaultRepoList...)
|
|
}
|
|
|
|
subStore := r.storeController.SubStore
|
|
for _, store := range subStore {
|
|
subRepoList, err := store.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting substore repo list")
|
|
|
|
return reposSummary, err
|
|
}
|
|
|
|
repoList = append(repoList, subRepoList...)
|
|
}
|
|
|
|
reposSummary, err = repoListWithNewestImage(ctx, repoList, olu, r.cveInfo, r.log)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting substore image list")
|
|
|
|
return reposSummary, err
|
|
}
|
|
|
|
return reposSummary, nil
|
|
}
|
|
|
|
// ImageList is the resolver for the ImageList field.
|
|
func (r *queryResolver) ImageList(ctx context.Context, repo string) ([]*gql_generated.ImageSummary, error) {
|
|
r.log.Info().Msg("extension api: getting a list of all images")
|
|
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
defaultStore := r.storeController.DefaultStore
|
|
|
|
dsImageList, err := r.getImageList(defaultStore, repo)
|
|
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.getImageList(store, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("extension api: error extracting substore image list")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
if len(ssImageList) != 0 {
|
|
imageList = append(imageList, ssImageList...)
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// ExpandedRepoInfo is the resolver for the ExpandedRepoInfo field.
|
|
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, repo string) (*gql_generated.RepoInfo, error) {
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
origRepoInfo, err := olu.GetExpandedRepoInfo(repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msgf("error getting repo '%s'", repo)
|
|
|
|
return &gql_generated.RepoInfo{}, err
|
|
}
|
|
|
|
// repos type is of common deep copy this to search
|
|
repoInfo := &gql_generated.RepoInfo{}
|
|
|
|
images := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
summary := &gql_generated.RepoSummary{}
|
|
|
|
summary.LastUpdated = &origRepoInfo.Summary.LastUpdated
|
|
summary.Name = &origRepoInfo.Summary.Name
|
|
summary.Platforms = []*gql_generated.OsArch{}
|
|
summary.NewestImage = &gql_generated.ImageSummary{
|
|
RepoName: &origRepoInfo.Summary.NewestImage.RepoName,
|
|
Tag: &origRepoInfo.Summary.NewestImage.Tag,
|
|
LastUpdated: &origRepoInfo.Summary.NewestImage.LastUpdated,
|
|
Digest: &origRepoInfo.Summary.NewestImage.Digest,
|
|
ConfigDigest: &origRepoInfo.Summary.NewestImage.ConfigDigest,
|
|
IsSigned: &origRepoInfo.Summary.NewestImage.IsSigned,
|
|
Size: &origRepoInfo.Summary.NewestImage.Size,
|
|
Platform: &gql_generated.OsArch{
|
|
Os: &origRepoInfo.Summary.NewestImage.Platform.Os,
|
|
Arch: &origRepoInfo.Summary.NewestImage.Platform.Arch,
|
|
},
|
|
Vendor: &origRepoInfo.Summary.NewestImage.Vendor,
|
|
Score: &origRepoInfo.Summary.NewestImage.Score,
|
|
Description: &origRepoInfo.Summary.NewestImage.Description,
|
|
Title: &origRepoInfo.Summary.NewestImage.Title,
|
|
Documentation: &origRepoInfo.Summary.NewestImage.Documentation,
|
|
Licenses: &origRepoInfo.Summary.NewestImage.Licenses,
|
|
Labels: &origRepoInfo.Summary.NewestImage.Labels,
|
|
Source: &origRepoInfo.Summary.NewestImage.Source,
|
|
}
|
|
|
|
for _, platform := range origRepoInfo.Summary.Platforms {
|
|
platform := platform
|
|
|
|
summary.Platforms = append(summary.Platforms, &gql_generated.OsArch{
|
|
Os: &platform.Os,
|
|
Arch: &platform.Arch,
|
|
})
|
|
}
|
|
|
|
summary.Size = &origRepoInfo.Summary.Size
|
|
|
|
for _, vendor := range origRepoInfo.Summary.Vendors {
|
|
vendor := vendor
|
|
summary.Vendors = append(summary.Vendors, &vendor)
|
|
}
|
|
|
|
score := -1 // score not relevant for this query
|
|
summary.Score = &score
|
|
|
|
for _, image := range origRepoInfo.ImageSummaries {
|
|
tag := image.Tag
|
|
digest := image.Digest
|
|
configDigest := image.ConfigDigest
|
|
isSigned := image.IsSigned
|
|
size := image.Size
|
|
|
|
imageSummary := &gql_generated.ImageSummary{
|
|
Tag: &tag,
|
|
Digest: &digest,
|
|
ConfigDigest: &configDigest,
|
|
IsSigned: &isSigned,
|
|
RepoName: &repo,
|
|
}
|
|
|
|
layers := make([]*gql_generated.LayerSummary, 0)
|
|
|
|
for _, l := range image.Layers {
|
|
size := l.Size
|
|
digest := l.Digest
|
|
|
|
layerInfo := &gql_generated.LayerSummary{Digest: &digest, Size: &size}
|
|
|
|
layers = append(layers, layerInfo)
|
|
}
|
|
|
|
imageSummary.Layers = layers
|
|
imageSummary.Size = &size
|
|
images = append(images, imageSummary)
|
|
}
|
|
|
|
repoInfo.Summary = summary
|
|
repoInfo.Images = images
|
|
|
|
return repoInfo, nil
|
|
}
|
|
|
|
// GlobalSearch is the resolver for the GlobalSearch field.
|
|
func (r *queryResolver) GlobalSearch(ctx context.Context, query string) (*gql_generated.GlobalSearchResult, error) {
|
|
query = cleanQuerry(query)
|
|
defaultStore := r.storeController.DefaultStore
|
|
olu := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
var name, tag string
|
|
|
|
_, err := fmt.Sscanf(query, "%s %s", &name, &tag)
|
|
if err != nil {
|
|
name = query
|
|
}
|
|
|
|
repoList, err := defaultStore.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to search repositories")
|
|
|
|
return &gql_generated.GlobalSearchResult{}, err
|
|
}
|
|
|
|
availableRepos, err := userAvailableRepos(ctx, repoList)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to filter user available repositories")
|
|
|
|
return &gql_generated.GlobalSearchResult{}, err
|
|
}
|
|
|
|
repos, images, layers := globalSearch(availableRepos, name, tag, olu, r.cveInfo, r.log)
|
|
|
|
return &gql_generated.GlobalSearchResult{
|
|
Images: images,
|
|
Repos: repos,
|
|
Layers: layers,
|
|
}, nil
|
|
}
|
|
|
|
// DependencyListForImage is the resolver for the DependencyListForImage field.
|
|
func (r *queryResolver) DerivedImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
repoList, err := layoutUtils.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if len(repoList) == 0 {
|
|
r.log.Info().Msg("no repositories found")
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
|
|
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
if err != nil {
|
|
r.log.Info().Str("image", image).Msg("image not found")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
imageLayers := imageManifest.Layers
|
|
|
|
for _, repo := range repoList {
|
|
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get image list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
imageSummaries := repoInfo.Images
|
|
|
|
// verify every image
|
|
for _, imageSummary := range imageSummaries {
|
|
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
continue
|
|
}
|
|
|
|
layers := imageSummary.Layers
|
|
|
|
sameLayer := 0
|
|
|
|
for _, l := range imageLayers {
|
|
for _, k := range layers {
|
|
if *k.Digest == l.Digest.String() {
|
|
sameLayer++
|
|
}
|
|
}
|
|
}
|
|
|
|
// if all layers are the same
|
|
if sameLayer == len(imageLayers) {
|
|
// add to returned list
|
|
imageList = append(imageList, imageSummary)
|
|
}
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// BaseImageList is the resolver for the BaseImageList field.
|
|
func (r *queryResolver) BaseImageList(ctx context.Context, image string) ([]*gql_generated.ImageSummary, error) {
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
repoList, err := layoutUtils.GetRepositories()
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get repositories list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
if len(repoList) == 0 {
|
|
r.log.Info().Msg("no repositories found")
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
imageDir, imageTag := common.GetImageDirAndTag(image)
|
|
|
|
imageManifest, _, err := layoutUtils.GetImageManifest(imageDir, imageTag)
|
|
if err != nil {
|
|
r.log.Info().Str("image", image).Msg("image not found")
|
|
|
|
return imageList, err
|
|
}
|
|
|
|
imageLayers := imageManifest.Layers
|
|
|
|
// This logic may not scale well in the future as we need to read all the
|
|
// manifest files from the disk when the call is made, we should improve in a future PR
|
|
for _, repo := range repoList {
|
|
repoInfo, err := r.ExpandedRepoInfo(ctx, repo)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get image list")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
imageSummaries := repoInfo.Images
|
|
|
|
var addImageToList bool
|
|
// verify every image
|
|
for _, imageSummary := range imageSummaries {
|
|
if imageTag == *imageSummary.Tag && imageDir == repo {
|
|
continue
|
|
}
|
|
|
|
addImageToList = true
|
|
layers := imageSummary.Layers
|
|
|
|
for _, l := range layers {
|
|
foundLayer := false
|
|
|
|
for _, k := range imageLayers {
|
|
if *l.Digest == k.Digest.String() {
|
|
foundLayer = true
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundLayer {
|
|
addImageToList = false
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if addImageToList {
|
|
imageList = append(imageList, imageSummary)
|
|
}
|
|
}
|
|
}
|
|
|
|
return imageList, nil
|
|
}
|
|
|
|
// Image is the resolver for the Image field.
|
|
func (r *queryResolver) Image(ctx context.Context, image string) (*gql_generated.ImageSummary, error) {
|
|
repo, tag := common.GetImageDirAndTag(image)
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
digest, manifest, imageConfig, err := extractImageDetails(ctx, layoutUtils, repo, tag, r.log)
|
|
if err != nil {
|
|
r.log.Error().Err(err).Msg("unable to get image details")
|
|
|
|
return nil, err
|
|
}
|
|
|
|
isSigned := layoutUtils.CheckManifestSignature(repo, digest)
|
|
result := BuildImageInfo(repo, tag, digest, *manifest, *imageConfig, isSigned)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Query returns gql_generated.QueryResolver implementation.
|
|
func (r *Resolver) Query() gql_generated.QueryResolver { return &queryResolver{r} }
|
|
|
|
type queryResolver struct{ *Resolver }
|