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  }