zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/resolver.go (about) 1 package search 2 3 // This file will not be regenerated automatically. 4 // 5 // It serves as dependency injection for your app, add any dependencies you require here. 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "sort" 12 "strings" 13 14 godigest "github.com/opencontainers/go-digest" 15 ispec "github.com/opencontainers/image-spec/specs-go/v1" 16 "github.com/vektah/gqlparser/v2/gqlerror" 17 18 zerr "zotregistry.dev/zot/errors" 19 zcommon "zotregistry.dev/zot/pkg/common" 20 "zotregistry.dev/zot/pkg/extensions/search/convert" 21 cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve" 22 cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model" 23 "zotregistry.dev/zot/pkg/extensions/search/gql_generated" 24 "zotregistry.dev/zot/pkg/extensions/search/pagination" 25 "zotregistry.dev/zot/pkg/log" 26 mTypes "zotregistry.dev/zot/pkg/meta/types" 27 reqCtx "zotregistry.dev/zot/pkg/requestcontext" 28 "zotregistry.dev/zot/pkg/storage" 29 ) 30 31 // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES. 32 33 const ( 34 querySizeLimit = 256 35 ) 36 37 // Resolver ... 38 type Resolver struct { 39 cveInfo cveinfo.CveInfo 40 metaDB mTypes.MetaDB 41 storeController storage.StoreController 42 log log.Logger 43 } 44 45 // GetResolverConfig ... 46 func GetResolverConfig(log log.Logger, storeController storage.StoreController, 47 metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, 48 ) gql_generated.Config { 49 resConfig := &Resolver{ 50 cveInfo: cveInfo, 51 metaDB: metaDB, 52 storeController: storeController, 53 log: log, 54 } 55 56 return gql_generated.Config{ 57 Resolvers: resConfig, Directives: gql_generated.DirectiveRoot{}, 58 Complexity: gql_generated.ComplexityRoot{}, 59 } 60 } 61 62 func NewResolver(log log.Logger, storeController storage.StoreController, 63 metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, 64 ) *Resolver { 65 resolver := &Resolver{ 66 cveInfo: cveInfo, 67 metaDB: metaDB, 68 storeController: storeController, 69 log: log, 70 } 71 72 return resolver 73 } 74 75 func FilterByDigest(digest string) mTypes.FilterFunc { 76 // imageMeta will always contain 1 manifest 77 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 78 lookupDigest := digest 79 80 // Check in case of an index if the index digest matches the search digest 81 // For Manifests, this is equivalent to imageMeta.Manifests[0] 82 if imageMeta.Digest.String() == lookupDigest { 83 return true 84 } 85 86 manifest := imageMeta.Manifests[0] 87 88 manifestDigest := manifest.Digest.String() 89 90 // Check the image manifest in index.json matches the search digest 91 // This is a blob with mediaType application/vnd.oci.image.manifest.v1+json 92 if strings.Contains(manifestDigest, lookupDigest) { 93 return true 94 } 95 96 // Check the image config matches the search digest 97 // This is a blob with mediaType application/vnd.oci.image.config.v1+json 98 if strings.Contains(manifest.Manifest.Config.Digest.String(), lookupDigest) { 99 return true 100 } 101 102 // Check to see if the individual layers in the oci image manifest match the digest 103 // These are blobs with mediaType application/vnd.oci.image.layer.v1.tar+gzip 104 for _, layer := range manifest.Manifest.Layers { 105 if strings.Contains(layer.Digest.String(), lookupDigest) { 106 return true 107 } 108 } 109 110 return false 111 } 112 } 113 114 func getImageListForDigest(ctx context.Context, digest string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, 115 requestedPage *gql_generated.PageInput, 116 ) (*gql_generated.PaginatedImagesResult, error) { 117 if requestedPage == nil { 118 requestedPage = &gql_generated.PageInput{} 119 } 120 121 skip := convert.SkipQGLField{ 122 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"), 123 } 124 125 pageInput := pagination.PageInput{ 126 Limit: deref(requestedPage.Limit, 0), 127 Offset: deref(requestedPage.Offset, 0), 128 SortBy: pagination.SortCriteria( 129 deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance), 130 ), 131 } 132 133 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(digest)) 134 if err != nil { 135 return &gql_generated.PaginatedImagesResult{}, err 136 } 137 138 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, 139 cveInfo, mTypes.Filter{}, pageInput) 140 if err != nil { 141 return &gql_generated.PaginatedImagesResult{}, err 142 } 143 144 return &gql_generated.PaginatedImagesResult{ 145 Results: imageSummaries, 146 Page: &gql_generated.PageInfo{ 147 TotalCount: pageInfo.TotalCount, 148 ItemCount: pageInfo.ItemCount, 149 }, 150 }, nil 151 } 152 153 func getImageSummary(ctx context.Context, repo, tag string, digest *string, skipCVE convert.SkipQGLField, 154 metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam 155 ) ( 156 *gql_generated.ImageSummary, error, 157 ) { 158 repoMeta, err := metaDB.GetRepoMeta(ctx, repo) 159 if err != nil { 160 return nil, err 161 } 162 163 manifestDescriptor, ok := repoMeta.Tags[tag] 164 if !ok { 165 return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag) 166 } 167 168 repoMeta.Tags = map[mTypes.Tag]mTypes.Descriptor{tag: manifestDescriptor} 169 170 imageDigest := manifestDescriptor.Digest 171 if digest != nil { 172 imageDigest = *digest 173 repoMeta.Tags[tag] = mTypes.Descriptor{ 174 Digest: imageDigest, 175 MediaType: ispec.MediaTypeImageManifest, 176 } 177 } 178 179 imageMetaMap, err := metaDB.FilterImageMeta(ctx, []string{imageDigest}) 180 if err != nil { 181 return &gql_generated.ImageSummary{}, err 182 } 183 184 imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, imageMetaMap, skipCVE, cveInfo) 185 186 if len(imageSummaries) == 0 { 187 return &gql_generated.ImageSummary{}, nil 188 } 189 190 return imageSummaries[0], nil 191 } 192 193 func getCVEListForImage( 194 ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions 195 image string, 196 cveInfo cveinfo.CveInfo, 197 requestedPage *gql_generated.PageInput, 198 searchedCVE string, 199 excludedCVE string, 200 severity string, 201 log log.Logger, //nolint:unparam // may be used by devs for debugging 202 ) (*gql_generated.CVEResultForImage, error) { 203 if requestedPage == nil { 204 requestedPage = &gql_generated.PageInput{} 205 } 206 207 pageInput := cvemodel.PageInput{ 208 Limit: deref(requestedPage.Limit, 0), 209 Offset: deref(requestedPage.Offset, 0), 210 SortBy: cvemodel.SortCriteria( 211 deref(requestedPage.SortBy, gql_generated.SortCriteriaSeverity), 212 ), 213 } 214 215 repo, ref, _ := zcommon.GetImageDirAndReference(image) 216 217 if ref == "" { 218 return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided") 219 } 220 221 cveList, imageCveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo, ref, 222 searchedCVE, excludedCVE, severity, pageInput) 223 if err != nil { 224 return &gql_generated.CVEResultForImage{}, err 225 } 226 227 cveids := []*gql_generated.Cve{} 228 229 for _, cveDetail := range cveList { 230 vulID := cveDetail.ID 231 desc := cveDetail.Description 232 title := cveDetail.Title 233 severity := cveDetail.Severity 234 referenceURL := cveDetail.Reference 235 236 pkgList := make([]*gql_generated.PackageInfo, 0) 237 238 for _, pkg := range cveDetail.PackageList { 239 pkg := pkg 240 241 pkgList = append(pkgList, 242 &gql_generated.PackageInfo{ 243 Name: &pkg.Name, 244 PackagePath: &pkg.PackagePath, 245 InstalledVersion: &pkg.InstalledVersion, 246 FixedVersion: &pkg.FixedVersion, 247 }, 248 ) 249 } 250 251 cveids = append(cveids, 252 &gql_generated.Cve{ 253 ID: &vulID, 254 Title: &title, 255 Description: &desc, 256 Severity: &severity, 257 Reference: &referenceURL, 258 PackageList: pkgList, 259 }, 260 ) 261 } 262 263 return &gql_generated.CVEResultForImage{ 264 Tag: &ref, 265 CVEList: cveids, 266 Summary: &gql_generated.ImageVulnerabilitySummary{ 267 MaxSeverity: &imageCveSummary.MaxSeverity, 268 UnknownCount: &imageCveSummary.UnknownCount, 269 LowCount: &imageCveSummary.LowCount, 270 MediumCount: &imageCveSummary.MediumCount, 271 HighCount: &imageCveSummary.HighCount, 272 CriticalCount: &imageCveSummary.CriticalCount, 273 Count: &imageCveSummary.Count, 274 }, 275 Page: &gql_generated.PageInfo{ 276 TotalCount: pageInfo.TotalCount, 277 ItemCount: pageInfo.ItemCount, 278 }, 279 }, nil 280 } 281 282 func getCVEDiffListForImages( 283 ctx context.Context, //nolint:unparam // may be used in the future to filter by permissions 284 minuend gql_generated.ImageInput, 285 subtrahend gql_generated.ImageInput, 286 metaDB mTypes.MetaDB, 287 cveInfo cveinfo.CveInfo, 288 requestedPage *gql_generated.PageInput, 289 searchedCVE string, 290 excludedCVE string, 291 log log.Logger, //nolint:unparam // may be used by devs for debugging 292 ) (*gql_generated.CVEDiffResult, error) { 293 minuend, err := resolveImageData(ctx, minuend, metaDB) 294 if err != nil { 295 return nil, err 296 } 297 298 resultMinuend := getImageIdentifier(minuend) 299 resultSubtrahend := gql_generated.ImageIdentifier{} 300 301 if subtrahend.Repo != "" { 302 subtrahend, err = resolveImageData(ctx, subtrahend, metaDB) 303 if err != nil { 304 return nil, err 305 } 306 resultSubtrahend = getImageIdentifier(subtrahend) 307 } else { 308 // search for base images 309 // get minuend image meta 310 minuendSummary, err := metaDB.GetImageMeta(godigest.Digest(deref(minuend.Digest, ""))) 311 if err != nil { 312 return &gql_generated.CVEDiffResult{}, err 313 } 314 315 // get the base images for the minuend 316 minuendBaseImages, err := metaDB.FilterTags(ctx, mTypes.AcceptOnlyRepo(minuend.Repo), 317 filterBaseImagesForMeta(minuendSummary)) 318 if err != nil { 319 return &gql_generated.CVEDiffResult{}, err 320 } 321 322 // get the best base image as subtrahend 323 // get the one with most layers in common 324 imgLayers := map[string]struct{}{} 325 326 for _, layer := range minuendSummary.Manifests[0].Manifest.Layers { 327 imgLayers[layer.Digest.String()] = struct{}{} 328 } 329 330 bestMatchingScore := 0 331 332 for _, baseImage := range minuendBaseImages { 333 for _, baseManifest := range baseImage.Manifests { 334 currentMatchingScore := 0 335 336 for _, layer := range baseManifest.Manifest.Layers { 337 if _, ok := imgLayers[layer.Digest.String()]; ok { 338 currentMatchingScore++ 339 } 340 } 341 342 if currentMatchingScore > bestMatchingScore { 343 bestMatchingScore = currentMatchingScore 344 345 resultSubtrahend = gql_generated.ImageIdentifier{ 346 Repo: baseImage.Repo, 347 Tag: baseImage.Tag, 348 Digest: ref(baseImage.Manifests[0].Digest.String()), 349 Platform: &gql_generated.Platform{ 350 Os: ref(baseImage.Manifests[0].Config.OS), 351 Arch: ref(getArch(baseImage.Manifests[0].Config.Platform)), 352 }, 353 } 354 subtrahend.Repo = baseImage.Repo 355 subtrahend.Tag = baseImage.Tag 356 subtrahend.Digest = ref(baseImage.Manifests[0].Digest.String()) 357 } 358 } 359 } 360 } 361 362 minuendRepoRef := minuend.Repo + "@" + deref(minuend.Digest, "") 363 subtrahendRepoRef := subtrahend.Repo + "@" + deref(subtrahend.Digest, "") 364 page := dderef(requestedPage) 365 366 diffCVEs, diffSummary, _, err := cveInfo.GetCVEDiffListForImages(ctx, minuendRepoRef, subtrahendRepoRef, searchedCVE, 367 excludedCVE, cvemodel.PageInput{ 368 Limit: deref(page.Limit, 0), 369 Offset: deref(page.Offset, 0), 370 SortBy: cvemodel.SortCriteria(deref(page.SortBy, gql_generated.SortCriteriaSeverity)), 371 }) 372 if err != nil { 373 return nil, err 374 } 375 376 cveids := []*gql_generated.Cve{} 377 378 for _, cveDetail := range diffCVEs { 379 vulID := cveDetail.ID 380 desc := cveDetail.Description 381 title := cveDetail.Title 382 severity := cveDetail.Severity 383 referenceURL := cveDetail.Reference 384 385 pkgList := make([]*gql_generated.PackageInfo, 0) 386 387 for _, pkg := range cveDetail.PackageList { 388 pkg := pkg 389 390 pkgList = append(pkgList, 391 &gql_generated.PackageInfo{ 392 Name: &pkg.Name, 393 InstalledVersion: &pkg.InstalledVersion, 394 FixedVersion: &pkg.FixedVersion, 395 }, 396 ) 397 } 398 399 cveids = append(cveids, 400 &gql_generated.Cve{ 401 ID: &vulID, 402 Title: &title, 403 Description: &desc, 404 Severity: &severity, 405 Reference: &referenceURL, 406 PackageList: pkgList, 407 }, 408 ) 409 } 410 411 return &gql_generated.CVEDiffResult{ 412 Minuend: &resultMinuend, 413 Subtrahend: &resultSubtrahend, 414 Summary: &gql_generated.ImageVulnerabilitySummary{ 415 Count: &diffSummary.Count, 416 UnknownCount: &diffSummary.UnknownCount, 417 LowCount: &diffSummary.LowCount, 418 MediumCount: &diffSummary.MediumCount, 419 HighCount: &diffSummary.HighCount, 420 CriticalCount: &diffSummary.CriticalCount, 421 MaxSeverity: &diffSummary.MaxSeverity, 422 }, 423 CVEList: cveids, 424 Page: &gql_generated.PageInfo{}, 425 }, nil 426 } 427 428 func getImageIdentifier(img gql_generated.ImageInput) gql_generated.ImageIdentifier { 429 return gql_generated.ImageIdentifier{ 430 Repo: img.Repo, 431 Tag: img.Tag, 432 Digest: img.Digest, 433 Platform: getIdentifierPlatform(img.Platform), 434 } 435 } 436 437 func getIdentifierPlatform(platform *gql_generated.PlatformInput) *gql_generated.Platform { 438 if platform == nil { 439 return nil 440 } 441 442 return &gql_generated.Platform{ 443 Os: platform.Os, 444 Arch: platform.Arch, 445 } 446 } 447 448 // rename idea: identify image from input. 449 func resolveImageData(ctx context.Context, imageInput gql_generated.ImageInput, metaDB mTypes.MetaDB, 450 ) (gql_generated.ImageInput, error) { 451 if imageInput.Repo == "" { 452 return gql_generated.ImageInput{}, zerr.ErrEmptyRepoName 453 } 454 455 if imageInput.Tag == "" { 456 return gql_generated.ImageInput{}, zerr.ErrEmptyTag 457 } 458 459 // try checking if the tag is a simple image first 460 repoMeta, err := metaDB.GetRepoMeta(ctx, imageInput.Repo) 461 if err != nil { 462 return gql_generated.ImageInput{}, err 463 } 464 465 descriptor, ok := repoMeta.Tags[imageInput.Tag] 466 if !ok { 467 return gql_generated.ImageInput{}, zerr.ErrImageNotFound 468 } 469 470 switch descriptor.MediaType { 471 case ispec.MediaTypeImageManifest: 472 imageInput.Digest = ref(descriptor.Digest) 473 474 return imageInput, nil 475 case ispec.MediaTypeImageIndex: 476 if dderef(imageInput.Digest) == "" && !isPlatformSpecified(imageInput.Platform) { 477 return gql_generated.ImageInput{}, 478 fmt.Errorf("%w: platform or specific manifest digest needed", zerr.ErrAmbiguousInput) 479 } 480 481 imageMeta, err := metaDB.GetImageMeta(godigest.Digest(descriptor.Digest)) 482 if err != nil { 483 return gql_generated.ImageInput{}, err 484 } 485 486 for _, manifest := range imageMeta.Manifests { 487 if manifest.Digest.String() == dderef(imageInput.Digest) || 488 isMatchingPlatform(manifest.Config.Platform, dderef(imageInput.Platform)) { 489 imageInput.Digest = ref(manifest.Digest.String()) 490 imageInput.Platform = &gql_generated.PlatformInput{ 491 Os: ref(manifest.Config.OS), 492 Arch: ref(getArch(manifest.Config.Platform)), 493 } 494 495 return imageInput, nil 496 } 497 } 498 499 return imageInput, zerr.ErrImageNotFound 500 } 501 502 return imageInput, nil 503 } 504 505 func isPlatformSpecified(platformInput *gql_generated.PlatformInput) bool { 506 if platformInput == nil { 507 return false 508 } 509 510 if dderef(platformInput.Os) == "" || dderef(platformInput.Arch) == "" { 511 return false 512 } 513 514 return true 515 } 516 517 func isMatchingPlatform(platform ispec.Platform, platformInput gql_generated.PlatformInput) bool { 518 if platform.OS != deref(platformInput.Os, "") { 519 return false 520 } 521 522 arch := getArch(platform) 523 524 return arch == deref(platformInput.Arch, "") 525 } 526 527 func getArch(platform ispec.Platform) string { 528 arch := platform.Architecture 529 if arch != "" && platform.Variant != "" { 530 arch += "/" + platform.Variant 531 } 532 533 return arch 534 } 535 536 func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc { 537 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 538 manifestDigest := imageMeta.Manifests[0].Digest.String() 539 540 for _, tagInfo := range tagsInfo { 541 switch tagInfo.Descriptor.MediaType { 542 case ispec.MediaTypeImageManifest: 543 if tagInfo.Descriptor.Digest.String() == manifestDigest { 544 return true 545 } 546 case ispec.MediaTypeImageIndex: 547 for _, manifestDesc := range tagInfo.Manifests { 548 if manifestDesc.Digest.String() == manifestDigest { 549 return true 550 } 551 } 552 } 553 } 554 555 return false 556 } 557 } 558 559 func FilterByRepoAndTagInfo(repo string, tagsInfo []cvemodel.TagInfo) mTypes.FilterFunc { 560 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 561 if repoMeta.Name != repo { 562 return false 563 } 564 565 manifestDigest := imageMeta.Manifests[0].Digest.String() 566 567 for _, tagInfo := range tagsInfo { 568 switch tagInfo.Descriptor.MediaType { 569 case ispec.MediaTypeImageManifest: 570 if tagInfo.Descriptor.Digest.String() == manifestDigest { 571 return true 572 } 573 case ispec.MediaTypeImageIndex: 574 for _, manifestDesc := range tagInfo.Manifests { 575 if manifestDesc.Digest.String() == manifestDigest { 576 return true 577 } 578 } 579 } 580 } 581 582 return false 583 } 584 } 585 586 func getImageListForCVE( 587 ctx context.Context, 588 cveID string, 589 cveInfo cveinfo.CveInfo, 590 filter *gql_generated.Filter, 591 requestedPage *gql_generated.PageInput, 592 metaDB mTypes.MetaDB, 593 log log.Logger, 594 ) (*gql_generated.PaginatedImagesResult, error) { 595 // Obtain all repos and tags 596 // Infinite page to make sure we scan all repos in advance, before filtering results 597 // The CVE scan logic is called from here, not in the actual filter, 598 // this is because we shouldn't keep the DB locked while we wait on scan results 599 reposMeta, err := metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMeta) bool { return true }) 600 if err != nil { 601 return &gql_generated.PaginatedImagesResult{}, err 602 } 603 604 affectedImages := []cvemodel.TagInfo{} 605 606 for _, repoMeta := range reposMeta { 607 repo := repoMeta.Name 608 609 log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags affected by this cve") 610 611 tagsInfo, err := cveInfo.GetImageListForCVE(ctx, repo, cveID) 612 if err != nil { 613 log.Error().Str("repository", repo).Str("CVE", cveID).Err(err). 614 Msg("failed to get image list for this cve from repository") 615 616 return &gql_generated.PaginatedImagesResult{}, err 617 } 618 619 affectedImages = append(affectedImages, tagsInfo...) 620 } 621 622 // We're not interested in other vulnerabilities 623 skip := convert.SkipQGLField{Vulnerabilities: true} 624 625 if requestedPage == nil { 626 requestedPage = &gql_generated.PageInput{} 627 } 628 629 localFilter := mTypes.Filter{} 630 if filter != nil { 631 localFilter = mTypes.Filter{ 632 Os: filter.Os, 633 Arch: filter.Arch, 634 HasToBeSigned: filter.HasToBeSigned, 635 IsBookmarked: filter.IsBookmarked, 636 IsStarred: filter.IsStarred, 637 } 638 } 639 640 // Actual page requested by user 641 pageInput := pagination.PageInput{ 642 Limit: deref(requestedPage.Limit, 0), 643 Offset: deref(requestedPage.Offset, 0), 644 SortBy: pagination.SortCriteria( 645 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 646 ), 647 } 648 649 // get all repos 650 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByTagInfo(affectedImages)) 651 if err != nil { 652 return &gql_generated.PaginatedImagesResult{}, err 653 } 654 655 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, 656 skip, cveInfo, localFilter, pageInput) 657 if err != nil { 658 return &gql_generated.PaginatedImagesResult{}, err 659 } 660 661 return &gql_generated.PaginatedImagesResult{ 662 Results: imageSummaries, 663 Page: &gql_generated.PageInfo{ 664 TotalCount: pageInfo.TotalCount, 665 ItemCount: pageInfo.ItemCount, 666 }, 667 }, nil 668 } 669 670 func getImageListWithCVEFixed( 671 ctx context.Context, 672 cveID string, 673 repo string, 674 cveInfo cveinfo.CveInfo, 675 filter *gql_generated.Filter, 676 requestedPage *gql_generated.PageInput, 677 metaDB mTypes.MetaDB, 678 log log.Logger, 679 ) (*gql_generated.PaginatedImagesResult, error) { 680 imageList := make([]*gql_generated.ImageSummary, 0) 681 682 log.Info().Str("repository", repo).Str("CVE", cveID).Msg("extracting list of tags where this cve is fixed") 683 684 tagsInfo, err := cveInfo.GetImageListWithCVEFixed(ctx, repo, cveID) 685 if err != nil { 686 log.Error().Str("repository", repo).Str("CVE", cveID).Err(err). 687 Msg("failed to get image list with this cve fixed from repository") 688 689 return &gql_generated.PaginatedImagesResult{ 690 Page: &gql_generated.PageInfo{}, 691 Results: imageList, 692 }, err 693 } 694 695 // We're not interested in other vulnerabilities 696 skip := convert.SkipQGLField{Vulnerabilities: true} 697 698 if requestedPage == nil { 699 requestedPage = &gql_generated.PageInput{} 700 } 701 702 localFilter := mTypes.Filter{} 703 if filter != nil { 704 localFilter = mTypes.Filter{ 705 Os: filter.Os, 706 Arch: filter.Arch, 707 HasToBeSigned: filter.HasToBeSigned, 708 IsBookmarked: filter.IsBookmarked, 709 IsStarred: filter.IsStarred, 710 } 711 } 712 713 // Actual page requested by user 714 pageInput := pagination.PageInput{ 715 Limit: deref(requestedPage.Limit, 0), 716 Offset: deref(requestedPage.Offset, 0), 717 SortBy: pagination.SortCriteria( 718 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 719 ), 720 } 721 722 // get all repos 723 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByRepoAndTagInfo(repo, tagsInfo)) 724 if err != nil { 725 return &gql_generated.PaginatedImagesResult{}, err 726 } 727 728 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, 729 skip, cveInfo, localFilter, pageInput) 730 if err != nil { 731 return &gql_generated.PaginatedImagesResult{}, err 732 } 733 734 return &gql_generated.PaginatedImagesResult{ 735 Results: imageSummaries, 736 Page: &gql_generated.PageInfo{ 737 TotalCount: pageInfo.TotalCount, 738 ItemCount: pageInfo.ItemCount, 739 }, 740 }, nil 741 } 742 743 func repoListWithNewestImage( 744 ctx context.Context, 745 cveInfo cveinfo.CveInfo, 746 log log.Logger, //nolint:unparam // may be used by devs for debugging 747 requestedPage *gql_generated.PageInput, 748 metaDB mTypes.MetaDB, 749 ) (*gql_generated.PaginatedReposResult, error) { 750 paginatedRepos := &gql_generated.PaginatedReposResult{} 751 752 if requestedPage == nil { 753 requestedPage = &gql_generated.PageInput{} 754 } 755 756 skip := convert.SkipQGLField{ 757 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"), 758 } 759 760 pageInput := pagination.PageInput{ 761 Limit: deref(requestedPage.Limit, 0), 762 Offset: deref(requestedPage.Offset, 0), 763 SortBy: pagination.SortCriteria( 764 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 765 ), 766 } 767 768 repoMetaList, err := metaDB.SearchRepos(ctx, "") 769 if err != nil { 770 return &gql_generated.PaginatedReposResult{}, err 771 } 772 773 imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList)) 774 if err != nil { 775 return &gql_generated.PaginatedReposResult{}, err 776 } 777 778 repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap, 779 mTypes.Filter{}, pageInput, cveInfo, skip) 780 if err != nil { 781 return &gql_generated.PaginatedReposResult{}, err 782 } 783 784 paginatedRepos.Page = &gql_generated.PageInfo{ 785 TotalCount: pageInfo.TotalCount, 786 ItemCount: pageInfo.ItemCount, 787 } 788 789 paginatedRepos.Results = repos 790 791 return paginatedRepos, nil 792 } 793 794 func getBookmarkedRepos( 795 ctx context.Context, 796 cveInfo cveinfo.CveInfo, 797 log log.Logger, //nolint:unparam // may be used by devs for debugging 798 requestedPage *gql_generated.PageInput, 799 metaDB mTypes.MetaDB, 800 ) (*gql_generated.PaginatedReposResult, error) { 801 bookmarkedRepos, err := metaDB.GetBookmarkedRepos(ctx) 802 if err != nil { 803 return &gql_generated.PaginatedReposResult{}, err 804 } 805 806 filterByName := func(repo string) bool { 807 return zcommon.Contains(bookmarkedRepos, repo) 808 } 809 810 return getFilteredPaginatedRepos(ctx, cveInfo, filterByName, log, requestedPage, metaDB) 811 } 812 813 func getStarredRepos( 814 ctx context.Context, 815 cveInfo cveinfo.CveInfo, 816 log log.Logger, //nolint:unparam // may be used by devs for debugging 817 requestedPage *gql_generated.PageInput, 818 metaDB mTypes.MetaDB, 819 ) (*gql_generated.PaginatedReposResult, error) { 820 starredRepos, err := metaDB.GetStarredRepos(ctx) 821 if err != nil { 822 return &gql_generated.PaginatedReposResult{}, err 823 } 824 825 filterFn := func(repo string) bool { 826 return zcommon.Contains(starredRepos, repo) 827 } 828 829 return getFilteredPaginatedRepos(ctx, cveInfo, filterFn, log, requestedPage, metaDB) 830 } 831 832 func getFilteredPaginatedRepos( 833 ctx context.Context, 834 cveInfo cveinfo.CveInfo, 835 filterFn mTypes.FilterRepoNameFunc, 836 log log.Logger, //nolint:unparam // may be used by devs for debugging 837 requestedPage *gql_generated.PageInput, 838 metaDB mTypes.MetaDB, 839 ) (*gql_generated.PaginatedReposResult, error) { 840 if requestedPage == nil { 841 requestedPage = &gql_generated.PageInput{} 842 } 843 844 skip := convert.SkipQGLField{ 845 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Results.NewestImage.Vulnerabilities"), 846 } 847 848 pageInput := pagination.PageInput{ 849 Limit: deref(requestedPage.Limit, 0), 850 Offset: deref(requestedPage.Offset, 0), 851 SortBy: pagination.SortCriteria( 852 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 853 ), 854 } 855 856 repoMetaList, err := metaDB.FilterRepos(ctx, filterFn, mTypes.AcceptAllRepoMeta) 857 if err != nil { 858 return &gql_generated.PaginatedReposResult{}, err 859 } 860 861 latestImageMeta, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList)) 862 if err != nil { 863 return &gql_generated.PaginatedReposResult{}, err 864 } 865 866 repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, latestImageMeta, 867 mTypes.Filter{}, pageInput, cveInfo, skip) 868 if err != nil { 869 return &gql_generated.PaginatedReposResult{}, err 870 } 871 872 return &gql_generated.PaginatedReposResult{ 873 Results: repos, 874 Page: &gql_generated.PageInfo{ 875 TotalCount: pageInfo.TotalCount, 876 ItemCount: pageInfo.ItemCount, 877 }, 878 }, nil 879 } 880 881 func globalSearch(ctx context.Context, query string, metaDB mTypes.MetaDB, filter *gql_generated.Filter, 882 requestedPage *gql_generated.PageInput, cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam 883 ) (*gql_generated.PaginatedReposResult, []*gql_generated.ImageSummary, []*gql_generated.LayerSummary, error, 884 ) { 885 preloads := convert.GetPreloads(ctx) 886 paginatedRepos := gql_generated.PaginatedReposResult{} 887 images := []*gql_generated.ImageSummary{} 888 layers := []*gql_generated.LayerSummary{} 889 890 if requestedPage == nil { 891 requestedPage = &gql_generated.PageInput{} 892 } 893 894 localFilter := mTypes.Filter{} 895 if filter != nil { 896 localFilter = mTypes.Filter{ 897 Os: filter.Os, 898 Arch: filter.Arch, 899 HasToBeSigned: filter.HasToBeSigned, 900 IsBookmarked: filter.IsBookmarked, 901 IsStarred: filter.IsStarred, 902 } 903 } 904 905 switch getSearchTarget(query) { 906 case RepoTarget: 907 skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Repos.NewestImage.Vulnerabilities")} 908 pageInput := getPageInput(requestedPage) 909 910 repoMetaList, err := metaDB.SearchRepos(ctx, query) 911 if err != nil { 912 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, 913 []*gql_generated.LayerSummary{}, err 914 } 915 916 imageMetaMap, err := metaDB.FilterImageMeta(ctx, mTypes.GetLatestImageDigests(repoMetaList)) 917 if err != nil { 918 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, 919 []*gql_generated.LayerSummary{}, err 920 } 921 922 repos, pageInfo, err := convert.PaginatedRepoMeta2RepoSummaries(ctx, repoMetaList, imageMetaMap, localFilter, 923 pageInput, cveInfo, 924 skip) 925 if err != nil { 926 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, 927 []*gql_generated.LayerSummary{}, err 928 } 929 930 paginatedRepos.Page = &gql_generated.PageInfo{ 931 TotalCount: pageInfo.TotalCount, 932 ItemCount: pageInfo.ItemCount, 933 } 934 935 paginatedRepos.Results = repos 936 case ImageTarget: 937 skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")} 938 pageInput := getPageInput(requestedPage) 939 940 fullImageMetaList, err := metaDB.SearchTags(ctx, query) 941 if err != nil { 942 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 943 } 944 945 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo, 946 localFilter, pageInput) 947 if err != nil { 948 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 949 } 950 951 images = imageSummaries 952 953 paginatedRepos.Page = &gql_generated.PageInfo{ 954 TotalCount: pageInfo.TotalCount, 955 ItemCount: pageInfo.ItemCount, 956 } 957 case TagTarget: 958 skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")} 959 pageInput := getPageInput(requestedPage) 960 961 expectedTag := strings.TrimPrefix(query, `:`) 962 matchTagName := func(repoName, actualTag string) bool { return strings.Contains(actualTag, expectedTag) } 963 964 fullImageMetaList, err := metaDB.FilterTags(ctx, matchTagName, mTypes.AcceptAllImageMeta) 965 if err != nil { 966 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 967 } 968 969 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo, 970 localFilter, pageInput) 971 if err != nil { 972 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 973 } 974 975 images = imageSummaries 976 977 paginatedRepos.Page = &gql_generated.PageInfo{ 978 TotalCount: pageInfo.TotalCount, 979 ItemCount: pageInfo.ItemCount, 980 } 981 case DigestTarget: 982 skip := convert.SkipQGLField{Vulnerabilities: canSkipField(preloads, "Images.Vulnerabilities")} 983 pageInput := getPageInput(requestedPage) 984 985 searchedDigest := query 986 987 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, FilterByDigest(searchedDigest)) 988 if err != nil { 989 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 990 } 991 992 imageSummaries, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo, 993 localFilter, pageInput) 994 if err != nil { 995 return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err 996 } 997 998 images = imageSummaries 999 1000 paginatedRepos.Page = &gql_generated.PageInfo{ 1001 TotalCount: pageInfo.TotalCount, 1002 ItemCount: pageInfo.ItemCount, 1003 } 1004 default: 1005 return &paginatedRepos, images, layers, zerr.ErrInvalidSearchQuery 1006 } 1007 1008 return &paginatedRepos, images, layers, nil 1009 } 1010 1011 func getPageInput(requestedPage *gql_generated.PageInput) pagination.PageInput { 1012 return pagination.PageInput{ 1013 Limit: deref(requestedPage.Limit, 0), 1014 Offset: deref(requestedPage.Offset, 0), 1015 SortBy: pagination.SortCriteria( 1016 deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance), 1017 ), 1018 } 1019 } 1020 1021 type SearchTarget int 1022 1023 const ( 1024 RepoTarget = iota 1025 ImageTarget 1026 DigestTarget 1027 InvalidTarget 1028 TagTarget 1029 ) 1030 1031 func getSearchTarget(query string) SearchTarget { 1032 if !strings.ContainsAny(query, ":@") { 1033 return RepoTarget 1034 } 1035 1036 if strings.HasPrefix(query, string(godigest.SHA256)+":") { 1037 return DigestTarget 1038 } 1039 1040 if before, after, found := strings.Cut(query, ":"); found { 1041 if before != "" { 1042 return ImageTarget 1043 } else if after != "" { 1044 return TagTarget 1045 } 1046 } 1047 1048 return InvalidTarget 1049 } 1050 1051 func canSkipField(preloads map[string]bool, s string) bool { 1052 fieldIsPresent := preloads[s] 1053 1054 return !fieldIsPresent 1055 } 1056 1057 func derivedImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB, 1058 requestedPage *gql_generated.PageInput, 1059 cveInfo cveinfo.CveInfo, log log.Logger, 1060 ) (*gql_generated.PaginatedImagesResult, error) { 1061 if requestedPage == nil { 1062 requestedPage = &gql_generated.PageInput{} 1063 } 1064 1065 pageInput := pagination.PageInput{ 1066 Limit: deref(requestedPage.Limit, 0), 1067 Offset: deref(requestedPage.Offset, 0), 1068 SortBy: pagination.SortCriteria( 1069 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 1070 ), 1071 } 1072 1073 skip := convert.SkipQGLField{ 1074 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"), 1075 } 1076 1077 imageRepo, imageTag := zcommon.GetImageDirAndTag(image) 1078 if imageTag == "" { 1079 return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided") 1080 } 1081 1082 skipReferenceImage := convert.SkipQGLField{ 1083 Vulnerabilities: true, 1084 } 1085 1086 searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log) 1087 if err != nil { 1088 if errors.Is(err, zerr.ErrRepoMetaNotFound) { 1089 return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found") 1090 } 1091 1092 return &gql_generated.PaginatedImagesResult{}, err 1093 } 1094 1095 // we need all available tags 1096 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterDerivedImages(searchedImage)) 1097 if err != nil { 1098 return &gql_generated.PaginatedImagesResult{}, err 1099 } 1100 1101 derivedList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, skip, cveInfo, 1102 mTypes.Filter{}, pageInput) 1103 if err != nil { 1104 return &gql_generated.PaginatedImagesResult{}, err 1105 } 1106 1107 return &gql_generated.PaginatedImagesResult{ 1108 Results: derivedList, 1109 Page: &gql_generated.PageInfo{ 1110 TotalCount: pageInfo.TotalCount, 1111 ItemCount: pageInfo.ItemCount, 1112 }, 1113 }, nil 1114 } 1115 1116 func filterDerivedImages(image *gql_generated.ImageSummary) mTypes.FilterFunc { 1117 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 1118 var addImageToList bool 1119 1120 imageManifest := imageMeta.Manifests[0] 1121 1122 for i := range image.Manifests { 1123 manifestDigest := imageManifest.Digest.String() 1124 if manifestDigest == *image.Manifests[i].Digest { 1125 return false 1126 } 1127 imageLayers := image.Manifests[i].Layers 1128 1129 addImageToList = false 1130 layers := imageManifest.Manifest.Layers 1131 1132 sameLayer := 0 1133 1134 for _, l := range imageLayers { 1135 for _, k := range layers { 1136 if k.Digest.String() == *l.Digest { 1137 sameLayer++ 1138 } 1139 } 1140 } 1141 1142 // if all layers are the same 1143 if sameLayer == len(imageLayers) { 1144 // it's a derived image 1145 addImageToList = true 1146 } 1147 1148 if addImageToList { 1149 return true 1150 } 1151 } 1152 1153 return false 1154 } 1155 } 1156 1157 func baseImageList(ctx context.Context, image string, digest *string, metaDB mTypes.MetaDB, 1158 requestedPage *gql_generated.PageInput, 1159 cveInfo cveinfo.CveInfo, log log.Logger, 1160 ) (*gql_generated.PaginatedImagesResult, error) { 1161 if requestedPage == nil { 1162 requestedPage = &gql_generated.PageInput{} 1163 } 1164 1165 pageInput := pagination.PageInput{ 1166 Limit: deref(requestedPage.Limit, 0), 1167 Offset: deref(requestedPage.Offset, 0), 1168 SortBy: pagination.SortCriteria( 1169 deref(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime), 1170 ), 1171 } 1172 1173 skip := convert.SkipQGLField{ 1174 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"), 1175 } 1176 1177 imageRepo, imageTag := zcommon.GetImageDirAndTag(image) 1178 1179 if imageTag == "" { 1180 return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided") 1181 } 1182 1183 skipReferenceImage := convert.SkipQGLField{ 1184 Vulnerabilities: true, 1185 } 1186 1187 searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, skipReferenceImage, metaDB, cveInfo, log) 1188 if err != nil { 1189 if errors.Is(err, zerr.ErrRepoMetaNotFound) { 1190 return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository not found") 1191 } 1192 1193 return &gql_generated.PaginatedImagesResult{}, err 1194 } 1195 1196 // we need all available tags 1197 fullImageMetaList, err := metaDB.FilterTags(ctx, mTypes.AcceptAllRepoTag, filterBaseImages(searchedImage)) 1198 if err != nil { 1199 return &gql_generated.PaginatedImagesResult{}, err 1200 } 1201 1202 baseList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, fullImageMetaList, 1203 skip, cveInfo, mTypes.Filter{}, pageInput) 1204 if err != nil { 1205 return &gql_generated.PaginatedImagesResult{}, err 1206 } 1207 1208 return &gql_generated.PaginatedImagesResult{ 1209 Page: &gql_generated.PageInfo{ 1210 TotalCount: pageInfo.TotalCount, 1211 ItemCount: pageInfo.ItemCount, 1212 }, 1213 Results: baseList, 1214 }, nil 1215 } 1216 1217 func filterBaseImages(image *gql_generated.ImageSummary) mTypes.FilterFunc { 1218 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 1219 var addImageToList bool 1220 1221 manifest := imageMeta.Manifests[0] 1222 1223 for i := range image.Manifests { 1224 manifestDigest := manifest.Digest.String() 1225 if manifestDigest == *image.Manifests[i].Digest { 1226 return false 1227 } 1228 1229 addImageToList = true 1230 1231 for _, l := range manifest.Manifest.Layers { 1232 foundLayer := false 1233 1234 for _, k := range image.Manifests[i].Layers { 1235 if l.Digest.String() == *k.Digest { 1236 foundLayer = true 1237 1238 break 1239 } 1240 } 1241 1242 if !foundLayer { 1243 addImageToList = false 1244 1245 break 1246 } 1247 } 1248 1249 if addImageToList { 1250 return true 1251 } 1252 } 1253 1254 return false 1255 } 1256 } 1257 1258 func filterBaseImagesForMeta(image mTypes.ImageMeta) mTypes.FilterFunc { 1259 return func(repoMeta mTypes.RepoMeta, imageMeta mTypes.ImageMeta) bool { 1260 var addImageToList bool 1261 1262 manifest := imageMeta.Manifests[0] 1263 1264 for i := range image.Manifests { 1265 manifestDigest := manifest.Digest.String() 1266 if manifestDigest == image.Manifests[i].Digest.String() { 1267 return false 1268 } 1269 1270 addImageToList = true 1271 1272 for _, l := range manifest.Manifest.Layers { 1273 foundLayer := false 1274 1275 for _, k := range image.Manifests[i].Manifest.Layers { 1276 if l.Digest.String() == k.Digest.String() { 1277 foundLayer = true 1278 1279 break 1280 } 1281 } 1282 1283 if !foundLayer { 1284 addImageToList = false 1285 1286 break 1287 } 1288 } 1289 1290 if addImageToList { 1291 return true 1292 } 1293 } 1294 1295 return false 1296 } 1297 } 1298 1299 func validateGlobalSearchInput(query string, filter *gql_generated.Filter, 1300 requestedPage *gql_generated.PageInput, 1301 ) error { 1302 if len(query) > querySizeLimit { 1303 return fmt.Errorf("max string size limit exceeded for query parameter. max=%d current=%d %w", 1304 querySizeLimit, len(query), zerr.ErrInvalidRequestParams) 1305 } 1306 1307 err := checkFilter(filter) 1308 if err != nil { 1309 return err 1310 } 1311 1312 err = checkRequestedPage(requestedPage) 1313 if err != nil { 1314 return err 1315 } 1316 1317 return nil 1318 } 1319 1320 func checkFilter(filter *gql_generated.Filter) error { 1321 if filter == nil { 1322 return nil 1323 } 1324 1325 for _, arch := range filter.Arch { 1326 if len(*arch) > querySizeLimit { 1327 return fmt.Errorf("max string size limit exceeded for arch parameter. max=%d current=%d %w", 1328 querySizeLimit, len(*arch), zerr.ErrInvalidRequestParams) 1329 } 1330 } 1331 1332 for _, osSys := range filter.Os { 1333 if len(*osSys) > querySizeLimit { 1334 return fmt.Errorf("max string size limit exceeded for os parameter. max=%d current=%d %w", 1335 querySizeLimit, len(*osSys), zerr.ErrInvalidRequestParams) 1336 } 1337 } 1338 1339 return nil 1340 } 1341 1342 func checkRequestedPage(requestedPage *gql_generated.PageInput) error { 1343 if requestedPage == nil { 1344 return nil 1345 } 1346 1347 if requestedPage.Limit != nil && *requestedPage.Limit < 0 { 1348 return fmt.Errorf("requested page limit parameter can't be negative %w", 1349 zerr.ErrInvalidRequestParams) 1350 } 1351 1352 if requestedPage.Offset != nil && *requestedPage.Offset < 0 { 1353 return fmt.Errorf("requested page offset parameter can't be negative %w", 1354 zerr.ErrInvalidRequestParams) 1355 } 1356 1357 return nil 1358 } 1359 1360 func cleanQuery(query string) string { 1361 query = strings.TrimSpace(query) 1362 query = strings.Trim(query, "/") 1363 query = strings.ToLower(query) 1364 1365 return query 1366 } 1367 1368 func cleanFilter(filter *gql_generated.Filter) *gql_generated.Filter { 1369 if filter == nil { 1370 return nil 1371 } 1372 1373 if filter.Arch != nil { 1374 for i := range filter.Arch { 1375 *filter.Arch[i] = strings.ToLower(*filter.Arch[i]) 1376 *filter.Arch[i] = strings.TrimSpace(*filter.Arch[i]) 1377 } 1378 1379 filter.Arch = deleteEmptyElements(filter.Arch) 1380 } 1381 1382 if filter.Os != nil { 1383 for i := range filter.Os { 1384 *filter.Os[i] = strings.ToLower(*filter.Os[i]) 1385 *filter.Os[i] = strings.TrimSpace(*filter.Os[i]) 1386 } 1387 1388 filter.Os = deleteEmptyElements(filter.Os) 1389 } 1390 1391 return filter 1392 } 1393 1394 func deleteEmptyElements(slice []*string) []*string { 1395 i := 0 1396 for i < len(slice) { 1397 if elementIsEmpty(*slice[i]) { 1398 slice = deleteElementAt(slice, i) 1399 } else { 1400 i++ 1401 } 1402 } 1403 1404 return slice 1405 } 1406 1407 func elementIsEmpty(s string) bool { 1408 return s == "" 1409 } 1410 1411 func deleteElementAt(slice []*string, i int) []*string { 1412 slice[i] = slice[len(slice)-1] 1413 slice = slice[:len(slice)-1] 1414 1415 return slice 1416 } 1417 1418 func expandedRepoInfo(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, log log.Logger, 1419 ) (*gql_generated.RepoInfo, error) { 1420 if ok, err := reqCtx.RepoIsUserAvailable(ctx, repo); !ok || err != nil { 1421 log.Info().Err(err).Str("repository", repo).Bool("availability", ok).Str("component", "graphql"). 1422 Msg("repo user availability") 1423 1424 return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker 1425 } 1426 1427 repoMeta, err := metaDB.GetRepoMeta(ctx, repo) 1428 if err != nil { 1429 log.Error().Err(err).Str("repository", repo).Str("component", "graphql"). 1430 Msg("can't retrieve repoMeta for repository") 1431 1432 return &gql_generated.RepoInfo{}, err 1433 } 1434 1435 tagsDigests := []string{} 1436 1437 for i := range repoMeta.Tags { 1438 if i == "" { 1439 continue 1440 } 1441 1442 tagsDigests = append(tagsDigests, repoMeta.Tags[i].Digest) 1443 } 1444 1445 imageMetaMap, err := metaDB.FilterImageMeta(ctx, tagsDigests) 1446 if err != nil { 1447 log.Error().Err(err).Str("repository", repo).Str("component", "graphql"). 1448 Msg("can't retrieve imageMeta for repo") 1449 1450 return &gql_generated.RepoInfo{}, err 1451 } 1452 1453 skip := convert.SkipQGLField{ 1454 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities") && 1455 canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"), 1456 } 1457 1458 repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, imageMetaMap, 1459 skip, cveInfo, log) 1460 1461 dateSortedImages := make(timeSlice, 0, len(imageSummaries)) 1462 for _, imgSummary := range imageSummaries { 1463 dateSortedImages = append(dateSortedImages, imgSummary) 1464 } 1465 1466 sort.Sort(dateSortedImages) 1467 1468 return &gql_generated.RepoInfo{Summary: repoSummary, Images: dateSortedImages}, nil 1469 } 1470 1471 type timeSlice []*gql_generated.ImageSummary 1472 1473 func (p timeSlice) Len() int { 1474 return len(p) 1475 } 1476 1477 func (p timeSlice) Less(i, j int) bool { 1478 return p[i].LastUpdated.After(*p[j].LastUpdated) 1479 } 1480 1481 func (p timeSlice) Swap(i, j int) { 1482 p[i], p[j] = p[j], p[i] 1483 } 1484 1485 // dderef is a default deref. 1486 func dderef[T any](pointer *T) T { 1487 var defValue T 1488 1489 if pointer != nil { 1490 return *pointer 1491 } 1492 1493 return defValue 1494 } 1495 1496 func deref[T any](pointer *T, defaultVal T) T { 1497 if pointer != nil { 1498 return *pointer 1499 } 1500 1501 return defaultVal 1502 } 1503 1504 func ref[T any](input T) *T { 1505 ref := input 1506 1507 return &ref 1508 } 1509 1510 func getImageList(ctx context.Context, repo string, metaDB mTypes.MetaDB, cveInfo cveinfo.CveInfo, 1511 requestedPage *gql_generated.PageInput, log log.Logger, //nolint:unparam 1512 ) (*gql_generated.PaginatedImagesResult, error) { 1513 if requestedPage == nil { 1514 requestedPage = &gql_generated.PageInput{} 1515 } 1516 1517 skip := convert.SkipQGLField{ 1518 Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"), 1519 } 1520 1521 pageInput := pagination.PageInput{ 1522 Limit: deref(requestedPage.Limit, 0), 1523 Offset: deref(requestedPage.Offset, 0), 1524 SortBy: pagination.SortCriteria( 1525 deref(requestedPage.SortBy, gql_generated.SortCriteriaRelevance), 1526 ), 1527 } 1528 1529 var matchRepoName mTypes.FilterRepoTagFunc 1530 1531 if repo == "" { 1532 matchRepoName = mTypes.AcceptAllRepoTag 1533 } else { 1534 matchRepoName = func(repoName, tag string) bool { return repoName == repo } 1535 } 1536 1537 imageMeta, err := metaDB.FilterTags(ctx, matchRepoName, mTypes.AcceptAllImageMeta) 1538 if err != nil { 1539 return &gql_generated.PaginatedImagesResult{}, err 1540 } 1541 1542 imageList, pageInfo, err := convert.PaginatedFullImageMeta2ImageSummaries(ctx, imageMeta, skip, 1543 cveInfo, mTypes.Filter{}, pageInput) 1544 if err != nil { 1545 return &gql_generated.PaginatedImagesResult{}, err 1546 } 1547 1548 return &gql_generated.PaginatedImagesResult{ 1549 Results: imageList, 1550 Page: &gql_generated.PageInfo{ 1551 TotalCount: pageInfo.TotalCount, 1552 ItemCount: pageInfo.ItemCount, 1553 }, 1554 }, nil 1555 } 1556 1557 func getReferrers(metaDB mTypes.MetaDB, repo string, referredDigest string, artifactTypes []string, 1558 log log.Logger, 1559 ) ([]*gql_generated.Referrer, error) { 1560 refDigest := godigest.Digest(referredDigest) 1561 if err := refDigest.Validate(); err != nil { 1562 log.Error().Err(err).Str("digest", referredDigest).Str("component", "graphql"). 1563 Msg("bad referenced digest string from request") 1564 1565 return []*gql_generated.Referrer{}, fmt.Errorf("bad digest string from request '%s' %w", 1566 referredDigest, err) 1567 } 1568 1569 referrers, err := metaDB.GetReferrersInfo(repo, refDigest, artifactTypes) 1570 if err != nil { 1571 return nil, err 1572 } 1573 1574 results := make([]*gql_generated.Referrer, 0, len(referrers)) 1575 1576 for _, referrer := range referrers { 1577 referrer := referrer 1578 1579 results = append(results, &gql_generated.Referrer{ 1580 MediaType: &referrer.MediaType, 1581 ArtifactType: &referrer.ArtifactType, 1582 Digest: &referrer.Digest, 1583 Size: &referrer.Size, 1584 Annotations: convert.StringMap2Annotations(referrer.Annotations), 1585 }) 1586 } 1587 1588 return results, nil 1589 }