cscli: new tables, --color yes|no|auto option (#1763)

This commit is contained in:
mmetc 2022-10-07 11:05:35 +02:00 committed by GitHub
parent b95a67751e
commit ddd75eae9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1055 additions and 679 deletions

View file

@ -7,21 +7,20 @@ import (
"fmt"
"net/url"
"os"
"sort"
"strconv"
"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/cwversion"
"github.com/crowdsecurity/crowdsec/pkg/database"
"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
@ -83,39 +82,11 @@ func AlertsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
x, _ := json.MarshalIndent(alerts, "", " ")
fmt.Printf("%s", string(x))
} 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 {
fmt.Println("No active alerts")
return nil
}
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)
}
table.Append(row)
}
table.Render() // Send output
alertsTable(colorable.NewColorableStdout(), alerts, printMachine)
}
return nil
}
@ -138,53 +109,13 @@ func DisplayOneAlert(alert *models.Alert, withDetail bool) error {
fmt.Printf(" - AS : %s\n", alert.Source.AsName)
fmt.Printf(" - Begin : %s\n", *alert.StartAt)
fmt.Printf(" - End : %s\n\n", *alert.StopAt)
foundActive := false
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "scope:value", "action", "expiration", "created_at"})
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
}
alertDecisionsTable(colorable.NewColorableStdout(), alert)
if withDetail {
fmt.Printf("\n - Events :\n")
for _, event := range alert.Events {
fmt.Printf("\n- Date: %s\n", *event.Timestamp)
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
alertEventTable(colorable.NewColorableStdout(), event)
}
}
}

View 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
}

View file

@ -1,62 +1,45 @@
package main
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"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"
"github.com/crowdsecurity/crowdsec/pkg/database"
"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 keyLength int
var key string
func getBouncers(dbClient *database.Client) ([]byte, error) {
func getBouncers(out io.Writer, dbClient *database.Client) error {
bouncers, err := dbClient.ListBouncers()
w := bytes.NewBuffer(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" {
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()
getBouncersTable(out, bouncers)
} else if csConfig.Cscli.Output == "json" {
x, err := json.MarshalIndent(bouncers, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal")
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
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" {
csvwriter := csv.NewWriter(w)
csvwriter := csv.NewWriter(out)
err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"})
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 {
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})
if err != nil {
return nil, errors.Wrap(err, "failed to write raw")
return errors.Wrap(err, "failed to write raw")
}
}
csvwriter.Flush()
}
return w.Bytes(), nil
return nil
}
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),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, arg []string) {
bouncers, err := getBouncers(dbClient)
err := getBouncers(colorable.NewColorableStdout(), dbClient)
if err != nil {
log.Fatalf("unable to list bouncers: %s", err)
}
fmt.Printf("%s", bouncers)
},
}
cmdBouncers.AddCommand(cmdBouncersList)

View 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()
}

View file

@ -3,11 +3,11 @@ package main
import (
"fmt"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
colorable "github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
)
func NewCollectionsCmd() *cobra.Command {
@ -173,8 +173,7 @@ func NewCollectionsCmd() *cobra.Command {
Args: cobra.ExactArgs(0),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
items := ListItems([]string{cwhub.COLLECTIONS}, args, false, true, all)
fmt.Printf("%s\n", string(items))
ListItems(colorable.NewColorableStdout(), []string{cwhub.COLLECTIONS}, args, false, true, all)
},
}
cmdCollectionsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")

View file

