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