2020-06-26 14:09:10 -05:00
|
|
|
package search
|
|
|
|
|
2022-07-15 06:10:51 -05:00
|
|
|
// This file will not be regenerated automatically.
|
|
|
|
//
|
|
|
|
// It serves as dependency injection for your app, add any dependencies you require here.
|
2020-06-26 14:09:10 -05:00
|
|
|
|
|
|
|
import (
|
2022-08-16 03:57:09 -05:00
|
|
|
"context"
|
2023-01-17 17:31:54 -05:00
|
|
|
"encoding/json"
|
2022-07-12 07:58:04 -05:00
|
|
|
"strings"
|
2020-06-26 14:09:10 -05:00
|
|
|
|
2022-07-29 10:33:34 -05:00
|
|
|
"github.com/99designs/gqlgen/graphql"
|
2021-01-25 13:04:03 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
2022-08-02 10:58:30 -05:00
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
2023-01-09 15:37:44 -05:00
|
|
|
"github.com/pkg/errors"
|
2022-07-29 10:33:34 -05:00
|
|
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
zerr "zotregistry.io/zot/errors"
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/search/common"
|
2023-01-09 15:37:44 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/search/convert"
|
2021-12-03 22:50:58 -05:00
|
|
|
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
|
|
|
digestinfo "zotregistry.io/zot/pkg/extensions/search/digest"
|
2022-07-15 06:10:51 -05:00
|
|
|
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
2022-10-20 11:39:20 -05:00
|
|
|
"zotregistry.io/zot/pkg/log"
|
2023-01-09 15:37:44 -05:00
|
|
|
"zotregistry.io/zot/pkg/meta/repodb"
|
2022-08-16 03:57:09 -05:00
|
|
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
2021-12-03 22:50:58 -05:00
|
|
|
"zotregistry.io/zot/pkg/storage"
|
2020-06-26 14:09:10 -05:00
|
|
|
) // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
const (
|
|
|
|
querySizeLimit = 256
|
|
|
|
)
|
|
|
|
|
2020-06-26 14:09:10 -05:00
|
|
|
// Resolver ...
|
|
|
|
type Resolver struct {
|
2022-09-28 13:39:54 -05:00
|
|
|
cveInfo cveinfo.CveInfo
|
2023-01-09 15:37:44 -05:00
|
|
|
repoDB repodb.RepoDB
|
2021-04-05 19:40:33 -05:00
|
|
|
storeController storage.StoreController
|
2021-05-26 12:22:31 -05:00
|
|
|
digestInfo *digestinfo.DigestInfo
|
2021-01-25 13:04:03 -05:00
|
|
|
log log.Logger
|
2020-06-26 14:09:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetResolverConfig ...
|
2023-01-09 15:37:44 -05:00
|
|
|
func GetResolverConfig(log log.Logger, storeController storage.StoreController,
|
|
|
|
repoDB repodb.RepoDB, cveInfo cveinfo.CveInfo,
|
2022-09-28 13:39:54 -05:00
|
|
|
) gql_generated.Config {
|
2021-09-30 08:27:13 -05:00
|
|
|
digestInfo := digestinfo.NewDigestInfo(storeController, log)
|
2020-06-26 14:09:10 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
resConfig := &Resolver{
|
|
|
|
cveInfo: cveInfo,
|
|
|
|
repoDB: repoDB,
|
|
|
|
storeController: storeController,
|
|
|
|
digestInfo: digestInfo,
|
|
|
|
log: log,
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2022-07-15 06:10:51 -05:00
|
|
|
return gql_generated.Config{
|
|
|
|
Resolvers: resConfig, Directives: gql_generated.DirectiveRoot{},
|
|
|
|
Complexity: gql_generated.ComplexityRoot{},
|
2020-06-26 14:09:10 -05:00
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
2020-06-26 14:09:10 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func NewResolver(log log.Logger, storeController storage.StoreController,
|
|
|
|
repoDB repodb.RepoDB, cveInfo cveinfo.CveInfo,
|
|
|
|
) *Resolver {
|
|
|
|
digestInfo := digestinfo.NewDigestInfo(storeController, log)
|
|
|
|
|
|
|
|
resolver := &Resolver{
|
|
|
|
cveInfo: cveInfo,
|
|
|
|
repoDB: repoDB,
|
|
|
|
storeController: storeController,
|
|
|
|
digestInfo: digestInfo,
|
|
|
|
log: log,
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolver
|
|
|
|
}
|
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
func FilterByDigest(digest string) repodb.FilterFunc {
|
|
|
|
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
|
|
|
|
lookupDigest := digest
|
|
|
|
contains := false
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
var manifest ispec.Manifest
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
err := json.Unmarshal(manifestMeta.ManifestBlob, &manifest)
|
2021-05-26 12:22:31 -05:00
|
|
|
if err != nil {
|
2023-01-17 17:31:54 -05:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
|
|
|
|
|
|
|
// Check the image manifest in index.json matches the search digest
|
|
|
|
// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
|
|
|
|
if strings.Contains(manifestDigest, lookupDigest) {
|
|
|
|
contains = true
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
// Check the image config matches the search digest
|
|
|
|
// This is a blob with mediaType application/vnd.oci.image.config.v1+json
|
|
|
|
if strings.Contains(manifest.Config.Digest.String(), lookupDigest) {
|
|
|
|
contains = true
|
2021-05-26 12:22:31 -05:00
|
|
|
}
|
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
// Check to see if the individual layers in the oci image manifest match the digest
|
|
|
|
// These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip
|
|
|
|
for _, layer := range manifest.Layers {
|
|
|
|
if strings.Contains(layer.Digest.String(), lookupDigest) {
|
|
|
|
contains = true
|
2022-09-21 12:53:56 -05:00
|
|
|
}
|
2023-01-17 17:31:54 -05:00
|
|
|
}
|
2022-09-21 12:53:56 -05:00
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
return contains
|
|
|
|
}
|
|
|
|
}
|
2022-09-21 12:53:56 -05:00
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.RepoDB, cveInfo cveinfo.CveInfo,
|
|
|
|
requestedPage *gql_generated.PageInput,
|
|
|
|
) ([]*gql_generated.ImageSummary, error) {
|
|
|
|
imageList := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
|
|
|
|
if requestedPage == nil {
|
|
|
|
requestedPage = &gql_generated.PageInput{}
|
|
|
|
}
|
|
|
|
|
|
|
|
skip := convert.SkipQGLField{
|
|
|
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
|
|
|
|
}
|
|
|
|
|
|
|
|
pageInput := repodb.PageInput{
|
|
|
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
|
|
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
|
|
|
SortBy: repodb.SortCriteria(
|
|
|
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
|
|
|
|
// get all repos
|
|
|
|
reposMeta, manifestMetaMap, err := repoDB.FilterTags(ctx, FilterByDigest(digest), pageInput)
|
|
|
|
if err != nil {
|
|
|
|
return []*gql_generated.ImageSummary{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, repoMeta := range reposMeta {
|
|
|
|
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
|
|
|
|
|
|
|
imageList = append(imageList, imageSummaries...)
|
2021-05-26 12:22:31 -05:00
|
|
|
}
|
|
|
|
|
2023-01-17 17:31:54 -05:00
|
|
|
return imageList, nil
|
2021-05-26 12:22:31 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func getImageSummary(ctx context.Context, repo, tag string, repoDB repodb.RepoDB,
|
|
|
|
cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
|
|
|
|
) (
|
|
|
|
*gql_generated.ImageSummary, error,
|
|
|
|
) {
|
|
|
|
repoMeta, err := repoDB.GetRepoMeta(repo)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-29 10:33:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestDescriptor, ok := repoMeta.Tags[tag]
|
|
|
|
if !ok {
|
|
|
|
return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag)
|
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestDigest := manifestDescriptor.Digest
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for t := range repoMeta.Tags {
|
|
|
|
if t != tag {
|
|
|
|
delete(repoMeta.Tags, t)
|
2021-01-25 13:04:03 -05:00
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestMeta, err := repoDB.GetManifestMeta(repo, godigest.Digest(manifestDigest))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestMetaMap := map[string]repodb.ManifestMetadata{
|
|
|
|
manifestDigest: manifestMeta,
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
skip := convert.SkipQGLField{
|
|
|
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return imageSummaries[0], nil
|
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func repoListWithNewestImage(
|
|
|
|
ctx context.Context,
|
|
|
|
cveInfo cveinfo.CveInfo,
|
|
|
|
log log.Logger, //nolint:unparam // may be used by devs for debugging
|
|
|
|
requestedPage *gql_generated.PageInput,
|
|
|
|
repoDB repodb.RepoDB,
|
2023-01-18 17:20:55 -05:00
|
|
|
) (*gql_generated.PaginatedReposResult, error) {
|
2023-01-09 15:37:44 -05:00
|
|
|
repos := []*gql_generated.RepoSummary{}
|
2023-01-18 17:20:55 -05:00
|
|
|
paginatedRepos := &gql_generated.PaginatedReposResult{}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if requestedPage == nil {
|
|
|
|
requestedPage = &gql_generated.PageInput{}
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
skip := convert.SkipQGLField{
|
2023-01-18 17:20:55 -05:00
|
|
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"),
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
pageInput := repodb.PageInput{
|
|
|
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
|
|
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
|
|
|
SortBy: repodb.SortCriteria(
|
|
|
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
|
|
|
),
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-18 17:20:55 -05:00
|
|
|
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchRepos(ctx, "", repodb.Filter{}, pageInput)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-01-18 17:20:55 -05:00
|
|
|
return &gql_generated.PaginatedReposResult{}, err
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-07-29 10:33:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for _, repoMeta := range reposMeta {
|
|
|
|
repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
|
|
|
repos = append(repos, repoSummary)
|
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-18 17:20:55 -05:00
|
|
|
paginatedRepos.Page = &gql_generated.PageInfo{
|
|
|
|
TotalCount: pageInfo.TotalCount,
|
|
|
|
ItemCount: pageInfo.ItemCount,
|
|
|
|
}
|
|
|
|
paginatedRepos.Results = repos
|
|
|
|
|
|
|
|
return paginatedRepos, nil
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filter *gql_generated.Filter,
|
|
|
|
requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
|
2023-01-18 17:20:55 -05:00
|
|
|
) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error,
|
2023-01-09 15:37:44 -05:00
|
|
|
) {
|
|
|
|
preloads := convert.GetPreloads(ctx)
|
|
|
|
repos := []*gql_generated.RepoSummary{}
|
2023-01-18 17:20:55 -05:00
|
|
|
paginatedRepos := gql_generated.PaginatedReposResult{}
|
2023-01-09 15:37:44 -05:00
|
|
|
images := []*gql_generated.ImageSummary{}
|
|
|
|
layers := []*gql_generated.LayerSummary{}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if requestedPage == nil {
|
|
|
|
requestedPage = &gql_generated.PageInput{}
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
localFilter := repodb.Filter{}
|
|
|
|
if filter != nil {
|
|
|
|
localFilter = repodb.Filter{
|
|
|
|
Os: filter.Os,
|
|
|
|
Arch: filter.Arch,
|
|
|
|
HasToBeSigned: filter.HasToBeSigned,
|
|
|
|
}
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if searchingForRepos(query) {
|
|
|
|
skip := convert.SkipQGLField{
|
|
|
|
Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities"),
|
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
pageInput := repodb.PageInput{
|
|
|
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
|
|
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
|
|
|
SortBy: repodb.SortCriteria(
|
|
|
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
|
|
|
),
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-18 17:20:55 -05:00
|
|
|
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchRepos(ctx, query, localFilter, pageInput)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-01-18 17:20:55 -05:00
|
|
|
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for _, repoMeta := range reposMeta {
|
|
|
|
repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
2022-09-13 09:20:44 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
repos = append(repos, repoSummary)
|
|
|
|
}
|
2023-01-18 17:20:55 -05:00
|
|
|
|
|
|
|
paginatedRepos.Page = &gql_generated.PageInfo{
|
|
|
|
TotalCount: pageInfo.TotalCount,
|
|
|
|
ItemCount: pageInfo.ItemCount,
|
|
|
|
}
|
|
|
|
|
|
|
|
paginatedRepos.Results = repos
|
2023-01-09 15:37:44 -05:00
|
|
|
} else { // search for images
|
|
|
|
skip := convert.SkipQGLField{
|
|
|
|
Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities"),
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
pageInput := repodb.PageInput{
|
|
|
|
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
|
|
|
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
|
|
|
SortBy: repodb.SortCriteria(
|
|
|
|
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
|
|
|
),
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-18 17:20:55 -05:00
|
|
|
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchTags(ctx, query, localFilter, pageInput)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-01-18 17:20:55 -05:00
|
|
|
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-09-28 13:39:54 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for _, repoMeta := range reposMeta {
|
|
|
|
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
2022-09-28 13:39:54 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
images = append(images, imageSummaries...)
|
|
|
|
}
|
2023-01-18 17:20:55 -05:00
|
|
|
|
|
|
|
paginatedRepos.Page = &gql_generated.PageInfo{
|
|
|
|
TotalCount: pageInfo.TotalCount,
|
|
|
|
ItemCount: pageInfo.ItemCount,
|
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-11-10 18:02:17 -05:00
|
|
|
|
2023-01-18 17:20:55 -05:00
|
|
|
return &paginatedRepos, images, layers, nil
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-07-29 10:33:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func canSkipField(preloads map[string]bool, s string) bool {
|
|
|
|
fieldIsPresent := preloads[s]
|
2022-07-29 10:33:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return !fieldIsPresent
|
|
|
|
}
|
2022-07-29 10:33:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func validateGlobalSearchInput(query string, filter *gql_generated.Filter,
|
|
|
|
requestedPage *gql_generated.PageInput,
|
|
|
|
) error {
|
|
|
|
if len(query) > querySizeLimit {
|
|
|
|
format := "global-search: max string size limit exeeded for query parameter. max=%d current=%d"
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return errors.Wrapf(zerr.ErrInvalidRequestParams, format, querySizeLimit, len(query))
|
2021-01-25 13:04:03 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
err := checkFilter(filter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-25 13:04:03 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
err = checkRequestedPage(requestedPage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return nil
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func checkFilter(filter *gql_generated.Filter) error {
|
|
|
|
if filter == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for _, arch := range filter.Arch {
|
|
|
|
if len(*arch) > querySizeLimit {
|
|
|
|
format := "global-search: max string size limit exeeded for arch parameter. max=%d current=%d"
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return errors.Wrapf(zerr.ErrInvalidRequestParams, format, querySizeLimit, len(*arch))
|
|
|
|
}
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for _, osSys := range filter.Os {
|
|
|
|
if len(*osSys) > querySizeLimit {
|
|
|
|
format := "global-search: max string size limit exeeded for os parameter. max=%d current=%d"
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return errors.Wrapf(zerr.ErrInvalidRequestParams, format, querySizeLimit, len(*osSys))
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return nil
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func checkRequestedPage(requestedPage *gql_generated.PageInput) error {
|
|
|
|
if requestedPage == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if requestedPage.Limit != nil && *requestedPage.Limit < 0 {
|
|
|
|
format := "global-search: requested page limit parameter can't be negative"
|
2022-07-22 15:01:38 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return errors.Wrap(zerr.ErrInvalidRequestParams, format)
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if requestedPage.Offset != nil && *requestedPage.Offset < 0 {
|
|
|
|
format := "global-search: requested page offset parameter can't be negative"
|
2022-07-20 04:30:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return errors.Wrap(zerr.ErrInvalidRequestParams, format)
|
|
|
|
}
|
2022-08-02 10:58:30 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return nil
|
|
|
|
}
|
2022-08-02 10:58:30 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func cleanQuery(query string) string {
|
|
|
|
query = strings.TrimSpace(query)
|
|
|
|
query = strings.Trim(query, "/")
|
|
|
|
query = strings.ToLower(query)
|
2022-07-20 04:30:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return query
|
|
|
|
}
|
2022-07-20 04:30:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func cleanFilter(filter *gql_generated.Filter) *gql_generated.Filter {
|
|
|
|
if filter == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-07-20 04:30:34 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if filter.Arch != nil {
|
|
|
|
for i := range filter.Arch {
|
|
|
|
*filter.Arch[i] = strings.ToLower(*filter.Arch[i])
|
|
|
|
*filter.Arch[i] = strings.TrimSpace(*filter.Arch[i])
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
filter.Arch = deleteEmptyElements(filter.Arch)
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if filter.Os != nil {
|
|
|
|
for i := range filter.Os {
|
|
|
|
*filter.Os[i] = strings.ToLower(*filter.Os[i])
|
|
|
|
*filter.Os[i] = strings.TrimSpace(*filter.Os[i])
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
filter.Os = deleteEmptyElements(filter.Os)
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return filter
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func deleteEmptyElements(slice []*string) []*string {
|
|
|
|
i := 0
|
|
|
|
for i < len(slice) {
|
|
|
|
if elementIsEmpty(*slice[i]) {
|
|
|
|
slice = deleteElementAt(slice, i)
|
|
|
|
} else {
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return slice
|
|
|
|
}
|
2022-07-19 08:16:15 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func elementIsEmpty(s string) bool {
|
|
|
|
return s == ""
|
|
|
|
}
|
2022-07-19 08:16:15 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func deleteElementAt(slice []*string, i int) []*string {
|
|
|
|
slice[i] = slice[len(slice)-1]
|
|
|
|
slice = slice[:len(slice)-1]
|
2022-07-19 08:16:15 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return slice
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInfo cveinfo.CveInfo, log log.Logger,
|
|
|
|
) (*gql_generated.RepoInfo, error) {
|
|
|
|
if ok, err := localCtx.RepoIsUserAvailable(ctx, repo); !ok || err != nil {
|
|
|
|
log.Info().Err(err).Msgf("resolver: 'repo %s is user available' = %v", repo, ok)
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker
|
|
|
|
}
|
2022-09-28 13:39:54 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
repoMeta, err := repoDB.GetRepoMeta(repo)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msgf("resolver: can't retrieve repoMeta for repo %s", repo)
|
2022-11-10 18:02:17 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return &gql_generated.RepoInfo{}, err
|
|
|
|
}
|
2022-07-22 15:01:38 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestMetaMap := map[string]repodb.ManifestMetadata{}
|
2022-07-22 15:01:38 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
for tag, descriptor := range repoMeta.Tags {
|
|
|
|
digest := descriptor.Digest
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
if _, alreadyDownloaded := manifestMetaMap[digest]; alreadyDownloaded {
|
|
|
|
continue
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestMeta, err := repoDB.GetManifestMeta(repo, godigest.Digest(digest))
|
|
|
|
if err != nil {
|
|
|
|
graphql.AddError(ctx, errors.Wrapf(err,
|
|
|
|
"resolver: failed to get manifest meta for image %s:%s with manifest digest %s", repo, tag, digest))
|
|
|
|
|
|
|
|
continue
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
manifestMetaMap[digest] = manifestMeta
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
skip := convert.SkipQGLField{
|
|
|
|
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities"),
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, manifestMetaMap, skip, cveInfo)
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return &gql_generated.RepoInfo{Summary: repoSummary, Images: imageSummaries}, nil
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func safeDerefferencing[T any](pointer *T, defaultVal T) T {
|
|
|
|
if pointer != nil {
|
|
|
|
return *pointer
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return defaultVal
|
|
|
|
}
|
2022-07-12 07:58:04 -05:00
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
func searchingForRepos(query string) bool {
|
|
|
|
return !strings.Contains(query, ":")
|
2022-07-12 07:58:04 -05:00
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
func (r *queryResolver) getImageList(store storage.ImageStore, imageName string) (
|
|
|
|
[]*gql_generated.ImageSummary, error,
|
|
|
|
) {
|
|
|
|
results := make([]*gql_generated.ImageSummary, 0)
|
|
|
|
|
|
|
|
repoList, err := store.GetRepositories()
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error().Err(err).Msg("extension api: error extracting repositories list")
|
|
|
|
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
|
|
|
layoutUtils := common.NewBaseOciLayoutUtils(r.storeController, r.log)
|
|
|
|
|
|
|
|
for _, repo := range repoList {
|
|
|
|
if (imageName != "" && repo == imageName) || imageName == "" {
|
|
|
|
tagsInfo, err := layoutUtils.GetImageTagsWithTimestamp(repo)
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error().Err(err).Msg("extension api: error getting tag timestamp info")
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tagsInfo) == 0 {
|
|
|
|
r.log.Info().Str("no tagsinfo found for repo", repo).Msg(" continuing traversing")
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range tagsInfo {
|
|
|
|
// using a loop variable called tag would be reassigned after each iteration, using the same memory address
|
|
|
|
// directly access the value at the current index in the slice as ImageInfo requires pointers to tag fields
|
|
|
|
tag := tagsInfo[i]
|
2022-10-22 15:46:13 -05:00
|
|
|
digest := tag.Digest
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
manifest, err := layoutUtils.GetImageBlobManifest(repo, digest)
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error().Err(err).Msg("extension api: error reading manifest")
|
|
|
|
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
2022-09-21 12:53:56 -05:00
|
|
|
imageConfig, err := layoutUtils.GetImageConfigInfo(repo, digest)
|
|
|
|
if err != nil {
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
2022-09-30 12:32:32 -05:00
|
|
|
isSigned := layoutUtils.CheckManifestSignature(repo, digest)
|
2022-10-20 11:35:24 -05:00
|
|
|
|
|
|
|
tagPrefix := strings.HasPrefix(tag.Name, "sha256-")
|
|
|
|
tagSuffix := strings.HasSuffix(tag.Name, ".sig")
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
imageInfo := convert.BuildImageInfo(repo, tag.Name, digest, manifest,
|
2022-09-30 12:32:32 -05:00
|
|
|
imageConfig, isSigned)
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2022-10-20 11:35:24 -05:00
|
|
|
// check if it's an image or a signature
|
|
|
|
if !tagPrefix && !tagSuffix {
|
|
|
|
results = append(results, imageInfo)
|
|
|
|
}
|
2022-01-19 10:57:10 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(results) == 0 {
|
|
|
|
r.log.Info().Msg("no repositories found")
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2022-11-23 13:53:28 -05:00
|
|
|
func getReferrers(store storage.ImageStore, repoName string, digest string, artifactType string, log log.Logger) (
|
|
|
|
[]*gql_generated.Referrer, error,
|
|
|
|
) {
|
|
|
|
results := make([]*gql_generated.Referrer, 0)
|
|
|
|
|
|
|
|
index, err := store.GetReferrers(repoName, godigest.Digest(digest), artifactType)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("error extracting referrers list")
|
|
|
|
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, manifest := range index.Manifests {
|
|
|
|
size := int(manifest.Size)
|
|
|
|
digest := manifest.Digest.String()
|
|
|
|
annotations := make([]*gql_generated.Annotation, 0)
|
|
|
|
artifactType := manifest.ArtifactType
|
|
|
|
mediaType := manifest.MediaType
|
|
|
|
|
|
|
|
for k, v := range manifest.Annotations {
|
|
|
|
key := k
|
|
|
|
value := v
|
|
|
|
|
|
|
|
annotations = append(annotations, &gql_generated.Annotation{
|
|
|
|
Key: &key,
|
|
|
|
Value: &value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
results = append(results, &gql_generated.Referrer{
|
|
|
|
MediaType: &mediaType,
|
|
|
|
ArtifactType: &artifactType,
|
|
|
|
Digest: &digest,
|
|
|
|
Size: &size,
|
|
|
|
Annotations: annotations,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2022-08-16 03:57:09 -05:00
|
|
|
// get passed context from authzHandler and filter out repos based on permissions.
|
|
|
|
func userAvailableRepos(ctx context.Context, repoList []string) ([]string, error) {
|
|
|
|
var availableRepos []string
|
|
|
|
|
2022-11-18 12:35:28 -05:00
|
|
|
// authz request context (set in authz middleware)
|
|
|
|
acCtx, err := localCtx.GetAccessControlContext(ctx)
|
|
|
|
if err != nil {
|
2023-01-09 15:37:44 -05:00
|
|
|
err := zerr.ErrBadType
|
2022-08-16 03:57:09 -05:00
|
|
|
|
2022-11-18 12:35:28 -05:00
|
|
|
return []string{}, err
|
|
|
|
}
|
2022-08-16 03:57:09 -05:00
|
|
|
|
2022-11-18 12:35:28 -05:00
|
|
|
if acCtx != nil {
|
2022-08-16 03:57:09 -05:00
|
|
|
for _, r := range repoList {
|
2022-11-18 12:35:28 -05:00
|
|
|
if acCtx.IsAdmin || acCtx.CanReadRepo(r) {
|
2022-08-16 03:57:09 -05:00
|
|
|
availableRepos = append(availableRepos, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
availableRepos = repoList
|
|
|
|
}
|
|
|
|
|
|
|
|
return availableRepos, nil
|
|
|
|
}
|
2022-09-30 12:32:32 -05:00
|
|
|
|
|
|
|
func extractImageDetails(
|
|
|
|
ctx context.Context,
|
|
|
|
layoutUtils common.OciLayoutUtils,
|
2023-01-09 15:37:44 -05:00
|
|
|
repo, tag string, //nolint:unparam // function only called in the tests
|
2022-09-30 12:32:32 -05:00
|
|
|
log log.Logger) (
|
2022-10-22 15:46:13 -05:00
|
|
|
godigest.Digest, *ispec.Manifest, *ispec.Image, error,
|
2022-09-30 12:32:32 -05:00
|
|
|
) {
|
|
|
|
validRepoList, err := userAvailableRepos(ctx, []string{repo})
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("unable to retrieve access token")
|
|
|
|
|
|
|
|
return "", nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(validRepoList) == 0 {
|
|
|
|
log.Error().Err(err).Msg("user is not authorized")
|
|
|
|
|
2023-01-09 15:37:44 -05:00
|
|
|
return "", nil, nil, zerr.ErrUnauthorizedAccess
|
2022-09-30 12:32:32 -05:00
|
|
|
}
|
|
|
|
|
2022-10-22 15:46:13 -05:00
|
|
|
manifest, dig, err := layoutUtils.GetImageManifest(repo, tag)
|
2022-09-30 12:32:32 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("Could not retrieve image ispec manifest")
|
|
|
|
|
|
|
|
return "", nil, nil, err
|
|
|
|
}
|
|
|
|
|
2022-10-22 15:46:13 -05:00
|
|
|
digest := dig
|
2022-09-30 12:32:32 -05:00
|
|
|
|
|
|
|
imageConfig, err := layoutUtils.GetImageConfigInfo(repo, digest)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("Could not retrieve image config")
|
|
|
|
|
|
|
|
return "", nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return digest, &manifest, &imageConfig, nil
|
|
|
|
}
|