From 9c8ca5c73a55e5c69cdf763981a66ecff49b056d Mon Sep 17 00:00:00 2001 From: AlteredCoder <64792091+AlteredCoder@users.noreply.github.com> Date: Wed, 29 Dec 2021 14:08:47 +0100 Subject: [PATCH] Alert inspect improvement / Use correct CSV output when listing in raw format (#1127) --- cmd/crowdsec-cli/alerts.go | 55 +++++++++++++++++-------------- cmd/crowdsec-cli/bouncers.go | 12 ++++++- cmd/crowdsec-cli/collections.go | 2 +- cmd/crowdsec-cli/decisions.go | 26 ++++++++++----- cmd/crowdsec-cli/hub.go | 8 ++--- cmd/crowdsec-cli/machines.go | 13 ++++++-- cmd/crowdsec-cli/parsers.go | 2 +- cmd/crowdsec-cli/postoverflows.go | 2 +- cmd/crowdsec-cli/scenarios.go | 2 +- cmd/crowdsec-cli/utils.go | 34 +++++++++++++++++-- 10 files changed, 109 insertions(+), 47 deletions(-) diff --git a/cmd/crowdsec-cli/alerts.go b/cmd/crowdsec-cli/alerts.go index 54b9972bb..5d96c71ca 100644 --- a/cmd/crowdsec-cli/alerts.go +++ b/cmd/crowdsec-cli/alerts.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/csv" "encoding/json" "fmt" "net/url" @@ -48,36 +49,38 @@ func DecisionsFromAlert(alert *models.Alert) string { func AlertsToTable(alerts *models.GetAlertsResponse, printMachine bool) error { if csConfig.Cscli.Output == "raw" { + csvwriter := csv.NewWriter(os.Stdout) if printMachine { - fmt.Printf("id,scope,value,reason,country,as,decisions,created_at,machine\n") + err := csvwriter.Write([]string{"id", "scope", "value", "reason", "country", "as", "decisions", "created_at", "machine"}) + if err != nil { + return err + } } else { - fmt.Printf("id,scope,value,reason,country,as,decisions,created_at\n") + err := csvwriter.Write([]string{"id", "scope", "value", "reason", "country", "as", "decisions", "created_at"}) + if err != nil { + return err + } } for _, alertItem := range *alerts { - if printMachine { - fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v,%v\n", - alertItem.ID, - *alertItem.Source.Scope, - *alertItem.Source.Value, - *alertItem.Scenario, - alertItem.Source.Cn, - alertItem.Source.AsNumber+" "+alertItem.Source.AsName, - DecisionsFromAlert(alertItem), - *alertItem.StartAt, - alertItem.MachineID) - } else { - fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v\n", - alertItem.ID, - *alertItem.Source.Scope, - *alertItem.Source.Value, - *alertItem.Scenario, - alertItem.Source.Cn, - alertItem.Source.AsNumber+" "+alertItem.Source.AsName, - DecisionsFromAlert(alertItem), - *alertItem.StartAt) + row := []string{ + fmt.Sprintf("%d", alertItem.ID), + *alertItem.Source.Scope, + *alertItem.Source.Value, + *alertItem.Scenario, + alertItem.Source.Cn, + alertItem.Source.AsNumber + " " + alertItem.Source.AsName, + DecisionsFromAlert(alertItem), + *alertItem.StartAt, + } + if printMachine { + row = append(row, alertItem.MachineID) + } + err := csvwriter.Write(row) + if err != nil { + return err } - } + csvwriter.Flush() } else if csConfig.Cscli.Output == "json" { x, _ := json.MarshalIndent(alerts, "", " ") fmt.Printf("%s", string(x)) @@ -143,7 +146,9 @@ func DisplayOneAlert(alert *models.Alert, withDetail bool) error { fmt.Printf(" - Events Count : %d\n", *alert.EventsCount) fmt.Printf(" - Scope:Value: %s\n", scopeAndValue) fmt.Printf(" - Country : %s\n", alert.Source.Cn) - fmt.Printf(" - AS : %s\n\n", alert.Source.AsName) + fmt.Printf(" - AS : %s\n", alert.Source.AsName) + fmt.Printf(" - Begin : %s\n", *alert.StartAt) + fmt.Printf(" - End : %s\n\n", *alert.StopAt) foundActive := false table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{"ID", "scope:value", "action", "expiration", "created_at"}) diff --git a/cmd/crowdsec-cli/bouncers.go b/cmd/crowdsec-cli/bouncers.go index 41491b22f..1005f17ad 100644 --- a/cmd/crowdsec-cli/bouncers.go +++ b/cmd/crowdsec-cli/bouncers.go @@ -1,6 +1,7 @@ package main import ( + "encoding/csv" "encoding/json" "fmt" "os" @@ -82,6 +83,11 @@ Note: This command requires database direct access, so is intended to be run on } fmt.Printf("%s", string(x)) } else if csConfig.Cscli.Output == "raw" { + csvwriter := csv.NewWriter(os.Stdout) + err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version"}) + if err != nil { + log.Fatalf("failed to write raw header: %s", err) + } for _, b := range blockers { var revoked string if !b.Revoked { @@ -89,8 +95,12 @@ Note: This command requires database direct access, so is intended to be run on } else { revoked = "pending" } - fmt.Printf("%s,%s,%s,%s,%s\n", b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Version) + err := csvwriter.Write([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version}) + if err != nil { + log.Fatalf("failed to write raw: %s", err) + } } + csvwriter.Flush() } }, } diff --git a/cmd/crowdsec-cli/collections.go b/cmd/crowdsec-cli/collections.go index 2b4f27542..5eebc9b1d 100644 --- a/cmd/crowdsec-cli/collections.go +++ b/cmd/crowdsec-cli/collections.go @@ -139,7 +139,7 @@ func NewCollectionsCmd() *cobra.Command { Args: cobra.ExactArgs(0), DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { - ListItem(cwhub.COLLECTIONS, args) + ListItem(cwhub.COLLECTIONS, args, false, true) }, } cmdCollectionsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well disabled items") diff --git a/cmd/crowdsec-cli/decisions.go b/cmd/crowdsec-cli/decisions.go index c487bbf7f..b06879f15 100644 --- a/cmd/crowdsec-cli/decisions.go +++ b/cmd/crowdsec-cli/decisions.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/csv" "encoding/json" "fmt" "net/url" @@ -52,23 +53,32 @@ func DecisionsToTable(alerts *models.GetAlertsResponse) error { alertItem.Decisions = newDecisions } if csConfig.Cscli.Output == "raw" { - fmt.Printf("id,source,ip,reason,action,country,as,events_count,expiration,simulated,alert_id\n") + csvwriter := csv.NewWriter(os.Stdout) + err := csvwriter.Write([]string{"id", "source", "ip", "reason", "action", "country", "as", "events_count", "expiration", "simulated", "alert_id"}) + if err != nil { + return err + } for _, alertItem := range *alerts { for _, decisionItem := range alertItem.Decisions { - fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v\n", - decisionItem.ID, + err := csvwriter.Write([]string{ + fmt.Sprintf("%d", decisionItem.ID), *decisionItem.Origin, - *decisionItem.Scope+":"+*decisionItem.Value, + *decisionItem.Scope + ":" + *decisionItem.Value, *decisionItem.Scenario, *decisionItem.Type, alertItem.Source.Cn, - alertItem.Source.AsNumber+" "+alertItem.Source.AsName, - *alertItem.EventsCount, + alertItem.Source.AsNumber + " " + alertItem.Source.AsName, + fmt.Sprintf("%d", *alertItem.EventsCount), *decisionItem.Duration, - *decisionItem.Simulated, - alertItem.ID) + fmt.Sprintf("%t", *decisionItem.Simulated), + fmt.Sprintf("%d", alertItem.ID), + }) + if err != nil { + return err + } } } + csvwriter.Flush() } else if csConfig.Cscli.Output == "json" { x, _ := json.MarshalIndent(alerts, "", " ") fmt.Printf("%s", string(x)) diff --git a/cmd/crowdsec-cli/hub.go b/cmd/crowdsec-cli/hub.go index 8b2c6d5f4..21bcfd7f6 100644 --- a/cmd/crowdsec-cli/hub.go +++ b/cmd/crowdsec-cli/hub.go @@ -57,13 +57,13 @@ cscli hub update # Download list of available configurations from the hub } cwhub.DisplaySummary() log.Printf("PARSERS:") - ListItem(cwhub.PARSERS, args) + ListItem(cwhub.PARSERS, args, true, true) log.Printf("SCENARIOS:") - ListItem(cwhub.SCENARIOS, args) + ListItem(cwhub.SCENARIOS, args, true, false) log.Printf("COLLECTIONS:") - ListItem(cwhub.COLLECTIONS, args) + ListItem(cwhub.COLLECTIONS, args, true, false) log.Printf("POSTOVERFLOWS:") - ListItem(cwhub.PARSERS_OVFLW, args) + ListItem(cwhub.PARSERS_OVFLW, args, true, false) }, } cmdHubList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well disabled items") diff --git a/cmd/crowdsec-cli/machines.go b/cmd/crowdsec-cli/machines.go index 7e3a69351..2d7a9241b 100644 --- a/cmd/crowdsec-cli/machines.go +++ b/cmd/crowdsec-cli/machines.go @@ -2,6 +2,7 @@ package main import ( saferand "crypto/rand" + "encoding/csv" "encoding/json" "fmt" "io/ioutil" @@ -144,7 +145,11 @@ Note: This command requires database direct access, so is intended to be run on } fmt.Printf("%s", string(x)) } else if csConfig.Cscli.Output == "raw" { - fmt.Printf("machine_id,ip_address,updated_at,validated,version\n") + csvwriter := csv.NewWriter(os.Stdout) + err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version"}) + if err != nil { + log.Fatalf("failed to write header: %s", err) + } for _, w := range machines { var validated string if w.IsValidated { @@ -152,8 +157,12 @@ Note: This command requires database direct access, so is intended to be run on } else { validated = "false" } - fmt.Printf("%s,%s,%s,%s,%s\n", w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version) + err := csvwriter.Write([]string{w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version}) + if err != nil { + log.Fatalf("failed to write raw output : %s", err) + } } + csvwriter.Flush() } else { log.Errorf("unknown output '%s'", csConfig.Cscli.Output) } diff --git a/cmd/crowdsec-cli/parsers.go b/cmd/crowdsec-cli/parsers.go index 138897814..2235bd8a1 100644 --- a/cmd/crowdsec-cli/parsers.go +++ b/cmd/crowdsec-cli/parsers.go @@ -132,7 +132,7 @@ cscli parsers remove crowdsecurity/sshd-logs cscli parser list crowdsecurity/xxx`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { - ListItem(cwhub.PARSERS, args) + ListItem(cwhub.PARSERS, args, false, true) }, } cmdParsersList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well disabled items") diff --git a/cmd/crowdsec-cli/postoverflows.go b/cmd/crowdsec-cli/postoverflows.go index df7d584c4..8c38fe880 100644 --- a/cmd/crowdsec-cli/postoverflows.go +++ b/cmd/crowdsec-cli/postoverflows.go @@ -130,7 +130,7 @@ func NewPostOverflowsCmd() *cobra.Command { cscli postoverflows list crowdsecurity/xxx`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { - ListItem(cwhub.PARSERS_OVFLW, args) + ListItem(cwhub.PARSERS_OVFLW, args, false, true) }, } cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well disabled items") diff --git a/cmd/crowdsec-cli/scenarios.go b/cmd/crowdsec-cli/scenarios.go index 024825b10..059c01f99 100644 --- a/cmd/crowdsec-cli/scenarios.go +++ b/cmd/crowdsec-cli/scenarios.go @@ -132,7 +132,7 @@ cscli scenarios remove crowdsecurity/ssh-bf cscli scenarios list crowdsecurity/xxx`, DisableAutoGenTag: true, Run: func(cmd *cobra.Command, args []string) { - ListItem(cwhub.SCENARIOS, args) + ListItem(cwhub.SCENARIOS, args, false, true) }, } cmdScenariosList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well disabled items") diff --git a/cmd/crowdsec-cli/utils.go b/cmd/crowdsec-cli/utils.go index 256a5ba13..071831b6b 100644 --- a/cmd/crowdsec-cli/utils.go +++ b/cmd/crowdsec-cli/utils.go @@ -1,6 +1,7 @@ package main import ( + "encoding/csv" "encoding/json" "fmt" "io/ioutil" @@ -101,7 +102,7 @@ func setHubBranch() error { return nil } -func ListItem(itemType string, args []string) { +func ListItem(itemType string, args []string, showType bool, showHeader bool) { var hubStatus []map[string]string @@ -131,13 +132,40 @@ func ListItem(itemType string, args []string) { } fmt.Printf("%s", string(x)) } else if csConfig.Cscli.Output == "raw" { - fmt.Printf("name,status,version,description\n") + csvwriter := csv.NewWriter(os.Stdout) + if showHeader { + if showType { + err := csvwriter.Write([]string{"name", "status", "version", "description", "type"}) + if err != nil { + log.Fatalf("failed to write header: %s", err) + } + } else { + err := csvwriter.Write([]string{"name", "status", "version", "description"}) + if err != nil { + log.Fatalf("failed to write header: %s", err) + } + } + + } for _, v := range hubStatus { if v["local_version"] == "" { v["local_version"] = "n/a" } - fmt.Printf("%s,%s,%s,%s\n", v["name"], v["status"], v["local_version"], v["description"]) + row := []string{ + v["name"], + v["status"], + v["local_version"], + v["description"], + } + if showType { + row = append(row, itemType) + } + err := csvwriter.Write(row) + if err != nil { + log.Fatalf("failed to write raw output : %s", err) + } } + csvwriter.Flush() } }