Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 57 additions & 48 deletions cmd/krel/cmd/cve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions pkg/cve/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
25 changes: 16 additions & 9 deletions pkg/cve/cve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
24 changes: 24 additions & 0 deletions pkg/cve/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}