diff --git a/cmd/harbor/root/cmd.go b/cmd/harbor/root/cmd.go index 8ff6bb73..4821e1c7 100644 --- a/cmd/harbor/root/cmd.go +++ b/cmd/harbor/root/cmd.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/goharbor/harbor-cli/cmd/harbor/root/artifact" + "github.com/goharbor/harbor-cli/cmd/harbor/root/cveallowlist" "github.com/goharbor/harbor-cli/cmd/harbor/root/project" "github.com/goharbor/harbor-cli/cmd/harbor/root/registry" repositry "github.com/goharbor/harbor-cli/cmd/harbor/root/repository" @@ -66,6 +67,7 @@ harbor help user.User(), artifact.Artifact(), HealthCommand(), + cveallowlist.CVEAllowlist(), schedule.Schedule(), ) diff --git a/cmd/harbor/root/cveallowlist/add.go b/cmd/harbor/root/cveallowlist/add.go new file mode 100644 index 00000000..749f0ec0 --- /dev/null +++ b/cmd/harbor/root/cveallowlist/add.go @@ -0,0 +1,46 @@ +package cveallowlist + +import ( + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/views/systemcve/update" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func AddCveAllowlistCommand() *cobra.Command { + var opts update.UpdateView + + cmd := &cobra.Command{ + Use: "add", + Short: "add cve allowlist", + Run: func(cmd *cobra.Command, args []string) { + var err error + updateView := &update.UpdateView{ + CveId: opts.CveId, + IsExpire: opts.IsExpire, + ExpireDate: opts.ExpireDate, + } + + err = updatecveView(updateView) + if err != nil { + log.Errorf("failed to add cveallowlist: %v", err) + } + }, + } + + flags := cmd.Flags() + flags.BoolVarP(&opts.IsExpire, "isexpire", "i", false, "Systemcve allowlist expire or not") + flags.StringVarP(&opts.CveId, "cveid", "n", "", "CVE ids seperate with commas") + flags.StringVarP(&opts.ExpireDate, "expiredate", "d", "", "If it expire,give Expiry date in the format MM/DD/YYYY") + + return cmd +} + +func updatecveView(updateView *update.UpdateView) error { + if updateView == nil { + updateView = &update.UpdateView{} + } + + update.UpdateCveView(updateView) + return api.UpdateSystemCve(*updateView) +} diff --git a/cmd/harbor/root/cveallowlist/cmd.go b/cmd/harbor/root/cveallowlist/cmd.go new file mode 100644 index 00000000..dd4a6b51 --- /dev/null +++ b/cmd/harbor/root/cveallowlist/cmd.go @@ -0,0 +1,20 @@ +package cveallowlist + +import ( + "github.com/spf13/cobra" +) + +func CVEAllowlist() *cobra.Command { + cmd := &cobra.Command{ + Use: "cve-allowlist", + Short: "Manage system CVE allowlist", + Long: `Manage system level allowlist of CVE`, + Example: `harbor cve-allowlist list`, + } + cmd.AddCommand( + ListCveCommand(), + AddCveAllowlistCommand(), + ) + + return cmd +} diff --git a/cmd/harbor/root/cveallowlist/list.go b/cmd/harbor/root/cveallowlist/list.go new file mode 100644 index 00000000..6d7272cf --- /dev/null +++ b/cmd/harbor/root/cveallowlist/list.go @@ -0,0 +1,32 @@ +package cveallowlist + +import ( + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/systemcve/list" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func ListCveCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "list system level allowlist of cve", + Run: func(cmd *cobra.Command, args []string) { + cve, err := api.ListSystemCve() + if err != nil { + log.Fatalf("failed to get system cve list: %v", err) + } + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + utils.PrintPayloadInJSONFormat(cve) + return + } + + list.ListSystemCve(cve.Payload) + }, + } + + return cmd +} diff --git a/pkg/api/systemcve_handler.go b/pkg/api/systemcve_handler.go new file mode 100644 index 00000000..94a7c6a8 --- /dev/null +++ b/pkg/api/systemcve_handler.go @@ -0,0 +1,59 @@ +package api + +import ( + "strings" + "time" + + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/system_cve_allowlist" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/systemcve/update" + log "github.com/sirupsen/logrus" +) + +func ListSystemCve() (system_cve_allowlist.GetSystemCVEAllowlistOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return system_cve_allowlist.GetSystemCVEAllowlistOK{}, err + } + + response, err := client.SystemCVEAllowlist.GetSystemCVEAllowlist(ctx, &system_cve_allowlist.GetSystemCVEAllowlistParams{}) + if err != nil { + return system_cve_allowlist.GetSystemCVEAllowlistOK{}, err + } + + return *response, nil +} + +func UpdateSystemCve(opts update.UpdateView) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return err + } + var unixTimestamp int64 + if opts.IsExpire { + expiresAt, err := time.Parse("2006/01/02", opts.ExpireDate) + if err != nil { + return err + } + unixTimestamp = expiresAt.Unix() + } else { + unixTimestamp = 0 + } + + var items []*models.CVEAllowlistItem + cveIds := strings.Split(opts.CveId, ",") + for _, id := range cveIds { + id = strings.TrimSpace(id) + items = append(items, &models.CVEAllowlistItem{CVEID: id}) + } + response, err := client.SystemCVEAllowlist.PutSystemCVEAllowlist(ctx, &system_cve_allowlist.PutSystemCVEAllowlistParams{Allowlist: &models.CVEAllowlist{Items: items, ExpiresAt: &unixTimestamp}}) + if err != nil { + return err + } + + if response != nil { + log.Info("cveallowlist added successfully") + } + return nil +} diff --git a/pkg/views/systemcve/list/view.go b/pkg/views/systemcve/list/view.go new file mode 100644 index 00000000..09e12063 --- /dev/null +++ b/pkg/views/systemcve/list/view.go @@ -0,0 +1,50 @@ +package list + +import ( + "fmt" + "os" + "strconv" + "time" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" +) + +var columns = []table.Column{ + {Title: "ID", Width: 6}, + {Title: "CVE Name", Width: 18}, + {Title: "Expires At", Width: 18}, + {Title: "Creation Time", Width: 24}, +} + +func ListSystemCve(systemcve *models.CVEAllowlist) { + var rows []table.Row + var expiresAtStr string + for _, cve := range systemcve.Items { + CveName := cve.CVEID + + if systemcve.ExpiresAt != nil && *systemcve.ExpiresAt != 0 { + expiresAt := time.Unix(int64(*systemcve.ExpiresAt), 0) + expiresAtStr = expiresAt.Format("01/02/2006") + } else { + expiresAtStr = "Never expires" + } + + createdTime, _ := utils.FormatCreatedTime(systemcve.CreationTime.String()) + rows = append(rows, table.Row{ + strconv.FormatInt(systemcve.ID, 10), + CveName, + expiresAtStr, + createdTime, + }) + } + m := tablelist.NewModel(columns, rows, len(rows)) + + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} diff --git a/pkg/views/systemcve/update/view.go b/pkg/views/systemcve/update/view.go new file mode 100644 index 00000000..adcb682d --- /dev/null +++ b/pkg/views/systemcve/update/view.go @@ -0,0 +1,55 @@ +package update + +import ( + "errors" + + "github.com/charmbracelet/huh" + log "github.com/sirupsen/logrus" +) + +type UpdateView struct { + CveId string + IsExpire bool + ExpireDate string +} + +func UpdateCveView(updateView *UpdateView) { + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + huh.NewInput(). + Title("CVE ID"). + Value(&updateView.CveId). + Description("CVE IDs are separator by commas"). + Validate(func(str string) error { + if str == "" { + return errors.New("cve id cannot be empty") + } + return nil + }), + huh.NewConfirm(). + Title("Expires"). + Value(&updateView.IsExpire). + Affirmative("Date"). + Negative("never"), + ), + huh.NewGroup( + huh.NewInput(). + Validate(func(str string) error { + if str == "" { + return errors.New("ExpireDate cannot be empty") + } + return nil + }). + Description("Expire Date in the format YYYY/MM/DD"). + Title("Expire Date"). + Value(&updateView.ExpireDate), + ).WithHideFunc(func() bool { + return !updateView.IsExpire + }), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } +}