cscli: new tables, --color yes|no|auto option (#1763)
This commit is contained in:
parent
b95a67751e
commit
ddd75eae9a
|
@ -7,21 +7,20 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var printMachine bool
|
var printMachine bool
|
||||||
|
@ -83,39 +82,11 @@ func AlertsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
|
||||||
x, _ := json.MarshalIndent(alerts, "", " ")
|
x, _ := json.MarshalIndent(alerts, "", " ")
|
||||||
fmt.Printf("%s", string(x))
|
fmt.Printf("%s", string(x))
|
||||||
} else if csConfig.Cscli.Output == "human" {
|
} else if csConfig.Cscli.Output == "human" {
|
||||||
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
|
||||||
header := []string{"ID", "value", "reason", "country", "as", "decisions", "created_at"}
|
|
||||||
if printMachine {
|
|
||||||
header = append(header, "machine")
|
|
||||||
}
|
|
||||||
table.SetHeader(header)
|
|
||||||
|
|
||||||
if len(*alerts) == 0 {
|
if len(*alerts) == 0 {
|
||||||
fmt.Println("No active alerts")
|
fmt.Println("No active alerts")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, alertItem := range *alerts {
|
alertsTable(colorable.NewColorableStdout(), alerts, printMachine)
|
||||||
|
|
||||||
displayVal := *alertItem.Source.Scope
|
|
||||||
if *alertItem.Source.Value != "" {
|
|
||||||
displayVal += ":" + *alertItem.Source.Value
|
|
||||||
}
|
|
||||||
row := []string{
|
|
||||||
strconv.Itoa(int(alertItem.ID)),
|
|
||||||
displayVal,
|
|
||||||
*alertItem.Scenario,
|
|
||||||
alertItem.Source.Cn,
|
|
||||||
alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
|
|
||||||
DecisionsFromAlert(alertItem),
|
|
||||||
*alertItem.StartAt,
|
|
||||||
}
|
|
||||||
if printMachine {
|
|
||||||
row = append(row, alertItem.MachineID)
|
|
||||||
}
|
|
||||||
table.Append(row)
|
|
||||||
}
|
|
||||||
table.Render() // Send output
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -138,53 +109,13 @@ func DisplayOneAlert(alert *models.Alert, withDetail bool) error {
|
||||||
fmt.Printf(" - AS : %s\n", alert.Source.AsName)
|
fmt.Printf(" - AS : %s\n", alert.Source.AsName)
|
||||||
fmt.Printf(" - Begin : %s\n", *alert.StartAt)
|
fmt.Printf(" - Begin : %s\n", *alert.StartAt)
|
||||||
fmt.Printf(" - End : %s\n\n", *alert.StopAt)
|
fmt.Printf(" - End : %s\n\n", *alert.StopAt)
|
||||||
foundActive := false
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
alertDecisionsTable(colorable.NewColorableStdout(), alert)
|
||||||
table.SetHeader([]string{"ID", "scope:value", "action", "expiration", "created_at"})
|
|
||||||
for _, decision := range alert.Decisions {
|
|
||||||
parsedDuration, err := time.ParseDuration(*decision.Duration)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(err.Error())
|
|
||||||
}
|
|
||||||
expire := time.Now().UTC().Add(parsedDuration)
|
|
||||||
if time.Now().UTC().After(expire) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
foundActive = true
|
|
||||||
scopeAndValue := *decision.Scope
|
|
||||||
if *decision.Value != "" {
|
|
||||||
scopeAndValue += ":" + *decision.Value
|
|
||||||
}
|
|
||||||
table.Append([]string{
|
|
||||||
strconv.Itoa(int(decision.ID)),
|
|
||||||
scopeAndValue,
|
|
||||||
*decision.Type,
|
|
||||||
*decision.Duration,
|
|
||||||
alert.CreatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if foundActive {
|
|
||||||
fmt.Printf(" - Active Decisions :\n")
|
|
||||||
table.Render() // Send output
|
|
||||||
}
|
|
||||||
|
|
||||||
if withDetail {
|
if withDetail {
|
||||||
fmt.Printf("\n - Events :\n")
|
fmt.Printf("\n - Events :\n")
|
||||||
for _, event := range alert.Events {
|
for _, event := range alert.Events {
|
||||||
fmt.Printf("\n- Date: %s\n", *event.Timestamp)
|
alertEventTable(colorable.NewColorableStdout(), event)
|
||||||
table = tablewriter.NewWriter(os.Stdout)
|
|
||||||
table.SetHeader([]string{"Key", "Value"})
|
|
||||||
sort.Slice(event.Meta, func(i, j int) bool {
|
|
||||||
return event.Meta[i].Key < event.Meta[j].Key
|
|
||||||
})
|
|
||||||
for _, meta := range event.Meta {
|
|
||||||
table.Append([]string{
|
|
||||||
meta.Key,
|
|
||||||
meta.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
table.Render() // Send output
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
cmd/crowdsec-cli/alerts_table.go
Normal file
100
cmd/crowdsec-cli/alerts_table.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func alertsTable(out io.Writer, alerts *models.GetAlertsResponse, printMachine bool) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
header := []string{"ID", "value", "reason", "country", "as", "decisions", "created_at"}
|
||||||
|
if printMachine {
|
||||||
|
header = append(header, "machine")
|
||||||
|
}
|
||||||
|
t.SetHeaders(header...)
|
||||||
|
|
||||||
|
for _, alertItem := range *alerts {
|
||||||
|
displayVal := *alertItem.Source.Scope
|
||||||
|
if *alertItem.Source.Value != "" {
|
||||||
|
displayVal += ":" + *alertItem.Source.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
row := []string{
|
||||||
|
strconv.Itoa(int(alertItem.ID)),
|
||||||
|
displayVal,
|
||||||
|
*alertItem.Scenario,
|
||||||
|
alertItem.Source.Cn,
|
||||||
|
alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
|
||||||
|
DecisionsFromAlert(alertItem),
|
||||||
|
*alertItem.StartAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if printMachine {
|
||||||
|
row = append(row, alertItem.MachineID)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(row...)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertDecisionsTable(out io.Writer, alert *models.Alert) {
|
||||||
|
foundActive := false
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("ID", "scope:value", "action", "expiration", "created_at")
|
||||||
|
for _, decision := range alert.Decisions {
|
||||||
|
parsedDuration, err := time.ParseDuration(*decision.Duration)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
expire := time.Now().UTC().Add(parsedDuration)
|
||||||
|
if time.Now().UTC().After(expire) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
foundActive = true
|
||||||
|
scopeAndValue := *decision.Scope
|
||||||
|
if *decision.Value != "" {
|
||||||
|
scopeAndValue += ":" + *decision.Value
|
||||||
|
}
|
||||||
|
t.AddRow(
|
||||||
|
strconv.Itoa(int(decision.ID)),
|
||||||
|
scopeAndValue,
|
||||||
|
*decision.Type,
|
||||||
|
*decision.Duration,
|
||||||
|
alert.CreatedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if foundActive {
|
||||||
|
fmt.Printf(" - Active Decisions :\n")
|
||||||
|
t.Render() // Send output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertEventTable(out io.Writer, event *models.Event) {
|
||||||
|
fmt.Fprintf(out, "\n- Date: %s\n", *event.Timestamp)
|
||||||
|
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetHeaders("Key", "Value")
|
||||||
|
sort.Slice(event.Meta, func(i, j int) bool {
|
||||||
|
return event.Meta[i].Key < event.Meta[j].Key
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, meta := range event.Meta {
|
||||||
|
t.AddRow(
|
||||||
|
meta.Key,
|
||||||
|
meta.Value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render() // Send output
|
||||||
|
}
|
|
@ -1,62 +1,45 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/enescakir/emoji"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var keyIP string
|
var keyIP string
|
||||||
var keyLength int
|
var keyLength int
|
||||||
var key string
|
var key string
|
||||||
|
|
||||||
func getBouncers(dbClient *database.Client) ([]byte, error) {
|
func getBouncers(out io.Writer, dbClient *database.Client) error {
|
||||||
bouncers, err := dbClient.ListBouncers()
|
bouncers, err := dbClient.ListBouncers()
|
||||||
w := bytes.NewBuffer(nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to list bouncers: %s", err)
|
return fmt.Errorf("unable to list bouncers: %s", err)
|
||||||
}
|
}
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
|
getBouncersTable(out, bouncers)
|
||||||
table := tablewriter.NewWriter(w)
|
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetHeader([]string{"Name", "IP Address", "Valid", "Last API pull", "Type", "Version", "Auth Type"})
|
|
||||||
for _, b := range bouncers {
|
|
||||||
var revoked string
|
|
||||||
if !b.Revoked {
|
|
||||||
revoked = emoji.CheckMark.String()
|
|
||||||
} else {
|
|
||||||
revoked = emoji.Prohibited.String()
|
|
||||||
}
|
|
||||||
table.Append([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
} else if csConfig.Cscli.Output == "json" {
|
} else if csConfig.Cscli.Output == "json" {
|
||||||
x, err := json.MarshalIndent(bouncers, "", " ")
|
enc := json.NewEncoder(out)
|
||||||
if err != nil {
|
enc.SetIndent("", " ")
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal")
|
if err := enc.Encode(bouncers); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to unmarshal")
|
||||||
}
|
}
|
||||||
return x, nil
|
return nil
|
||||||
} else if csConfig.Cscli.Output == "raw" {
|
} else if csConfig.Cscli.Output == "raw" {
|
||||||
csvwriter := csv.NewWriter(w)
|
csvwriter := csv.NewWriter(out)
|
||||||
err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"})
|
err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to write raw header")
|
return errors.Wrap(err, "failed to write raw header")
|
||||||
}
|
}
|
||||||
for _, b := range bouncers {
|
for _, b := range bouncers {
|
||||||
var revoked string
|
var revoked string
|
||||||
|
@ -67,12 +50,12 @@ func getBouncers(dbClient *database.Client) ([]byte, error) {
|
||||||
}
|
}
|
||||||
err := csvwriter.Write([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType})
|
err := csvwriter.Write([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to write raw")
|
return errors.Wrap(err, "failed to write raw")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
csvwriter.Flush()
|
csvwriter.Flush()
|
||||||
}
|
}
|
||||||
return w.Bytes(), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBouncersCmd() *cobra.Command {
|
func NewBouncersCmd() *cobra.Command {
|
||||||
|
@ -109,11 +92,10 @@ Note: This command requires database direct access, so is intended to be run on
|
||||||
Args: cobra.ExactArgs(0),
|
Args: cobra.ExactArgs(0),
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(cmd *cobra.Command, arg []string) {
|
Run: func(cmd *cobra.Command, arg []string) {
|
||||||
bouncers, err := getBouncers(dbClient)
|
err := getBouncers(colorable.NewColorableStdout(), dbClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to list bouncers: %s", err)
|
log.Fatalf("unable to list bouncers: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s", bouncers)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdBouncers.AddCommand(cmdBouncersList)
|
cmdBouncers.AddCommand(cmdBouncersList)
|
||||||
|
|
31
cmd/crowdsec-cli/bouncers_table.go
Normal file
31
cmd/crowdsec-cli/bouncers_table.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getBouncersTable(out io.Writer, bouncers []*ent.Bouncer) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Name", "IP Address", "Valid", "Last API pull", "Type", "Version", "Auth Type")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, b := range bouncers {
|
||||||
|
var revoked string
|
||||||
|
if !b.Revoked {
|
||||||
|
revoked = emoji.CheckMark.String()
|
||||||
|
} else {
|
||||||
|
revoked = emoji.Prohibited.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -3,11 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCollectionsCmd() *cobra.Command {
|
func NewCollectionsCmd() *cobra.Command {
|
||||||
|
@ -173,8 +173,7 @@ func NewCollectionsCmd() *cobra.Command {
|
||||||
Args: cobra.ExactArgs(0),
|
Args: cobra.ExactArgs(0),
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
items := ListItems([]string{cwhub.COLLECTIONS}, args, false, true, all)
|
ListItems(colorable.NewColorableStdout(), []string{cwhub.COLLECTIONS}, args, false, true, all)
|
||||||
fmt.Printf("%s\n", string(items))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdCollectionsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
cmdCollectionsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||||
|
|
|
@ -10,16 +10,16 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/enescakir/emoji"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConsoleCmd() *cobra.Command {
|
func NewConsoleCmd() *cobra.Command {
|
||||||
|
@ -194,34 +194,7 @@ Disable given information push to the central API.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
switch csConfig.Cscli.Output {
|
switch csConfig.Cscli.Output {
|
||||||
case "human":
|
case "human":
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
cmdConsoleStatusTable(colorable.NewColorableStdout(), *csConfig)
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetHeader([]string{"Option Name", "Activated", "Description"})
|
|
||||||
for _, option := range csconfig.CONSOLE_CONFIGS {
|
|
||||||
switch option {
|
|
||||||
case csconfig.SEND_CUSTOM_SCENARIOS:
|
|
||||||
activated := string(emoji.CrossMark)
|
|
||||||
if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios {
|
|
||||||
activated = string(emoji.CheckMarkButton)
|
|
||||||
}
|
|
||||||
table.Append([]string{option, activated, "Send alerts from custom scenarios to the console"})
|
|
||||||
case csconfig.SEND_MANUAL_SCENARIOS:
|
|
||||||
activated := string(emoji.CrossMark)
|
|
||||||
if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions {
|
|
||||||
activated = string(emoji.CheckMarkButton)
|
|
||||||
}
|
|
||||||
table.Append([]string{option, activated, "Send manual decisions to the console"})
|
|
||||||
case csconfig.SEND_TAINTED_SCENARIOS:
|
|
||||||
activated := string(emoji.CrossMark)
|
|
||||||
if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios {
|
|
||||||
activated = string(emoji.CheckMarkButton)
|
|
||||||
}
|
|
||||||
table.Append([]string{option, activated, "Send alerts from tainted scenarios to the console"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
case "json":
|
case "json":
|
||||||
data, err := json.MarshalIndent(csConfig.API.Server.ConsoleConfig, "", " ")
|
data, err := json.MarshalIndent(csConfig.API.Server.ConsoleConfig, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
48
cmd/crowdsec-cli/console_table.go
Normal file
48
cmd/crowdsec-cli/console_table.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdConsoleStatusTable(out io.Writer, csConfig csconfig.Config) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
|
||||||
|
t.SetHeaders("Option Name", "Activated", "Description")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, option := range csconfig.CONSOLE_CONFIGS {
|
||||||
|
switch option {
|
||||||
|
case csconfig.SEND_CUSTOM_SCENARIOS:
|
||||||
|
activated := string(emoji.CrossMark)
|
||||||
|
if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios {
|
||||||
|
activated = string(emoji.CheckMarkButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(option, activated, "Send alerts from custom scenarios to the console")
|
||||||
|
|
||||||
|
case csconfig.SEND_MANUAL_SCENARIOS:
|
||||||
|
activated := string(emoji.CrossMark)
|
||||||
|
if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions {
|
||||||
|
activated = string(emoji.CheckMarkButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(option, activated, "Send manual decisions to the console")
|
||||||
|
|
||||||
|
case csconfig.SEND_TAINTED_SCENARIOS:
|
||||||
|
activated := string(emoji.CrossMark)
|
||||||
|
if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios {
|
||||||
|
activated = string(emoji.CheckMarkButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(option, activated, "Send alerts from tainted scenarios to the console")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -12,16 +12,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/jszwec/csvutil"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/jszwec/csvutil"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Client *apiclient.ApiClient
|
var Client *apiclient.ApiClient
|
||||||
|
@ -92,44 +93,11 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
|
||||||
x, _ := json.MarshalIndent(alerts, "", " ")
|
x, _ := json.MarshalIndent(alerts, "", " ")
|
||||||
fmt.Printf("%s", string(x))
|
fmt.Printf("%s", string(x))
|
||||||
} else if csConfig.Cscli.Output == "human" {
|
} else if csConfig.Cscli.Output == "human" {
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
|
||||||
header := []string{"ID", "Source", "Scope:Value", "Reason", "Action", "Country", "AS", "Events", "expiration", "Alert ID"}
|
|
||||||
if printMachine {
|
|
||||||
header = append(header, "Machine")
|
|
||||||
}
|
|
||||||
table.SetHeader(header)
|
|
||||||
|
|
||||||
if len(*alerts) == 0 {
|
if len(*alerts) == 0 {
|
||||||
fmt.Println("No active decisions")
|
fmt.Println("No active decisions")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
decisionsTable(colorable.NewColorableStdout(), alerts, printMachine)
|
||||||
for _, alertItem := range *alerts {
|
|
||||||
for _, decisionItem := range alertItem.Decisions {
|
|
||||||
if *alertItem.Simulated {
|
|
||||||
*decisionItem.Type = fmt.Sprintf("(simul)%s", *decisionItem.Type)
|
|
||||||
}
|
|
||||||
raw := []string{
|
|
||||||
strconv.Itoa(int(decisionItem.ID)),
|
|
||||||
*decisionItem.Origin,
|
|
||||||
*decisionItem.Scope + ":" + *decisionItem.Value,
|
|
||||||
*decisionItem.Scenario,
|
|
||||||
*decisionItem.Type,
|
|
||||||
alertItem.Source.Cn,
|
|
||||||
alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
|
|
||||||
strconv.Itoa(int(*alertItem.EventsCount)),
|
|
||||||
*decisionItem.Duration,
|
|
||||||
strconv.Itoa(int(alertItem.ID)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if printMachine {
|
|
||||||
raw = append(raw, alertItem.MachineID)
|
|
||||||
}
|
|
||||||
|
|
||||||
table.Append(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Render() // Send output
|
|
||||||
if skipped > 0 {
|
if skipped > 0 {
|
||||||
fmt.Printf("%d duplicated entries skipped\n", skipped)
|
fmt.Printf("%d duplicated entries skipped\n", skipped)
|
||||||
}
|
}
|
||||||
|
|
46
cmd/crowdsec-cli/decisions_table.go
Normal file
46
cmd/crowdsec-cli/decisions_table.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decisionsTable(out io.Writer, alerts *models.GetAlertsResponse, printMachine bool) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
header := []string{"ID", "Source", "Scope:Value", "Reason", "Action", "Country", "AS", "Events", "expiration", "Alert ID"}
|
||||||
|
if printMachine {
|
||||||
|
header = append(header, "Machine")
|
||||||
|
}
|
||||||
|
t.SetHeaders(header...)
|
||||||
|
|
||||||
|
for _, alertItem := range *alerts {
|
||||||
|
for _, decisionItem := range alertItem.Decisions {
|
||||||
|
if *alertItem.Simulated {
|
||||||
|
*decisionItem.Type = fmt.Sprintf("(simul)%s", *decisionItem.Type)
|
||||||
|
}
|
||||||
|
row := []string{
|
||||||
|
strconv.Itoa(int(decisionItem.ID)),
|
||||||
|
*decisionItem.Origin,
|
||||||
|
*decisionItem.Scope + ":" + *decisionItem.Value,
|
||||||
|
*decisionItem.Scenario,
|
||||||
|
*decisionItem.Type,
|
||||||
|
alertItem.Source.Cn,
|
||||||
|
alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
|
||||||
|
strconv.Itoa(int(*alertItem.EventsCount)),
|
||||||
|
*decisionItem.Duration,
|
||||||
|
strconv.Itoa(int(alertItem.ID)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if printMachine {
|
||||||
|
row = append(row, alertItem.MachineID)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(row...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -3,10 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHubCmd() *cobra.Command {
|
func NewHubCmd() *cobra.Command {
|
||||||
|
@ -56,10 +57,9 @@ cscli hub update # Download list of available configurations from the hub
|
||||||
log.Info(v)
|
log.Info(v)
|
||||||
}
|
}
|
||||||
cwhub.DisplaySummary()
|
cwhub.DisplaySummary()
|
||||||
items := ListItems([]string{
|
ListItems(colorable.NewColorableStdout(), []string{
|
||||||
cwhub.COLLECTIONS, cwhub.PARSERS, cwhub.SCENARIOS, cwhub.PARSERS_OVFLW,
|
cwhub.COLLECTIONS, cwhub.PARSERS, cwhub.SCENARIOS, cwhub.PARSERS_OVFLW,
|
||||||
}, args, true, false, all)
|
}, args, true, false, all)
|
||||||
fmt.Printf("%s\n", items)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdHubList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
cmdHubList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||||
|
|
|
@ -9,12 +9,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cstest"
|
|
||||||
"github.com/enescakir/emoji"
|
"github.com/enescakir/emoji"
|
||||||
"github.com/olekukonko/tablewriter"
|
colorable "github.com/mattn/go-colorable"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cstest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -272,22 +273,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
hubTestResultTable(colorable.NewColorableStdout(), testResult)
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
|
|
||||||
table.SetHeader([]string{"Test", "Result"})
|
|
||||||
for testName, success := range testResult {
|
|
||||||
status := emoji.CheckMarkButton.String()
|
|
||||||
if !success {
|
|
||||||
status = emoji.CrossMark.String()
|
|
||||||
}
|
|
||||||
table.Append([]string{testName, status})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
} else if csConfig.Cscli.Output == "json" {
|
} else if csConfig.Cscli.Output == "json" {
|
||||||
jsonResult := make(map[string][]string, 0)
|
jsonResult := make(map[string][]string, 0)
|
||||||
jsonResult["success"] = make([]string, 0)
|
jsonResult["success"] = make([]string, 0)
|
||||||
|
@ -367,18 +353,18 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
|
||||||
log.Fatalf("unable to load all tests: %+v", err)
|
log.Fatalf("unable to load all tests: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
switch csConfig.Cscli.Output {
|
||||||
table.SetCenterSeparator("")
|
case "human":
|
||||||
table.SetColumnSeparator("")
|
hubTestListTable(colorable.NewColorableStdout(), HubTest.Tests)
|
||||||
|
case "json":
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
j, err := json.MarshalIndent(HubTest.Tests, " ", " ")
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
if err != nil {
|
||||||
table.SetHeader([]string{"Name", "Path"})
|
log.Fatal(err)
|
||||||
for _, test := range HubTest.Tests {
|
}
|
||||||
table.Append([]string{test.Name, test.Path})
|
fmt.Println(string(j))
|
||||||
|
default:
|
||||||
|
log.Fatalf("only human/json output modes are supported")
|
||||||
}
|
}
|
||||||
table.Render()
|
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdHubTest.AddCommand(cmdHubTestList)
|
cmdHubTest.AddCommand(cmdHubTestList)
|
||||||
|
@ -399,11 +385,9 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
|
||||||
parserCoverage := []cstest.ParserCoverage{}
|
parserCoverage := []cstest.ParserCoverage{}
|
||||||
scenarioCoveragePercent := 0
|
scenarioCoveragePercent := 0
|
||||||
parserCoveragePercent := 0
|
parserCoveragePercent := 0
|
||||||
showAll := false
|
|
||||||
|
|
||||||
if !showScenarioCov && !showParserCov { // if both are false (flag by default), show both
|
// if both are false (flag by default), show both
|
||||||
showAll = true
|
showAll := !showScenarioCov && !showParserCov
|
||||||
}
|
|
||||||
|
|
||||||
if showParserCov || showAll {
|
if showParserCov || showAll {
|
||||||
parserCoverage, err = HubTest.GetParsersCoverage()
|
parserCoverage, err = HubTest.GetParsersCoverage()
|
||||||
|
@ -446,43 +430,11 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
|
||||||
|
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
if showParserCov || showAll {
|
if showParserCov || showAll {
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
hubTestParserCoverageTable(colorable.NewColorableStdout(), parserCoverage)
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
|
|
||||||
table.SetHeader([]string{"Parser", "Status", "Number of tests"})
|
|
||||||
parserTested := 0
|
|
||||||
for _, test := range parserCoverage {
|
|
||||||
status := emoji.RedCircle.String()
|
|
||||||
if test.TestsCount > 0 {
|
|
||||||
status = emoji.GreenCircle.String()
|
|
||||||
parserTested += 1
|
|
||||||
}
|
|
||||||
table.Append([]string{test.Parser, status, fmt.Sprintf("%d times (across %d tests)", test.TestsCount, len(test.PresentIn))})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if showScenarioCov || showAll {
|
if showScenarioCov || showAll {
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
hubTestScenarioCoverageTable(colorable.NewColorableStdout(), scenarioCoverage)
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
|
|
||||||
table.SetHeader([]string{"Scenario", "Status", "Number of tests"})
|
|
||||||
for _, test := range scenarioCoverage {
|
|
||||||
status := emoji.RedCircle.String()
|
|
||||||
if test.TestsCount > 0 {
|
|
||||||
status = emoji.GreenCircle.String()
|
|
||||||
}
|
|
||||||
table.Append([]string{test.Scenario, status, fmt.Sprintf("%d times (across %d tests)", test.TestsCount, len(test.PresentIn))})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
if showParserCov || showAll {
|
if showParserCov || showAll {
|
||||||
|
|
80
cmd/crowdsec-cli/hubtest_table.go
Normal file
80
cmd/crowdsec-cli/hubtest_table.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hubTestResultTable(out io.Writer, testResult map[string]bool) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Test", "Result")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft)
|
||||||
|
|
||||||
|
for testName, success := range testResult {
|
||||||
|
status := emoji.CheckMarkButton.String()
|
||||||
|
if !success {
|
||||||
|
status = emoji.CrossMark.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(testName, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hubTestListTable(out io.Writer, tests []*cstest.HubTestItem) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Name", "Path")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.AddRow(test.Name, test.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hubTestParserCoverageTable(out io.Writer, coverage []cstest.ParserCoverage) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Parser", "Status", "Number of tests")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
parserTested := 0
|
||||||
|
for _, test := range coverage {
|
||||||
|
status := emoji.RedCircle.String()
|
||||||
|
if test.TestsCount > 0 {
|
||||||
|
status = emoji.GreenCircle.String()
|
||||||
|
parserTested++
|
||||||
|
}
|
||||||
|
t.AddRow(test.Parser, status, fmt.Sprintf("%d times (across %d tests)", test.TestsCount, len(test.PresentIn)))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hubTestScenarioCoverageTable(out io.Writer, coverage []cstest.ScenarioCoverage) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Scenario", "Status", "Number of tests")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
parserTested := 0
|
||||||
|
for _, test := range coverage {
|
||||||
|
status := emoji.RedCircle.String()
|
||||||
|
if test.TestsCount > 0 {
|
||||||
|
status = emoji.GreenCircle.String()
|
||||||
|
parserTested++
|
||||||
|
}
|
||||||
|
t.AddRow(test.Scenario, status, fmt.Sprintf("%d times (across %d tests)", test.TestsCount, len(test.PresentIn)))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -1,30 +1,32 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
saferand "crypto/rand"
|
saferand "crypto/rand"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
|
||||||
"github.com/crowdsecurity/machineid"
|
|
||||||
"github.com/enescakir/emoji"
|
"github.com/enescakir/emoji"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/olekukonko/tablewriter"
|
colorable "github.com/mattn/go-colorable"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/machineid"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var machineID string
|
var machineID string
|
||||||
|
@ -43,7 +45,6 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func generatePassword(length int) string {
|
func generatePassword(length int) string {
|
||||||
|
|
||||||
charset := upper + lower + digits
|
charset := upper + lower + digits
|
||||||
charsetLength := len(charset)
|
charsetLength := len(charset)
|
||||||
|
|
||||||
|
@ -109,50 +110,34 @@ func displayLastHeartBeat(m *ent.Machine, fancy bool) string {
|
||||||
return hbDisplay
|
return hbDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAgents(dbClient *database.Client) ([]byte, error) {
|
func getAgents(out io.Writer, dbClient *database.Client) error {
|
||||||
w := bytes.NewBuffer(nil)
|
|
||||||
machines, err := dbClient.ListMachines()
|
machines, err := dbClient.ListMachines()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to list machines: %s", err)
|
return fmt.Errorf("unable to list machines: %s", err)
|
||||||
}
|
}
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
table := tablewriter.NewWriter(w)
|
getAgentsTable(out, machines)
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetHeader([]string{"Name", "IP Address", "Last Update", "Status", "Version", "Auth Type", "Last Heartbeat"})
|
|
||||||
for _, w := range machines {
|
|
||||||
var validated string
|
|
||||||
if w.IsValidated {
|
|
||||||
validated = emoji.CheckMark.String()
|
|
||||||
} else {
|
|
||||||
validated = emoji.Prohibited.String()
|
|
||||||
}
|
|
||||||
table.Append([]string{w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version, w.AuthType, displayLastHeartBeat(w, true)})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
} else if csConfig.Cscli.Output == "json" {
|
} else if csConfig.Cscli.Output == "json" {
|
||||||
x, err := json.MarshalIndent(machines, "", " ")
|
enc := json.NewEncoder(out)
|
||||||
if err != nil {
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(machines); err != nil {
|
||||||
log.Fatalf("failed to unmarshal")
|
log.Fatalf("failed to unmarshal")
|
||||||
}
|
}
|
||||||
return x, nil
|
return nil
|
||||||
} else if csConfig.Cscli.Output == "raw" {
|
} else if csConfig.Cscli.Output == "raw" {
|
||||||
csvwriter := csv.NewWriter(w)
|
csvwriter := csv.NewWriter(out)
|
||||||
err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"})
|
err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to write header: %s", err)
|
log.Fatalf("failed to write header: %s", err)
|
||||||
}
|
}
|
||||||
for _, w := range machines {
|
for _, m := range machines {
|
||||||
var validated string
|
var validated string
|
||||||
if w.IsValidated {
|
if m.IsValidated {
|
||||||
validated = "true"
|
validated = "true"
|
||||||
} else {
|
} else {
|
||||||
validated = "false"
|
validated = "false"
|
||||||
}
|
}
|
||||||
err := csvwriter.Write([]string{w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version, w.AuthType, displayLastHeartBeat(w, false)})
|
err := csvwriter.Write([]string{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, displayLastHeartBeat(m, false)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to write raw output : %s", err)
|
log.Fatalf("failed to write raw output : %s", err)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +146,7 @@ func getAgents(dbClient *database.Client) ([]byte, error) {
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("unknown output '%s'", csConfig.Cscli.Output)
|
log.Errorf("unknown output '%s'", csConfig.Cscli.Output)
|
||||||
}
|
}
|
||||||
return w.Bytes(), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMachinesCmd() *cobra.Command {
|
func NewMachinesCmd() *cobra.Command {
|
||||||
|
@ -204,11 +189,10 @@ Note: This command requires database direct access, so is intended to be run on
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
agents, err := getAgents(dbClient)
|
err := getAgents(colorable.NewColorableStdout(), dbClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to list machines: %s", err)
|
log.Fatalf("unable to list machines: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s\n", agents)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdMachines.AddCommand(cmdMachinesList)
|
cmdMachines.AddCommand(cmdMachinesList)
|
||||||
|
|
31
cmd/crowdsec-cli/machines_table.go
Normal file
31
cmd/crowdsec-cli/machines_table.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getAgentsTable(out io.Writer, machines []*ent.Machine) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Name", "IP Address", "Last Update", "Status", "Version", "Auth Type", "Last Heartbeat")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, m := range machines {
|
||||||
|
var validated string
|
||||||
|
if m.IsValidated {
|
||||||
|
validated = emoji.CheckMark.String()
|
||||||
|
} else {
|
||||||
|
validated = emoji.Prohibited.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.AddRow(m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, displayLastHeartBeat(m, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -8,14 +8,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/confluentinc/bincover"
|
"github.com/confluentinc/bincover"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/cobra/doc"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/cobra/doc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bincoverTesting = ""
|
var bincoverTesting = ""
|
||||||
|
@ -27,6 +27,7 @@ var csConfig *csconfig.Config
|
||||||
var dbClient *database.Client
|
var dbClient *database.Client
|
||||||
|
|
||||||
var OutputFormat string
|
var OutputFormat string
|
||||||
|
var OutputColor string
|
||||||
|
|
||||||
var downloadOnly bool
|
var downloadOnly bool
|
||||||
var forceAction bool
|
var forceAction bool
|
||||||
|
@ -88,6 +89,12 @@ func initConfig() {
|
||||||
log.SetLevel(log.ErrorLevel)
|
log.SetLevel(log.ErrorLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if OutputColor != "" {
|
||||||
|
csConfig.Cscli.Color = OutputColor
|
||||||
|
if OutputColor != "yes" && OutputColor != "no" && OutputColor != "auto" {
|
||||||
|
log.Fatalf("output color %s unknown", OutputColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var validArgs = []string{
|
var validArgs = []string{
|
||||||
|
@ -160,6 +167,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVarP(&ConfigFilePath, "config", "c", csconfig.DefaultConfigPath("config.yaml"), "path to crowdsec config file")
|
rootCmd.PersistentFlags().StringVarP(&ConfigFilePath, "config", "c", csconfig.DefaultConfigPath("config.yaml"), "path to crowdsec config file")
|
||||||
rootCmd.PersistentFlags().StringVarP(&OutputFormat, "output", "o", "", "Output format: human, json, raw.")
|
rootCmd.PersistentFlags().StringVarP(&OutputFormat, "output", "o", "", "Output format: human, json, raw.")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&OutputColor, "color", "", csconfig.ColorDefault(), "Output color: yes, no, auto.")
|
||||||
rootCmd.PersistentFlags().BoolVar(&dbg_lvl, "debug", false, "Set logging to debug.")
|
rootCmd.PersistentFlags().BoolVar(&dbg_lvl, "debug", false, "Set logging to debug.")
|
||||||
rootCmd.PersistentFlags().BoolVar(&nfo_lvl, "info", false, "Set logging to info.")
|
rootCmd.PersistentFlags().BoolVar(&nfo_lvl, "info", false, "Set logging to info.")
|
||||||
rootCmd.PersistentFlags().BoolVar(&wrn_lvl, "warning", false, "Set logging to warning.")
|
rootCmd.PersistentFlags().BoolVar(&wrn_lvl, "warning", false, "Set logging to warning.")
|
||||||
|
|
|
@ -1,96 +1,27 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
colorable "github.com/mattn/go-colorable"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
"github.com/prometheus/prom2json"
|
"github.com/prometheus/prom2json"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func lapiMetricsToTable(table *tablewriter.Table, stats map[string]map[string]map[string]int) error {
|
// FormatPrometheusMetrics is a complete rip from prom2json
|
||||||
|
func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error {
|
||||||
//stats : machine -> route -> method -> count
|
|
||||||
/*we want consistent display order*/
|
|
||||||
machineKeys := []string{}
|
|
||||||
for k := range stats {
|
|
||||||
machineKeys = append(machineKeys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(machineKeys)
|
|
||||||
|
|
||||||
for _, machine := range machineKeys {
|
|
||||||
//oneRow : route -> method -> count
|
|
||||||
machineRow := stats[machine]
|
|
||||||
for routeName, route := range machineRow {
|
|
||||||
for methodName, count := range route {
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, machine)
|
|
||||||
row = append(row, routeName)
|
|
||||||
row = append(row, methodName)
|
|
||||||
if count != 0 {
|
|
||||||
row = append(row, fmt.Sprintf("%d", count))
|
|
||||||
} else {
|
|
||||||
row = append(row, "-")
|
|
||||||
}
|
|
||||||
table.Append(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func metricsToTable(table *tablewriter.Table, stats map[string]map[string]int, keys []string) error {
|
|
||||||
|
|
||||||
var sortedKeys []string
|
|
||||||
|
|
||||||
if table == nil {
|
|
||||||
return fmt.Errorf("nil table")
|
|
||||||
}
|
|
||||||
//sort keys to keep consistent order when printing
|
|
||||||
sortedKeys = []string{}
|
|
||||||
for akey := range stats {
|
|
||||||
sortedKeys = append(sortedKeys, akey)
|
|
||||||
}
|
|
||||||
sort.Strings(sortedKeys)
|
|
||||||
//
|
|
||||||
for _, alabel := range sortedKeys {
|
|
||||||
astats, ok := stats[alabel]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, alabel) //name
|
|
||||||
for _, sl := range keys {
|
|
||||||
if v, ok := astats[sl]; ok && v != 0 {
|
|
||||||
numberToShow := fmt.Sprintf("%d", v)
|
|
||||||
if !noUnit {
|
|
||||||
numberToShow = formatNumber(v)
|
|
||||||
}
|
|
||||||
row = append(row, numberToShow)
|
|
||||||
} else {
|
|
||||||
row = append(row, "-")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Append(row)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*This is a complete rip from prom2json*/
|
|
||||||
func FormatPrometheusMetric(url string, formatType string) ([]byte, error) {
|
|
||||||
mfChan := make(chan *dto.MetricFamily, 1024)
|
mfChan := make(chan *dto.MetricFamily, 1024)
|
||||||
|
|
||||||
// Start with the DefaultTransport for sane defaults.
|
// Start with the DefaultTransport for sane defaults.
|
||||||
|
@ -284,171 +215,37 @@ func FormatPrometheusMetric(url string, formatType string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := bytes.NewBuffer(nil)
|
|
||||||
|
|
||||||
if formatType == "human" {
|
if formatType == "human" {
|
||||||
|
acquisStatsTable(out, acquis_stats)
|
||||||
acquisTable := tablewriter.NewWriter(ret)
|
bucketStatsTable(out, buckets_stats)
|
||||||
acquisTable.SetHeader([]string{"Source", "Lines read", "Lines parsed", "Lines unparsed", "Lines poured to bucket"})
|
parserStatsTable(out, parsers_stats)
|
||||||
keys := []string{"reads", "parsed", "unparsed", "pour"}
|
lapiStatsTable(out, lapi_stats)
|
||||||
if err := metricsToTable(acquisTable, acquis_stats, keys); err != nil {
|
lapiMachineStatsTable(out, lapi_machine_stats)
|
||||||
log.Warningf("while collecting acquis stats : %s", err)
|
lapiBouncerStatsTable(out, lapi_bouncer_stats)
|
||||||
}
|
lapiDecisionStatsTable(out, lapi_decisions_stats)
|
||||||
bucketsTable := tablewriter.NewWriter(ret)
|
decisionStatsTable(out, decisions_stats)
|
||||||
bucketsTable.SetHeader([]string{"Bucket", "Current Count", "Overflows", "Instantiated", "Poured", "Expired"})
|
alertStatsTable(out, alerts_stats)
|
||||||
keys = []string{"curr_count", "overflow", "instantiation", "pour", "underflow"}
|
|
||||||
if err := metricsToTable(bucketsTable, buckets_stats, keys); err != nil {
|
|
||||||
log.Warningf("while collecting acquis stats : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsersTable := tablewriter.NewWriter(ret)
|
|
||||||
parsersTable.SetHeader([]string{"Parsers", "Hits", "Parsed", "Unparsed"})
|
|
||||||
keys = []string{"hits", "parsed", "unparsed"}
|
|
||||||
if err := metricsToTable(parsersTable, parsers_stats, keys); err != nil {
|
|
||||||
log.Warningf("while collecting acquis stats : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lapiMachinesTable := tablewriter.NewWriter(ret)
|
|
||||||
lapiMachinesTable.SetHeader([]string{"Machine", "Route", "Method", "Hits"})
|
|
||||||
if err := lapiMetricsToTable(lapiMachinesTable, lapi_machine_stats); err != nil {
|
|
||||||
log.Warningf("while collecting machine lapi stats : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//lapiMetricsToTable
|
|
||||||
lapiBouncersTable := tablewriter.NewWriter(ret)
|
|
||||||
lapiBouncersTable.SetHeader([]string{"Bouncer", "Route", "Method", "Hits"})
|
|
||||||
if err := lapiMetricsToTable(lapiBouncersTable, lapi_bouncer_stats); err != nil {
|
|
||||||
log.Warningf("while collecting bouncer lapi stats : %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lapiDecisionsTable := tablewriter.NewWriter(ret)
|
|
||||||
lapiDecisionsTable.SetHeader([]string{"Bouncer", "Empty answers", "Non-empty answers"})
|
|
||||||
for bouncer, hits := range lapi_decisions_stats {
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, bouncer)
|
|
||||||
row = append(row, fmt.Sprintf("%d", hits.Empty))
|
|
||||||
row = append(row, fmt.Sprintf("%d", hits.NonEmpty))
|
|
||||||
lapiDecisionsTable.Append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*unfortunately, we can't reuse metricsToTable as the structure is too different :/*/
|
|
||||||
lapiTable := tablewriter.NewWriter(ret)
|
|
||||||
lapiTable.SetHeader([]string{"Route", "Method", "Hits"})
|
|
||||||
sortedKeys := []string{}
|
|
||||||
for akey := range lapi_stats {
|
|
||||||
sortedKeys = append(sortedKeys, akey)
|
|
||||||
}
|
|
||||||
sort.Strings(sortedKeys)
|
|
||||||
for _, alabel := range sortedKeys {
|
|
||||||
astats := lapi_stats[alabel]
|
|
||||||
subKeys := []string{}
|
|
||||||
for skey := range astats {
|
|
||||||
subKeys = append(subKeys, skey)
|
|
||||||
}
|
|
||||||
sort.Strings(subKeys)
|
|
||||||
for _, sl := range subKeys {
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, alabel)
|
|
||||||
row = append(row, sl)
|
|
||||||
row = append(row, fmt.Sprintf("%d", astats[sl]))
|
|
||||||
lapiTable.Append(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decisionsTable := tablewriter.NewWriter(ret)
|
|
||||||
decisionsTable.SetHeader([]string{"Reason", "Origin", "Action", "Count"})
|
|
||||||
for reason, origins := range decisions_stats {
|
|
||||||
for origin, actions := range origins {
|
|
||||||
for action, hits := range actions {
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, reason)
|
|
||||||
row = append(row, origin)
|
|
||||||
row = append(row, action)
|
|
||||||
row = append(row, fmt.Sprintf("%d", hits))
|
|
||||||
decisionsTable.Append(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alertsTable := tablewriter.NewWriter(ret)
|
|
||||||
alertsTable.SetHeader([]string{"Reason", "Count"})
|
|
||||||
for scenario, hits := range alerts_stats {
|
|
||||||
row := []string{}
|
|
||||||
row = append(row, scenario)
|
|
||||||
row = append(row, fmt.Sprintf("%d", hits))
|
|
||||||
alertsTable.Append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bucketsTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Buckets Metrics:\n")
|
|
||||||
bucketsTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
bucketsTable.Render()
|
|
||||||
}
|
|
||||||
if acquisTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Acquisition Metrics:\n")
|
|
||||||
acquisTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
acquisTable.Render()
|
|
||||||
}
|
|
||||||
if parsersTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Parser Metrics:\n")
|
|
||||||
parsersTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
parsersTable.Render()
|
|
||||||
}
|
|
||||||
if lapiTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Metrics:\n")
|
|
||||||
lapiTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
lapiTable.Render()
|
|
||||||
}
|
|
||||||
if lapiMachinesTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Machines Metrics:\n")
|
|
||||||
lapiMachinesTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
lapiMachinesTable.Render()
|
|
||||||
}
|
|
||||||
if lapiBouncersTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Bouncers Metrics:\n")
|
|
||||||
lapiBouncersTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
lapiBouncersTable.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if lapiDecisionsTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Bouncers Decisions:\n")
|
|
||||||
lapiDecisionsTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
lapiDecisionsTable.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if decisionsTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Decisions:\n")
|
|
||||||
decisionsTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
decisionsTable.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if alertsTable.NumLines() > 0 {
|
|
||||||
fmt.Fprintf(ret, "Local Api Alerts:\n")
|
|
||||||
alertsTable.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
alertsTable.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if formatType == "json" {
|
} else if formatType == "json" {
|
||||||
for _, val := range []interface{}{acquis_stats, parsers_stats, buckets_stats, lapi_stats, lapi_bouncer_stats, lapi_machine_stats, lapi_decisions_stats, decisions_stats, alerts_stats} {
|
for _, val := range []interface{}{acquis_stats, parsers_stats, buckets_stats, lapi_stats, lapi_bouncer_stats, lapi_machine_stats, lapi_decisions_stats, decisions_stats, alerts_stats} {
|
||||||
x, err := json.MarshalIndent(val, "", " ")
|
x, err := json.MarshalIndent(val, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal metrics : %v", err)
|
return fmt.Errorf("failed to unmarshal metrics : %v", err)
|
||||||
}
|
}
|
||||||
ret.Write(x)
|
out.Write(x)
|
||||||
}
|
}
|
||||||
return ret.Bytes(), nil
|
return nil
|
||||||
|
|
||||||
} else if formatType == "raw" {
|
} else if formatType == "raw" {
|
||||||
for _, val := range []interface{}{acquis_stats, parsers_stats, buckets_stats, lapi_stats, lapi_bouncer_stats, lapi_machine_stats, lapi_decisions_stats, decisions_stats, alerts_stats} {
|
for _, val := range []interface{}{acquis_stats, parsers_stats, buckets_stats, lapi_stats, lapi_bouncer_stats, lapi_machine_stats, lapi_decisions_stats, decisions_stats, alerts_stats} {
|
||||||
x, err := yaml.Marshal(val)
|
x, err := yaml.Marshal(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal metrics : %v", err)
|
return fmt.Errorf("failed to unmarshal metrics : %v", err)
|
||||||
}
|
}
|
||||||
ret.Write(x)
|
out.Write(x)
|
||||||
}
|
}
|
||||||
return ret.Bytes(), nil
|
return nil
|
||||||
}
|
}
|
||||||
return ret.Bytes(), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var noUnit bool
|
var noUnit bool
|
||||||
|
@ -479,11 +276,10 @@ func NewMetricsCmd() *cobra.Command {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics, err := FormatPrometheusMetric(prometheusURL+"/metrics", csConfig.Cscli.Output)
|
err := FormatPrometheusMetrics(colorable.NewColorableStdout(), prometheusURL+"/metrics", csConfig.Cscli.Output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("could not fetch prometheus metrics: %s", err)
|
log.Fatalf("could not fetch prometheus metrics: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%s", metrics)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdMetrics.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url (http://<ip>:<port>/metrics)")
|
cmdMetrics.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url (http://<ip>:<port>/metrics)")
|
||||||
|
|
272
cmd/crowdsec-cli/metrics_table.go
Normal file
272
cmd/crowdsec-cli/metrics_table.go
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lapiMetricsToTable(t *table.Table, stats map[string]map[string]map[string]int) int {
|
||||||
|
// stats: machine -> route -> method -> count
|
||||||
|
|
||||||
|
// sort keys to keep consistent order when printing
|
||||||
|
machineKeys := []string{}
|
||||||
|
for k := range stats {
|
||||||
|
machineKeys = append(machineKeys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(machineKeys)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for _, machine := range machineKeys {
|
||||||
|
// oneRow: route -> method -> count
|
||||||
|
machineRow := stats[machine]
|
||||||
|
for routeName, route := range machineRow {
|
||||||
|
for methodName, count := range route {
|
||||||
|
row := []string{
|
||||||
|
machine,
|
||||||
|
routeName,
|
||||||
|
methodName,
|
||||||
|
}
|
||||||
|
if count != 0 {
|
||||||
|
row = append(row, fmt.Sprintf("%d", count))
|
||||||
|
} else {
|
||||||
|
row = append(row, "-")
|
||||||
|
}
|
||||||
|
t.AddRow(row...)
|
||||||
|
numRows++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return numRows
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricsToTable(t *table.Table, stats map[string]map[string]int, keys []string) (int, error) {
|
||||||
|
if t == nil {
|
||||||
|
return 0, fmt.Errorf("nil table")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort keys to keep consistent order when printing
|
||||||
|
sortedKeys := []string{}
|
||||||
|
for k := range stats {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for _, alabel := range sortedKeys {
|
||||||
|
astats, ok := stats[alabel]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
row := []string{
|
||||||
|
alabel,
|
||||||
|
}
|
||||||
|
for _, sl := range keys {
|
||||||
|
if v, ok := astats[sl]; ok && v != 0 {
|
||||||
|
numberToShow := fmt.Sprintf("%d", v)
|
||||||
|
if !noUnit {
|
||||||
|
numberToShow = formatNumber(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
row = append(row, numberToShow)
|
||||||
|
} else {
|
||||||
|
row = append(row, "-")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.AddRow(row...)
|
||||||
|
}
|
||||||
|
return numRows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bucketStatsTable(out io.Writer, stats map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Bucket", "Current Count", "Overflows", "Instantiated", "Poured", "Expired")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
keys := []string{"curr_count", "overflow", "instantiation", "pour", "underflow"}
|
||||||
|
|
||||||
|
if numRows, err := metricsToTable(t, stats, keys); err != nil {
|
||||||
|
log.Warningf("while collecting acquis stats: %s", err)
|
||||||
|
} else if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nBucket Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func acquisStatsTable(out io.Writer, stats map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Source", "Lines read", "Lines parsed", "Lines unparsed", "Lines poured to bucket")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
keys := []string{"reads", "parsed", "unparsed", "pour"}
|
||||||
|
|
||||||
|
if numRows, err := metricsToTable(t, stats, keys); err != nil {
|
||||||
|
log.Warningf("while collecting acquis stats: %s", err)
|
||||||
|
} else if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nAcquisition Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserStatsTable(out io.Writer, stats map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Parsers", "Hits", "Parsed", "Unparsed")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
keys := []string{"hits", "parsed", "unparsed"}
|
||||||
|
|
||||||
|
if numRows, err := metricsToTable(t, stats, keys); err != nil {
|
||||||
|
log.Warningf("while collecting acquis stats: %s", err)
|
||||||
|
} else if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nParser Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lapiStatsTable(out io.Writer, stats map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Route", "Method", "Hits")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
// unfortunately, we can't reuse metricsToTable as the structure is too different :/
|
||||||
|
sortedKeys := []string{}
|
||||||
|
for k := range stats {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for _, alabel := range sortedKeys {
|
||||||
|
astats := stats[alabel]
|
||||||
|
|
||||||
|
subKeys := []string{}
|
||||||
|
for skey := range astats {
|
||||||
|
subKeys = append(subKeys, skey)
|
||||||
|
}
|
||||||
|
sort.Strings(subKeys)
|
||||||
|
|
||||||
|
for _, sl := range subKeys {
|
||||||
|
row := []string{
|
||||||
|
alabel,
|
||||||
|
sl,
|
||||||
|
fmt.Sprintf("%d", astats[sl]),
|
||||||
|
}
|
||||||
|
t.AddRow(row...)
|
||||||
|
numRows++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lapiMachineStatsTable(out io.Writer, stats map[string]map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Machine", "Route", "Method", "Hits")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
numRows := lapiMetricsToTable(t, stats)
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Machines Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lapiBouncerStatsTable(out io.Writer, stats map[string]map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Bouncer", "Route", "Method", "Hits")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
numRows := lapiMetricsToTable(t, stats)
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Bouncers Metrics:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lapiDecisionStatsTable(out io.Writer, stats map[string]struct {
|
||||||
|
NonEmpty int
|
||||||
|
Empty int
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Bouncer", "Empty answers", "Non-empty answers")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for bouncer, hits := range stats {
|
||||||
|
t.AddRow(
|
||||||
|
bouncer,
|
||||||
|
fmt.Sprintf("%d", hits.Empty),
|
||||||
|
fmt.Sprintf("%d", hits.NonEmpty),
|
||||||
|
)
|
||||||
|
numRows++
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Bouncers Decisions:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decisionStatsTable(out io.Writer, stats map[string]map[string]map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Reason", "Origin", "Action", "Count")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for reason, origins := range stats {
|
||||||
|
for origin, actions := range origins {
|
||||||
|
for action, hits := range actions {
|
||||||
|
t.AddRow(
|
||||||
|
reason,
|
||||||
|
origin,
|
||||||
|
action,
|
||||||
|
fmt.Sprintf("%d", hits),
|
||||||
|
)
|
||||||
|
numRows++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Decisions:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func alertStatsTable(out io.Writer, stats map[string]int) {
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetHeaders("Reason", "Count")
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
numRows := 0
|
||||||
|
for scenario, hits := range stats {
|
||||||
|
t.AddRow(
|
||||||
|
scenario,
|
||||||
|
fmt.Sprintf("%d", hits),
|
||||||
|
)
|
||||||
|
numRows++
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRows > 0 {
|
||||||
|
renderTableTitle(out, "\nLocal Api Alerts:")
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,17 +13,18 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/tomb.v2"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
|
"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"gopkg.in/tomb.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationsCfg struct {
|
type NotificationsCfg struct {
|
||||||
|
@ -67,22 +68,7 @@ func NewNotificationsCmd() *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
notificationListTable(colorable.NewColorableStdout(), ncfgs)
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetHeader([]string{"Name", "Type", "Profile name"})
|
|
||||||
for _, b := range ncfgs {
|
|
||||||
profilesList := []string{}
|
|
||||||
for _, p := range b.Profiles {
|
|
||||||
profilesList = append(profilesList, p.Name)
|
|
||||||
}
|
|
||||||
table.Append([]string{b.Config.Name, b.Config.Type, strings.Join(profilesList, ", ")})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
|
|
||||||
} else if csConfig.Cscli.Output == "json" {
|
} else if csConfig.Cscli.Output == "json" {
|
||||||
x, err := json.MarshalIndent(ncfgs, "", " ")
|
x, err := json.MarshalIndent(ncfgs, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
25
cmd/crowdsec-cli/notifications_table.go
Normal file
25
cmd/crowdsec-cli/notifications_table.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notificationListTable(out io.Writer, ncfgs map[string]NotificationsCfg) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Name", "Type", "Profile name")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, b := range ncfgs {
|
||||||
|
profilesList := []string{}
|
||||||
|
for _, p := range b.Profiles {
|
||||||
|
profilesList = append(profilesList, p.Name)
|
||||||
|
}
|
||||||
|
t.AddRow(b.Config.Name, b.Config.Type, strings.Join(profilesList, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Render()
|
||||||
|
}
|
|
@ -3,11 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewParsersCmd() *cobra.Command {
|
func NewParsersCmd() *cobra.Command {
|
||||||
|
@ -164,8 +164,7 @@ cscli parsers remove crowdsecurity/sshd-logs
|
||||||
cscli parser list crowdsecurity/xxx`,
|
cscli parser list crowdsecurity/xxx`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
items := ListItems([]string{cwhub.PARSERS}, args, false, true, all)
|
ListItems(colorable.NewColorableStdout(), []string{cwhub.PARSERS}, args, false, true, all)
|
||||||
fmt.Printf("%s\n", items)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdParsersList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
cmdParsersList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||||
|
|
|
@ -3,11 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPostOverflowsCmd() *cobra.Command {
|
func NewPostOverflowsCmd() *cobra.Command {
|
||||||
|
@ -162,8 +162,7 @@ func NewPostOverflowsCmd() *cobra.Command {
|
||||||
cscli postoverflows list crowdsecurity/xxx`,
|
cscli postoverflows list crowdsecurity/xxx`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
items := ListItems([]string{cwhub.PARSERS_OVFLW}, args, false, true, all)
|
ListItems(colorable.NewColorableStdout(), []string{cwhub.PARSERS_OVFLW}, args, false, true, all)
|
||||||
fmt.Printf("%s\n", items)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||||
|
|
|
@ -3,11 +3,12 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewScenariosCmd() *cobra.Command {
|
func NewScenariosCmd() *cobra.Command {
|
||||||
|
@ -166,8 +167,7 @@ cscli scenarios remove crowdsecurity/ssh-bf
|
||||||
cscli scenarios list crowdsecurity/xxx`,
|
cscli scenarios list crowdsecurity/xxx`,
|
||||||
DisableAutoGenTag: true,
|
DisableAutoGenTag: true,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
items := ListItems([]string{cwhub.SCENARIOS}, args, false, true, all)
|
ListItems(colorable.NewColorableStdout(), []string{cwhub.SCENARIOS}, args, false, true, all)
|
||||||
fmt.Printf("%s\n", items)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmdScenariosList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
cmdScenariosList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||||
|
|
|
@ -14,15 +14,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/blackfireio/osinfo"
|
"github.com/blackfireio/osinfo"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -55,7 +56,8 @@ func collectMetrics() ([]byte, []byte, error) {
|
||||||
return nil, nil, fmt.Errorf("prometheus_uri is not set")
|
return nil, nil, fmt.Errorf("prometheus_uri is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
humanMetrics, err := FormatPrometheusMetric(csConfig.Cscli.PrometheusUrl+"/metrics", "human")
|
humanMetrics := bytes.NewBuffer(nil)
|
||||||
|
err = FormatPrometheusMetrics(humanMetrics, csConfig.Cscli.PrometheusUrl+"/metrics", "human")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("could not fetch promtheus metrics: %s", err)
|
return nil, nil, fmt.Errorf("could not fetch promtheus metrics: %s", err)
|
||||||
|
@ -79,7 +81,7 @@ func collectMetrics() ([]byte, []byte, error) {
|
||||||
return nil, nil, fmt.Errorf("could not read metrics from prometheus endpoint: %s", err)
|
return nil, nil, fmt.Errorf("could not read metrics from prometheus endpoint: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return humanMetrics, body, nil
|
return humanMetrics.Bytes(), body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectVersion() []byte {
|
func collectVersion() []byte {
|
||||||
|
@ -126,17 +128,28 @@ func initHub() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectHubItems(itemType string) []byte {
|
func collectHubItems(itemType string) []byte {
|
||||||
|
out := bytes.NewBuffer(nil)
|
||||||
log.Infof("Collecting %s list", itemType)
|
log.Infof("Collecting %s list", itemType)
|
||||||
items := ListItems([]string{itemType}, []string{}, false, true, all)
|
ListItems(out, []string{itemType}, []string{}, false, true, all)
|
||||||
return items
|
return out.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectBouncers(dbClient *database.Client) ([]byte, error) {
|
func collectBouncers(dbClient *database.Client) ([]byte, error) {
|
||||||
return getBouncers(dbClient)
|
out := bytes.NewBuffer(nil)
|
||||||
|
err := getBouncers(out, dbClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectAgents(dbClient *database.Client) ([]byte, error) {
|
func collectAgents(dbClient *database.Client) ([]byte, error) {
|
||||||
return getAgents(dbClient)
|
out := bytes.NewBuffer(nil)
|
||||||
|
err := getAgents(out, dbClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectAPIStatus(login string, password string, endpoint string, prefix string) []byte {
|
func collectAPIStatus(login string, password string, endpoint string, prefix string) []byte {
|
||||||
|
@ -374,7 +387,7 @@ cscli support dump -f /tmp/crowdsec-support.zip
|
||||||
log.Errorf("Could not add zip entry for %s: %s", filename, err)
|
log.Errorf("Could not add zip entry for %s: %s", filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fw.Write(data)
|
fw.Write([]byte(types.StripAnsiString(string(data))))
|
||||||
}
|
}
|
||||||
err = zipWriter.Close()
|
err = zipWriter.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
95
cmd/crowdsec-cli/tables.go
Normal file
95
cmd/crowdsec-cli/tables.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
isatty "github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shouldWeColorize() bool {
|
||||||
|
if csConfig.Cscli.Color == "yes" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if csConfig.Cscli.Color == "no" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTable(out io.Writer) *table.Table {
|
||||||
|
if out == nil {
|
||||||
|
panic("newTable: out is nil")
|
||||||
|
}
|
||||||
|
t := table.New(out)
|
||||||
|
if shouldWeColorize() {
|
||||||
|
t.SetLineStyle(table.StyleBrightBlack)
|
||||||
|
t.SetHeaderStyle(table.StyleItalic)
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldWeColorize() {
|
||||||
|
t.SetDividers(table.UnicodeRoundedDividers)
|
||||||
|
} else {
|
||||||
|
t.SetDividers(table.ASCIIDividers)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLightTable(out io.Writer) *table.Table {
|
||||||
|
if out == nil {
|
||||||
|
panic("newTable: out is nil")
|
||||||
|
}
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetRowLines(false)
|
||||||
|
t.SetBorderLeft(false)
|
||||||
|
t.SetBorderRight(false)
|
||||||
|
// This leaves three spaces between columns:
|
||||||
|
// left padding, invisible border, right padding
|
||||||
|
// There is no way to make two spaces without
|
||||||
|
// a SetColumnLines() method, but it's close enough.
|
||||||
|
t.SetPadding(1)
|
||||||
|
|
||||||
|
if shouldWeColorize() {
|
||||||
|
t.SetDividers(table.Dividers{
|
||||||
|
ALL: "─",
|
||||||
|
NES: "─",
|
||||||
|
NSW: "─",
|
||||||
|
NEW: "─",
|
||||||
|
ESW: "─",
|
||||||
|
NE: "─",
|
||||||
|
NW: "─",
|
||||||
|
SW: "─",
|
||||||
|
ES: "─",
|
||||||
|
EW: "─",
|
||||||
|
NS: " ",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
t.SetDividers(table.Dividers{
|
||||||
|
ALL: "-",
|
||||||
|
NES: "-",
|
||||||
|
NSW: "-",
|
||||||
|
NEW: "-",
|
||||||
|
ESW: "-",
|
||||||
|
NE: "-",
|
||||||
|
NW: "-",
|
||||||
|
SW: "-",
|
||||||
|
ES: "-",
|
||||||
|
EW: "-",
|
||||||
|
NS: " ",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTableTitle(out io.Writer, title string) {
|
||||||
|
if out == nil {
|
||||||
|
panic("renderTableTitle: out is nil")
|
||||||
|
}
|
||||||
|
if title == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(out, title)
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,16 +13,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
colorable "github.com/mattn/go-colorable"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
|
||||||
"github.com/enescakir/emoji"
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
"github.com/prometheus/prom2json"
|
"github.com/prometheus/prom2json"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/texttheater/golang-levenshtein/levenshtein"
|
"github.com/texttheater/golang-levenshtein/levenshtein"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxDistance = 7
|
const MaxDistance = 7
|
||||||
|
@ -161,8 +161,7 @@ func compInstalledItems(itemType string, args []string, toComplete string) ([]st
|
||||||
return comp, cobra.ShellCompDirectiveNoFileComp
|
return comp, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListItems(itemTypes []string, args []string, showType bool, showHeader bool, all bool) []byte {
|
func ListItems(out io.Writer, itemTypes []string, args []string, showType bool, showHeader bool, all bool) {
|
||||||
|
|
||||||
var hubStatusByItemType = make(map[string][]cwhub.ItemHubStatus)
|
var hubStatusByItemType = make(map[string][]cwhub.ItemHubStatus)
|
||||||
|
|
||||||
for _, itemType := range itemTypes {
|
for _, itemType := range itemTypes {
|
||||||
|
@ -173,8 +172,6 @@ func ListItems(itemTypes []string, args []string, showType bool, showHeader bool
|
||||||
hubStatusByItemType[itemType] = cwhub.GetHubStatusForItemType(itemType, itemName, all)
|
hubStatusByItemType[itemType] = cwhub.GetHubStatusForItemType(itemType, itemName, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
w := bytes.NewBuffer(nil)
|
|
||||||
|
|
||||||
if csConfig.Cscli.Output == "human" {
|
if csConfig.Cscli.Output == "human" {
|
||||||
for _, itemType := range itemTypes {
|
for _, itemType := range itemTypes {
|
||||||
var statuses []cwhub.ItemHubStatus
|
var statuses []cwhub.ItemHubStatus
|
||||||
|
@ -183,26 +180,16 @@ func ListItems(itemTypes []string, args []string, showType bool, showHeader bool
|
||||||
log.Errorf("unknown item type: %s", itemType)
|
log.Errorf("unknown item type: %s", itemType)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\n", strings.ToUpper(itemType))
|
listHubItemTable(out, "\n"+strings.ToUpper(itemType), statuses)
|
||||||
table := tablewriter.NewWriter(w)
|
|
||||||
table.SetCenterSeparator("")
|
|
||||||
table.SetColumnSeparator("")
|
|
||||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
||||||
table.SetHeader([]string{"Name", fmt.Sprintf("%v Status", emoji.Package), "Version", "Local Path"})
|
|
||||||
for _, status := range statuses {
|
|
||||||
table.Append([]string{status.Name, status.UTF8_Status, status.LocalVersion, status.LocalPath})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
}
|
}
|
||||||
} else if csConfig.Cscli.Output == "json" {
|
} else if csConfig.Cscli.Output == "json" {
|
||||||
x, err := json.MarshalIndent(hubStatusByItemType, "", " ")
|
x, err := json.MarshalIndent(hubStatusByItemType, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to unmarshal")
|
log.Fatalf("failed to unmarshal")
|
||||||
}
|
}
|
||||||
w.Write(x)
|
out.Write(x)
|
||||||
} else if csConfig.Cscli.Output == "raw" {
|
} else if csConfig.Cscli.Output == "raw" {
|
||||||
csvwriter := csv.NewWriter(w)
|
csvwriter := csv.NewWriter(out)
|
||||||
if showHeader {
|
if showHeader {
|
||||||
header := []string{"name", "status", "version", "description"}
|
header := []string{"name", "status", "version", "description"}
|
||||||
if showType {
|
if showType {
|
||||||
|
@ -242,7 +229,6 @@ func ListItems(itemTypes []string, args []string, showType bool, showHeader bool
|
||||||
}
|
}
|
||||||
csvwriter.Flush()
|
csvwriter.Flush()
|
||||||
}
|
}
|
||||||
return w.Bytes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InspectItem(name string, objecitemType string) {
|
func InspectItem(name string, objecitemType string) {
|
||||||
|
@ -279,7 +265,7 @@ func InspectItem(name string, objecitemType string) {
|
||||||
log.Debugf("No prometheus URL provided using: %s:%d", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort)
|
log.Debugf("No prometheus URL provided using: %s:%d", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort)
|
||||||
prometheusURL = fmt.Sprintf("http://%s:%d/metrics", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort)
|
prometheusURL = fmt.Sprintf("http://%s:%d/metrics", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort)
|
||||||
}
|
}
|
||||||
fmt.Printf("\nCurrent metrics : \n\n")
|
fmt.Printf("\nCurrent metrics : \n")
|
||||||
ShowMetrics(hubItem)
|
ShowMetrics(hubItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,18 +304,18 @@ func ShowMetrics(hubItem *cwhub.Item) {
|
||||||
switch hubItem.Type {
|
switch hubItem.Type {
|
||||||
case cwhub.PARSERS:
|
case cwhub.PARSERS:
|
||||||
metrics := GetParserMetric(prometheusURL, hubItem.Name)
|
metrics := GetParserMetric(prometheusURL, hubItem.Name)
|
||||||
ShowParserMetric(hubItem.Name, metrics)
|
parserMetricsTable(colorable.NewColorableStdout(), hubItem.Name, metrics)
|
||||||
case cwhub.SCENARIOS:
|
case cwhub.SCENARIOS:
|
||||||
metrics := GetScenarioMetric(prometheusURL, hubItem.Name)
|
metrics := GetScenarioMetric(prometheusURL, hubItem.Name)
|
||||||
ShowScenarioMetric(hubItem.Name, metrics)
|
scenarioMetricsTable(colorable.NewColorableStdout(), hubItem.Name, metrics)
|
||||||
case cwhub.COLLECTIONS:
|
case cwhub.COLLECTIONS:
|
||||||
for _, item := range hubItem.Parsers {
|
for _, item := range hubItem.Parsers {
|
||||||
metrics := GetParserMetric(prometheusURL, item)
|
metrics := GetParserMetric(prometheusURL, item)
|
||||||
ShowParserMetric(item, metrics)
|
parserMetricsTable(colorable.NewColorableStdout(), item, metrics)
|
||||||
}
|
}
|
||||||
for _, item := range hubItem.Scenarios {
|
for _, item := range hubItem.Scenarios {
|
||||||
metrics := GetScenarioMetric(prometheusURL, item)
|
metrics := GetScenarioMetric(prometheusURL, item)
|
||||||
ShowScenarioMetric(item, metrics)
|
scenarioMetricsTable(colorable.NewColorableStdout(), item, metrics)
|
||||||
}
|
}
|
||||||
for _, item := range hubItem.Collections {
|
for _, item := range hubItem.Collections {
|
||||||
hubItem = cwhub.GetItem(cwhub.COLLECTIONS, item)
|
hubItem = cwhub.GetItem(cwhub.COLLECTIONS, item)
|
||||||
|
@ -343,7 +329,7 @@ func ShowMetrics(hubItem *cwhub.Item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*This is a complete rip from prom2json*/
|
// GetParserMetric is a complete rip from prom2json
|
||||||
func GetParserMetric(url string, itemName string) map[string]map[string]int {
|
func GetParserMetric(url string, itemName string) map[string]map[string]int {
|
||||||
stats := make(map[string]map[string]int)
|
stats := make(map[string]map[string]int)
|
||||||
|
|
||||||
|
@ -539,37 +525,6 @@ func GetPrometheusMetric(url string) []*prom2json.Family {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func ShowScenarioMetric(itemName string, metrics map[string]int) {
|
|
||||||
if metrics["instantiation"] == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
|
||||||
table.SetHeader([]string{"Current Count", "Overflows", "Instantiated", "Poured", "Expired"})
|
|
||||||
table.Append([]string{fmt.Sprintf("%d", metrics["curr_count"]), fmt.Sprintf("%d", metrics["overflow"]), fmt.Sprintf("%d", metrics["instantiation"]), fmt.Sprintf("%d", metrics["pour"]), fmt.Sprintf("%d", metrics["underflow"])})
|
|
||||||
|
|
||||||
fmt.Printf(" - (Scenario) %s: \n", itemName)
|
|
||||||
table.Render()
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShowParserMetric(itemName string, metrics map[string]map[string]int) {
|
|
||||||
skip := true
|
|
||||||
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
|
||||||
table.SetHeader([]string{"Parsers", "Hits", "Parsed", "Unparsed"})
|
|
||||||
for source, stats := range metrics {
|
|
||||||
if stats["hits"] > 0 {
|
|
||||||
table.Append([]string{source, fmt.Sprintf("%d", stats["hits"]), fmt.Sprintf("%d", stats["parsed"]), fmt.Sprintf("%d", stats["unparsed"])})
|
|
||||||
skip = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !skip {
|
|
||||||
fmt.Printf(" - (Parser) %s: \n", itemName)
|
|
||||||
table.Render()
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestoreHub(dirPath string) error {
|
func RestoreHub(dirPath string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
66
cmd/crowdsec-cli/utils_table.go
Normal file
66
cmd/crowdsec-cli/utils_table.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/table"
|
||||||
|
"github.com/enescakir/emoji"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listHubItemTable(out io.Writer, title string, statuses []cwhub.ItemHubStatus) {
|
||||||
|
t := newLightTable(out)
|
||||||
|
t.SetHeaders("Name", fmt.Sprintf("%v Status", emoji.Package), "Version", "Local Path")
|
||||||
|
t.SetHeaderAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
t.SetAlignment(table.AlignLeft, table.AlignLeft, table.AlignLeft, table.AlignLeft)
|
||||||
|
|
||||||
|
for _, status := range statuses {
|
||||||
|
t.AddRow(status.Name, status.UTF8_Status, status.LocalVersion, status.LocalPath)
|
||||||
|
}
|
||||||
|
renderTableTitle(out, title)
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scenarioMetricsTable(out io.Writer, itemName string, metrics map[string]int) {
|
||||||
|
if metrics["instantiation"] == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetHeaders("Current Count", "Overflows", "Instantiated", "Poured", "Expired")
|
||||||
|
|
||||||
|
t.AddRow(
|
||||||
|
fmt.Sprintf("%d", metrics["curr_count"]),
|
||||||
|
fmt.Sprintf("%d", metrics["overflow"]),
|
||||||
|
fmt.Sprintf("%d", metrics["instantiation"]),
|
||||||
|
fmt.Sprintf("%d", metrics["pour"]),
|
||||||
|
fmt.Sprintf("%d", metrics["underflow"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
renderTableTitle(out, fmt.Sprintf("\n - (Scenario) %s:", itemName))
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserMetricsTable(out io.Writer, itemName string, metrics map[string]map[string]int) {
|
||||||
|
skip := true
|
||||||
|
t := newTable(out)
|
||||||
|
t.SetHeaders("Parsers", "Hits", "Parsed", "Unparsed")
|
||||||
|
|
||||||
|
for source, stats := range metrics {
|
||||||
|
if stats["hits"] > 0 {
|
||||||
|
t.AddRow(
|
||||||
|
source,
|
||||||
|
fmt.Sprintf("%d", stats["hits"]),
|
||||||
|
fmt.Sprintf("%d", stats["parsed"]),
|
||||||
|
fmt.Sprintf("%d", stats["unparsed"]),
|
||||||
|
)
|
||||||
|
skip = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skip {
|
||||||
|
renderTableTitle(out, fmt.Sprintf("\n - (Parser) %s:", itemName))
|
||||||
|
t.Render()
|
||||||
|
}
|
||||||
|
}
|
6
go.mod
6
go.mod
|
@ -6,7 +6,6 @@ require (
|
||||||
entgo.io/ent v0.11.3
|
entgo.io/ent v0.11.3
|
||||||
github.com/AlecAivazis/survey/v2 v2.2.7
|
github.com/AlecAivazis/survey/v2 v2.2.7
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
|
|
||||||
github.com/alexliesenfeld/health v0.5.1
|
github.com/alexliesenfeld/health v0.5.1
|
||||||
github.com/antonmedv/expr v1.9.0
|
github.com/antonmedv/expr v1.9.0
|
||||||
github.com/appleboy/gin-jwt/v2 v2.8.0
|
github.com/appleboy/gin-jwt/v2 v2.8.0
|
||||||
|
@ -15,6 +14,7 @@ require (
|
||||||
github.com/c-robinson/iplib v1.0.3
|
github.com/c-robinson/iplib v1.0.3
|
||||||
github.com/confluentinc/bincover v0.2.0
|
github.com/confluentinc/bincover v0.2.0
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
|
||||||
github.com/crowdsecurity/grokky v0.1.0
|
github.com/crowdsecurity/grokky v0.1.0
|
||||||
github.com/crowdsecurity/machineid v1.0.2
|
github.com/crowdsecurity/machineid v1.0.2
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
@ -83,8 +83,8 @@ require (
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/agext/levenshtein v1.2.3 // indirect
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 // indirect
|
|
||||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||||
|
github.com/aquasecurity/table v1.8.0 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
@ -165,7 +165,7 @@ require (
|
||||||
go.mongodb.org/mongo-driver v1.9.0 // indirect
|
go.mongodb.org/mongo-driver v1.9.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 // indirect
|
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect
|
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -88,6 +88,8 @@ github.com/appleboy/gin-jwt/v2 v2.8.0 h1:Glo7cb9eBR+hj8Y7WzgfkOlqCaNLjP+RV4dNO3f
|
||||||
github.com/appleboy/gin-jwt/v2 v2.8.0/go.mod h1:KsK7E8HTvRg3vOiumTsr/ntNTHbZ3IbHLe4Eto31p7k=
|
github.com/appleboy/gin-jwt/v2 v2.8.0/go.mod h1:KsK7E8HTvRg3vOiumTsr/ntNTHbZ3IbHLe4Eto31p7k=
|
||||||
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||||
|
github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0=
|
||||||
|
github.com/aquasecurity/table v1.8.0/go.mod h1:eqOmvjjB7AhXFgFqpJUEE/ietg7RrMSJZXyTN8E/wZw=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||||
|
@ -140,6 +142,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
|
||||||
|
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
|
||||||
github.com/crowdsecurity/grokky v0.0.0-20220120093523-d5b3478363fa h1:pcHZgbBbIkNDO1cAgipEgaGeFJ0se+FOPvq6A4d/g9c=
|
github.com/crowdsecurity/grokky v0.0.0-20220120093523-d5b3478363fa h1:pcHZgbBbIkNDO1cAgipEgaGeFJ0se+FOPvq6A4d/g9c=
|
||||||
github.com/crowdsecurity/grokky v0.0.0-20220120093523-d5b3478363fa/go.mod h1:fx5UYUYAFIrOUNAkFCUOM2wJcsp9EWSQE9R0/9kaFJg=
|
github.com/crowdsecurity/grokky v0.0.0-20220120093523-d5b3478363fa/go.mod h1:fx5UYUYAFIrOUNAkFCUOM2wJcsp9EWSQE9R0/9kaFJg=
|
||||||
github.com/crowdsecurity/grokky v0.1.0 h1:jLUzZd3vKxYrM4hQ8n5HWLfvs5ag4UP08eT9OTekI4U=
|
github.com/crowdsecurity/grokky v0.1.0 h1:jLUzZd3vKxYrM4hQ8n5HWLfvs5ag4UP08eT9OTekI4U=
|
||||||
|
@ -997,6 +1001,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
||||||
|
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -1207,5 +1213,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26 h1:r97WNVC30Uen+7WnLs4xDScS/Ex988+id2k6mDf8psU=
|
|
||||||
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26/go.mod h1:zpv7r+7KXwgVUZnUNjyP22zc/D7LKjyoY02weH2RBbk=
|
|
|
@ -5,11 +5,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultConfigDir is the base path to all configuration files, to be overridden in the Makefile */
|
// defaultConfigDir is the base path to all configuration files, to be overridden in the Makefile */
|
||||||
|
@ -94,6 +95,7 @@ func NewDefaultConfig() *Config {
|
||||||
|
|
||||||
cscliCfg := CscliCfg{
|
cscliCfg := CscliCfg{
|
||||||
Output: "human",
|
Output: "human",
|
||||||
|
Color: ColorDefault(),
|
||||||
}
|
}
|
||||||
|
|
||||||
apiCfg := APICfg{
|
apiCfg := APICfg{
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package csconfig
|
package csconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
/*cscli specific config, such as hub directory*/
|
/*cscli specific config, such as hub directory*/
|
||||||
type CscliCfg struct {
|
type CscliCfg struct {
|
||||||
Output string `yaml:"output,omitempty"`
|
Output string `yaml:"output,omitempty"`
|
||||||
|
Color string `yaml:"color,omitempty"`
|
||||||
HubBranch string `yaml:"hub_branch"`
|
HubBranch string `yaml:"hub_branch"`
|
||||||
SimulationConfig *SimulationConfig `yaml:"-"`
|
SimulationConfig *SimulationConfig `yaml:"-"`
|
||||||
DbConfig *DatabaseCfg `yaml:"-"`
|
DbConfig *DatabaseCfg `yaml:"-"`
|
||||||
|
@ -14,6 +19,13 @@ type CscliCfg struct {
|
||||||
PrometheusUrl string `yaml:"prometheus_uri"`
|
PrometheusUrl string `yaml:"prometheus_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ColorDefault() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "no"
|
||||||
|
}
|
||||||
|
return "auto"
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) LoadCSCLI() error {
|
func (c *Config) LoadCSCLI() error {
|
||||||
if c.Cscli == nil {
|
if c.Cscli == nil {
|
||||||
c.Cscli = &CscliCfg{}
|
c.Cscli = &CscliCfg{}
|
||||||
|
|
|
@ -8,14 +8,16 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logFormatter log.Formatter
|
var logFormatter log.Formatter
|
||||||
|
@ -257,3 +259,11 @@ func GetLineCountForFile(filepath string) int {
|
||||||
}
|
}
|
||||||
return lc
|
return lc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from https://github.com/acarl005/stripansi
|
||||||
|
var reStripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
|
||||||
|
|
||||||
|
func StripAnsiString(str string) string {
|
||||||
|
// the byte version doesn't strip correctly
|
||||||
|
return reStripAnsi.ReplaceAllString(str, "")
|
||||||
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ declare stderr
|
||||||
@test "cscli metrics" {
|
@test "cscli metrics" {
|
||||||
run -0 cscli lapi status
|
run -0 cscli lapi status
|
||||||
run -0 --separate-stderr cscli metrics
|
run -0 --separate-stderr cscli metrics
|
||||||
assert_output --partial "ROUTE"
|
assert_output --partial "Route"
|
||||||
assert_output --partial '/v1/watchers/login'
|
assert_output --partial '/v1/watchers/login'
|
||||||
assert_output --partial "Local Api Metrics:"
|
assert_output --partial "Local Api Metrics:"
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,7 @@ teardown() {
|
||||||
./instance-crowdsec start
|
./instance-crowdsec start
|
||||||
run -0 cscli lapi status
|
run -0 cscli lapi status
|
||||||
run -0 --separate-stderr cscli metrics
|
run -0 --separate-stderr cscli metrics
|
||||||
assert_output --partial "ROUTE"
|
assert_output --partial "Route"
|
||||||
assert_output --partial '/v1/watchers/login'
|
assert_output --partial '/v1/watchers/login'
|
||||||
assert_output --partial "Local Api Metrics:"
|
assert_output --partial "Local Api Metrics:"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,27 +28,28 @@ teardown() {
|
||||||
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
||||||
|
|
||||||
run -0 cscli alerts list
|
run -0 cscli alerts list
|
||||||
refute_output --partial 'MACHINE'
|
refute_output --partial 'machine'
|
||||||
# machine name appears quoted in the "REASON" column
|
# machine name appears quoted in the "REASON" column
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
refute_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
refute_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
|
|
||||||
run -0 cscli alerts list -m
|
run -0 cscli alerts list -m
|
||||||
assert_output --partial 'MACHINE'
|
assert_output --partial 'machine'
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
|
|
||||||
run -0 cscli alerts list --machine
|
run -0 cscli alerts list --machine
|
||||||
assert_output --partial 'MACHINE'
|
assert_output --partial 'machine'
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cscli alerts list, human/json/raw" {
|
@test "cscli alerts list, human/json/raw" {
|
||||||
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
||||||
|
|
||||||
run -0 cscli alerts list -o human
|
run -0 cscli alerts list -o human
|
||||||
assert_output --regexp ".* ID .* VALUE .* REASON .* COUNTRY .* AS .* DECISIONS .* CREATED AT .*"
|
run -0 plaintext < <(output)
|
||||||
|
assert_output --regexp ".* ID .* value .* reason .* country .* as .* decisions .* created_at .*"
|
||||||
assert_output --regexp ".*Ip:10.20.30.40.*manual 'ban' from.*ban:1.*"
|
assert_output --regexp ".*Ip:10.20.30.40.*manual 'ban' from.*ban:1.*"
|
||||||
|
|
||||||
run -0 cscli alerts list -o json
|
run -0 cscli alerts list -o json
|
||||||
|
@ -72,6 +73,7 @@ teardown() {
|
||||||
ALERT_ID="${output}"
|
ALERT_ID="${output}"
|
||||||
|
|
||||||
run -0 cscli alerts inspect "${ALERT_ID}" -o human
|
run -0 cscli alerts inspect "${ALERT_ID}" -o human
|
||||||
|
run -0 plaintext < <(output)
|
||||||
assert_line --regexp '^#+$'
|
assert_line --regexp '^#+$'
|
||||||
assert_line --regexp "^ - ID *: ${ALERT_ID}$"
|
assert_line --regexp "^ - ID *: ${ALERT_ID}$"
|
||||||
assert_line --regexp "^ - Date *: .*$"
|
assert_line --regexp "^ - Date *: .*$"
|
||||||
|
@ -85,7 +87,7 @@ teardown() {
|
||||||
assert_line --regexp "^ - Begin *: .*$"
|
assert_line --regexp "^ - Begin *: .*$"
|
||||||
assert_line --regexp "^ - End *: .*$"
|
assert_line --regexp "^ - End *: .*$"
|
||||||
assert_line --regexp "^ - Active Decisions *:$"
|
assert_line --regexp "^ - Active Decisions *:$"
|
||||||
assert_line --regexp "^.* ID .* SCOPE:VALUE .* ACTION .* EXPIRATION .* CREATED AT .*$"
|
assert_line --regexp "^.* ID .* scope:value .* action .* expiration .* created_at .*$"
|
||||||
assert_line --regexp "^.* Ip:10.20.30.40 .* ban .*$"
|
assert_line --regexp "^.* Ip:10.20.30.40 .* ban .*$"
|
||||||
|
|
||||||
run -0 cscli alerts inspect "${ALERT_ID}" -o human --details
|
run -0 cscli alerts inspect "${ALERT_ID}" -o human --details
|
||||||
|
|
|
@ -41,20 +41,20 @@ declare stderr
|
||||||
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
run -0 cscli decisions add -i 10.20.30.40 -t ban
|
||||||
|
|
||||||
run -0 cscli decisions list
|
run -0 cscli decisions list
|
||||||
refute_output --partial 'MACHINE'
|
refute_output --partial 'Machine'
|
||||||
# machine name appears quoted in the "REASON" column
|
# machine name appears quoted in the "REASON" column
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
refute_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
refute_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
|
|
||||||
run -0 cscli decisions list -m
|
run -0 cscli decisions list -m
|
||||||
assert_output --partial 'MACHINE'
|
assert_output --partial 'Machine'
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
|
|
||||||
run -0 cscli decisions list --machine
|
run -0 cscli decisions list --machine
|
||||||
assert_output --partial 'MACHINE'
|
assert_output --partial 'Machine'
|
||||||
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
|
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
|
||||||
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
|
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cscli decisions list, incorrect parameters" {
|
@test "cscli decisions list, incorrect parameters" {
|
||||||
|
|
|
@ -37,7 +37,7 @@ EOT
|
||||||
|
|
||||||
echo "Generating hub tests..."
|
echo "Generating hub tests..."
|
||||||
|
|
||||||
for testname in $("${CSCLI}" --crowdsec "${CROWDSEC}" --cscli "${CSCLI}" hubtest --hub "${hubdir}" list -o json | grep -v NAME | grep -v -- '-------' | awk '{print $1}'); do
|
for testname in $("${CSCLI}" --crowdsec "${CROWDSEC}" --cscli "${CSCLI}" hubtest --hub "${hubdir}" list -o json | jq -r '.[] | .Name'); do
|
||||||
cat << EOT >> "${HUBTESTS_BATS}"
|
cat << EOT >> "${HUBTESTS_BATS}"
|
||||||
|
|
||||||
@test "${testname}" {
|
@test "${testname}" {
|
||||||
|
|
|
@ -148,6 +148,7 @@ assert_json() {
|
||||||
}
|
}
|
||||||
export -f assert_json
|
export -f assert_json
|
||||||
|
|
||||||
|
# like assert_output, but for stderr
|
||||||
assert_stderr() {
|
assert_stderr() {
|
||||||
oldout="${output}"
|
oldout="${output}"
|
||||||
run -0 echo "${stderr}"
|
run -0 echo "${stderr}"
|
||||||
|
@ -156,6 +157,7 @@ assert_stderr() {
|
||||||
}
|
}
|
||||||
export -f assert_stderr
|
export -f assert_stderr
|
||||||
|
|
||||||
|
# like refute_output, but for stderr
|
||||||
refute_stderr() {
|
refute_stderr() {
|
||||||
oldout="${output}"
|
oldout="${output}"
|
||||||
run -0 echo "${stderr}"
|
run -0 echo "${stderr}"
|
||||||
|
@ -164,6 +166,7 @@ refute_stderr() {
|
||||||
}
|
}
|
||||||
export -f refute_stderr
|
export -f refute_stderr
|
||||||
|
|
||||||
|
# like assert_output, but for stderr
|
||||||
assert_stderr_line() {
|
assert_stderr_line() {
|
||||||
oldout="${output}"
|
oldout="${output}"
|
||||||
run -0 echo "${stderr}"
|
run -0 echo "${stderr}"
|
||||||
|
@ -172,3 +175,8 @@ assert_stderr_line() {
|
||||||
}
|
}
|
||||||
export -f assert_stderr_line
|
export -f assert_stderr_line
|
||||||
|
|
||||||
|
# remove color and style sequences from stdin
|
||||||
|
plaintext() {
|
||||||
|
sed -E 's/\x1B\[[0-9;]*[JKmsu]//g'
|
||||||
|
}
|
||||||
|
export -f plaintext
|
||||||
|
|
Loading…
Reference in a new issue