From 15dd6b6516b48eb3cdc5c8417e0d93c135d6436f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Sun, 2 May 2021 14:05:00 -0500 Subject: [PATCH 1/2] Add cve.ValidateID and Client.EntryExists functions This commit adds two functions to the CVE package: `cve.ValidateID` checks CVE ids externally Client.EntryExists checks if we already have an entry for a specific CVE --- pkg/cve/client.go | 5 +++++ pkg/cve/cve.go | 25 ++++++++++++++++--------- pkg/cve/impl.go | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pkg/cve/client.go b/pkg/cve/client.go index a8bdbf58690..b3cbf849ffa 100644 --- a/pkg/cve/client.go +++ b/pkg/cve/client.go @@ -113,3 +113,8 @@ func (c *Client) CopyToTemp(cve string) (file *os.File, err error) { func (c *Client) CreateEmptyMap(cve string) (file *os.File, err error) { return c.impl.CreateEmptyFile(cve, &c.options) } + +// List return a list iof existing CVE entries +func (c *Client) EntryExists(cveID string) (bool, error) { + return c.impl.EntryExists(cveID, &c.options) +} diff --git a/pkg/cve/cve.go b/pkg/cve/cve.go index 21652583bf9..8997bffa02e 100644 --- a/pkg/cve/cve.go +++ b/pkg/cve/cve.go @@ -107,15 +107,8 @@ func (cve *CVE) Validate() error { return errors.New("CVSS score out of range, should be 0.0 - 10.0") } - // Check that the CVE ID is not empty - if cve.ID == "" { - return errors.New("ID missing from CVE data") - } - - // Verify that the CVE ID is well formed - cvsre := regexp.MustCompile(CVEIDRegExp) - if !cvsre.MatchString(cve.ID) { - return errors.New("CVS ID is not well formed") + if err := ValidateID(cve.ID); err != nil { + return errors.Wrap(err, "checking CVE ID") } // Title and description must not be empty @@ -129,3 +122,17 @@ func (cve *CVE) Validate() error { return nil } + +// ValidateID checks if a CVE IS string is valid +func ValidateID(cveID string) error { + if cveID == "" { + return errors.New("CVE ID string is empty") + } + + // Verify that the CVE ID is well formed + if !regexp.MustCompile(CVEIDRegExp).MatchString(cveID) { + return errors.New("CVS ID is not well formed") + } + + return nil +} diff --git a/pkg/cve/impl.go b/pkg/cve/impl.go index f6c385a0ab5..4376654881f 100644 --- a/pkg/cve/impl.go +++ b/pkg/cve/impl.go @@ -42,6 +42,7 @@ type ClientImplementation interface { CopyToTemp(string, *ClientOptions) (*os.File, error) ValidateCVEMap(string, string, *ClientOptions) error CreateEmptyFile(string, *ClientOptions) (*os.File, error) + EntryExists(string, *ClientOptions) (bool, error) } // defaultClientImplementation @@ -262,3 +263,26 @@ func (impl *defaultClientImplementation) CreateEmptyFile(cve string, opts *Clien return file, nil } + +// EntryExists returns true if a CVE already exists +func (impl *defaultClientImplementation) EntryExists( + cveID string, opts *ClientOptions, +) (exists bool, err error) { + // Check the ID string to be valid + if err := ValidateID(cveID); err != nil { + return exists, errors.Wrap(err, "checking CVE ID string") + } + + // Verify the expected file exists in the bucket + gcs := object.NewGCS() + // Normalizar the path to the CVE + path, err := gcs.NormalizePath( + object.GcsPrefix + filepath.Join( + opts.Bucket, opts.Directory, cveID+mapExt, + ), + ) + if err != nil { + return exists, errors.Wrap(err, "checking if CVE entry already exists") + } + return gcs.PathExists(path) +} From 7e46f1970ce87f43c66eb2516ca79d4f34da7a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Garc=C3=ADa=20Veytia=20=28Puerco=29?= Date: Sun, 2 May 2021 14:06:24 -0500 Subject: [PATCH 2/2] Drop krel cve write command This command drops the previous cve write command and modifies `krel cve edit` to open a new CVE if it does not exist in the bucket yet. --- cmd/krel/cmd/cve.go | 105 ++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/cmd/krel/cmd/cve.go b/cmd/krel/cmd/cve.go index a727855f6ab..dd884f1d441 100644 --- a/cmd/krel/cmd/cve.go +++ b/cmd/krel/cmd/cve.go @@ -46,25 +46,6 @@ release bucket. See each subcommand for more information. SilenceErrors: false, } -var cveWriteCmd = &cobra.Command{ - Use: "write", - Short: "Write a new CVE vulnerability entry", - Long: `Adds or updates a CVE map file. The write subcommand will open a new empty CVE data -map in the user's editor of choice. When saved, krel will verify the entry and uploaded -to the release bucket. - -A non interactive mode for use in scripts, and processes can be invoked by -passing the write subcommand a datamap using the --file flag. In this case, krel will -verify the map and if the CVE data is correct, it will upload the data to the bucket -`, - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - return writeCVE(cveOpts) - }, - Args: argFunc, -} - var cveDeleteCmd = &cobra.Command{ Use: "delete", Short: "Delete an existing cve map", @@ -79,8 +60,8 @@ var cveDeleteCmd = &cobra.Command{ var cveEditCmd = &cobra.Command{ Use: "edit", - Short: "Edit an already published CVE map file", - Long: `The edit command pulls a CVE map which has already been published and + Short: "Edit a CVE map file", + Long: `The edit command opens an editor pulls a CVE map which has already been published and opens it for editing in the user's editor of choice (defined by the $EDITOR or $KUBEEDITOR env vars). When saving and exiting the editor, krel will check the new CVE entry and upload it to the release bucket. @@ -123,43 +104,46 @@ func init() { "version tag for the notes", ) - cveCmd.AddCommand(cveWriteCmd, cveEditCmd, cveDeleteCmd) + cveCmd.AddCommand(cveEditCmd, cveDeleteCmd) rootCmd.AddCommand(cveCmd) } -func writeCVE(opts *cveOptions) (err error) { +// writeNewCVE opens an editor to edit a new CVE entry interactively +func writeNewCVE(opts *cveOptions) (err error) { client := cve.NewClient() - if len(opts.mapFiles) == 0 { - file, err := client.CreateEmptyMap(opts.CVE) - if err != nil { - return errors.Wrap(err, "creating new cve data map") - } + file, err := client.CreateEmptyMap(opts.CVE) + if err != nil { + return errors.Wrap(err, "creating new cve data map") + } - oldFile, err := os.ReadFile(file.Name()) - if err != nil { - return errors.Wrap(err, "reading local copy of CVE entry") - } + oldFile, err := os.ReadFile(file.Name()) + if err != nil { + return errors.Wrap(err, "reading local copy of CVE entry") + } - kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"}) - changes, tempFilePath, err := kubeEditor.LaunchTempFile( - "cve-datamap-", ".yaml", bytes.NewReader(oldFile), - ) - if err != nil { - return errors.Wrap(err, "launching editor") - } + kubeEditor := editor.NewDefaultEditor([]string{"KUBE_EDITOR", "EDITOR"}) + changes, tempFilePath, err := kubeEditor.LaunchTempFile( + "cve-datamap-", ".yaml", bytes.NewReader(oldFile), + ) + if err != nil { + return errors.Wrap(err, "launching editor") + } - if string(changes) == string(oldFile) || string(changes) == "" { - logrus.Info("CVE information not modified") - return nil - } + if string(changes) == string(oldFile) || string(changes) == "" { + logrus.Info("CVE information not modified") + return nil + } - logrus.Infof("Creating %s entry", opts.CVE) + logrus.Infof("Creating %s entry", opts.CVE) - // If the file was changed, re-write it: - return client.Write(opts.CVE, tempFilePath) - } + // If the file was changed, re-write it: + return client.Write(opts.CVE, tempFilePath) +} +// writeCVEFiles handles non interactive file writes +func writeCVEFiles(opts *cveOptions) error { + client := cve.NewClient() for _, mapFile := range opts.mapFiles { if err := client.Write(opts.CVE, mapFile); err != nil { return errors.Wrapf(err, "writing map file %s", mapFile) @@ -168,14 +152,39 @@ func writeCVE(opts *cveOptions) (err error) { return nil } +// deleteCVE removes an existing map file func deleteCVE(opts *cveOptions) (err error) { client := cve.NewClient() return client.Delete(opts.CVE) } -// editCVE +// editCVE main edit funcion func editCVE(opts *cveOptions) (err error) { client := cve.NewClient() + + // If yaml files were specified, skip the interactive mode + if len(opts.mapFiles) != 0 { + return writeCVEFiles(opts) + } + + // If we're editing interactively, check if it is a new CVE + // or we should first pull the data from the bucket + exists, err := client.EntryExists(opts.CVE) + if err != nil { + return errors.Wrap(err, "checking if cve entry exists") + } + + if exists { + return editExistingCVE(opts) + } + + return writeNewCVE(opts) +} + +// editExistingCVE loads an existing map from the bucket and opens is +// in the user's default editor +func editExistingCVE(opts *cveOptions) (err error) { + client := cve.NewClient() file, err := client.CopyToTemp(opts.CVE) if err != nil { return errors.Wrap(err, "copying CVE entry for edting")