zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/cli/client/cve_cmd_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package client_test
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"os"
    14  	"path"
    15  	"regexp"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	regTypes "github.com/google/go-containerregistry/pkg/v1/types"
    21  	godigest "github.com/opencontainers/go-digest"
    22  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	. "github.com/smartystreets/goconvey/convey"
    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/cli/client"
    29  	zcommon "zotregistry.io/zot/pkg/common"
    30  	extconf "zotregistry.io/zot/pkg/extensions/config"
    31  	"zotregistry.io/zot/pkg/extensions/monitoring"
    32  	cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
    33  	cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
    34  	"zotregistry.io/zot/pkg/log"
    35  	mTypes "zotregistry.io/zot/pkg/meta/types"
    36  	"zotregistry.io/zot/pkg/storage"
    37  	"zotregistry.io/zot/pkg/storage/local"
    38  	test "zotregistry.io/zot/pkg/test/common"
    39  	. "zotregistry.io/zot/pkg/test/image-utils"
    40  	"zotregistry.io/zot/pkg/test/mocks"
    41  	ociutils "zotregistry.io/zot/pkg/test/oci-utils"
    42  )
    43  
    44  func TestNegativeServerResponse(t *testing.T) {
    45  	Convey("Test from real server without search endpoint", t, func() {
    46  		port := test.GetFreePort()
    47  		url := test.GetBaseURL(port)
    48  		conf := config.New()
    49  		conf.HTTP.Port = port
    50  
    51  		dir := t.TempDir()
    52  
    53  		srcStorageCtlr := ociutils.GetDefaultStoreController(dir, log.NewLogger("debug", ""))
    54  		err := WriteImageToFileSystem(CreateDefaultVulnerableImage(), "zot-cve-test", "0.0.1", srcStorageCtlr)
    55  		So(err, ShouldBeNil)
    56  
    57  		conf.Storage.RootDirectory = dir
    58  		trivyConfig := &extconf.TrivyConfig{
    59  			DBRepository: "ghcr.io/project-zot/trivy-db",
    60  		}
    61  		cveConfig := &extconf.CVEConfig{
    62  			UpdateInterval: 2,
    63  			Trivy:          trivyConfig,
    64  		}
    65  		defaultVal := false
    66  		searchConfig := &extconf.SearchConfig{
    67  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    68  			CVE:        cveConfig,
    69  		}
    70  		conf.Extensions = &extconf.ExtensionConfig{
    71  			Search: searchConfig,
    72  		}
    73  
    74  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
    75  		if err != nil {
    76  			panic(err)
    77  		}
    78  
    79  		logPath := logFile.Name()
    80  		defer os.Remove(logPath)
    81  
    82  		writers := io.MultiWriter(os.Stdout, logFile)
    83  
    84  		ctlr := api.NewController(conf)
    85  		ctlr.Log.Logger = ctlr.Log.Output(writers)
    86  
    87  		cm := test.NewControllerManager(ctlr)
    88  		cm.StartAndWait(conf.HTTP.Port)
    89  		defer cm.StopServer()
    90  
    91  		_, err = test.ReadLogFileAndSearchString(logPath, "CVE config not provided, skipping CVE update", 90*time.Second)
    92  		if err != nil {
    93  			panic(err)
    94  		}
    95  
    96  		Convey("Status Code Not Found", func() {
    97  			args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"}
    98  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
    99  			defer os.Remove(configPath)
   100  			cveCmd := client.NewCVECommand(client.NewSearchService())
   101  			buff := bytes.NewBufferString("")
   102  			cveCmd.SetOut(buff)
   103  			cveCmd.SetErr(buff)
   104  			cveCmd.SetArgs(args)
   105  			err = cveCmd.Execute()
   106  			So(err, ShouldNotBeNil)
   107  			So(err.Error(), ShouldContainSubstring, zerr.ErrExtensionNotEnabled.Error())
   108  		})
   109  	})
   110  
   111  	Convey("Test non-existing manifest blob", t, func() {
   112  		port := test.GetFreePort()
   113  		url := test.GetBaseURL(port)
   114  		conf := config.New()
   115  		conf.HTTP.Port = port
   116  
   117  		dir := t.TempDir()
   118  
   119  		imageStore := local.NewImageStore(dir, false, false,
   120  			log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), nil, nil)
   121  
   122  		storeController := storage.StoreController{
   123  			DefaultStore: imageStore,
   124  		}
   125  
   126  		image := CreateRandomImage()
   127  
   128  		err := WriteImageToFileSystem(image, "zot-cve-test", "0.0.1", storeController)
   129  		So(err, ShouldBeNil)
   130  
   131  		err = os.RemoveAll(path.Join(dir, "zot-cve-test/blobs"))
   132  		if err != nil {
   133  			panic(err)
   134  		}
   135  
   136  		conf.Storage.RootDirectory = dir
   137  		trivyConfig := &extconf.TrivyConfig{
   138  			DBRepository: "ghcr.io/project-zot/trivy-db",
   139  		}
   140  		cveConfig := &extconf.CVEConfig{
   141  			UpdateInterval: 2,
   142  			Trivy:          trivyConfig,
   143  		}
   144  		defaultVal := true
   145  		searchConfig := &extconf.SearchConfig{
   146  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   147  			CVE:        cveConfig,
   148  		}
   149  		conf.Extensions = &extconf.ExtensionConfig{
   150  			Search: searchConfig,
   151  		}
   152  
   153  		logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   154  		if err != nil {
   155  			panic(err)
   156  		}
   157  
   158  		logPath := logFile.Name()
   159  		defer os.Remove(logPath)
   160  
   161  		writers := io.MultiWriter(os.Stdout, logFile)
   162  
   163  		ctlr := api.NewController(conf)
   164  		ctlr.Log.Logger = ctlr.Log.Output(writers)
   165  
   166  		ctx := context.Background()
   167  
   168  		if err := ctlr.Init(ctx); err != nil {
   169  			panic(err)
   170  		}
   171  
   172  		ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
   173  
   174  		go func() {
   175  			if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
   176  				panic(err)
   177  			}
   178  		}()
   179  
   180  		defer ctlr.Shutdown()
   181  
   182  		test.WaitTillServerReady(url)
   183  
   184  		_, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second)
   185  		if err != nil {
   186  			panic(err)
   187  		}
   188  
   189  		args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"}
   190  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   191  		defer os.Remove(configPath)
   192  		cveCmd := client.NewCVECommand(client.NewSearchService())
   193  		buff := bytes.NewBufferString("")
   194  		cveCmd.SetOut(buff)
   195  		cveCmd.SetErr(buff)
   196  		cveCmd.SetArgs(args)
   197  		err = cveCmd.Execute()
   198  		So(err, ShouldNotBeNil)
   199  	})
   200  }
   201  
   202  //nolint:dupl
   203  func TestServerCVEResponse(t *testing.T) {
   204  	port := test.GetFreePort()
   205  	url := test.GetBaseURL(port)
   206  	conf := config.New()
   207  	conf.HTTP.Port = port
   208  
   209  	dir := t.TempDir()
   210  
   211  	conf.Storage.RootDirectory = dir
   212  	trivyConfig := &extconf.TrivyConfig{
   213  		DBRepository: "ghcr.io/project-zot/trivy-db",
   214  	}
   215  	cveConfig := &extconf.CVEConfig{
   216  		UpdateInterval: 2,
   217  		Trivy:          trivyConfig,
   218  	}
   219  	defaultVal := true
   220  	searchConfig := &extconf.SearchConfig{
   221  		BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   222  		CVE:        cveConfig,
   223  	}
   224  	conf.Extensions = &extconf.ExtensionConfig{
   225  		Search: searchConfig,
   226  	}
   227  
   228  	logFile, err := os.CreateTemp(t.TempDir(), "zot-log*.txt")
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  
   233  	logPath := logFile.Name()
   234  	defer os.Remove(logPath)
   235  
   236  	writers := io.MultiWriter(os.Stdout, logFile)
   237  
   238  	ctlr := api.NewController(conf)
   239  	ctlr.Log.Logger = ctlr.Log.Output(writers)
   240  
   241  	ctx := context.Background()
   242  
   243  	if err := ctlr.Init(ctx); err != nil {
   244  		panic(err)
   245  	}
   246  
   247  	ctlr.CveScanner = getMockCveScanner(ctlr.MetaDB)
   248  
   249  	go func() {
   250  		if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
   251  			panic(err)
   252  		}
   253  	}()
   254  
   255  	defer ctlr.Shutdown()
   256  
   257  	test.WaitTillServerReady(url)
   258  
   259  	image := CreateDefaultImage()
   260  
   261  	err = UploadImage(image, url, "zot-cve-test", "0.0.1")
   262  	if err != nil {
   263  		panic(err)
   264  	}
   265  
   266  	_, err = test.ReadLogFileAndSearchString(logPath, "DB update completed, next update scheduled", 90*time.Second)
   267  	if err != nil {
   268  		panic(err)
   269  	}
   270  
   271  	Convey("Test CVE by image name - GQL - positive", t, func() {
   272  		args := []string{"list", "zot-cve-test:0.0.1", "--config", "cvetest"}
   273  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   274  		defer os.Remove(configPath)
   275  		cveCmd := client.NewCVECommand(client.NewSearchService())
   276  		buff := bytes.NewBufferString("")
   277  		cveCmd.SetOut(buff)
   278  		cveCmd.SetErr(buff)
   279  		cveCmd.SetArgs(args)
   280  		err = cveCmd.Execute()
   281  		space := regexp.MustCompile(`\s+`)
   282  		str := space.ReplaceAllString(buff.String(), " ")
   283  		str = strings.TrimSpace(str)
   284  		So(err, ShouldBeNil)
   285  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   286  		So(str, ShouldContainSubstring, "CVE")
   287  	})
   288  
   289  	Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() {
   290  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-C1", "--config", "cvetest"}
   291  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   292  		defer os.Remove(configPath)
   293  		cveCmd := client.NewCVECommand(client.NewSearchService())
   294  		buff := bytes.NewBufferString("")
   295  		cveCmd.SetOut(buff)
   296  		cveCmd.SetErr(buff)
   297  		cveCmd.SetArgs(args)
   298  		err = cveCmd.Execute()
   299  		space := regexp.MustCompile(`\s+`)
   300  		str := space.ReplaceAllString(buff.String(), " ")
   301  		str = strings.TrimSpace(str)
   302  		So(err, ShouldBeNil)
   303  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   304  		So(str, ShouldContainSubstring, "CVE-C1")
   305  		So(str, ShouldNotContainSubstring, "CVE-2")
   306  	})
   307  
   308  	Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() {
   309  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-2", "--config", "cvetest"}
   310  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   311  		defer os.Remove(configPath)
   312  		cveCmd := client.NewCVECommand(client.NewSearchService())
   313  		buff := bytes.NewBufferString("")
   314  		cveCmd.SetOut(buff)
   315  		cveCmd.SetErr(buff)
   316  		cveCmd.SetArgs(args)
   317  		err = cveCmd.Execute()
   318  		space := regexp.MustCompile(`\s+`)
   319  		str := space.ReplaceAllString(buff.String(), " ")
   320  		str = strings.TrimSpace(str)
   321  		So(err, ShouldBeNil)
   322  		So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
   323  		So(str, ShouldContainSubstring, "CVE-2")
   324  		So(str, ShouldNotContainSubstring, "CVE-1")
   325  	})
   326  
   327  	Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() {
   328  		args := []string{"list", "zot-cve-test:0.0.1", "--cve-id", "CVE-100", "--config", "cvetest"}
   329  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   330  		defer os.Remove(configPath)
   331  		cveCmd := client.NewCVECommand(client.NewSearchService())
   332  		buff := bytes.NewBufferString("")
   333  		cveCmd.SetOut(buff)
   334  		cveCmd.SetErr(buff)
   335  		cveCmd.SetArgs(args)
   336  		err = cveCmd.Execute()
   337  		space := regexp.MustCompile(`\s+`)
   338  		str := space.ReplaceAllString(buff.String(), " ")
   339  		str = strings.TrimSpace(str)
   340  		So(err, ShouldBeNil)
   341  		So(str, ShouldContainSubstring, "No CVEs found for image")
   342  	})
   343  
   344  	Convey("Test CVE by image name - GQL - invalid image", t, func() {
   345  		args := []string{"list", "invalid:0.0.1", "--config", "cvetest"}
   346  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   347  		defer os.Remove(configPath)
   348  		cveCmd := client.NewCVECommand(client.NewSearchService())
   349  		buff := bytes.NewBufferString("")
   350  		cveCmd.SetOut(buff)
   351  		cveCmd.SetErr(buff)
   352  		cveCmd.SetArgs(args)
   353  		err = cveCmd.Execute()
   354  		So(err, ShouldNotBeNil)
   355  	})
   356  
   357  	Convey("Test CVE by image name - GQL - invalid image name and tag", t, func() {
   358  		args := []string{"list", "invalid:", "--config", "cvetest"}
   359  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   360  		defer os.Remove(configPath)
   361  		cveCmd := client.NewCVECommand(client.NewSearchService())
   362  		buff := bytes.NewBufferString("")
   363  		cveCmd.SetOut(buff)
   364  		cveCmd.SetErr(buff)
   365  		cveCmd.SetArgs(args)
   366  		err = cveCmd.Execute()
   367  		So(err, ShouldNotBeNil)
   368  	})
   369  
   370  	Convey("Test CVE by image name - GQL - invalid output format", t, func() {
   371  		args := []string{"list", "zot-cve-test:0.0.1", "-f", "random", "--config", "cvetest"}
   372  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   373  		defer os.Remove(configPath)
   374  		cveCmd := client.NewCVECommand(client.NewSearchService())
   375  		buff := bytes.NewBufferString("")
   376  		cveCmd.SetOut(buff)
   377  		cveCmd.SetErr(buff)
   378  		cveCmd.SetArgs(args)
   379  		err = cveCmd.Execute()
   380  		So(err, ShouldNotBeNil)
   381  		So(buff.String(), ShouldContainSubstring, "invalid output format")
   382  	})
   383  
   384  	Convey("Test images by CVE ID - GQL - positive", t, func() {
   385  		args := []string{"affected", "CVE-2019-9923", "--config", "cvetest"}
   386  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   387  		defer os.Remove(configPath)
   388  		cveCmd := client.NewCVECommand(client.NewSearchService())
   389  		buff := bytes.NewBufferString("")
   390  		cveCmd.SetOut(buff)
   391  		cveCmd.SetErr(buff)
   392  		cveCmd.SetArgs(args)
   393  		err := cveCmd.Execute()
   394  		space := regexp.MustCompile(`\s+`)
   395  		str := space.ReplaceAllString(buff.String(), " ")
   396  		str = strings.TrimSpace(str)
   397  		So(err, ShouldBeNil)
   398  		So(str, ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B")
   399  	})
   400  
   401  	Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() {
   402  		args := []string{"affected", "CVE-invalid", "--config", "cvetest"}
   403  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   404  		defer os.Remove(configPath)
   405  		cveCmd := client.NewCVECommand(client.NewSearchService())
   406  		buff := bytes.NewBufferString("")
   407  		cveCmd.SetOut(buff)
   408  		cveCmd.SetErr(buff)
   409  		cveCmd.SetArgs(args)
   410  		err := cveCmd.Execute()
   411  		space := regexp.MustCompile(`\s+`)
   412  		str := space.ReplaceAllString(buff.String(), " ")
   413  		str = strings.TrimSpace(str)
   414  		So(err, ShouldBeNil)
   415  		So(str, ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   416  	})
   417  
   418  	Convey("Test images by CVE ID - GQL - invalid output format", t, func() {
   419  		args := []string{"affected", "CVE-2019-9923", "-f", "random", "--config", "cvetest"}
   420  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   421  		defer os.Remove(configPath)
   422  		cveCmd := client.NewCVECommand(client.NewSearchService())
   423  		buff := bytes.NewBufferString("")
   424  		cveCmd.SetOut(buff)
   425  		cveCmd.SetErr(buff)
   426  		cveCmd.SetArgs(args)
   427  		err = cveCmd.Execute()
   428  		So(err, ShouldNotBeNil)
   429  		So(buff.String(), ShouldContainSubstring, "invalid output format")
   430  	})
   431  
   432  	Convey("Test fixed tags by image name and CVE ID - GQL - positive", t, func() {
   433  		args := []string{"fixed", "zot-cve-test", "CVE-2019-9923", "--config", "cvetest"}
   434  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   435  		defer os.Remove(configPath)
   436  		cveCmd := client.NewCVECommand(client.NewSearchService())
   437  		buff := bytes.NewBufferString("")
   438  		cveCmd.SetOut(buff)
   439  		cveCmd.SetErr(buff)
   440  		cveCmd.SetArgs(args)
   441  		err := cveCmd.Execute()
   442  		space := regexp.MustCompile(`\s+`)
   443  		str := space.ReplaceAllString(buff.String(), " ")
   444  		str = strings.TrimSpace(str)
   445  		So(err, ShouldBeNil)
   446  		So(str, ShouldEqual, "")
   447  	})
   448  
   449  	Convey("Test fixed tags by image name and CVE ID - GQL - random cve", t, func() {
   450  		args := []string{"fixed", "zot-cve-test", "random", "--config", "cvetest"}
   451  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   452  		defer os.Remove(configPath)
   453  		cveCmd := client.NewCVECommand(client.NewSearchService())
   454  		buff := bytes.NewBufferString("")
   455  		cveCmd.SetOut(buff)
   456  		cveCmd.SetErr(buff)
   457  		cveCmd.SetArgs(args)
   458  		err := cveCmd.Execute()
   459  		space := regexp.MustCompile(`\s+`)
   460  		str := space.ReplaceAllString(buff.String(), " ")
   461  		str = strings.TrimSpace(str)
   462  		So(err, ShouldBeNil)
   463  		So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   464  	})
   465  
   466  	Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() {
   467  		args := []string{"fixed", "zot-cv-test", "CVE-2019-20807", "--config", "cvetest"}
   468  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   469  		defer os.Remove(configPath)
   470  		cveCmd := client.NewCVECommand(client.NewSearchService())
   471  		buff := bytes.NewBufferString("")
   472  		cveCmd.SetOut(buff)
   473  		cveCmd.SetErr(buff)
   474  		cveCmd.SetArgs(args)
   475  		err := cveCmd.Execute()
   476  		space := regexp.MustCompile(`\s+`)
   477  		str := space.ReplaceAllString(buff.String(), " ")
   478  		str = strings.TrimSpace(str)
   479  		So(err, ShouldNotBeNil)
   480  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   481  	})
   482  
   483  	Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() {
   484  		args := []string{"fixed", "zot-cv-test:tag", "CVE-2019-20807", "--config", "cvetest"}
   485  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   486  		defer os.Remove(configPath)
   487  		cveCmd := client.NewCVECommand(client.NewSearchService())
   488  		buff := bytes.NewBufferString("")
   489  		cveCmd.SetOut(buff)
   490  		cveCmd.SetErr(buff)
   491  		cveCmd.SetArgs(args)
   492  		err := cveCmd.Execute()
   493  		space := regexp.MustCompile(`\s+`)
   494  		str := space.ReplaceAllString(buff.String(), " ")
   495  		str = strings.TrimSpace(str)
   496  		So(err, ShouldNotBeNil)
   497  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   498  	})
   499  
   500  	Convey("Test CVE by name and CVE ID - GQL - positive", t, func() {
   501  		args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "--config", "cvetest"}
   502  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   503  		defer os.Remove(configPath)
   504  		cveCmd := client.NewCVECommand(client.NewSearchService())
   505  		buff := bytes.NewBufferString("")
   506  		cveCmd.SetOut(buff)
   507  		cveCmd.SetErr(buff)
   508  		cveCmd.SetArgs(args)
   509  		err := cveCmd.Execute()
   510  		space := regexp.MustCompile(`\s+`)
   511  		str := space.ReplaceAllString(buff.String(), " ")
   512  		So(err, ShouldBeNil)
   513  		So(strings.TrimSpace(str), ShouldEqual,
   514  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 db573b01 false 854B")
   515  	})
   516  
   517  	Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() {
   518  		args := []string{"affected", "CVE-20807", "--repo", "test", "--config", "cvetest"}
   519  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   520  		defer os.Remove(configPath)
   521  		cveCmd := client.NewCVECommand(client.NewSearchService())
   522  		buff := bytes.NewBufferString("")
   523  		cveCmd.SetOut(buff)
   524  		cveCmd.SetErr(buff)
   525  		cveCmd.SetArgs(args)
   526  		err := cveCmd.Execute()
   527  		space := regexp.MustCompile(`\s+`)
   528  		str := space.ReplaceAllString(buff.String(), " ")
   529  		So(err, ShouldBeNil)
   530  		So(strings.TrimSpace(str), ShouldNotContainSubstring, "REPOSITORY TAG OS/ARCH SIGNED SIZE")
   531  	})
   532  
   533  	Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() {
   534  		args := []string{"affected", "CVE-2019-9923", "--repo", "zot-cve-test", "-f", "random", "--config", "cvetest"}
   535  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
   536  		defer os.Remove(configPath)
   537  		cveCmd := client.NewCVECommand(client.NewSearchService())
   538  		buff := bytes.NewBufferString("")
   539  		cveCmd.SetOut(buff)
   540  		cveCmd.SetErr(buff)
   541  		cveCmd.SetArgs(args)
   542  		err = cveCmd.Execute()
   543  		So(err, ShouldNotBeNil)
   544  		So(buff.String(), ShouldContainSubstring, "invalid output format")
   545  	})
   546  }
   547  
   548  func TestCVESort(t *testing.T) {
   549  	rootDir := t.TempDir()
   550  	port := test.GetFreePort()
   551  	baseURL := test.GetBaseURL(port)
   552  	conf := config.New()
   553  	conf.HTTP.Port = port
   554  
   555  	defaultVal := true
   556  	conf.Extensions = &extconf.ExtensionConfig{
   557  		Search: &extconf.SearchConfig{
   558  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   559  			CVE: &extconf.CVEConfig{
   560  				UpdateInterval: 2,
   561  				Trivy: &extconf.TrivyConfig{
   562  					DBRepository: "ghcr.io/project-zot/trivy-db",
   563  				},
   564  			},
   565  		},
   566  	}
   567  	ctlr := api.NewController(conf)
   568  	ctlr.Config.Storage.RootDirectory = rootDir
   569  
   570  	image1 := CreateRandomImage()
   571  
   572  	storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log)
   573  
   574  	err := WriteImageToFileSystem(image1, "repo", "tag", storeController)
   575  	if err != nil {
   576  		t.FailNow()
   577  	}
   578  
   579  	ctx := context.Background()
   580  
   581  	if err := ctlr.Init(ctx); err != nil {
   582  		panic(err)
   583  	}
   584  
   585  	ctlr.CveScanner = mocks.CveScannerMock{
   586  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   587  			return map[string]cvemodel.CVE{
   588  				"CVE-2023-1255": {
   589  					ID:       "CVE-2023-1255",
   590  					Severity: "LOW",
   591  					Title:    "Input buffer over-read in AES-XTS implementation and testing",
   592  				},
   593  				"CVE-2023-2650": {
   594  					ID:       "CVE-2023-2650",
   595  					Severity: "MEDIUM",
   596  					Title:    "Possible DoS translating ASN.1 object identifier and executer",
   597  				},
   598  				"CVE-2023-2975": {
   599  					ID:       "CVE-2023-2975",
   600  					Severity: "HIGH",
   601  					Title:    "AES-SIV cipher implementation contains a bug that can break",
   602  				},
   603  				"CVE-2023-3446": {
   604  					ID:       "CVE-2023-3446",
   605  					Severity: "CRITICAL",
   606  					Title:    "Excessive time spent checking DH keys and parenthesis",
   607  				},
   608  				"CVE-2023-3817": {
   609  					ID:       "CVE-2023-3817",
   610  					Severity: "MEDIUM",
   611  					Title:    "Excessive time spent checking DH q parameter and arguments",
   612  				},
   613  			}, nil
   614  		},
   615  	}
   616  
   617  	go func() {
   618  		if err := ctlr.Run(ctx); !errors.Is(err, http.ErrServerClosed) {
   619  			panic(err)
   620  		}
   621  	}()
   622  
   623  	defer ctlr.Shutdown()
   624  
   625  	test.WaitTillServerReady(baseURL)
   626  
   627  	space := regexp.MustCompile(`\s+`)
   628  
   629  	Convey("test sorting", t, func() {
   630  		args := []string{"list", "repo:tag", "--sort-by", "severity", "--url", baseURL}
   631  		cmd := client.NewCVECommand(client.NewSearchService())
   632  		buff := bytes.NewBufferString("")
   633  		cmd.SetOut(buff)
   634  		cmd.SetErr(buff)
   635  		cmd.SetArgs(args)
   636  		err := cmd.Execute()
   637  		So(err, ShouldBeNil)
   638  		str := space.ReplaceAllString(buff.String(), " ")
   639  		actual := strings.TrimSpace(str)
   640  		So(actual, ShouldResemble,
   641  			"ID SEVERITY TITLE "+
   642  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   643  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   644  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   645  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
   646  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
   647  
   648  		args = []string{"list", "repo:tag", "--sort-by", "alpha-asc", "--url", baseURL}
   649  		cmd = client.NewCVECommand(client.NewSearchService())
   650  		buff = bytes.NewBufferString("")
   651  		cmd.SetOut(buff)
   652  		cmd.SetErr(buff)
   653  		cmd.SetArgs(args)
   654  		err = cmd.Execute()
   655  		So(err, ShouldBeNil)
   656  		str = space.ReplaceAllString(buff.String(), " ")
   657  		actual = strings.TrimSpace(str)
   658  		So(actual, ShouldResemble,
   659  			"ID SEVERITY TITLE "+
   660  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+
   661  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   662  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   663  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   664  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ...")
   665  
   666  		args = []string{"list", "repo:tag", "--sort-by", "alpha-dsc", "--url", baseURL}
   667  		cmd = client.NewCVECommand(client.NewSearchService())
   668  		buff = bytes.NewBufferString("")
   669  		cmd.SetOut(buff)
   670  		cmd.SetErr(buff)
   671  		cmd.SetArgs(args)
   672  		err = cmd.Execute()
   673  		So(err, ShouldBeNil)
   674  		str = space.ReplaceAllString(buff.String(), " ")
   675  		actual = strings.TrimSpace(str)
   676  		So(actual, ShouldResemble,
   677  			"ID SEVERITY TITLE "+
   678  				"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
   679  				"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
   680  				"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
   681  				"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
   682  				"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat...")
   683  	})
   684  }
   685  
   686  func getMockCveScanner(metaDB mTypes.MetaDB) cveinfo.Scanner {
   687  	// MetaDB loaded with initial data now mock the scanner
   688  	// Setup test CVE data in mock scanner
   689  	scanner := mocks.CveScannerMock{
   690  		ScanImageFn: func(ctx context.Context, image string) (map[string]cvemodel.CVE, error) {
   691  			if strings.Contains(image, "zot-cve-test@sha256:db573b01") ||
   692  				image == "zot-cve-test:0.0.1" {
   693  				return map[string]cvemodel.CVE{
   694  					"CVE-1": {
   695  						ID:          "CVE-1",
   696  						Severity:    "CRITICAL",
   697  						Title:       "Title for CVE-C1",
   698  						Description: "Description of CVE-1",
   699  					},
   700  					"CVE-2019-9923": {
   701  						ID:          "CVE-2019-9923",
   702  						Severity:    "HIGH",
   703  						Title:       "Title for CVE-2",
   704  						Description: "Description of CVE-2",
   705  					},
   706  					"CVE-3": {
   707  						ID:          "CVE-3",
   708  						Severity:    "MEDIUM",
   709  						Title:       "Title for CVE-3",
   710  						Description: "Description of CVE-3",
   711  					},
   712  					"CVE-4": {
   713  						ID:          "CVE-4",
   714  						Severity:    "LOW",
   715  						Title:       "Title for CVE-4",
   716  						Description: "Description of CVE-4",
   717  					},
   718  					"CVE-5": {
   719  						ID:          "CVE-5",
   720  						Severity:    "UNKNOWN",
   721  						Title:       "Title for CVE-5",
   722  						Description: "Description of CVE-5",
   723  					},
   724  				}, nil
   725  			}
   726  
   727  			// By default the image has no vulnerabilities
   728  			return map[string]cvemodel.CVE{}, nil
   729  		},
   730  		IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
   731  			// Almost same logic compared to actual Trivy specific implementation
   732  			imageDir := repo
   733  			inputTag := reference
   734  
   735  			repoMeta, err := metaDB.GetRepoMeta(context.Background(), imageDir)
   736  			if err != nil {
   737  				return false, err
   738  			}
   739  
   740  			manifestDigestStr := reference
   741  
   742  			if zcommon.IsTag(reference) {
   743  				var ok bool
   744  
   745  				descriptor, ok := repoMeta.Tags[inputTag]
   746  				if !ok {
   747  					return false, zerr.ErrTagMetaNotFound
   748  				}
   749  
   750  				manifestDigestStr = descriptor.Digest
   751  			}
   752  
   753  			manifestDigest, err := godigest.Parse(manifestDigestStr)
   754  			if err != nil {
   755  				return false, err
   756  			}
   757  
   758  			manifestData, err := metaDB.GetImageMeta(manifestDigest)
   759  			if err != nil {
   760  				return false, err
   761  			}
   762  
   763  			for _, imageLayer := range manifestData.Manifests[0].Manifest.Layers {
   764  				switch imageLayer.MediaType {
   765  				case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
   766  
   767  					return true, nil
   768  				default:
   769  
   770  					return false, zerr.ErrScanNotSupported
   771  				}
   772  			}
   773  
   774  			return false, nil
   775  		},
   776  	}
   777  
   778  	return &scanner
   779  }