0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/cli/cve_cmd.go
Andrei Aaron 7fee57e7cc
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>
2023-07-05 09:08:16 -07:00

196 lines
5 KiB
Go

//go:build search
// +build search
package cli
import (
"fmt"
"os"
"path"
"strings"
"time"
"github.com/briandowns/spinner"
"github.com/spf13/cobra"
zotErrors "zotregistry.io/zot/errors"
)
const (
cveDBRetryInterval = 3
)
func NewCveCommand(searchService SearchService) *cobra.Command {
searchCveParams := make(map[string]*string)
var servURL, user, outputFormat string
var isSpinner, verifyTLS, fixedFlag, verbose, debug bool
cveCmd := &cobra.Command{
Use: "cve [config-name]",
Short: "Lookup CVEs in images hosted on the zot registry",
Long: `List CVEs (Common Vulnerabilities and Exposures) of images hosted on the zot registry`,
RunE: func(cmd *cobra.Command, args []string) error {
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
configPath := path.Join(home + "/.zot")
if servURL == "" {
if len(args) > 0 {
urlFromConfig, err := getConfigValue(configPath, args[0], "url")
if err != nil {
cmd.SilenceUsage = true
return err
}
if urlFromConfig == "" {
return zotErrors.ErrNoURLProvided
}
servURL = urlFromConfig
} else {
return zotErrors.ErrNoURLProvided
}
}
if len(args) > 0 {
var err error
isSpinner, err = parseBooleanConfig(configPath, args[0], showspinnerConfig)
if err != nil {
cmd.SilenceUsage = true
return err
}
verifyTLS, err = parseBooleanConfig(configPath, args[0], verifyTLSConfig)
if err != nil {
cmd.SilenceUsage = true
return err
}
}
spin := spinner.New(spinner.CharSets[39], spinnerDuration, spinner.WithWriter(cmd.ErrOrStderr()))
spin.Prefix = fmt.Sprintf("Fetching from %s..", servURL)
spin.Suffix = "\n\b"
verbose = false
searchConfig := searchConfig{
params: searchCveParams,
searchService: searchService,
servURL: &servURL,
user: &user,
outputFormat: &outputFormat,
fixedFlag: &fixedFlag,
verifyTLS: &verifyTLS,
verbose: &verbose,
debug: &debug,
resultWriter: cmd.OutOrStdout(),
spinner: spinnerState{spin, isSpinner},
}
err = searchCve(searchConfig)
if err != nil {
cmd.SilenceUsage = true
return err
}
return nil
},
}
vars := cveFlagVariables{
searchCveParams: searchCveParams,
servURL: &servURL,
user: &user,
outputFormat: &outputFormat,
fixedFlag: &fixedFlag,
debug: &debug,
}
setupCveFlags(cveCmd, vars)
return cveCmd
}
func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) {
variables.searchCveParams["imageName"] = cveCmd.Flags().StringP("image", "I", "", "List CVEs by IMAGENAME[:TAG]")
variables.searchCveParams["cveID"] = cveCmd.Flags().StringP("cve-id", "i", "", "List images affected by a CVE")
variables.searchCveParams["searchedCVE"] = cveCmd.Flags().StringP("search", "s", "", "Search specific CVEs by name/id")
cveCmd.Flags().StringVar(variables.servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+
`zot server in USERNAME:PASSWORD format`)
cveCmd.Flags().StringVarP(variables.outputFormat, "output", "o", "", "Specify output format [text/json/yaml]."+
" JSON and YAML format return all info for CVEs")
cveCmd.Flags().BoolVar(variables.fixedFlag, "fixed", false, "List tags which have fixed a CVE")
cveCmd.Flags().BoolVar(variables.debug, "debug", false, "Show debug output")
}
type cveFlagVariables struct {
searchCveParams map[string]*string
servURL *string
user *string
outputFormat *string
fixedFlag *bool
debug *bool
}
func searchCve(searchConfig searchConfig) error {
var searchers []searcher
if checkExtEndPoint(searchConfig) {
searchers = getCveSearchersGQL()
} else {
searchers = getCveSearchers()
}
for _, searcher := range searchers {
// 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
}
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
}
}
return zotErrors.ErrInvalidFlagsCombination
}