@ -10,16 +10,16 @@ import (
"net/url"
"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/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
"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 {
@ -194,34 +194,7 @@ Disable given information push to the central API.`,
Run: func(cmd *cobra.Command, args []string) {
switch csConfig.Cscli.Output {
case "human":
table := tablewriter.NewWriter(os.Stdout)
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()
cmdConsoleStatusTable(colorable.NewColorableStdout(), *csConfig)
case "json":
data, err := json.MarshalIndent(csConfig.API.Server.ConsoleConfig, "", " ")
if err != nil {

View 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()
}

View file

@ -12,16 +12,17 @@ import (
"strings"
"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/cwversion"
"github.com/crowdsecurity/crowdsec/pkg/models"
"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
@ -92,44 +93,11 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
x, _ := json.MarshalIndent(alerts, "", " ")
fmt.Printf("%s", string(x))
} 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 {
fmt.Println("No active decisions")
return nil
}
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
decisionsTable(colorable.NewColorableStdout(), alerts, printMachine)
if skipped > 0 {
fmt.Printf("%d duplicated entries skipped\n", skipped)
}

View 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()
}

View file

@ -3,10 +3,11 @@ package main
import (
"fmt"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
colorable "github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
)
func NewHubCmd() *cobra.Command {
@ -56,10 +57,9 @@ cscli hub update # Download list of available configurations from the hub
log.Info(v)
}
cwhub.DisplaySummary()
items := ListItems([]string{
ListItems(colorable.NewColorableStdout(), []string{
cwhub.COLLECTIONS, cwhub.PARSERS, cwhub.SCENARIOS, cwhub.PARSERS_OVFLW,
}, args, true, false, all)
fmt.Printf("%s\n", items)
},
}
cmdHubList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")

View file

@ -9,12 +9,13 @@ import (
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/crowdsecurity/crowdsec/pkg/cstest"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
colorable "github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
"github.com/crowdsecurity/crowdsec/pkg/cstest"
)
var (
@ -272,22 +273,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
}
}
if csConfig.Cscli.Output == "human" {
table := tablewriter.NewWriter(os.Stdout)
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()
hubTestResultTable(colorable.NewColorableStdout(), testResult)
} else if csConfig.Cscli.Output == "json" {
jsonResult := make(map[string][]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)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetHeader([]string{"Name", "Path"})
for _, test := range HubTest.Tests {
table.Append([]string{test.Name, test.Path})
switch csConfig.Cscli.Output {
case "human":
hubTestListTable(colorable.NewColorableStdout(), HubTest.Tests)
case "json":
j, err := json.MarshalIndent(HubTest.Tests, " ", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(j))
default:
log.Fatalf("only human/json output modes are supported")
}
table.Render()
},
}
cmdHubTest.AddCommand(cmdHubTestList)
@ -399,11 +385,9 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
parserCoverage := []cstest.ParserCoverage{}
scenarioCoveragePercent := 0
parserCoveragePercent := 0
showAll := false
if !showScenarioCov && !showParserCov { // if both are false (flag by default), show both
showAll = true
}
// if both are false (flag by default), show both
showAll := !showScenarioCov && !showParserCov
if showParserCov || showAll {
parserCoverage, err = HubTest.GetParsersCoverage()
@ -446,43 +430,11 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
if csConfig.Cscli.Output == "human" {
if showParserCov || showAll {
table := tablewriter.NewWriter(os.Stdout)
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()
hubTestParserCoverageTable(colorable.NewColorableStdout(), parserCoverage)
}
if showScenarioCov || showAll {
table := tablewriter.NewWriter(os.Stdout)
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()
hubTestScenarioCoverageTable(colorable.NewColorableStdout(), scenarioCoverage)
}
fmt.Println()
if showParserCov || showAll {

View 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()
}

View file

@ -1,30 +1,32 @@
package main
import (
"bytes"
saferand "crypto/rand"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"strings"
"time"
"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/go-openapi/strfmt"
"github.com/google/uuid"
"github.com/olekukonko/tablewriter"
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/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
@ -43,7 +45,6 @@ var (
)
func generatePassword(length int) string {
charset := upper + lower + digits
charsetLength := len(charset)
@ -109,50 +110,34 @@ func displayLastHeartBeat(m *ent.Machine, fancy bool) string {
return hbDisplay
}
func getAgents(dbClient *database.Client) ([]byte, error) {
w := bytes.NewBuffer(nil)
func getAgents(out io.Writer, dbClient *database.Client) error {
machines, err := dbClient.ListMachines()
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" {
table := tablewriter.NewWriter(w)
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()
getAgentsTable(out, machines)
} else if csConfig.Cscli.Output == "json" {
x, err := json.MarshalIndent(machines, "", " ")
if err != nil {
enc := json.NewEncoder(out)
enc.SetIndent("", " ")
if err := enc.Encode(machines); err != nil {
log.Fatalf("failed to unmarshal")
}
return x, nil
return nil
} 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"})
if err != nil {
log.Fatalf("failed to write header: %s", err)
}
for _, w := range machines {
for _, m := range machines {
var validated string
if w.IsValidated {
if m.IsValidated {
validated = "true"
} else {
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 {
log.Fatalf("failed to write raw output : %s", err)
}
@ -161,7 +146,7 @@ func getAgents(dbClient *database.Client) ([]byte, error) {
} else {
log.Errorf("unknown output '%s'", csConfig.Cscli.Output)
}
return w.Bytes(), nil
return nil
}
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) {
agents, err := getAgents(dbClient)
err := getAgents(colorable.NewColorableStdout(), dbClient)
if err != nil {
log.Fatalf("unable to list machines: %s", err)
}
fmt.Printf("%s\n", agents)
},
}
cmdMachines.AddCommand(cmdMachinesList)

View 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()
}

View file

@ -8,14 +8,14 @@ import (
"strings"
"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/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
"github.com/crowdsecurity/crowdsec/pkg/database"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
var bincoverTesting = ""
@ -27,6 +27,7 @@ var csConfig *csconfig.Config
var dbClient *database.Client
var OutputFormat string
var OutputColor string
var downloadOnly bool
var forceAction bool
@ -88,6 +89,12 @@ func initConfig() {
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{
@ -159,7 +166,8 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
rootCmd.AddCommand(cmdVersion)
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(&nfo_lvl, "info", false, "Set logging to info.")
rootCmd.PersistentFlags().BoolVar(&wrn_lvl, "warning", false, "Set logging to warning.")

View file

@ -1,96 +1,27 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/crowdsecurity/crowdsec/pkg/types"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/olekukonko/tablewriter"
colorable "github.com/mattn/go-colorable"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/prom2json"
log "github.com/sirupsen/logrus"
"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 {
//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) {
// FormatPrometheusMetrics is a complete rip from prom2json
func FormatPrometheusMetrics(out io.Writer, url string, formatType string) error {
mfChan := make(chan *dto.MetricFamily, 1024)
// 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" {
acquisTable := tablewriter.NewWriter(ret)
acquisTable.SetHeader([]string{"Source", "Lines read", "Lines parsed", "Lines unparsed", "Lines poured to bucket"})
keys := []string{"reads", "parsed", "unparsed", "pour"}
if err := metricsToTable(acquisTable, acquis_stats, keys); err != nil {
log.Warningf("while collecting acquis stats : %s", err)
}
bucketsTable := tablewriter.NewWriter(ret)
bucketsTable.SetHeader([]string{"Bucket", "Current Count", "Overflows", "Instantiated", "Poured", "Expired"})
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()
}
acquisStatsTable(out, acquis_stats)
bucketStatsTable(out, buckets_stats)
parserStatsTable(out, parsers_stats)
lapiStatsTable(out, lapi_stats)
lapiMachineStatsTable(out, lapi_machine_stats)
lapiBouncerStatsTable(out, lapi_bouncer_stats)
lapiDecisionStatsTable(out, lapi_decisions_stats)
decisionStatsTable(out, decisions_stats)
alertStatsTable(out, alerts_stats)
} 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} {
x, err := json.MarshalIndent(val, "", " ")
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" {
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)
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
@ -479,11 +276,10 @@ func NewMetricsCmd() *cobra.Command {
os.Exit(1)
}
metrics, err := FormatPrometheusMetric(prometheusURL+"/metrics", csConfig.Cscli.Output)
err := FormatPrometheusMetrics(colorable.NewColorableStdout(), prometheusURL+"/metrics", csConfig.Cscli.Output)
if err != nil {
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)")

View 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()
}
}

View file

@ -13,17 +13,18 @@ import (
"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/tomb.v2"
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
"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 {
@ -67,22 +68,7 @@ func NewNotificationsCmd() *cobra.Command {
}
if csConfig.Cscli.Output == "human" {
table := tablewriter.NewWriter(os.Stdout)
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()
notificationListTable(colorable.NewColorableStdout(), ncfgs)
} else if csConfig.Cscli.Output == "json" {
x, err := json.MarshalIndent(ncfgs, "", " ")
if err != nil {

View 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()
}

View file

@ -3,11 +3,11 @@ package main
import (
"fmt"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
colorable "github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
)
func NewParsersCmd() *cobra.Command {
@ -164,8 +164,7 @@ cscli parsers remove crowdsecurity/sshd-logs
cscli parser list crowdsecurity/xxx`,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
items := ListItems([]string{cwhub.PARSERS}, args, false, true, all)
fmt.Printf("%s\n", items)
ListItems(colorable.NewColorableStdout(), []string{cwhub.PARSERS}, args, false, true, all)
},
}
cmdParsersList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")

