zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/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.io/zot/pkg/extensions/search/cve"
    17  	cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
    18  	"zotregistry.io/zot/pkg/log"
    19  	"zotregistry.io/zot/pkg/meta/boltdb"
    20  	. "zotregistry.io/zot/pkg/test/image-utils"
    21  	"zotregistry.io/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, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{})
   144  				So(err, ShouldBeNil)
   145  				So(len(cves), ShouldEqual, 5)
   146  				So(pageInfo.ItemCount, ShouldEqual, 5)
   147  				So(pageInfo.TotalCount, ShouldEqual, 5)
   148  				previousSeverity := 4
   149  				for _, cve := range cves {
   150  					So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
   151  					previousSeverity = severityToInt[cve.Severity]
   152  				}
   153  
   154  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", cvemodel.PageInput{})
   155  				So(err, ShouldBeNil)
   156  				So(len(cves), ShouldEqual, 30)
   157  				So(pageInfo.ItemCount, ShouldEqual, 30)
   158  				So(pageInfo.TotalCount, ShouldEqual, 30)
   159  				previousSeverity = 4
   160  				for _, cve := range cves {
   161  					So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
   162  					previousSeverity = severityToInt[cve.Severity]
   163  				}
   164  			})
   165  
   166  			Convey("no limit or offset", func() {
   167  				cveIds := []string{}
   168  				for i := 0; i < 30; i++ {
   169  					cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
   170  				}
   171  
   172  				cves, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "",
   173  					cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
   174  				So(err, ShouldBeNil)
   175  				So(len(cves), ShouldEqual, 5)
   176  				So(pageInfo.ItemCount, ShouldEqual, 5)
   177  				So(pageInfo.TotalCount, ShouldEqual, 5)
   178  				for i, cve := range cves {
   179  					So(cve.ID, ShouldEqual, cveIds[i])
   180  				}
   181  
   182  				sort.Strings(cveIds)
   183  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
   184  					cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
   185  				So(err, ShouldBeNil)
   186  				So(len(cves), ShouldEqual, 30)
   187  				So(pageInfo.ItemCount, ShouldEqual, 30)
   188  				So(pageInfo.TotalCount, ShouldEqual, 30)
   189  				for i, cve := range cves {
   190  					So(cve.ID, ShouldEqual, cveIds[i])
   191  				}
   192  
   193  				sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
   194  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
   195  					cvemodel.PageInput{SortBy: cveinfo.AlphabeticDsc})
   196  				So(err, ShouldBeNil)
   197  				So(len(cves), ShouldEqual, 30)
   198  				So(pageInfo.ItemCount, ShouldEqual, 30)
   199  				So(pageInfo.TotalCount, ShouldEqual, 30)
   200  				for i, cve := range cves {
   201  					So(cve.ID, ShouldEqual, cveIds[i])
   202  				}
   203  
   204  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
   205  					cvemodel.PageInput{SortBy: cveinfo.SeverityDsc})
   206  				So(err, ShouldBeNil)
   207  				So(len(cves), ShouldEqual, 30)
   208  				So(pageInfo.ItemCount, ShouldEqual, 30)
   209  				So(pageInfo.TotalCount, ShouldEqual, 30)
   210  				previousSeverity := 4
   211  				for _, cve := range cves {
   212  					So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
   213  					previousSeverity = severityToInt[cve.Severity]
   214  				}
   215  			})
   216  
   217  			Convey("limit < len(cves)", func() {
   218  				cveIds := []string{}
   219  				for i := 0; i < 30; i++ {
   220  					cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
   221  				}
   222  
   223  				cves, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   224  					Limit:  3,
   225  					Offset: 1,
   226  					SortBy: cveinfo.AlphabeticAsc,
   227  				},
   228  				)
   229  				So(err, ShouldBeNil)
   230  				So(len(cves), ShouldEqual, 3)
   231  				So(pageInfo.ItemCount, ShouldEqual, 3)
   232  				So(pageInfo.TotalCount, ShouldEqual, 5)
   233  				So(cves[0].ID, ShouldEqual, "CVE1") // CVE0 is first ID and is not part of the page
   234  				So(cves[1].ID, ShouldEqual, "CVE2")
   235  				So(cves[2].ID, ShouldEqual, "CVE3")
   236  
   237  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   238  					Limit:  2,
   239  					Offset: 1,
   240  					SortBy: cveinfo.AlphabeticDsc,
   241  				},
   242  				)
   243  				So(err, ShouldBeNil)
   244  				So(len(cves), ShouldEqual, 2)
   245  				So(pageInfo.ItemCount, ShouldEqual, 2)
   246  				So(pageInfo.TotalCount, ShouldEqual, 5)
   247  				So(cves[0].ID, ShouldEqual, "CVE3")
   248  				So(cves[1].ID, ShouldEqual, "CVE2")
   249  
   250  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   251  					Limit:  3,
   252  					Offset: 1,
   253  					SortBy: cveinfo.SeverityDsc,
   254  				},
   255  				)
   256  				So(err, ShouldBeNil)
   257  				So(len(cves), ShouldEqual, 3)
   258  				So(pageInfo.ItemCount, ShouldEqual, 3)
   259  				So(pageInfo.TotalCount, ShouldEqual, 5)
   260  				previousSeverity := 4
   261  				for _, cve := range cves {
   262  					So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
   263  					previousSeverity = severityToInt[cve.Severity]
   264  				}
   265  
   266  				sort.Strings(cveIds)
   267  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", cvemodel.PageInput{
   268  					Limit:  5,
   269  					Offset: 20,
   270  					SortBy: cveinfo.AlphabeticAsc,
   271  				},
   272  				)
   273  				So(err, ShouldBeNil)
   274  				So(len(cves), ShouldEqual, 5)
   275  				So(pageInfo.ItemCount, ShouldEqual, 5)
   276  				So(pageInfo.TotalCount, ShouldEqual, 30)
   277  				for i, cve := range cves {
   278  					So(cve.ID, ShouldEqual, cveIds[i+20])
   279  				}
   280  			})
   281  
   282  			Convey("limit > len(cves)", func() {
   283  				cves, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   284  					Limit:  6,
   285  					Offset: 3,
   286  					SortBy: cveinfo.AlphabeticAsc,
   287  				},
   288  				)
   289  				So(err, ShouldBeNil)
   290  				So(len(cves), ShouldEqual, 2)
   291  				So(pageInfo.ItemCount, ShouldEqual, 2)
   292  				So(pageInfo.TotalCount, ShouldEqual, 5)
   293  				So(cves[0].ID, ShouldEqual, "CVE3")
   294  				So(cves[1].ID, ShouldEqual, "CVE4")
   295  
   296  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   297  					Limit:  6,
   298  					Offset: 3,
   299  					SortBy: cveinfo.AlphabeticDsc,
   300  				},
   301  				)
   302  				So(err, ShouldBeNil)
   303  				So(len(cves), ShouldEqual, 2)
   304  				So(pageInfo.ItemCount, ShouldEqual, 2)
   305  				So(pageInfo.TotalCount, ShouldEqual, 5)
   306  				So(cves[0].ID, ShouldEqual, "CVE1")
   307  				So(cves[1].ID, ShouldEqual, "CVE0")
   308  
   309  				cves, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
   310  					Limit:  6,
   311  					Offset: 3,
   312  					SortBy: cveinfo.SeverityDsc,
   313  				},
   314  				)
   315  				So(err, ShouldBeNil)
   316  				So(len(cves), ShouldEqual, 2)
   317  				So(pageInfo.ItemCount, ShouldEqual, 2)
   318  				So(pageInfo.TotalCount, ShouldEqual, 5)
   319  				previousSeverity := 4
   320  				for _, cve := range cves {
   321  					So(severityToInt[cve.Severity], ShouldBeLessThanOrEqualTo, previousSeverity)
   322  					previousSeverity = severityToInt[cve.Severity]
   323  				}
   324  			})
   325  		})
   326  	})
   327  }