zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/search/cve/cve_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  //nolint:lll,gosimple
     5  package cveinfo_test
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"net/url"
    13  	"os"
    14  	"path"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	regTypes "github.com/google/go-containerregistry/pkg/v1/types"
    20  	godigest "github.com/opencontainers/go-digest"
    21  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    22  	. "github.com/smartystreets/goconvey/convey"
    23  	"gopkg.in/resty.v1"
    24  
    25  	zerr "zotregistry.io/zot/errors"
    26  	"zotregistry.io/zot/pkg/api"
    27  	"zotregistry.io/zot/pkg/api/config"
    28  	"zotregistry.io/zot/pkg/api/constants"
    29  	apiErr "zotregistry.io/zot/pkg/api/errors"
    30  	zcommon "zotregistry.io/zot/pkg/common"
    31  	extconf "zotregistry.io/zot/pkg/extensions/config"
    32  	"zotregistry.io/zot/pkg/extensions/monitoring"
    33  	cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
    34  	cvecache "zotregistry.io/zot/pkg/extensions/search/cve/cache"
    35  	cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
    36  	"zotregistry.io/zot/pkg/log"
    37  	"zotregistry.io/zot/pkg/meta"
    38  	"zotregistry.io/zot/pkg/meta/boltdb"
    39  	mTypes "zotregistry.io/zot/pkg/meta/types"
    40  	"zotregistry.io/zot/pkg/storage"
    41  	"zotregistry.io/zot/pkg/storage/local"
    42  	test "zotregistry.io/zot/pkg/test/common"
    43  	"zotregistry.io/zot/pkg/test/deprecated"
    44  	. "zotregistry.io/zot/pkg/test/image-utils"
    45  	"zotregistry.io/zot/pkg/test/mocks"
    46  	ociutils "zotregistry.io/zot/pkg/test/oci-utils"
    47  )
    48  
    49  type CveResult struct {
    50  	ImgList ImgList    `json:"data"`
    51  	Errors  []ErrorGQL `json:"errors"`
    52  }
    53  
    54  //nolint:tagliatelle // graphQL schema
    55  type ImgListWithCVEFixed struct {
    56  	Images []ImageInfo `json:"ImageListWithCVEFixed"`
    57  }
    58  
    59  type ImageInfo struct {
    60  	RepoName    string
    61  	LastUpdated time.Time
    62  }
    63  
    64  //nolint:tagliatelle // graphQL schema
    65  type ImgList struct {
    66  	CVEResultForImage CVEResultForImage `json:"CVEListForImage"`
    67  }
    68  
    69  type ErrorGQL struct {
    70  	Message string   `json:"message"`
    71  	Path    []string `json:"path"`
    72  }
    73  
    74  //nolint:tagliatelle // graphQL schema
    75  type CVEResultForImage struct {
    76  	Tag     string         `json:"Tag"`
    77  	CVEList []cvemodel.CVE `json:"CVEList"`
    78  }
    79  
    80  func testSetup(t *testing.T) (string, error) {
    81  	t.Helper()
    82  	dir := t.TempDir()
    83  
    84  	err := generateTestData(dir)
    85  	if err != nil {
    86  		return "", err
    87  	}
    88  
    89  	testStorageCtrl := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
    90  
    91  	err = WriteImageToFileSystem(CreateRandomVulnerableImage(), "zot-test", "0.0.1", testStorageCtrl)
    92  	if err != nil {
    93  		return "", err
    94  	}
    95  
    96  	err = WriteImageToFileSystem(CreateRandomVulnerableImage(), "zot-cve-test", "0.0.1", testStorageCtrl)
    97  	if err != nil {
    98  		return "", err
    99  	}
   100  
   101  	return dir, nil
   102  }
   103  
   104  func generateTestData(dbDir string) error { //nolint: gocyclo
   105  	// Image dir with no files
   106  	err := os.Mkdir(path.Join(dbDir, "zot-noindex-test"), 0o755)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	err = os.Mkdir(path.Join(dbDir, "zot-nonreadable-test"), 0o755)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	index := ispec.Index{}
   117  	index.SchemaVersion = 2
   118  
   119  	buf, err := json.Marshal(index)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	if err = os.WriteFile(path.Join(dbDir, "zot-nonreadable-test", "index.json"), //nolint:gosec // test code
   125  		buf, 0o111); err != nil {
   126  		return err
   127  	}
   128  
   129  	// Image dir with invalid index.json
   130  	err = os.Mkdir(path.Join(dbDir, "zot-squashfs-invalid-index"), 0o755)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	content := `{"schemaVersion": 2,"manifests"[{"mediaType": "application/vnd.oci.image.manifest.v1+json","digest": "sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size": 1240,"annotations": {"org.opencontainers.image.ref.name": "commit-aaa7c6e7-squashfs"},"platform": {"architecture": "amd64","os": "linux"}}]}`
   136  
   137  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-index", "index.json"), content)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	// Image dir with no blobs
   143  	err = os.Mkdir(path.Join(dbDir, "zot-squashfs-noblobs"), 0o755)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	content = `{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]}`
   149  
   150  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-noblobs", "index.json"), content)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// Image dir with invalid blob
   156  	err = os.MkdirAll(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256"), 0o755)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]}
   162  	`)
   163  
   164  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "index.json"), content)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	content = fmt.Sprint(`{"schemaVersion":2,"config"{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:4b37d4133908ac9a3032ba996020f2ad41354a616b071ca7e726a1df18a0f354","size":1691},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:a01a66356aace53222e92fb6fd990b23eb44ab0e58dff6f853fa9f771ecf3ac5","size":54996992},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91c26d6934ef2b5c5c4d8458af9bfc4ca46cf90c22380193154964abc8298a7a","size":52330496},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:f281a550ca49746cfc6b8f1ac52f8086b3d5845db2ca18fde980dab62ae3bf7d","size":23343104},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:7ee02568717acdda336c9d56d4dc6ea7f3b1c553e43bb0c0ecc6fd3bbd059d1a","size":5910528},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:8fb33b130588b239235dedd560cdf49d29bbf6f2db5419ac68e4592a85c1f416","size":123269120},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:1b49f0b33d4a696bb94d84c9acab3623e2c195bfb446d446a583a2f9f27b04c3","size":113901568}],"annotations":{"com.cisco.stacker.git_version":"7-dev19-63-gaaa7c6e7","ws.tycho.stacker.git_version":"0.3.26"}}
   170  	`)
   171  
   172  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256", "2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929"), content)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	// Create a squashfs image
   178  
   179  	err = os.MkdirAll(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256"), 0o755)
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion}
   185  	buf, err = json.Marshal(il)
   186  
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	if err = os.WriteFile(path.Join(dbDir, "zot-squashfs-test", "oci-layout"), buf, 0o644); err != nil { //nolint: gosec
   192  		return err
   193  	}
   194  
   195  	err = os.Mkdir(path.Join(dbDir, "zot-squashfs-test", ".uploads"), 0o755)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
   201  
   202  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "index.json"), content)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8","size":1738},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:3414b5ef0ad2f0390daaf55b63c422eeedef6191d47036a69d8ee396fabdce72","size":58993484},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:a3b04fff744c13dfa4883e01fa35e01af8daa7f72d9e9b6b7fad1f28843846b6","size":55631733},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:754f517f58f302190aa94e025c25890c18e1e811127aed1ef25c189278ec4ab0","size":113612795},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:ec004cd43488b803d6e232599e83a3164394d44fcd9f44755fed7b5791087ede","size":108889651}],"annotations":{"ws.tycho.stacker.git_version":"0.3.19"}}`)
   208  
   209  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099"), content)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-04-08T05:08:43.590117872Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
   215  
   216  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8"), content)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
   222  
   223  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
   229  
   230  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0","size":1218},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:c40d72b1556293c00a3e4b6c64c46ef4c7ae4d876dc18bad942b7d1903e8e5b7","size":54745420},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:4115890e3e2563e545e03f264bfecb0097e24e02306ae3e7668dea52e00c6cc2","size":52213357},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91859e13e0cf704d5405199d73a2d1a0718391dbb183a77c4cb85d99e923ff57","size":109479329},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:20aef84d8098d47a0643a2f99ce05f0deed957b3a259fb708c538f23ed97cc82","size":103996238}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
   236  
   237  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76"), content)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-05-11T19:30:02.467956112Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
   243  
   244  	err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0"), content)
   245  	if err != nil {
   246  		return err
   247  	}
   248  
   249  	// Create a image with invalid layer blob
   250  
   251  	err = os.MkdirAll(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256"), 0o755)
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
   257  
   258  	err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "index.json"), content)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
   264  
   265  	err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
   271  
   272  	err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	// Create a image with no layer blob
   278  
   279  	err = os.MkdirAll(path.Join(dbDir, "zot-no-layer", "blobs/sha256"), 0o755)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
   285  
   286  	err = makeTestFile(path.Join(dbDir, "zot-no-layer", "index.json"), content)
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
   292  
   293  	err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
   299  
   300  	err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a"), content)
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  func makeTestFile(fileName, content string) error {
   309  	if err := os.WriteFile(fileName, []byte(content), 0o600); err != nil {
   310  		panic(err)
   311  	}
   312  
   313  	return nil
   314  }
   315  
   316  func TestImageFormat(t *testing.T) {
   317  	Convey("Test valid image", t, func() {
   318  		log := log.NewLogger("debug", "")
   319  		imgDir := "../../../../test/data"
   320  		dbDir := t.TempDir()
   321  
   322  		metrics := monitoring.NewMetricsServer(false, log)
   323  		defaultStore := local.NewImageStore(imgDir, false, false, log, metrics, nil, nil)
   324  		storeController := storage.StoreController{DefaultStore: defaultStore}
   325  
   326  		params := boltdb.DBParameters{
   327  			RootDir: dbDir,
   328  		}
   329  		boltDriver, err := boltdb.GetBoltDriver(params)
   330  		So(err, ShouldBeNil)
   331  
   332  		metaDB, err := boltdb.New(boltDriver, log)
   333  		So(err, ShouldBeNil)
   334  
   335  		err = meta.ParseStorage(metaDB, storeController, log)
   336  		So(err, ShouldBeNil)
   337  
   338  		scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   339  
   340  		isValidImage, err := scanner.IsImageFormatScannable("zot-test", "")
   341  		So(err, ShouldNotBeNil)
   342  		So(isValidImage, ShouldEqual, false)
   343  
   344  		isValidImage, err = scanner.IsImageFormatScannable("zot-test", "0.0.1")
   345  		So(err, ShouldBeNil)
   346  		So(isValidImage, ShouldEqual, true)
   347  
   348  		isValidImage, err = scanner.IsImageFormatScannable("zot-test", "0.0.")
   349  		So(err, ShouldNotBeNil)
   350  		So(isValidImage, ShouldEqual, false)
   351  
   352  		isValidImage, err = scanner.IsImageFormatScannable("zot-noindex-test", "")
   353  		So(err, ShouldNotBeNil)
   354  		So(isValidImage, ShouldEqual, false)
   355  
   356  		isValidImage, err = scanner.IsImageFormatScannable("zot--tet", "")
   357  		So(err, ShouldNotBeNil)
   358  		So(isValidImage, ShouldEqual, false)
   359  
   360  		isValidImage, err = scanner.IsImageFormatScannable("zot-noindex-test", "")
   361  		So(err, ShouldNotBeNil)
   362  		So(isValidImage, ShouldEqual, false)
   363  
   364  		isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-noblobs", "")
   365  		So(err, ShouldNotBeNil)
   366  		So(isValidImage, ShouldEqual, false)
   367  
   368  		isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-invalid-index", "")
   369  		So(err, ShouldNotBeNil)
   370  		So(isValidImage, ShouldEqual, false)
   371  
   372  		isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-invalid-blob", "")
   373  		So(err, ShouldNotBeNil)
   374  		So(isValidImage, ShouldEqual, false)
   375  
   376  		isValidImage, err = scanner.IsImageFormatScannable("zot-squashfs-test:0.3.22-squashfs", "")
   377  		So(err, ShouldNotBeNil)
   378  		So(isValidImage, ShouldEqual, false)
   379  
   380  		isValidImage, err = scanner.IsImageFormatScannable("zot-nonreadable-test", "")
   381  		So(err, ShouldNotBeNil)
   382  		So(isValidImage, ShouldEqual, false)
   383  	})
   384  
   385  	Convey("isIndexScanable", t, func() {
   386  		log := log.NewLogger("debug", "")
   387  
   388  		metaDB := &mocks.MetaDBMock{
   389  			GetRepoMetaFn: func(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   390  				return mTypes.RepoMeta{
   391  					Tags: map[string]mTypes.Descriptor{
   392  						"tag": {
   393  							MediaType: ispec.MediaTypeImageIndex,
   394  							Digest:    godigest.FromString("digest").String(),
   395  						},
   396  					},
   397  				}, nil
   398  			},
   399  			GetImageMetaFn: func(digest godigest.Digest) (mTypes.ImageMeta, error) {
   400  				return mTypes.ImageMeta{
   401  					MediaType: ispec.MediaTypeImageIndex,
   402  					Digest:    godigest.FromString("digest"),
   403  					Index:     &ispec.Index{},
   404  				}, nil
   405  			},
   406  		}
   407  		storeController := storage.StoreController{
   408  			DefaultStore: mocks.MockedImageStore{},
   409  		}
   410  
   411  		scanner := cveinfo.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db", "", log)
   412  
   413  		isScanable, err := scanner.IsImageFormatScannable("repo", "tag")
   414  		So(err, ShouldBeNil)
   415  		So(isScanable, ShouldBeTrue)
   416  	})
   417  }
   418  
   419  func TestCVESearchDisabled(t *testing.T) {
   420  	Convey("Test with CVE search disabled", t, func() {
   421  		port := test.GetFreePort()
   422  		baseURL := test.GetBaseURL(port)
   423  		conf := config.New()
   424  		conf.HTTP.Port = port
   425  		username, seedUser := test.GenerateRandomString()
   426  		password, seedPass := test.GenerateRandomString()
   427  		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
   428  		defer os.Remove(htpasswdPath)
   429  
   430  		conf.HTTP.Auth = &config.AuthConfig{
   431  			HTPasswd: config.AuthHTPasswd{
   432  				Path: htpasswdPath,
   433  			},
   434  		}
   435  
   436  		dbDir := t.TempDir()
   437  
   438  		conf.Storage.RootDirectory = dbDir
   439  		defaultVal := true
   440  		searchConfig := &extconf.SearchConfig{
   441  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   442  		}
   443  		conf.Extensions = &extconf.ExtensionConfig{
   444  			Search: searchConfig,
   445  		}
   446  
   447  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   448  		if err != nil {
   449  			panic(err)
   450  		}
   451  
   452  		logPath := logFile.Name()
   453  		defer os.Remove(logPath)
   454  
   455  		writers := io.MultiWriter(os.Stdout, logFile)
   456  
   457  		ctlr := api.NewController(conf)
   458  		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")
   459  		ctlr.Log.Logger = ctlr.Log.Output(writers)
   460  		ctrlManager := test.NewControllerManager(ctlr)
   461  
   462  		ctrlManager.StartAndWait(port)
   463  
   464  		// Wait for trivy db to download
   465  		found, err := test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second)
   466  		So(err, ShouldBeNil)
   467  		So(found, ShouldBeTrue)
   468  
   469  		defer ctrlManager.StopServer()
   470  
   471  		resp, _ := resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   472  		So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
   473  		So(resp.StatusCode(), ShouldEqual, 200)
   474  
   475  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Results{RepoName%20Tag}}}")
   476  		So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
   477  		So(resp.StatusCode(), ShouldEqual, 200)
   478  
   479  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + "randomId" + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
   480  		So(resp, ShouldNotBeNil)
   481  		So(string(resp.Body()), ShouldContainSubstring, "search: CVE search is disabled")
   482  		So(resp.StatusCode(), ShouldEqual, 200)
   483  	})
   484  }
   485  
   486  func TestCVESearch(t *testing.T) {
   487  	Convey("Test image vulnerability scanning", t, func() {
   488  		updateDuration, _ := time.ParseDuration("1h")
   489  		port := test.GetFreePort()
   490  		baseURL := test.GetBaseURL(port)
   491  		conf := config.New()
   492  		conf.HTTP.Port = port
   493  		username, seedUser := test.GenerateRandomString()
   494  		password, seedPass := test.GenerateRandomString()
   495  		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))
   496  		defer os.Remove(htpasswdPath)
   497  
   498  		dbDir, err := testSetup(t)
   499  		So(err, ShouldBeNil)
   500  
   501  		conf.HTTP.Auth = &config.AuthConfig{
   502  			HTPasswd: config.AuthHTPasswd{
   503  				Path: htpasswdPath,
   504  			},
   505  		}
   506  
   507  		conf.Storage.RootDirectory = dbDir
   508  
   509  		trivyConfig := &extconf.TrivyConfig{
   510  			DBRepository: "ghcr.io/project-zot/trivy-db",
   511  		}
   512  		cveConfig := &extconf.CVEConfig{
   513  			UpdateInterval: updateDuration,
   514  			Trivy:          trivyConfig,
   515  		}
   516  		defaultVal := true
   517  		searchConfig := &extconf.SearchConfig{
   518  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   519  			CVE:        cveConfig,
   520  		}
   521  		conf.Extensions = &extconf.ExtensionConfig{
   522  			Search: searchConfig,
   523  		}
   524  
   525  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   526  		if err != nil {
   527  			panic(err)
   528  		}
   529  
   530  		logPath := logFile.Name()
   531  		defer os.Remove(logPath)
   532  
   533  		writers := io.MultiWriter(os.Stdout, logFile)
   534  
   535  		ctlr := api.NewController(conf)
   536  		ctlr.Log.Logger = ctlr.Log.Output(writers)
   537  		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")
   538  		ctrlManager := test.NewControllerManager(ctlr)
   539  
   540  		ctrlManager.StartAndWait(port)
   541  
   542  		// trivy db download fail
   543  		err = os.Mkdir(dbDir+"/_trivy", 0o000)
   544  		So(err, ShouldBeNil)
   545  		found, err := test.ReadLogFileAndSearchString(logPath, "Error downloading Trivy DB to destination dir", 180*time.Second)
   546  		So(err, ShouldBeNil)
   547  		So(found, ShouldBeTrue)
   548  
   549  		err = os.Chmod(dbDir+"/_trivy", 0o755)
   550  		So(err, ShouldBeNil)
   551  
   552  		// Wait for trivy db to download
   553  		found, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 180*time.Second)
   554  		So(err, ShouldBeNil)
   555  		So(found, ShouldBeTrue)
   556  
   557  		defer ctrlManager.StopServer()
   558  
   559  		// without creds, should get access error
   560  		resp, err := resty.R().Get(baseURL + "/v2/")
   561  		So(err, ShouldBeNil)
   562  		So(resp, ShouldNotBeNil)
   563  		So(resp.StatusCode(), ShouldEqual, 401)
   564  		var apiErr apiErr.Error
   565  		err = json.Unmarshal(resp.Body(), &apiErr)
   566  		So(err, ShouldBeNil)
   567  
   568  		resp, err = resty.R().Get(baseURL + constants.FullSearchPrefix)
   569  		So(err, ShouldBeNil)
   570  		So(resp, ShouldNotBeNil)
   571  		So(resp.StatusCode(), ShouldEqual, 401)
   572  		err = json.Unmarshal(resp.Body(), &apiErr)
   573  		So(err, ShouldBeNil)
   574  
   575  		// with creds, should get expected status code
   576  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL)
   577  		So(resp, ShouldNotBeNil)
   578  		So(resp.StatusCode(), ShouldEqual, 404)
   579  
   580  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + "/v2/")
   581  		So(resp, ShouldNotBeNil)
   582  		So(resp.StatusCode(), ShouldEqual, 200)
   583  
   584  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix)
   585  		So(resp, ShouldNotBeNil)
   586  		So(resp.StatusCode(), ShouldEqual, 422)
   587  
   588  		var cveResult CveResult
   589  		contains := false
   590  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   591  		err = json.Unmarshal(resp.Body(), &cveResult)
   592  		So(err, ShouldBeNil)
   593  		for _, err := range cveResult.Errors {
   594  			result := strings.Contains(err.Message, "no reference provided")
   595  			if result {
   596  				contains = result
   597  			}
   598  		}
   599  		So(contains, ShouldBeTrue)
   600  
   601  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   602  		So(resp, ShouldNotBeNil)
   603  		So(resp.StatusCode(), ShouldEqual, 200)
   604  
   605  		err = json.Unmarshal(resp.Body(), &cveResult)
   606  		So(err, ShouldBeNil)
   607  		So(len(cveResult.ImgList.CVEResultForImage.CVEList), ShouldNotBeZeroValue)
   608  
   609  		cveid := cveResult.ImgList.CVEResultForImage.CVEList[0].ID
   610  
   611  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
   612  		So(resp, ShouldNotBeNil)
   613  		So(resp.StatusCode(), ShouldEqual, 200)
   614  
   615  		var imgListWithCVEFixed ImgListWithCVEFixed
   616  		err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
   617  		So(err, ShouldBeNil)
   618  		So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
   619  
   620  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-cve-test\"){Results{RepoName%20LastUpdated}}}")
   621  		So(resp, ShouldNotBeNil)
   622  		So(resp.StatusCode(), ShouldEqual, 200)
   623  
   624  		err = json.Unmarshal(resp.Body(), &imgListWithCVEFixed)
   625  		So(err, ShouldBeNil)
   626  		So(len(imgListWithCVEFixed.Images), ShouldEqual, 0)
   627  
   628  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-test\"){Results{RepoName%20LastUpdated}}}")
   629  		So(resp, ShouldNotBeNil)
   630  		So(resp.StatusCode(), ShouldEqual, 200)
   631  
   632  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"b/zot-squashfs-test:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   633  		So(resp, ShouldNotBeNil)
   634  		So(resp.StatusCode(), ShouldEqual, 200)
   635  
   636  		var cveSquashFSResult CveResult
   637  		err = json.Unmarshal(resp.Body(), &cveSquashFSResult)
   638  		So(err, ShouldBeNil)
   639  		So(len(cveSquashFSResult.ImgList.CVEResultForImage.CVEList), ShouldBeZeroValue)
   640  
   641  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noindex:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   642  		So(resp, ShouldNotBeNil)
   643  		So(resp.StatusCode(), ShouldEqual, 200)
   644  
   645  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noindex\"){Results{RepoName%20LastUpdated}}}")
   646  		So(resp, ShouldNotBeNil)
   647  		So(resp.StatusCode(), ShouldEqual, 200)
   648  
   649  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-index:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   650  		So(resp, ShouldNotBeNil)
   651  		So(resp.StatusCode(), ShouldEqual, 200)
   652  
   653  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-index\"){Results{RepoName%20LastUpdated}}}")
   654  		So(resp, ShouldNotBeNil)
   655  		So(resp.StatusCode(), ShouldEqual, 200)
   656  
   657  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-noblobs:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   658  		So(resp, ShouldNotBeNil)
   659  		So(resp.StatusCode(), ShouldEqual, 200)
   660  
   661  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-noblob\"){Results{RepoName%20LastUpdated}}}")
   662  		So(resp, ShouldNotBeNil)
   663  		So(resp.StatusCode(), ShouldEqual, 200)
   664  
   665  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-test\"){Results{RepoName%20LastUpdated}}}")
   666  		So(resp, ShouldNotBeNil)
   667  		So(resp.StatusCode(), ShouldEqual, 200)
   668  
   669  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-invalid-blob:commit-aaa7c6e7-squashfs\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   670  		So(resp, ShouldNotBeNil)
   671  		So(resp.StatusCode(), ShouldEqual, 200)
   672  
   673  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListWithCVEFixed(id:\"" + cveid + "\",image:\"zot-squashfs-invalid-blob\"){Results{RepoName%20LastUpdated}}}")
   674  		So(resp, ShouldNotBeNil)
   675  		So(resp.StatusCode(), ShouldEqual, 200)
   676  
   677  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-squashfs-test\"){Tag%20CVEList{Id%20Description%20Severity%20PackageList{Name%20InstalledVersion%20FixedVersion}}}}")
   678  		So(resp, ShouldNotBeNil)
   679  		So(resp.StatusCode(), ShouldEqual, 200)
   680  
   681  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"cntos\"){Tag%20CVEList{Id%20Description%20Severity}}}")
   682  		So(resp, ShouldNotBeNil)
   683  		So(resp.StatusCode(), ShouldEqual, 200)
   684  
   685  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-201-20482\"){Results{RepoName%20Tag}}}")
   686  		So(resp, ShouldNotBeNil)
   687  		So(resp.StatusCode(), ShouldEqual, 200)
   688  
   689  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test\"){Tag%20CVEList{Id%20Description}}}")
   690  		So(resp, ShouldNotBeNil)
   691  		So(resp.StatusCode(), ShouldEqual, 200)
   692  
   693  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Tag}}")
   694  		So(resp, ShouldNotBeNil)
   695  		So(resp.StatusCode(), ShouldEqual, 200)
   696  
   697  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description%20Severity}}}")
   698  		So(resp, ShouldNotBeNil)
   699  		So(resp.StatusCode(), ShouldEqual, 200)
   700  
   701  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description%20Severity}}}")
   702  		So(resp, ShouldNotBeNil)
   703  		So(resp.StatusCode(), ShouldEqual, 200)
   704  
   705  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Severity}}}")
   706  		So(resp, ShouldNotBeNil)
   707  		So(resp.StatusCode(), ShouldEqual, 200)
   708  
   709  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id%20Description}}}")
   710  		So(resp, ShouldNotBeNil)
   711  		So(resp.StatusCode(), ShouldEqual, 200)
   712  
   713  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Id}}}")
   714  		So(resp, ShouldNotBeNil)
   715  		So(resp.StatusCode(), ShouldEqual, 200)
   716  
   717  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){CVEList{Description}}}")
   718  		So(resp, ShouldNotBeNil)
   719  		So(resp.StatusCode(), ShouldEqual, 200)
   720  
   721  		// Testing Invalid Search URL
   722  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(image:\"zot-test:0.0.1\"){Ta%20CVEList{Id%20Description%20Severity}}}")
   723  		So(resp, ShouldNotBeNil)
   724  		So(resp.StatusCode(), ShouldEqual, 422)
   725  
   726  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(tet:\"CVE-2018-20482\"){Results{RepoName%20Tag}}}")
   727  		So(resp, ShouldNotBeNil)
   728  		So(resp.StatusCode(), ShouldEqual, 422)
   729  
   730  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageistForCVE(id:\"CVE-2018-20482\"){Results{RepoName%20Tag}}}")
   731  		So(resp, ShouldNotBeNil)
   732  		So(resp.StatusCode(), ShouldEqual, 422)
   733  
   734  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"CVE-2018-20482\"){ame%20Tags}}")
   735  		So(resp, ShouldNotBeNil)
   736  		So(resp.StatusCode(), ShouldEqual, 422)
   737  
   738  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={CVEListForImage(reo:\"zot-test:1.0.0\"){Tag%20CVEList{Id%20Description%20Severity}}}")
   739  		So(resp, ShouldNotBeNil)
   740  		So(resp.StatusCode(), ShouldEqual, 422)
   741  
   742  		resp, _ = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullSearchPrefix + "?query={ImageListForCVE(id:\"" + cveid + "\"){Results{RepoName%20Tag}}}")
   743  		So(resp, ShouldNotBeNil)
   744  		So(resp.StatusCode(), ShouldEqual, 200)
   745  	})
   746  }
   747  
   748  func TestCVEStruct(t *testing.T) { //nolint:gocyclo
   749  	Convey("Unit test the CVE struct", t, func() {
   750  		const repo1 = "repo1"
   751  		const repo2 = "repo2"
   752  		const repo3 = "repo3"
   753  		const repo4 = "repo4"
   754  		const repo5 = "repo5"
   755  		const repo6 = "repo6"
   756  		const repo7 = "repo7"
   757  		const repo100 = "repo100"
   758  		const repoMultiarch = "repoIndex"
   759  
   760  		params := boltdb.DBParameters{
   761  			RootDir: t.TempDir(),
   762  		}
   763  		boltDriver, err := boltdb.GetBoltDriver(params)
   764  		So(err, ShouldBeNil)
   765  
   766  		metaDB, err := boltdb.New(boltDriver, log.NewLogger("debug", ""))
   767  		So(err, ShouldBeNil)
   768  
   769  		// Create metadb data for scannable image with vulnerabilities
   770  		image11 := CreateImageWith().DefaultLayers().
   771  			ImageConfig(ispec.Image{Created: DateRef(2008, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   772  
   773  		err = metaDB.SetRepoReference(context.Background(), repo1, "0.1.0", image11.AsImageMeta())
   774  		So(err, ShouldBeNil)
   775  
   776  		image12 := CreateImageWith().DefaultLayers().
   777  			ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   778  
   779  		err = metaDB.SetRepoReference(context.Background(), repo1, "1.0.0", image12.AsImageMeta())
   780  		So(err, ShouldBeNil)
   781  
   782  		image13 := CreateImageWith().DefaultLayers().
   783  			ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   784  
   785  		err = metaDB.SetRepoReference(context.Background(), repo1, "1.1.0", image13.AsImageMeta())
   786  		So(err, ShouldBeNil)
   787  
   788  		image14 := CreateImageWith().DefaultLayers().
   789  			ImageConfig(ispec.Image{Created: DateRef(2011, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   790  
   791  		err = metaDB.SetRepoReference(context.Background(), repo1, "1.0.1", image14.AsImageMeta())
   792  		So(err, ShouldBeNil)
   793  
   794  		// Create metadb data for scannable image with no vulnerabilities
   795  		image61 := CreateImageWith().DefaultLayers().
   796  			ImageConfig(ispec.Image{Created: DateRef(2016, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   797  
   798  		err = metaDB.SetRepoReference(context.Background(), repo6, "1.0.0", image61.AsImageMeta())
   799  		So(err, ShouldBeNil)
   800  
   801  		// Create metadb data for image not supporting scanning
   802  		image21 := CreateImageWith().Layers([]Layer{{
   803  			MediaType: ispec.MediaTypeImageLayerNonDistributableGzip, //nolint:staticcheck
   804  			Blob:      []byte{10, 10, 10},
   805  			Digest:    godigest.FromBytes([]byte{10, 10, 10}),
   806  		}}).ImageConfig(ispec.Image{Created: DateRef(2009, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   807  
   808  		err = metaDB.SetRepoReference(context.Background(), repo2, "1.0.0", image21.AsImageMeta())
   809  		So(err, ShouldBeNil)
   810  
   811  		// Create metadb data for invalid images/negative tests
   812  		image := CreateRandomImage()
   813  		err = metaDB.SetRepoReference(context.Background(), repo3, "invalid-manifest", image.AsImageMeta())
   814  		So(err, ShouldBeNil)
   815  
   816  		image41 := CreateImageWith().DefaultLayers().
   817  			CustomConfigBlob([]byte("invalid config blob"), ispec.MediaTypeImageConfig).Build()
   818  
   819  		err = metaDB.SetRepoReference(context.Background(), repo4, "invalid-config", image41.AsImageMeta())
   820  		So(err, ShouldBeNil)
   821  
   822  		digest51 := godigest.FromString("abc8")
   823  		randomImgData := CreateRandomImage().AsImageMeta()
   824  		randomImgData.Digest = digest51
   825  		randomImgData.Manifests[0].Digest = digest51
   826  		err = metaDB.SetRepoReference(context.Background(), repo5, "nonexitent-manifest", randomImgData)
   827  		So(err, ShouldBeNil)
   828  
   829  		// Create metadb data for scannable image which errors during scan
   830  		image71 := CreateImageWith().DefaultLayers().
   831  			ImageConfig(ispec.Image{Created: DateRef(2000, 1, 1, 12, 0, 0, 0, time.UTC)}).Build()
   832  
   833  		err = metaDB.SetRepoReference(context.Background(), repo7, "1.0.0", image71.AsImageMeta())
   834  		So(err, ShouldBeNil)
   835  
   836  		// create multiarch image with vulnerabilities
   837  		multiarchImage := CreateRandomMultiarch()
   838  
   839  		err = metaDB.SetRepoReference(context.Background(), repoMultiarch, multiarchImage.Images[0].DigestStr(),
   840  			multiarchImage.Images[0].AsImageMeta())
   841  		So(err, ShouldBeNil)
   842  
   843  		err = metaDB.SetRepoReference(context.Background(), repoMultiarch, multiarchImage.Images[1].DigestStr(),
   844  			multiarchImage.Images[1].AsImageMeta())
   845  		So(err, ShouldBeNil)
   846  
   847  		err = metaDB.SetRepoReference(context.Background(), repoMultiarch, multiarchImage.Images[2].DigestStr(),
   848  			multiarchImage.Images[2].AsImageMeta())
   849  		So(err, ShouldBeNil)
   850  
   851  		err = metaDB.SetRepoReference(context.Background(), repoMultiarch, "tagIndex", multiarchImage.AsImageMeta())
   852  		So(err, ShouldBeNil)
   853  
   854  		err = metaDB.SetRepoMeta("repo-with-bad-tag-digest", mTypes.RepoMeta{
   855  			Name: "repo-with-bad-tag-digest",
   856  			Tags: map[string]mTypes.Descriptor{
   857  				"tag": {MediaType: ispec.MediaTypeImageManifest, Digest: godigest.FromString("1").String()},
   858  			},
   859  		})
   860  		So(err, ShouldBeNil)
   861  		// Keep a record of all the image references / digest pairings
   862  		// This is normally done in MetaDB, but we want to verify
   863  		// the whole flow, including MetaDB
   864  		imageMap := map[string]string{}
   865  
   866  		image11Digest := image11.ManifestDescriptor.Digest.String()
   867  		image11Media := image11.ManifestDescriptor.MediaType
   868  		image11Name := repo1 + ":0.1.0"
   869  		imageMap[image11Name] = image11Digest
   870  		image12Digest := image12.ManifestDescriptor.Digest.String()
   871  		image12Media := image12.ManifestDescriptor.MediaType
   872  		image12Name := repo1 + ":1.0.0"
   873  		imageMap[image12Name] = image12Digest
   874  		image13Digest := image13.ManifestDescriptor.Digest.String()
   875  		image13Media := image13.ManifestDescriptor.MediaType
   876  		image13Name := repo1 + ":1.1.0"
   877  		imageMap[image13Name] = image13Digest
   878  		image14Digest := image14.ManifestDescriptor.Digest.String()
   879  		image14Media := image14.ManifestDescriptor.MediaType
   880  		image14Name := repo1 + ":1.0.1"
   881  		imageMap[image14Name] = image14Digest
   882  		image21Digest := image21.ManifestDescriptor.Digest.String()
   883  		image21Media := image21.ManifestDescriptor.MediaType
   884  		image21Name := repo2 + ":1.0.0"
   885  		imageMap[image21Name] = image21Digest
   886  		image61Digest := image61.ManifestDescriptor.Digest.String()
   887  		image61Media := image61.ManifestDescriptor.MediaType
   888  		image61Name := repo6 + ":1.0.0"
   889  		imageMap[image61Name] = image61Digest
   890  		image71Digest := image71.ManifestDescriptor.Digest.String()
   891  		image71Media := image71.ManifestDescriptor.MediaType
   892  		image71Name := repo7 + ":1.0.0"
   893  		imageMap[image71Name] = image71Digest
   894  		indexDigest := multiarchImage.IndexDescriptor.Digest.String()
   895  		indexMedia := multiarchImage.IndexDescriptor.MediaType
   896  		indexName := repoMultiarch + ":tagIndex"
   897  		imageMap[indexName] = indexDigest
   898  		indexM1Digest := multiarchImage.Images[0].ManifestDescriptor.Digest.String()
   899  		indexM1Name := "repoIndex@" + indexM1Digest
   900  		imageMap[indexM1Name] = indexM1Digest
   901  		indexM2Digest := multiarchImage.Images[1].ManifestDescriptor.Digest.String()
   902  		indexM2Name := "repoIndex@" + indexM2Digest
   903  		imageMap[indexM2Name] = indexM2Digest
   904  		indexM3Digest := multiarchImage.Images[2].ManifestDescriptor.Digest.String()
   905  		indexM3Name := "repoIndex@" + indexM3Digest
   906  		imageMap[indexM3Name] = indexM3Digest
   907  
   908  		log := log.NewLogger("debug", "")
   909  
   910  		// Initialize a test CVE cache
   911  		cache := cvecache.NewCveCache(100, log)
   912  
   913  		// MetaDB loaded with initial data, now mock the scanner
   914  		// Setup test CVE data in mock scanner
   915  		scanner := mocks.CveScannerMock{
   916  			ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   917  				result := cache.Get(image)
   918  				// Will not match sending the repo:tag as a parameter, but we don't care
   919  				if result != nil {
   920  					return result, nil
   921  				}
   922  
   923  				repo, ref, isTag := zcommon.GetImageDirAndReference(image)
   924  				if isTag {
   925  					foundRef, ok := imageMap[image]
   926  					if !ok {
   927  						return nil, ErrBadTest
   928  					}
   929  					ref = foundRef
   930  				}
   931  
   932  				defer func() {
   933  					t.Logf("ScanImageFn cached for image %s digest %s: %v", image, ref, cache.Get(ref))
   934  				}()
   935  
   936  				// Images in chronological order
   937  				if repo == repo1 && ref == image11Digest {
   938  					result := map[string]cvemodel.CVE{
   939  						"CVE1": {
   940  							ID:          "CVE1",
   941  							Severity:    "MEDIUM",
   942  							Title:       "Title CVE1",
   943  							Description: "Description CVE1",
   944  						},
   945  					}
   946  
   947  					cache.Add(ref, result)
   948  
   949  					return result, nil
   950  				}
   951  
   952  				if repo == repo1 && zcommon.Contains([]string{image12Digest, image21Digest}, ref) {
   953  					result := map[string]cvemodel.CVE{
   954  						"CVE1": {
   955  							ID:          "CVE1",
   956  							Severity:    "MEDIUM",
   957  							Title:       "Title CVE1",
   958  							Description: "Description CVE1",
   959  						},
   960  						"CVE2": {
   961  							ID:          "CVE2",
   962  							Severity:    "HIGH",
   963  							Title:       "Title CVE2",
   964  							Description: "Description CVE2",
   965  						},
   966  						"CVE3": {
   967  							ID:          "CVE3",
   968  							Severity:    "LOW",
   969  							Title:       "Title CVE3",
   970  							Description: "Description CVE3",
   971  						},
   972  					}
   973  
   974  					cache.Add(ref, result)
   975  
   976  					return result, nil
   977  				}
   978  
   979  				if repo == repo1 && ref == image13Digest {
   980  					result := map[string]cvemodel.CVE{
   981  						"CVE3": {
   982  							ID:          "CVE3",
   983  							Severity:    "LOW",
   984  							Title:       "Title CVE3",
   985  							Description: "Description CVE3",
   986  						},
   987  					}
   988  
   989  					cache.Add(ref, result)
   990  
   991  					return result, nil
   992  				}
   993  
   994  				// As a minor release on 1.0.0 banch
   995  				// does not include all fixes published in 1.1.0
   996  				if repo == repo1 && ref == image14Digest {
   997  					result := map[string]cvemodel.CVE{
   998  						"CVE1": {
   999  							ID:          "CVE1",
  1000  							Severity:    "MEDIUM",
  1001  							Title:       "Title CVE1",
  1002  							Description: "Description CVE1",
  1003  						},
  1004  						"CVE3": {
  1005  							ID:          "CVE3",
  1006  							Severity:    "LOW",
  1007  							Title:       "Title CVE3",
  1008  							Description: "Description CVE3",
  1009  						},
  1010  					}
  1011  
  1012  					cache.Add(ref, result)
  1013  
  1014  					return result, nil
  1015  				}
  1016  
  1017  				// Unexpected error while scanning
  1018  				if repo == repo7 {
  1019  					return map[string]cvemodel.CVE{}, ErrFailedScan
  1020  				}
  1021  
  1022  				if (repo == repoMultiarch && ref == indexDigest) ||
  1023  					(repo == repoMultiarch && ref == indexM1Digest) {
  1024  					result := map[string]cvemodel.CVE{
  1025  						"CVE1": {
  1026  							ID:          "CVE1",
  1027  							Severity:    "MEDIUM",
  1028  							Title:       "Title CVE1",
  1029  							Description: "Description CVE1",
  1030  						},
  1031  					}
  1032  
  1033  					// Simulate scanning an index results in scanning its manifests
  1034  					if ref == indexDigest {
  1035  						cache.Add(indexM1Digest, result)
  1036  						cache.Add(indexM2Digest, map[string]cvemodel.CVE{})
  1037  						cache.Add(indexM3Digest, map[string]cvemodel.CVE{})
  1038  					}
  1039  
  1040  					cache.Add(ref, result)
  1041  
  1042  					return result, nil
  1043  				}
  1044  
  1045  				// By default the image has no vulnerabilities
  1046  				result = map[string]cvemodel.CVE{}
  1047  				cache.Add(ref, result)
  1048  
  1049  				return result, nil
  1050  			},
  1051  			IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
  1052  				if repo == repoMultiarch {
  1053  					return true, nil
  1054  				}
  1055  
  1056  				// Almost same logic compared to actual Trivy specific implementation
  1057  				imageDir, inputTag := repo, reference
  1058  
  1059  				repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir)
  1060  				if err != nil {
  1061  					return false, err
  1062  				}
  1063  
  1064  				manifestDigestStr := reference
  1065  
  1066  				if zcommon.IsTag(reference) {
  1067  					var ok bool
  1068  
  1069  					descriptor, ok := repoMeta.Tags[inputTag]
  1070  					if !ok {
  1071  						return false, zerr.ErrTagMetaNotFound
  1072  					}
  1073  
  1074  					manifestDigestStr = descriptor.Digest
  1075  				}
  1076  
  1077  				manifestDigest, err := godigest.Parse(manifestDigestStr)
  1078  				if err != nil {
  1079  					return false, err
  1080  				}
  1081  
  1082  				manifestData, err := metaDB.GetImageMeta(manifestDigest)
  1083  				if err != nil {
  1084  					return false, err
  1085  				}
  1086  
  1087  				for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
  1088  					switch imageLayer.MediaType {
  1089  					case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
  1090  
  1091  						return true, nil
  1092  					default:
  1093  
  1094  						return false, zerr.ErrScanNotSupported
  1095  					}
  1096  				}
  1097  
  1098  				return false, nil
  1099  			},
  1100  			IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
  1101  				if repo == repo2 && digest == image21Digest {
  1102  					return false, zerr.ErrScanNotSupported
  1103  				}
  1104  				if repo == repo100 {
  1105  					return false, zerr.ErrRepoMetaNotFound
  1106  				}
  1107  
  1108  				return true, nil
  1109  			},
  1110  			IsResultCachedFn: func(digest string) bool {
  1111  				t.Logf("IsResultCachedFn found in cache for digest %s: %v", digest, cache.Get(digest))
  1112  
  1113  				return cache.Contains(digest)
  1114  			},
  1115  			GetCachedResultFn: func(digest string) map[string]cvemodel.CVE {
  1116  				t.Logf("GetCachedResultFn found in cache for digest %s: %v", digest, cache.Get(digest))
  1117  
  1118  				return cache.Get(digest)
  1119  			},
  1120  		}
  1121  
  1122  		cveInfo := cveinfo.BaseCveInfo{Log: log, Scanner: scanner, MetaDB: metaDB}
  1123  
  1124  		t.Log("\nTest GetCVEListForImage\n")
  1125  
  1126  		pageInput := cvemodel.PageInput{
  1127  			SortBy: cveinfo.SeverityDsc,
  1128  		}
  1129  
  1130  		ctx := context.Background()
  1131  
  1132  		// Image is found
  1133  		cveList, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", pageInput)
  1134  		So(err, ShouldBeNil)
  1135  		So(len(cveList), ShouldEqual, 1)
  1136  		So(cveList[0].ID, ShouldEqual, "CVE1")
  1137  		So(pageInfo.ItemCount, ShouldEqual, 1)
  1138  		So(pageInfo.TotalCount, ShouldEqual, 1)
  1139  
  1140  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.0", "", pageInput)
  1141  		So(err, ShouldBeNil)
  1142  		So(len(cveList), ShouldEqual, 3)
  1143  		So(cveList[0].ID, ShouldEqual, "CVE2")
  1144  		So(cveList[1].ID, ShouldEqual, "CVE1")
  1145  		So(cveList[2].ID, ShouldEqual, "CVE3")
  1146  		So(pageInfo.ItemCount, ShouldEqual, 3)
  1147  		So(pageInfo.TotalCount, ShouldEqual, 3)
  1148  
  1149  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.1", "", pageInput)
  1150  		So(err, ShouldBeNil)
  1151  		So(len(cveList), ShouldEqual, 2)
  1152  		So(cveList[0].ID, ShouldEqual, "CVE1")
  1153  		So(cveList[1].ID, ShouldEqual, "CVE3")
  1154  		So(pageInfo.ItemCount, ShouldEqual, 2)
  1155  		So(pageInfo.TotalCount, ShouldEqual, 2)
  1156  
  1157  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.1.0", "", pageInput)
  1158  		So(err, ShouldBeNil)
  1159  		So(len(cveList), ShouldEqual, 1)
  1160  		So(cveList[0].ID, ShouldEqual, "CVE3")
  1161  		So(pageInfo.ItemCount, ShouldEqual, 1)
  1162  		So(pageInfo.TotalCount, ShouldEqual, 1)
  1163  
  1164  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo6, "1.0.0", "", pageInput)
  1165  		So(err, ShouldBeNil)
  1166  		So(len(cveList), ShouldEqual, 0)
  1167  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1168  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1169  
  1170  		// Image is multiarch
  1171  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", pageInput)
  1172  		So(err, ShouldBeNil)
  1173  		So(len(cveList), ShouldEqual, 1)
  1174  		So(cveList[0].ID, ShouldEqual, "CVE1")
  1175  		So(pageInfo.ItemCount, ShouldEqual, 1)
  1176  		So(pageInfo.TotalCount, ShouldEqual, 1)
  1177  
  1178  		// Image is not scannable
  1179  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo2, "1.0.0", "", pageInput)
  1180  		So(err, ShouldEqual, zerr.ErrScanNotSupported)
  1181  		So(len(cveList), ShouldEqual, 0)
  1182  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1183  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1184  
  1185  		// Tag is not found
  1186  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo3, "1.0.0", "", pageInput)
  1187  		So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
  1188  		So(len(cveList), ShouldEqual, 0)
  1189  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1190  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1191  
  1192  		// Scan failed
  1193  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo7, "1.0.0", "", pageInput)
  1194  		So(err, ShouldEqual, ErrFailedScan)
  1195  		So(len(cveList), ShouldEqual, 0)
  1196  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1197  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1198  
  1199  		// Tag is not found
  1200  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo-with-bad-tag-digest", "tag", "", pageInput)
  1201  		So(err, ShouldEqual, zerr.ErrImageMetaNotFound)
  1202  		So(len(cveList), ShouldEqual, 0)
  1203  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1204  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1205  
  1206  		// Repo is not found
  1207  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo100, "1.0.0", "", pageInput)
  1208  		So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
  1209  		So(len(cveList), ShouldEqual, 0)
  1210  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1211  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1212  
  1213  		// By this point the cache should already be pupulated by previous function calls
  1214  		t.Log("\nTest GetCVESummaryForImage\n")
  1215  
  1216  		// Image is found
  1217  		cveSummary, err := cveInfo.GetCVESummaryForImageMedia(ctx, repo1, image11Digest, image11Media)
  1218  		So(err, ShouldBeNil)
  1219  		So(cveSummary.Count, ShouldEqual, 1)
  1220  		So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
  1221  
  1222  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo1, image12Digest, image12Media)
  1223  		So(err, ShouldBeNil)
  1224  		So(cveSummary.Count, ShouldEqual, 3)
  1225  		So(cveSummary.MaxSeverity, ShouldEqual, "HIGH")
  1226  
  1227  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo1, image14Digest, image14Media)
  1228  		So(err, ShouldBeNil)
  1229  		So(cveSummary.Count, ShouldEqual, 2)
  1230  		So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
  1231  
  1232  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo1, image13Digest, image13Media)
  1233  		So(err, ShouldBeNil)
  1234  		So(cveSummary.Count, ShouldEqual, 1)
  1235  		So(cveSummary.MaxSeverity, ShouldEqual, "LOW")
  1236  
  1237  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo6, image61Digest, image61Media)
  1238  		So(err, ShouldBeNil)
  1239  		So(cveSummary.Count, ShouldEqual, 0)
  1240  		So(cveSummary.MaxSeverity, ShouldEqual, "NONE")
  1241  
  1242  		// Image is multiarch
  1243  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repoMultiarch, indexDigest, indexMedia)
  1244  		So(err, ShouldBeNil)
  1245  		So(cveSummary.Count, ShouldEqual, 1)
  1246  		So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
  1247  
  1248  		// Image is not scannable
  1249  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo2, image21Digest, image21Media)
  1250  		So(err, ShouldEqual, zerr.ErrScanNotSupported)
  1251  		So(cveSummary.Count, ShouldEqual, 0)
  1252  		So(cveSummary.MaxSeverity, ShouldEqual, "")
  1253  
  1254  		// Scan failed
  1255  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo5, image71Digest, image71Media)
  1256  		So(err, ShouldBeNil)
  1257  		So(cveSummary.Count, ShouldEqual, 0)
  1258  		So(cveSummary.MaxSeverity, ShouldEqual, "")
  1259  
  1260  		// Repo is not found
  1261  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo100,
  1262  			godigest.FromString("missing_digest").String(), ispec.MediaTypeImageManifest)
  1263  		So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
  1264  		So(cveSummary.Count, ShouldEqual, 0)
  1265  		So(cveSummary.MaxSeverity, ShouldEqual, "")
  1266  
  1267  		t.Log("\nTest GetImageListWithCVEFixed\n")
  1268  
  1269  		// Image is found
  1270  		tagList, err := cveInfo.GetImageListWithCVEFixed(ctx, repo1, "CVE1")
  1271  		So(err, ShouldBeNil)
  1272  		So(len(tagList), ShouldEqual, 1)
  1273  		So(tagList[0].Tag, ShouldEqual, "1.1.0")
  1274  
  1275  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo1, "CVE2")
  1276  		So(err, ShouldBeNil)
  1277  		So(len(tagList), ShouldEqual, 2)
  1278  		expectedTags := []string{"1.0.1", "1.1.0"}
  1279  		So(expectedTags, ShouldContain, tagList[0].Tag)
  1280  		So(expectedTags, ShouldContain, tagList[1].Tag)
  1281  
  1282  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo1, "CVE3")
  1283  		So(err, ShouldBeNil)
  1284  		// CVE3 is not present in 0.1.0, but that is older than all other
  1285  		// images where it is present. The rest of the images explicitly  have it.
  1286  		// This means we consider it not fixed in any image.
  1287  		So(len(tagList), ShouldEqual, 0)
  1288  
  1289  		// Image doesn't have any CVEs in the first place
  1290  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo6, "CVE1")
  1291  		So(err, ShouldBeNil)
  1292  		So(len(tagList), ShouldEqual, 1)
  1293  		So(tagList[0].Tag, ShouldEqual, "1.0.0")
  1294  
  1295  		// Image is not scannable
  1296  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo2, "CVE100")
  1297  		// CVE is not considered fixed as scan is not possible
  1298  		// but do not return an error
  1299  		So(err, ShouldBeNil)
  1300  		So(len(tagList), ShouldEqual, 0)
  1301  
  1302  		// Repo is not found, there could potentially be unaffected tags in the repo
  1303  		// but we can't access their data
  1304  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo100, "CVE100")
  1305  		So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
  1306  		So(len(tagList), ShouldEqual, 0)
  1307  
  1308  		t.Log("\nTest GetImageListForCVE\n")
  1309  
  1310  		// Image is found
  1311  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo1, "CVE1")
  1312  		So(err, ShouldBeNil)
  1313  		So(len(tagList), ShouldEqual, 3)
  1314  		expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
  1315  		So(expectedTags, ShouldContain, tagList[0].Tag)
  1316  		So(expectedTags, ShouldContain, tagList[1].Tag)
  1317  		So(expectedTags, ShouldContain, tagList[2].Tag)
  1318  
  1319  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo1, "CVE2")
  1320  		So(err, ShouldBeNil)
  1321  		So(len(tagList), ShouldEqual, 1)
  1322  		So(tagList[0].Tag, ShouldEqual, "1.0.0")
  1323  
  1324  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo1, "CVE3")
  1325  		So(err, ShouldBeNil)
  1326  		So(len(tagList), ShouldEqual, 3)
  1327  		expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
  1328  		So(expectedTags, ShouldContain, tagList[0].Tag)
  1329  		So(expectedTags, ShouldContain, tagList[1].Tag)
  1330  		So(expectedTags, ShouldContain, tagList[2].Tag)
  1331  
  1332  		// Image/repo doesn't have the CVE at all
  1333  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo6, "CVE1")
  1334  		So(err, ShouldBeNil)
  1335  		So(len(tagList), ShouldEqual, 0)
  1336  
  1337  		// Image is not scannable
  1338  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo2, "CVE100")
  1339  		// Image is not considered affected with CVE as scan is not possible
  1340  		// but do not return an error
  1341  		So(err, ShouldBeNil)
  1342  		So(len(tagList), ShouldEqual, 0)
  1343  
  1344  		// Tag is not found, but we should not error
  1345  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo3, "CVE101")
  1346  		So(err, ShouldBeNil)
  1347  		So(len(tagList), ShouldEqual, 0)
  1348  
  1349  		// Repo is not found, assume it is affected by the CVE
  1350  		// But we don't have enough of its data to actually return it
  1351  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo100, "CVE100")
  1352  		So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
  1353  		So(len(tagList), ShouldEqual, 0)
  1354  
  1355  		t.Log("\nTest errors while scanning\n")
  1356  
  1357  		faultyScanner := mocks.CveScannerMock{
  1358  			ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
  1359  				// Could be any type of error, let's reuse this one
  1360  				return nil, zerr.ErrScanNotSupported
  1361  			},
  1362  		}
  1363  
  1364  		cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: faultyScanner, MetaDB: metaDB}
  1365  
  1366  		cveSummary, err = cveInfo.GetCVESummaryForImageMedia(ctx, repo1, image11Digest, image11Media)
  1367  		So(err, ShouldBeNil)
  1368  		So(cveSummary.Count, ShouldEqual, 0)
  1369  		So(cveSummary.MaxSeverity, ShouldEqual, "")
  1370  
  1371  		cveList, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", pageInput)
  1372  		So(err, ShouldNotBeNil)
  1373  		So(cveList, ShouldBeEmpty)
  1374  		So(pageInfo.ItemCount, ShouldEqual, 0)
  1375  		So(pageInfo.TotalCount, ShouldEqual, 0)
  1376  
  1377  		tagList, err = cveInfo.GetImageListWithCVEFixed(ctx, repo1, "CVE1")
  1378  		// CVE is not considered fixed as scan is not possible
  1379  		// but do not return an error
  1380  		So(err, ShouldBeNil)
  1381  		So(len(tagList), ShouldEqual, 0)
  1382  
  1383  		tagList, err = cveInfo.GetImageListForCVE(ctx, repo1, "CVE1")
  1384  		// Image is not considered affected with CVE as scan is not possible
  1385  		// but do not return an error
  1386  		So(err, ShouldBeNil)
  1387  		So(len(tagList), ShouldEqual, 0)
  1388  
  1389  		cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
  1390  			IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
  1391  				return false, nil
  1392  			},
  1393  		}, MetaDB: metaDB}
  1394  
  1395  		_, err = cveInfo.GetImageListForCVE(ctx, repoMultiarch, "CVE1")
  1396  		So(err, ShouldBeNil)
  1397  
  1398  		cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
  1399  			IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
  1400  				return true, nil
  1401  			},
  1402  			ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
  1403  				return nil, zerr.ErrTypeAssertionFailed
  1404  			},
  1405  		}, MetaDB: metaDB}
  1406  
  1407  		_, err = cveInfo.GetImageListForCVE(ctx, repoMultiarch, "CVE1")
  1408  		So(err, ShouldBeNil)
  1409  	})
  1410  }
  1411  
  1412  func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
  1413  	tags := make([]cvemodel.TagInfo, 0)
  1414  
  1415  	firstTag := cvemodel.TagInfo{
  1416  		Tag: "1.0.0",
  1417  		Descriptor: cvemodel.Descriptor{
  1418  			Digest:    "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
  1419  			MediaType: ispec.MediaTypeImageManifest,
  1420  		},
  1421  		Timestamp: time.Now(),
  1422  	}
  1423  	secondTag := cvemodel.TagInfo{
  1424  		Tag: "1.0.1",
  1425  		Descriptor: cvemodel.Descriptor{
  1426  			Digest:    "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
  1427  			MediaType: ispec.MediaTypeImageManifest,
  1428  		},
  1429  		Timestamp: time.Now(),
  1430  	}
  1431  	thirdTag := cvemodel.TagInfo{
  1432  		Tag: "1.0.2",
  1433  		Descriptor: cvemodel.Descriptor{
  1434  			Digest:    "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
  1435  			MediaType: ispec.MediaTypeImageManifest,
  1436  		},
  1437  		Timestamp: time.Now(),
  1438  	}
  1439  	fourthTag := cvemodel.TagInfo{
  1440  		Tag: "1.0.3",
  1441  		Descriptor: cvemodel.Descriptor{
  1442  			Digest:    "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
  1443  			MediaType: ispec.MediaTypeImageManifest,
  1444  		},
  1445  		Timestamp: time.Now(),
  1446  	}
  1447  
  1448  	tags = append(tags, firstTag, secondTag, thirdTag, fourthTag)
  1449  
  1450  	vulnerableTags := make([]cvemodel.TagInfo, 0)
  1451  	vulnerableTags = append(vulnerableTags, secondTag)
  1452  
  1453  	return tags, vulnerableTags
  1454  }
  1455  
  1456  func TestFixedTags(t *testing.T) {
  1457  	Convey("Test fixed tags", t, func() {
  1458  		allTags, vulnerableTags := getTags()
  1459  
  1460  		fixedTags := cveinfo.GetFixedTags(allTags, vulnerableTags)
  1461  		So(len(fixedTags), ShouldEqual, 2)
  1462  
  1463  		fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
  1464  			Tag: "taginfo",
  1465  			Descriptor: cvemodel.Descriptor{
  1466  				MediaType: ispec.MediaTypeImageManifest,
  1467  				Digest:    "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
  1468  			},
  1469  			Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
  1470  		}))
  1471  		So(len(fixedTags), ShouldEqual, 3)
  1472  	})
  1473  }
  1474  
  1475  func TestFixedTagsWithIndex(t *testing.T) {
  1476  	Convey("Test fixed tags", t, func() {
  1477  		tempDir := t.TempDir()
  1478  		port := test.GetFreePort()
  1479  		baseURL := test.GetBaseURL(port)
  1480  		conf := config.New()
  1481  		conf.HTTP.Port = port
  1482  		defaultVal := true
  1483  		conf.Storage.RootDirectory = tempDir
  1484  		conf.Extensions = &extconf.ExtensionConfig{
  1485  			Search: &extconf.SearchConfig{
  1486  				BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
  1487  				CVE: &extconf.CVEConfig{
  1488  					UpdateInterval: 24 * time.Hour,
  1489  					Trivy: &extconf.TrivyConfig{
  1490  						DBRepository: "ghcr.io/project-zot/trivy-db",
  1491  					},
  1492  				},
  1493  			},
  1494  		}
  1495  
  1496  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
  1497  		So(err, ShouldBeNil)
  1498  
  1499  		logPath := logFile.Name()
  1500  		defer os.Remove(logPath)
  1501  
  1502  		writers := io.MultiWriter(os.Stdout, logFile)
  1503  
  1504  		ctlr := api.NewController(conf)
  1505  		ctlr.Log.Logger = ctlr.Log.Output(writers)
  1506  
  1507  		cm := test.NewControllerManager(ctlr)
  1508  		cm.StartAndWait(port)
  1509  		defer cm.StopServer()
  1510  		// push index with 2 manifests: one with vulns and one without
  1511  		vulnManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
  1512  		vulnManifest, err := deprecated.GetVulnImageWithConfig(ispec.Image{ //nolint:staticcheck
  1513  			Created:  &vulnManifestCreated,
  1514  			Platform: ispec.Platform{OS: "linux", Architecture: "amd64"},
  1515  		})
  1516  		So(err, ShouldBeNil)
  1517  
  1518  		fixedManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
  1519  		fixedManifest, err := deprecated.GetImageWithConfig(ispec.Image{ //nolint:staticcheck
  1520  			Created:  &fixedManifestCreated,
  1521  			Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
  1522  		})
  1523  		So(err, ShouldBeNil)
  1524  		fixedDigest := fixedManifest.Digest()
  1525  
  1526  		multiArch := deprecated.GetMultiarchImageForImages([]Image{fixedManifest, //nolint:staticcheck
  1527  			vulnManifest})
  1528  
  1529  		err = UploadMultiarchImage(multiArch, baseURL, "repo", "multi-arch-tag")
  1530  		So(err, ShouldBeNil)
  1531  
  1532  		// oldest vulnerability
  1533  		simpleVulnCreated := time.Date(2005, 1, 1, 1, 1, 1, 1, time.UTC)
  1534  		simpleVulnImg, err := deprecated.GetVulnImageWithConfig(ispec.Image{ //nolint:staticcheck
  1535  			Created:  &simpleVulnCreated,
  1536  			Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
  1537  		})
  1538  		So(err, ShouldBeNil)
  1539  
  1540  		err = UploadImage(simpleVulnImg, baseURL, "repo", "vuln-img")
  1541  		So(err, ShouldBeNil)
  1542  
  1543  		// Wait for trivy db to download
  1544  		found, err := test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 180*time.Second)
  1545  		So(err, ShouldBeNil)
  1546  		So(found, ShouldBeTrue)
  1547  
  1548  		cveInfo := cveinfo.NewCVEInfo(ctlr.CveScanner, ctlr.MetaDB, ctlr.Log)
  1549  
  1550  		tagsInfo, err := cveInfo.GetImageListWithCVEFixed(context.Background(), "repo", Vulnerability1ID)
  1551  		So(err, ShouldBeNil)
  1552  		So(len(tagsInfo), ShouldEqual, 1)
  1553  		So(len(tagsInfo[0].Manifests), ShouldEqual, 1)
  1554  		So(tagsInfo[0].Manifests[0].Digest, ShouldResemble, fixedDigest)
  1555  
  1556  		const query = `
  1557  		{
  1558  			ImageListWithCVEFixed(id:"%s",image:"%s"){
  1559  				Results{
  1560  					RepoName
  1561  					Manifests {Digest}
  1562  				}
  1563  			}
  1564  		}`
  1565  
  1566  		resp, _ := resty.R().Get(baseURL + constants.FullSearchPrefix + "?query=" +
  1567  			url.QueryEscape(fmt.Sprintf(query, Vulnerability1ID, "repo")))
  1568  		So(resp, ShouldNotBeNil)
  1569  		So(resp.StatusCode(), ShouldEqual, 200)
  1570  
  1571  		responseStruct := &zcommon.ImageListWithCVEFixedResponse{}
  1572  		err = json.Unmarshal(resp.Body(), &responseStruct)
  1573  		So(err, ShouldBeNil)
  1574  		So(len(responseStruct.Results), ShouldEqual, 1)
  1575  		So(len(responseStruct.Results[0].Manifests), ShouldEqual, 1)
  1576  		fixedManifestResp := responseStruct.Results[0].Manifests[0]
  1577  		So(fixedManifestResp.Digest, ShouldResemble, fixedDigest.String())
  1578  	})
  1579  }
  1580  
  1581  func TestGetCVESummaryForImageMediaErrors(t *testing.T) {
  1582  	Convey("Errors", t, func() {
  1583  		storeController := storage.StoreController{}
  1584  		storeController.DefaultStore = mocks.MockedImageStore{}
  1585  
  1586  		metaDB := mocks.MetaDBMock{}
  1587  		log := log.NewLogger("debug", "")
  1588  
  1589  		Convey("IsImageMediaScannable returns false", func() {
  1590  			scanner := mocks.CveScannerMock{
  1591  				IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
  1592  					return false, zerr.ErrScanNotSupported
  1593  				},
  1594  			}
  1595  
  1596  			cveInfo := cveinfo.NewCVEInfo(scanner, metaDB, log)
  1597  
  1598  			_, err := cveInfo.GetCVESummaryForImageMedia(context.Background(), "repo", "digest", ispec.MediaTypeImageManifest)
  1599  			So(err, ShouldNotBeNil)
  1600  		})
  1601  	})
  1602  }