zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/search/cve/pagination_test.go (about) 1 //go:build search 2 // +build search 3 4 package cveinfo_test 5 6 import ( 7 "context" 8 "fmt" 9 "sort" 10 "testing" 11 "time" 12 13 ispec "github.com/opencontainers/image-spec/specs-go/v1" 14 . "github.com/smartystreets/goconvey/convey" 15 16 cveinfo "zotregistry.dev/zot/pkg/extensions/search/cve" 17 cvemodel "zotregistry.dev/zot/pkg/extensions/search/cve/model" 18 "zotregistry.dev/zot/pkg/log" 19 "zotregistry.dev/zot/pkg/meta/boltdb" 20 . "zotregistry.dev/zot/pkg/test/image-utils" 21 "zotregistry.dev/zot/pkg/test/mocks" 22 ) 23 24 func TestCVEPagination(t *testing.T) { 25 Convey("CVE Pagination", t, func() { 26 params := boltdb.DBParameters{ 27 RootDir: t.TempDir(), 28 } 29 boltDriver, err := boltdb.GetBoltDriver(params) 30 So(err, ShouldBeNil) 31 32 metaDB, err := boltdb.New(boltDriver, log.NewLogger("debug", "")) 33 So(err, ShouldBeNil) 34 35 // Create metadb data for scannable image with vulnerabilities 36 timeStamp11 := time.Date(2008, 1, 1, 12, 0, 0, 0, time.UTC) 37 38 image := CreateImageWith(). 39 Layers([]Layer{{ 40 MediaType: ispec.MediaTypeImageLayerGzip, 41 Digest: ispec.DescriptorEmptyJSON.Digest, 42 Blob: ispec.DescriptorEmptyJSON.Data, 43 }}).ImageConfig(ispec.Image{Created: &timeStamp11}).Build() 44 45 err = metaDB.SetRepoReference(context.Background(), "repo1", "0.1.0", image.AsImageMeta()) 46 So(err, ShouldBeNil) 47 48 timeStamp12 := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC) 49 50 image2 := CreateImageWith(). 51 Layers([]Layer{{ 52 MediaType: ispec.MediaTypeImageLayerGzip, 53 Digest: ispec.DescriptorEmptyJSON.Digest, 54 Blob: ispec.DescriptorEmptyJSON.Data, 55 }}).ImageConfig(ispec.Image{Created: &timeStamp12}).Build() 56 57 err = metaDB.SetRepoReference(context.Background(), "repo1", "1.0.0", image2.AsImageMeta()) 58 So(err, ShouldBeNil) 59 60 // MetaDB loaded with initial data, mock the scanner 61 severityToInt := map[string]int{ 62 "UNKNOWN": 0, 63 "LOW": 1, 64 "MEDIUM": 2, 65 "HIGH": 3, 66 "CRITICAL": 4, 67 } 68 69 intToSeverity := make(map[int]string, len(severityToInt)) 70 for k, v := range severityToInt { 71 intToSeverity[v] = k 72 } 73 74 // Setup test CVE data in mock scanner 75 scanner := mocks.CveScannerMock{ 76 ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) { 77 cveMap := map[string]cvemodel.CVE{} 78 79 if image == "repo1:0.1.0" { 80 for i := 0; i < 5; i++ { 81 cveMap[fmt.Sprintf("CVE%d", i)] = cvemodel.CVE{ 82 ID: fmt.Sprintf("CVE%d", i), 83 Severity: intToSeverity[i%5], 84 Title: fmt.Sprintf("Title for CVE%d", i), 85 Description: fmt.Sprintf("Description for CVE%d", i), 86 } 87 } 88 } 89 90 if image == "repo1:1.0.0" { 91 for i := 0; i < 30; i++ { 92 cveMap[fmt.Sprintf("CVE%d", i)] = cvemodel.CVE{ 93 ID: fmt.Sprintf("CVE%d", i), 94 Severity: intToSeverity[i%5], 95 Title: fmt.Sprintf("Title for CVE%d", i), 96 Description: fmt.Sprintf("Description for CVE%d", i), 97 } 98 } 99 } 100 101 // By default the image has no vulnerabilities 102 return cveMap, nil 103 }, 104 } 105 106 log := log.NewLogger("debug", "") 107 cveInfo := cveinfo.BaseCveInfo{Log: log, Scanner: scanner, MetaDB: metaDB} 108 109 ctx := context.Background() 110 111 Convey("create new paginator errors", func() { 112 paginator, err := cveinfo.NewCvePageFinder(-1, 10, cveinfo.AlphabeticAsc) 113 So(paginator, ShouldBeNil) 114 So(err, ShouldNotBeNil) 115 116 paginator, err = cveinfo.NewCvePageFinder(2, -1, cveinfo.AlphabeticAsc) 117 So(paginator, ShouldBeNil) 118 So(err, ShouldNotBeNil) 119 120 paginator, err = cveinfo.NewCvePageFinder(2, 1, "wrong sorting criteria") 121 So(paginator, ShouldBeNil) 122 So(err, ShouldNotBeNil) 123 }) 124 125 Convey("Reset", func() { 126 paginator, err := cveinfo.NewCvePageFinder(1, 0, cveinfo.AlphabeticAsc) 127 So(err, ShouldBeNil) 128 So(paginator, ShouldNotBeNil) 129 130 paginator.Add(cvemodel.CVE{}) 131 paginator.Add(cvemodel.CVE{}) 132 paginator.Add(cvemodel.CVE{}) 133 134 paginator.Reset() 135 136 result, _ := paginator.Page() 137 So(result, ShouldBeEmpty) 138 }) 139 140 Convey("Page", func() { 141 Convey("defaults", func() { 142 // By default expect unlimitted results sorted by severity 143 cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", 144 "", cvemodel.PageInput{}) 145 So(err, ShouldBeNil) 146 So(len(cves), ShouldEqual, 5) 147 So(pageInfo.ItemCount, ShouldEqual, 5) 148 So(pageInfo.TotalCount, ShouldEqual, 5) 149 So(cveSummary.Count, ShouldEqual, 5) 150 So(cveSummary.UnknownCount, ShouldEqual, 1) 151 So(cveSummary.LowCount, ShouldEqual, 1) 152 So(cveSummary.MediumCount, ShouldEqual, 1) 153 So(cveSummary.HighCount, ShouldEqual, 1) 154 So(cveSummary.CriticalCount, ShouldEqual, 1) 155 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 156 previousSeverity := 4 157 for _, cve := range cves { 158 So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity) 159 previousSeverity = severityToInt[cve.Severity] 160 } 161 162 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", "", 163 cvemodel.PageInput{}) 164 So(err, ShouldBeNil) 165 So(len(cves), ShouldEqual, 30) 166 So(pageInfo.ItemCount, ShouldEqual, 30) 167 So(pageInfo.TotalCount, ShouldEqual, 30) 168 So(cveSummary.Count, ShouldEqual, 30) 169 So(cveSummary.UnknownCount, ShouldEqual, 6) 170 So(cveSummary.LowCount, ShouldEqual, 6) 171 So(cveSummary.MediumCount, ShouldEqual, 6) 172 So(cveSummary.HighCount, ShouldEqual, 6) 173 So(cveSummary.CriticalCount, ShouldEqual, 6) 174 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 175 previousSeverity = 4 176 for _, cve := range cves { 177 So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity) 178 previousSeverity = severityToInt[cve.Severity] 179 } 180 }) 181 182 Convey("no limit or offset", func() { 183 cveIds := []string{} 184 for i := 0; i < 30; i++ { 185 cveIds = append(cveIds, fmt.Sprintf("CVE%d", i)) 186 } 187 188 cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", 189 cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc}) 190 So(err, ShouldBeNil) 191 So(len(cves), ShouldEqual, 5) 192 So(pageInfo.ItemCount, ShouldEqual, 5) 193 So(pageInfo.TotalCount, ShouldEqual, 5) 194 So(cveSummary.Count, ShouldEqual, 5) 195 So(cveSummary.UnknownCount, ShouldEqual, 1) 196 So(cveSummary.LowCount, ShouldEqual, 1) 197 So(cveSummary.MediumCount, ShouldEqual, 1) 198 So(cveSummary.HighCount, ShouldEqual, 1) 199 So(cveSummary.CriticalCount, ShouldEqual, 1) 200 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 201 for i, cve := range cves { 202 So(cve.ID, ShouldEqual, cveIds[i]) 203 } 204 205 sort.Strings(cveIds) 206 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", "", 207 cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc}) 208 So(err, ShouldBeNil) 209 So(len(cves), ShouldEqual, 30) 210 So(pageInfo.ItemCount, ShouldEqual, 30) 211 So(pageInfo.TotalCount, ShouldEqual, 30) 212 So(cveSummary.Count, ShouldEqual, 30) 213 So(cveSummary.UnknownCount, ShouldEqual, 6) 214 So(cveSummary.LowCount, ShouldEqual, 6) 215 So(cveSummary.MediumCount, ShouldEqual, 6) 216 So(cveSummary.HighCount, ShouldEqual, 6) 217 So(cveSummary.CriticalCount, ShouldEqual, 6) 218 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 219 for i, cve := range cves { 220 So(cve.ID, ShouldEqual, cveIds[i]) 221 } 222 223 sort.Sort(sort.Reverse(sort.StringSlice(cveIds))) 224 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", "", 225 cvemodel.PageInput{SortBy: cveinfo.AlphabeticDsc}) 226 So(err, ShouldBeNil) 227 So(len(cves), ShouldEqual, 30) 228 So(pageInfo.ItemCount, ShouldEqual, 30) 229 So(pageInfo.TotalCount, ShouldEqual, 30) 230 So(cveSummary.Count, ShouldEqual, 30) 231 So(cveSummary.UnknownCount, ShouldEqual, 6) 232 So(cveSummary.LowCount, ShouldEqual, 6) 233 So(cveSummary.MediumCount, ShouldEqual, 6) 234 So(cveSummary.HighCount, ShouldEqual, 6) 235 So(cveSummary.CriticalCount, ShouldEqual, 6) 236 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 237 for i, cve := range cves { 238 So(cve.ID, ShouldEqual, cveIds[i]) 239 } 240 241 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", "", 242 cvemodel.PageInput{SortBy: cveinfo.SeverityDsc}) 243 So(err, ShouldBeNil) 244 So(len(cves), ShouldEqual, 30) 245 So(pageInfo.ItemCount, ShouldEqual, 30) 246 So(pageInfo.TotalCount, ShouldEqual, 30) 247 So(cveSummary.Count, ShouldEqual, 30) 248 So(cveSummary.UnknownCount, ShouldEqual, 6) 249 So(cveSummary.LowCount, ShouldEqual, 6) 250 So(cveSummary.MediumCount, ShouldEqual, 6) 251 So(cveSummary.HighCount, ShouldEqual, 6) 252 So(cveSummary.CriticalCount, ShouldEqual, 6) 253 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 254 previousSeverity := 4 255 for _, cve := range cves { 256 So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity) 257 previousSeverity = severityToInt[cve.Severity] 258 } 259 }) 260 261 Convey("limit < len(cves)", func() { 262 cveIds := []string{} 263 for i := 0; i < 30; i++ { 264 cveIds = append(cveIds, fmt.Sprintf("CVE%d", i)) 265 } 266 267 cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 268 Limit: 3, 269 Offset: 1, 270 SortBy: cveinfo.AlphabeticAsc, 271 }, 272 ) 273 So(err, ShouldBeNil) 274 So(len(cves), ShouldEqual, 3) 275 So(pageInfo.ItemCount, ShouldEqual, 3) 276 So(pageInfo.TotalCount, ShouldEqual, 5) 277 So(cves[0].ID, ShouldEqual, "CVE1") // CVE0 is first ID and is not part of the page 278 So(cves[1].ID, ShouldEqual, "CVE2") 279 So(cves[2].ID, ShouldEqual, "CVE3") 280 So(cveSummary.Count, ShouldEqual, 5) 281 So(cveSummary.UnknownCount, ShouldEqual, 1) 282 So(cveSummary.LowCount, ShouldEqual, 1) 283 So(cveSummary.MediumCount, ShouldEqual, 1) 284 So(cveSummary.HighCount, ShouldEqual, 1) 285 So(cveSummary.CriticalCount, ShouldEqual, 1) 286 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 287 288 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 289 Limit: 2, 290 Offset: 1, 291 SortBy: cveinfo.AlphabeticDsc, 292 }, 293 ) 294 So(err, ShouldBeNil) 295 So(len(cves), ShouldEqual, 2) 296 So(pageInfo.ItemCount, ShouldEqual, 2) 297 So(pageInfo.TotalCount, ShouldEqual, 5) 298 So(cves[0].ID, ShouldEqual, "CVE3") 299 So(cves[1].ID, ShouldEqual, "CVE2") 300 So(cveSummary.Count, ShouldEqual, 5) 301 So(cveSummary.UnknownCount, ShouldEqual, 1) 302 So(cveSummary.LowCount, ShouldEqual, 1) 303 So(cveSummary.MediumCount, ShouldEqual, 1) 304 So(cveSummary.HighCount, ShouldEqual, 1) 305 So(cveSummary.CriticalCount, ShouldEqual, 1) 306 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 307 308 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 309 Limit: 3, 310 Offset: 1, 311 SortBy: cveinfo.SeverityDsc, 312 }, 313 ) 314 So(err, ShouldBeNil) 315 So(len(cves), ShouldEqual, 3) 316 So(pageInfo.ItemCount, ShouldEqual, 3) 317 So(pageInfo.TotalCount, ShouldEqual, 5) 318 So(cveSummary.Count, ShouldEqual, 5) 319 So(cveSummary.UnknownCount, ShouldEqual, 1) 320 So(cveSummary.LowCount, ShouldEqual, 1) 321 So(cveSummary.MediumCount, ShouldEqual, 1) 322 So(cveSummary.HighCount, ShouldEqual, 1) 323 So(cveSummary.CriticalCount, ShouldEqual, 1) 324 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 325 previousSeverity := 4 326 for _, cve := range cves { 327 So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity) 328 previousSeverity = severityToInt[cve.Severity] 329 } 330 331 sort.Strings(cveIds) 332 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", "", cvemodel.PageInput{ 333 Limit: 5, 334 Offset: 20, 335 SortBy: cveinfo.AlphabeticAsc, 336 }, 337 ) 338 So(err, ShouldBeNil) 339 So(len(cves), ShouldEqual, 5) 340 So(pageInfo.ItemCount, ShouldEqual, 5) 341 So(pageInfo.TotalCount, ShouldEqual, 30) 342 So(cveSummary.Count, ShouldEqual, 30) 343 So(cveSummary.UnknownCount, ShouldEqual, 6) 344 So(cveSummary.LowCount, ShouldEqual, 6) 345 So(cveSummary.MediumCount, ShouldEqual, 6) 346 So(cveSummary.HighCount, ShouldEqual, 6) 347 So(cveSummary.CriticalCount, ShouldEqual, 6) 348 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 349 for i, cve := range cves { 350 So(cve.ID, ShouldEqual, cveIds[i+20]) 351 } 352 }) 353 354 Convey("limit > len(cves)", func() { 355 cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 356 Limit: 6, 357 Offset: 3, 358 SortBy: cveinfo.AlphabeticAsc, 359 }, 360 ) 361 So(err, ShouldBeNil) 362 So(len(cves), ShouldEqual, 2) 363 So(pageInfo.ItemCount, ShouldEqual, 2) 364 So(pageInfo.TotalCount, ShouldEqual, 5) 365 So(cves[0].ID, ShouldEqual, "CVE3") 366 So(cves[1].ID, ShouldEqual, "CVE4") 367 So(cveSummary.Count, ShouldEqual, 5) 368 So(cveSummary.UnknownCount, ShouldEqual, 1) 369 So(cveSummary.LowCount, ShouldEqual, 1) 370 So(cveSummary.MediumCount, ShouldEqual, 1) 371 So(cveSummary.HighCount, ShouldEqual, 1) 372 So(cveSummary.CriticalCount, ShouldEqual, 1) 373 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 374 375 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 376 Limit: 6, 377 Offset: 3, 378 SortBy: cveinfo.AlphabeticDsc, 379 }, 380 ) 381 So(err, ShouldBeNil) 382 So(len(cves), ShouldEqual, 2) 383 So(pageInfo.ItemCount, ShouldEqual, 2) 384 So(pageInfo.TotalCount, ShouldEqual, 5) 385 So(cves[0].ID, ShouldEqual, "CVE1") 386 So(cves[1].ID, ShouldEqual, "CVE0") 387 So(cveSummary.Count, ShouldEqual, 5) 388 So(cveSummary.UnknownCount, ShouldEqual, 1) 389 So(cveSummary.LowCount, ShouldEqual, 1) 390 So(cveSummary.MediumCount, ShouldEqual, 1) 391 So(cveSummary.HighCount, ShouldEqual, 1) 392 So(cveSummary.CriticalCount, ShouldEqual, 1) 393 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 394 395 cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", "", cvemodel.PageInput{ 396 Limit: 6, 397 Offset: 3, 398 SortBy: cveinfo.SeverityDsc, 399 }, 400 ) 401 So(err, ShouldBeNil) 402 So(len(cves), ShouldEqual, 2) 403 So(pageInfo.ItemCount, ShouldEqual, 2) 404 So(pageInfo.TotalCount, ShouldEqual, 5) 405 So(cveSummary.Count, ShouldEqual, 5) 406 So(cveSummary.UnknownCount, ShouldEqual, 1) 407 So(cveSummary.LowCount, ShouldEqual, 1) 408 So(cveSummary.MediumCount, ShouldEqual, 1) 409 So(cveSummary.HighCount, ShouldEqual, 1) 410 So(cveSummary.CriticalCount, ShouldEqual, 1) 411 So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL") 412 previousSeverity := 4 413 for _, cve := range cves { 414 So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity) 415 previousSeverity = severityToInt[cve.Severity] 416 } 417 }) 418 }) 419 }) 420 }