View file

@ -3,11 +3,11 @@ package main
import (
"fmt"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
colorable "github.com/mattn/go-colorable"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
)
func NewPostOverflowsCmd() *cobra.Command {
@ -162,8 +162,7 @@ func NewPostOverflowsCmd() *cobra.Command {
cscli postoverflows list crowdsecurity/xxx`,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
items := ListItems([]string{cwhub.PARSERS_OVFLW}, args, false, true, all)
fmt.Printf("%s\n", items)
ListItems(colorable.NewColorableStdout(), []string{cwhub.PARSERS_OVFLW}, args, false, true, all)
},
}
cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")

View file

@ -3,11 +3,12 @@ package main
import (
"fmt"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
colorable "github.com/mattn/go-colorable"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
)
func NewScenariosCmd() *cobra.Command {
@ -166,8 +167,7 @@ cscli scenarios remove crowdsecurity/ssh-bf
cscli scenarios list crowdsecurity/xxx`,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
items := ListItems([]string{cwhub.SCENARIOS}, args, false, true, all)
fmt.Printf("%s\n", items)
ListItems(colorable.NewColorableStdout(), []string{cwhub.SCENARIOS}, args, false, true, all)
},
}
cmdScenariosList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")

View file

@ -14,15 +14,16 @@ import (
"strings"
"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/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
"github.com/crowdsecurity/crowdsec/pkg/database"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
const (
@ -55,7 +56,8 @@ func collectMetrics() ([]byte, []byte, error) {
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 {
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 humanMetrics, body, nil
return humanMetrics.Bytes(), body, nil
}
func collectVersion() []byte {
@ -126,17 +128,28 @@ func initHub() error {
}
func collectHubItems(itemType string) []byte {
out := bytes.NewBuffer(nil)
log.Infof("Collecting %s list", itemType)
items := ListItems([]string{itemType}, []string{}, false, true, all)
return items
ListItems(out, []string{itemType}, []string{}, false, true, all)
return out.Bytes()
}
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) {
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 {
@ -374,7 +387,7 @@ cscli support dump -f /tmp/crowdsec-support.zip
log.Errorf("Could not add zip entry for %s: %s", filename, err)
continue
}
fw.Write(data)
fw.Write([]byte(types.StripAnsiString(string(data))))
}
err = zipWriter.Close()
if err != nil {

View 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)
}

View file

@ -1,10 +1,10 @@
package main
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"math"
"net"
"net/http"
@ -13,16 +13,16 @@ import (
"strings"
"time"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/enescakir/emoji"
"github.com/olekukonko/tablewriter"
colorable "github.com/mattn/go-colorable"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/prom2json"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/texttheater/golang-levenshtein/levenshtein"
"gopkg.in/yaml.v2"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
const MaxDistance = 7
@ -161,8 +161,7 @@ func compInstalledItems(itemType string, args []string, toComplete string) ([]st
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)
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)
}
w := bytes.NewBuffer(nil)
if csConfig.Cscli.Output == "human" {
for _, itemType := range itemTypes {
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)
continue
}
fmt.Fprintf(w, "%s\n", strings.ToUpper(itemType))
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()
listHubItemTable(out, "\n"+strings.ToUpper(itemType), statuses)
}
} else if csConfig.Cscli.Output == "json" {
x, err := json.MarshalIndent(hubStatusByItemType, "", " ")
if err != nil {
log.Fatalf("failed to unmarshal")
}
w.Write(x)
out.Write(x)
} else if csConfig.Cscli.Output == "raw" {
csvwriter := csv.NewWriter(w)
csvwriter := csv.NewWriter(out)
if showHeader {
header := []string{"name", "status", "version", "description"}
if showType {
@ -242,7 +229,6 @@ func ListItems(itemTypes []string, args []string, showType bool, showHeader bool
}
csvwriter.Flush()
}
return w.Bytes()
}
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)
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)
}
}
@ -318,18 +304,18 @@ func ShowMetrics(hubItem *cwhub.Item) {
switch hubItem.Type {
case cwhub.PARSERS:
metrics := GetParserMetric(prometheusURL, hubItem.Name)
ShowParserMetric(hubItem.Name, metrics)
parserMetricsTable(colorable.NewColorableStdout(), hubItem.Name, metrics)
case cwhub.SCENARIOS:
metrics := GetScenarioMetric(prometheusURL, hubItem.Name)
ShowScenarioMetric(hubItem.Name, metrics)
scenarioMetricsTable(colorable.NewColorableStdout(), hubItem.Name, metrics)
case cwhub.COLLECTIONS:
for _, item := range hubItem.Parsers {
metrics := GetParserMetric(prometheusURL, item)
ShowParserMetric(item, metrics)
parserMetricsTable(colorable.NewColorableStdout(), item, metrics)
}
for _, item := range hubItem.Scenarios {
metrics := GetScenarioMetric(prometheusURL, item)
ShowScenarioMetric(item, metrics)
scenarioMetricsTable(colorable.NewColorableStdout(), item, metrics)
}
for _, item := range hubItem.Collections {
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 {
stats := make(map[string]map[string]int)
@ -480,7 +466,7 @@ func GetScenarioMetric(url string, itemName string) map[string]int {
return stats
}
//it's a rip of the cli version, but in silent-mode
// it's a rip of the cli version, but in silent-mode
func silenceInstallItem(name string, obtype string) (string, error) {
var item = cwhub.GetItem(obtype, name)
if item == nil {
@ -539,37 +525,6 @@ func GetPrometheusMetric(url string) []*prom2json.Family {
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 {
var err error

View 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
View file

@ -6,7 +6,6 @@ require (
entgo.io/ent v0.11.3
github.com/AlecAivazis/survey/v2 v2.2.7
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/antonmedv/expr v1.9.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/confluentinc/bincover v0.2.0
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/machineid v1.0.2
github.com/davecgh/go-spew v1.1.1
@ -83,8 +83,8 @@ require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // 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/aquasecurity/table v1.8.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
@ -165,7 +165,7 @@ require (
go.mongodb.org/mongo-driver v1.9.0 // indirect
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 // 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
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect

8
go.sum
View file

@ -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/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
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-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
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.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
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/go.mod h1:fx5UYUYAFIrOUNAkFCUOM2wJcsp9EWSQE9R0/9kaFJg=
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-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-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.3.0/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
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=

View file

@ -5,11 +5,12 @@ import (
"os"
"path/filepath"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/crowdsecurity/crowdsec/pkg/yamlpatch"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"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 */
@ -94,6 +95,7 @@ func NewDefaultConfig() *Config {
cscliCfg := CscliCfg{
Output: "human",
Color: ColorDefault(),
}
apiCfg := APICfg{

View file

@ -1,8 +1,13 @@
package csconfig
import (
"runtime"
)
/*cscli specific config, such as hub directory*/
type CscliCfg struct {
Output string `yaml:"output,omitempty"`
Color string `yaml:"color,omitempty"`
HubBranch string `yaml:"hub_branch"`
SimulationConfig *SimulationConfig `yaml:"-"`
DbConfig *DatabaseCfg `yaml:"-"`
@ -14,6 +19,13 @@ type CscliCfg struct {
PrometheusUrl string `yaml:"prometheus_uri"`
}
func ColorDefault() string {
if runtime.GOOS == "windows" {
return "no"
}
return "auto"
}
func (c *Config) LoadCSCLI() error {
if c.Cscli == nil {
c.Cscli = &CscliCfg{}

View file

@ -24,7 +24,7 @@ const (
TIMEMACHINE
)
//Leaky represents one instance of a bucket
// Leaky represents one instance of a bucket
type Leaky struct {
Name string
Mode int //LIVE or TIMEMACHINE

View file

@ -8,14 +8,16 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
log "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
)
var logFormatter log.Formatter
@ -257,3 +259,11 @@ func GetLineCountForFile(filepath string) int {
}
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, "")
}

View file

@ -213,7 +213,7 @@ declare stderr
@test "cscli metrics" {
run -0 cscli lapi status
run -0 --separate-stderr cscli metrics
assert_output --partial "ROUTE"
assert_output --partial "Route"
assert_output --partial '/v1/watchers/login'
assert_output --partial "Local Api Metrics:"

View file

@ -75,8 +75,7 @@ teardown() {
./instance-crowdsec start
run -0 cscli lapi status
run -0 --separate-stderr cscli metrics
assert_output --partial "ROUTE"
assert_output --partial "Route"
assert_output --partial '/v1/watchers/login'
assert_output --partial "Local Api Metrics:"
}

View file

@ -28,27 +28,28 @@ teardown() {
run -0 cscli decisions add -i 10.20.30.40 -t ban
run -0 cscli alerts list
refute_output --partial 'MACHINE'
refute_output --partial 'machine'
# machine name appears quoted in the "REASON" column
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
refute_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})? "
run -0 cscli alerts list -m
assert_output --partial 'MACHINE'
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
assert_output --partial 'machine'
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
run -0 cscli alerts list --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 --partial 'machine'
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" {
run -0 cscli decisions add -i 10.20.30.40 -t ban
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.*"
run -0 cscli alerts list -o json
@ -72,6 +73,7 @@ teardown() {
ALERT_ID="${output}"
run -0 cscli alerts inspect "${ALERT_ID}" -o human
run -0 plaintext < <(output)
assert_line --regexp '^#+$'
assert_line --regexp "^ - ID *: ${ALERT_ID}$"
assert_line --regexp "^ - Date *: .*$"
@ -85,7 +87,7 @@ teardown() {
assert_line --regexp "^ - Begin *: .*$"
assert_line --regexp "^ - End *: .*$"
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 .*$"
run -0 cscli alerts inspect "${ALERT_ID}" -o human --details

View file

@ -41,20 +41,20 @@ declare stderr
run -0 cscli decisions add -i 10.20.30.40 -t ban
run -0 cscli decisions list
refute_output --partial 'MACHINE'
refute_output --partial 'Machine'
# machine name appears quoted in the "REASON" column
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
refute_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})? "
run -0 cscli decisions list -m
assert_output --partial 'MACHINE'
assert_output --regexp "\| 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' \|"
assert_output --regexp "\| githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? \|"
assert_output --partial 'Machine'
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
run -0 cscli decisions list --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 --partial 'Machine'
assert_output --regexp " 'githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})?' "
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
}
@test "cscli decisions list, incorrect parameters" {

View file

@ -37,7 +37,7 @@ EOT
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}"
@test "${testname}" {

View file

@ -148,6 +148,7 @@ assert_json() {
}
export -f assert_json
# like assert_output, but for stderr
assert_stderr() {
oldout="${output}"
run -0 echo "${stderr}"
@ -156,6 +157,7 @@ assert_stderr() {
}
export -f assert_stderr
# like refute_output, but for stderr
refute_stderr() {
oldout="${output}"
run -0 echo "${stderr}"
@ -164,6 +166,7 @@ refute_stderr() {
}
export -f refute_stderr
# like assert_output, but for stderr
assert_stderr_line() {
oldout="${output}"
run -0 echo "${stderr}"
@ -172,3 +175,8 @@ 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