Skip to content

Commit

Permalink
refact "cscli decisions" (crowdsecurity#2804)
Browse files Browse the repository at this point in the history
* refact "cscli decisions"
* CI: relax mysql test timing
* lint
  • Loading branch information
mmetc authored Feb 1, 2024
1 parent f5fbe4a commit 4160bb8
Showing 14 changed files with 103 additions and 48 deletions.
3 changes: 2 additions & 1 deletion cmd/crowdsec-cli/dashboard.go
Original file line number Diff line number Diff line change
@@ -176,7 +176,7 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password <password>
flags.StringVar(&metabaseImage, "metabase-image", metabaseImage, "Metabase image to use")
flags.StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container")
flags.BoolVarP(&forceYes, "yes", "y", false, "force yes")
//flags.StringVarP(&metabaseUser, "user", "u", "[email protected]", "metabase user")
// flags.StringVarP(&metabaseUser, "user", "u", "[email protected]", "metabase user")
flags.StringVar(&metabasePassword, "password", "", "metabase password")

return cmd
@@ -443,6 +443,7 @@ func checkGroups(forceYes *bool) (*user.Group, error) {
func (cli *cliDashboard) chownDatabase(gid string) error {
cfg := cli.cfg()
intID, err := strconv.Atoi(gid)

if err != nil {
return fmt.Errorf("unable to convert group ID to int: %s", err)
}
57 changes: 33 additions & 24 deletions cmd/crowdsec-cli/decisions.go
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ import (

var Client *apiclient.ApiClient

func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
func (cli *cliDecisions) decisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
/*here we cheat a bit : to make it more readable for the user, we dedup some entries*/
spamLimit := make(map[string]bool)
skipped := 0
@@ -49,7 +49,8 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
alertItem.Decisions = newDecisions
}

if csConfig.Cscli.Output == "raw" {
switch cli.cfg().Cscli.Output {
case "raw":
csvwriter := csv.NewWriter(os.Stdout)
header := []string{"id", "source", "ip", "reason", "action", "country", "as", "events_count", "expiration", "simulated", "alert_id"}

@@ -89,21 +90,24 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
}

csvwriter.Flush()
} else if csConfig.Cscli.Output == "json" {
case "json":
if *alerts == nil {
// avoid returning "null" in `json"
// could be cleaner if we used slice of alerts directly
fmt.Println("[]")
return nil
}

x, _ := json.MarshalIndent(alerts, "", " ")
fmt.Printf("%s", string(x))
} else if csConfig.Cscli.Output == "human" {
case "human":
if len(*alerts) == 0 {
fmt.Println("No active decisions")
return nil
}
decisionsTable(color.Output, alerts, printMachine)

cli.decisionsTable(color.Output, alerts, printMachine)

if skipped > 0 {
fmt.Printf("%d duplicated entries skipped\n", skipped)
}
@@ -113,13 +117,17 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
}


type cliDecisions struct {}
type cliDecisions struct {
cfg configGetter
}

func NewCLIDecisions() *cliDecisions {
return &cliDecisions{}
func NewCLIDecisions(getconfig configGetter) *cliDecisions {
return &cliDecisions{
cfg: getconfig,
}
}

func (cli cliDecisions) NewCommand() *cobra.Command {
func (cli *cliDecisions) NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "decisions [action]",
Short: "Manage decisions",
@@ -130,16 +138,17 @@ func (cli cliDecisions) NewCommand() *cobra.Command {
Args: cobra.MinimumNArgs(1),
DisableAutoGenTag: true,
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
if err := csConfig.LoadAPIClient(); err != nil {
cfg := cli.cfg()
if err := cfg.LoadAPIClient(); err != nil {
return fmt.Errorf("loading api client: %w", err)
}
password := strfmt.Password(csConfig.API.Client.Credentials.Password)
apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL)
password := strfmt.Password(cfg.API.Client.Credentials.Password)
apiurl, err := url.Parse(cfg.API.Client.Credentials.URL)
if err != nil {
return fmt.Errorf("parsing api url %s: %w", csConfig.API.Client.Credentials.URL, err)
return fmt.Errorf("parsing api url %s: %w", cfg.API.Client.Credentials.URL, err)
}
Client, err = apiclient.NewClient(&apiclient.Config{
MachineID: csConfig.API.Client.Credentials.Login,
MachineID: cfg.API.Client.Credentials.Login,
Password: password,
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
URL: apiurl,
@@ -152,15 +161,15 @@ func (cli cliDecisions) NewCommand() *cobra.Command {
},
}

cmd.AddCommand(cli.NewListCmd())
cmd.AddCommand(cli.NewAddCmd())
cmd.AddCommand(cli.NewDeleteCmd())
cmd.AddCommand(cli.NewImportCmd())
cmd.AddCommand(cli.newListCmd())
cmd.AddCommand(cli.newAddCmd())
cmd.AddCommand(cli.newDeleteCmd())
cmd.AddCommand(cli.newImportCmd())

return cmd
}

func (cli cliDecisions) NewListCmd() *cobra.Command {
func (cli *cliDecisions) newListCmd() *cobra.Command {
var filter = apiclient.AlertsListOpts{
ValueEquals: new(string),
ScopeEquals: new(string),
@@ -262,7 +271,7 @@ cscli decisions list -t ban
return fmt.Errorf("unable to retrieve decisions: %w", err)
}

err = DecisionsToTable(alerts, printMachine)
err = cli.decisionsToTable(alerts, printMachine)
if err != nil {
return fmt.Errorf("unable to print decisions: %w", err)
}
@@ -289,7 +298,7 @@ cscli decisions list -t ban
return cmd
}

func (cli cliDecisions) NewAddCmd() *cobra.Command {
func (cli *cliDecisions) newAddCmd() *cobra.Command {
var (
addIP string
addRange string
@@ -325,7 +334,7 @@ cscli decisions add --scope username --value foobar
createdAt := time.Now().UTC().Format(time.RFC3339)

/*take care of shorthand options*/
if err := manageCliDecisionAlerts(&addIP, &addRange, &addScope, &addValue); err != nil {
if err = manageCliDecisionAlerts(&addIP, &addRange, &addScope, &addValue); err != nil {
return err
}

@@ -341,7 +350,7 @@ cscli decisions add --scope username --value foobar
}

if addReason == "" {
addReason = fmt.Sprintf("manual '%s' from '%s'", addType, csConfig.API.Client.Credentials.Login)
addReason = fmt.Sprintf("manual '%s' from '%s'", addType, cli.cfg().API.Client.Credentials.Login)
}
decision := models.Decision{
Duration: &addDuration,
@@ -400,7 +409,7 @@ cscli decisions add --scope username --value foobar
return cmd
}

func (cli cliDecisions) NewDeleteCmd() *cobra.Command {
func (cli *cliDecisions) newDeleteCmd() *cobra.Command {
var delFilter = apiclient.DecisionsDeleteOpts{
ScopeEquals: new(string),
ValueEquals: new(string),
4 changes: 2 additions & 2 deletions cmd/crowdsec-cli/decisions_import.go
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ func parseDecisionList(content []byte, format string) ([]decisionRaw, error) {
}


func (cli cliDecisions) runImport(cmd *cobra.Command, args []string) error {
func (cli *cliDecisions) runImport(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()

input, err := flags.GetString("input")
@@ -236,7 +236,7 @@ func (cli cliDecisions) runImport(cmd *cobra.Command, args []string) error {
}


func (cli cliDecisions) NewImportCmd() *cobra.Command {
func (cli *cliDecisions) newImportCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "import [options]",
Short: "Import decisions from a file or pipe",
6 changes: 5 additions & 1 deletion cmd/crowdsec-cli/decisions_table.go
Original file line number Diff line number Diff line change
@@ -8,20 +8,23 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/models"
)

func decisionsTable(out io.Writer, alerts *models.GetAlertsResponse, printMachine bool) {
func (cli *cliDecisions) 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,
@@ -42,5 +45,6 @@ func decisionsTable(out io.Writer, alerts *models.GetAlertsResponse, printMachin
t.AddRow(row...)
}
}

t.Render()
}
1 change: 1 addition & 0 deletions cmd/crowdsec-cli/flag.go
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ func (p *MachinePassword) Set(v string) error {
if len(v) > 72 {
return errors.New("password too long (max 72 characters)")
}

*p = MachinePassword(v)

return nil
16 changes: 12 additions & 4 deletions cmd/crowdsec-cli/machines.go
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ func generatePassword(length int) string {
if err != nil {
log.Fatalf("failed getting data from prng for password generation : %s", err)
}

buf[i] = charset[rInt.Int64()]
}

@@ -59,12 +60,14 @@ func generateIDPrefix() (string, error) {
if err == nil {
return prefix, nil
}

log.Debugf("failed to get machine-id with usual files: %s", err)

bID, err := uuid.NewRandom()
if err == nil {
return bID.String(), nil
}

return "", fmt.Errorf("generating machine id: %w", err)
}

@@ -75,11 +78,14 @@ func generateID(prefix string) (string, error) {
if prefix == "" {
prefix, err = generateIDPrefix()
}

if err != nil {
return "", err
}

prefix = strings.ReplaceAll(prefix, "-", "")[:32]
suffix := generatePassword(16)

return prefix + suffix, nil
}

@@ -289,6 +295,7 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri
if !autoAdd {
return fmt.Errorf("please specify a password with --password or use --auto")
}

machinePassword = generatePassword(passwordLength)
} else if machinePassword == "" && interactive {
qs := &survey.Password{
@@ -328,10 +335,10 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri
}

if dumpFile != "" && dumpFile != "-" {
err = os.WriteFile(dumpFile, apiConfigDump, 0o600)
if err != nil {
if err = os.WriteFile(dumpFile, apiConfigDump, 0o600); err != nil {
return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err)
}

fmt.Fprintf(os.Stderr, "API credentials written to '%s'.\n", dumpFile)
} else {
fmt.Print(string(apiConfigDump))
@@ -359,11 +366,11 @@ func (cli *cliMachines) deleteValid(cmd *cobra.Command, args []string, toComplet

func (cli *cliMachines) delete(machines []string) error {
for _, machineID := range machines {
err := cli.db.DeleteWatcher(machineID)
if err != nil {
if err := cli.db.DeleteWatcher(machineID); err != nil {
log.Errorf("unable to delete machine '%s': %s", machineID, err)
return nil
}

log.Infof("machine '%s' deleted successfully", machineID)
}

@@ -473,6 +480,7 @@ func (cli *cliMachines) validate(machineID string) error {
if err := cli.db.ValidateMachine(machineID); err != nil {
return fmt.Errorf("unable to validate machine '%s': %s", machineID, err)
}

log.Infof("machine '%s' validated successfully", machineID)

return nil
4 changes: 2 additions & 2 deletions cmd/crowdsec-cli/main.go
Original file line number Diff line number Diff line change
@@ -157,8 +157,8 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
cmd.PersistentFlags().BoolVar(&wrn_lvl, "warning", false, "Set logging to warning")
cmd.PersistentFlags().BoolVar(&err_lvl, "error", false, "Set logging to error")
cmd.PersistentFlags().BoolVar(&trace_lvl, "trace", false, "Set logging to trace")

cmd.PersistentFlags().StringVar(&flagBranch, "branch", "", "Override hub branch on github")

if err := cmd.PersistentFlags().MarkHidden("branch"); err != nil {
log.Fatalf("failed to hide flag: %s", err)
}
@@ -197,7 +197,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
cmd.AddCommand(NewCLIHub(getconfig).NewCommand())
cmd.AddCommand(NewMetricsCmd())
cmd.AddCommand(NewCLIDashboard(getconfig).NewCommand())
cmd.AddCommand(NewCLIDecisions().NewCommand())
cmd.AddCommand(NewCLIDecisions(getconfig).NewCommand())
cmd.AddCommand(NewCLIAlerts().NewCommand())
cmd.AddCommand(NewCLISimulation(getconfig).NewCommand())
cmd.AddCommand(NewCLIBouncers(getconfig).NewCommand())
6 changes: 3 additions & 3 deletions cmd/crowdsec-cli/papi.go
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ func (cli *cliPapi) NewCommand() *cobra.Command {
Short: "Manage interaction with Polling API (PAPI)",
Args: cobra.MinimumNArgs(1),
DisableAutoGenTag: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
cfg := cli.cfg()
if err := require.LAPI(cfg); err != nil {
return err
@@ -59,7 +59,7 @@ func (cli *cliPapi) NewStatusCmd() *cobra.Command {
Short: "Get status of the Polling API",
Args: cobra.MinimumNArgs(0),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
var err error
cfg := cli.cfg()
dbClient, err = database.NewClient(cfg.DbConfig)
@@ -111,7 +111,7 @@ func (cli *cliPapi) NewSyncCmd() *cobra.Command {
Short: "Sync with the Polling API, pulling all non-expired orders for the instance",
Args: cobra.MinimumNArgs(0),
DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
var err error
cfg := cli.cfg()
t := tomb.Tomb{}
Loading

0 comments on commit 4160bb8

Please sign in to comment.