mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +08:00
fix(CVE): attempt to scan now returns early with an error if trivyDB metadata json is missing (#1548)
Also modify zli to retry in case of such errors, assuming the trivyDB will eventually be downloaded by the scheduled task. Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
This commit is contained in:
+38
-4
@@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -14,6 +16,10 @@ import (
|
||||
zotErrors "zotregistry.io/zot/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
cveDBRetryInterval = 3
|
||||
)
|
||||
|
||||
func NewCveCommand(searchService SearchService) *cobra.Command {
|
||||
searchCveParams := make(map[string]*string)
|
||||
|
||||
@@ -148,13 +154,41 @@ func searchCve(searchConfig searchConfig) error {
|
||||
}
|
||||
|
||||
for _, searcher := range searchers {
|
||||
found, err := searcher.search(searchConfig)
|
||||
if found {
|
||||
if err != nil {
|
||||
// there can be CVE DB readyness issues on the server side
|
||||
// we need a retry mechanism for that specific type of errors
|
||||
maxAttempts := 20
|
||||
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
found, err := searcher.search(searchConfig)
|
||||
if !found {
|
||||
// searcher does not support this searchConfig
|
||||
// exit the attempts loop and try a different searcher
|
||||
break
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// searcher matcher search config and results are already printed
|
||||
return nil
|
||||
}
|
||||
|
||||
if i+1 >= maxAttempts {
|
||||
// searcher matches search config but there are errors
|
||||
// this is the last attempt and we cannot retry
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if strings.Contains(err.Error(), zotErrors.ErrCVEDBNotFound.Error()) {
|
||||
// searches matches search config but CVE DB is not ready server side
|
||||
// wait and retry a few more times
|
||||
fmt.Fprintln(searchConfig.resultWriter,
|
||||
"[warning] CVE DB is not ready [", i, "] - retry in ", cveDBRetryInterval, " seconds")
|
||||
time.Sleep(cveDBRetryInterval * time.Second)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// an unrecoverable error occurred
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -287,6 +289,48 @@ func TestSearchCVECmd(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Test images by CVE ID - positive with retries", t, func() {
|
||||
args := []string{"cvetest", "--cve-id", "aCVEID", "--url", "someURL"}
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||
defer os.Remove(configPath)
|
||||
mockService := mockServiceForRetry{succeedOn: 2} // CVE info will be provided in 2nd attempt
|
||||
cveCmd := NewCveCommand(&mockService)
|
||||
buff := bytes.NewBufferString("")
|
||||
cveCmd.SetOut(buff)
|
||||
cveCmd.SetErr(buff)
|
||||
cveCmd.SetArgs(args)
|
||||
err := cveCmd.Execute()
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
t.Logf("Output: %s", str)
|
||||
So(strings.TrimSpace(str), ShouldContainSubstring,
|
||||
"[warning] CVE DB is not ready [ 0 ] - retry in "+strconv.Itoa(cveDBRetryInterval)+" seconds")
|
||||
So(strings.TrimSpace(str), ShouldContainSubstring,
|
||||
"IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Test images by CVE ID - failed after retries", t, func() {
|
||||
args := []string{"cvetest", "--cve-id", "aCVEID", "--url", "someURL"}
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||
defer os.Remove(configPath)
|
||||
mockService := mockServiceForRetry{succeedOn: -1} // CVE info will be unavailable on all retries
|
||||
cveCmd := NewCveCommand(&mockService)
|
||||
buff := bytes.NewBufferString("")
|
||||
cveCmd.SetOut(buff)
|
||||
cveCmd.SetErr(buff)
|
||||
cveCmd.SetArgs(args)
|
||||
err := cveCmd.Execute()
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
t.Logf("Output: %s", str)
|
||||
So(strings.TrimSpace(str), ShouldContainSubstring,
|
||||
"[warning] CVE DB is not ready [ 0 ] - retry in "+strconv.Itoa(cveDBRetryInterval)+" seconds")
|
||||
So(strings.TrimSpace(str), ShouldNotContainSubstring,
|
||||
"IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Test images by CVE ID - invalid CVE ID", t, func() {
|
||||
args := []string{"cvetest", "--cve-id", "invalidCVEID"}
|
||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||
@@ -1222,3 +1266,26 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||
RepoDB: repoDB,
|
||||
}
|
||||
}
|
||||
|
||||
type mockServiceForRetry struct {
|
||||
mockService
|
||||
retryCounter int
|
||||
succeedOn int
|
||||
}
|
||||
|
||||
func (service *mockServiceForRetry) getImagesByCveID(ctx context.Context, config searchConfig,
|
||||
username, password, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
||||
) {
|
||||
service.retryCounter += 1
|
||||
|
||||
if service.retryCounter < service.succeedOn || service.succeedOn < 0 {
|
||||
rch <- stringResult{"", zotErrors.ErrCVEDBNotFound}
|
||||
close(rch)
|
||||
|
||||
wtgrp.Done()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
|
||||
}
|
||||
|
||||
+1
-1
@@ -943,7 +943,7 @@ func isContextDone(ctx context.Context) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Query using JQL, the query string is passed as a parameter
|
||||
// Query using GQL, the query string is passed as a parameter
|
||||
// errors are returned in the stringResult channel, the unmarshalled payload is in resultPtr.
|
||||
func (service searchService) makeGraphQLQuery(ctx context.Context,
|
||||
config searchConfig, username, password, query string,
|
||||
|
||||
Reference in New Issue
Block a user