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