cscli refact: package clialert, clidecision (#3203)

* cscli refact: package clialert, clidecision

* refact: function SanitizeScope()

* lint
This commit is contained in:
mmetc 2024-09-03 12:37:38 +02:00 committed by GitHub
parent 5a50fd06bb
commit bc6be99b97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 98 additions and 89 deletions

View file

@ -1,4 +1,4 @@
package main package clialert
import ( import (
"context" "context"
@ -24,6 +24,7 @@ import (
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cstable" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cstable"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
"github.com/crowdsecurity/crowdsec/pkg/apiclient" "github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"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"
@ -183,12 +184,14 @@ func (cli *cliAlerts) displayOneAlert(alert *models.Alert, withDetail bool) erro
return nil return nil
} }
type configGetter func() *csconfig.Config
type cliAlerts struct { type cliAlerts struct {
client *apiclient.ApiClient client *apiclient.ApiClient
cfg configGetter cfg configGetter
} }
func NewCLIAlerts(getconfig configGetter) *cliAlerts { func New(getconfig configGetter) *cliAlerts {
return &cliAlerts{ return &cliAlerts{
cfg: getconfig, cfg: getconfig,
} }
@ -235,8 +238,10 @@ func (cli *cliAlerts) NewCommand() *cobra.Command {
} }
func (cli *cliAlerts) list(alertListFilter apiclient.AlertsListOpts, limit *int, contained *bool, printMachine bool) error { func (cli *cliAlerts) list(alertListFilter apiclient.AlertsListOpts, limit *int, contained *bool, printMachine bool) error {
if err := manageCliDecisionAlerts(alertListFilter.IPEquals, alertListFilter.RangeEquals, var err error
alertListFilter.ScopeEquals, alertListFilter.ValueEquals); err != nil {
*alertListFilter.ScopeEquals, err = SanitizeScope(*alertListFilter.ScopeEquals, *alertListFilter.IPEquals, *alertListFilter.RangeEquals)
if err != nil {
return err return err
} }
@ -378,8 +383,8 @@ func (cli *cliAlerts) delete(alertDeleteFilter apiclient.AlertsDeleteOpts, Activ
var err error var err error
if !AlertDeleteAll { if !AlertDeleteAll {
if err = manageCliDecisionAlerts(alertDeleteFilter.IPEquals, alertDeleteFilter.RangeEquals, *alertDeleteFilter.ScopeEquals, err = SanitizeScope(*alertDeleteFilter.ScopeEquals, *alertDeleteFilter.IPEquals, *alertDeleteFilter.RangeEquals)
alertDeleteFilter.ScopeEquals, alertDeleteFilter.ValueEquals); err != nil { if err != nil {
return err return err
} }

View file

@ -0,0 +1,26 @@
package clialert
import (
"fmt"
"net"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
// SanitizeScope validates ip and range and sets the scope accordingly to our case convention.
func SanitizeScope(scope, ip, ipRange string) (string, error) {
if ipRange != "" {
_, _, err := net.ParseCIDR(ipRange)
if err != nil {
return "", fmt.Errorf("%s is not a valid range", ipRange)
}
}
if ip != "" {
if net.ParseIP(ip) == nil {
return "", fmt.Errorf("%s is not a valid ip", ip)
}
}
return types.NormalizeScope(scope), nil
}

View file

@ -1,4 +1,4 @@
package main package clialert
import ( import (
"fmt" "fmt"

View file

@ -1,4 +1,4 @@
package main package clidecision
import ( import (
"context" "context"
@ -17,7 +17,9 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clialert"
"github.com/crowdsecurity/crowdsec/pkg/apiclient" "github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"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"
@ -114,12 +116,14 @@ func (cli *cliDecisions) decisionsToTable(alerts *models.GetAlertsResponse, prin
return nil return nil
} }
type configGetter func() *csconfig.Config
type cliDecisions struct { type cliDecisions struct {
client *apiclient.ApiClient client *apiclient.ApiClient
cfg configGetter cfg configGetter
} }
func NewCLIDecisions(cfg configGetter) *cliDecisions { func New(cfg configGetter) *cliDecisions {
return &cliDecisions{ return &cliDecisions{
cfg: cfg, cfg: cfg,
} }
@ -170,8 +174,9 @@ func (cli *cliDecisions) NewCommand() *cobra.Command {
func (cli *cliDecisions) list(filter apiclient.AlertsListOpts, NoSimu *bool, contained *bool, printMachine bool) error { func (cli *cliDecisions) list(filter apiclient.AlertsListOpts, NoSimu *bool, contained *bool, printMachine bool) error {
var err error var err error
/*take care of shorthand options*/
if err = manageCliDecisionAlerts(filter.IPEquals, filter.RangeEquals, filter.ScopeEquals, filter.ValueEquals); err != nil { *filter.ScopeEquals, err = clialert.SanitizeScope(*filter.ScopeEquals, *filter.IPEquals, *filter.RangeEquals)
if err != nil {
return err return err
} }
@ -326,8 +331,10 @@ func (cli *cliDecisions) add(addIP, addRange, addDuration, addValue, addScope, a
stopAt := time.Now().UTC().Format(time.RFC3339) stopAt := time.Now().UTC().Format(time.RFC3339)
createdAt := time.Now().UTC().Format(time.RFC3339) createdAt := time.Now().UTC().Format(time.RFC3339)
/*take care of shorthand options*/ var err error
if err := manageCliDecisionAlerts(&addIP, &addRange, &addScope, &addValue); err != nil {
addScope, err = clialert.SanitizeScope(addScope, addIP, addRange)
if err != nil {
return err return err
} }
@ -381,7 +388,7 @@ func (cli *cliDecisions) add(addIP, addRange, addDuration, addValue, addScope, a
} }
alerts = append(alerts, &alert) alerts = append(alerts, &alert)
_, _, err := cli.client.Alerts.Add(context.Background(), alerts) _, _, err = cli.client.Alerts.Add(context.Background(), alerts)
if err != nil { if err != nil {
return err return err
} }
@ -435,7 +442,8 @@ func (cli *cliDecisions) delete(delFilter apiclient.DecisionsDeleteOpts, delDeci
var err error var err error
/*take care of shorthand options*/ /*take care of shorthand options*/
if err = manageCliDecisionAlerts(delFilter.IPEquals, delFilter.RangeEquals, delFilter.ScopeEquals, delFilter.ValueEquals); err != nil { *delFilter.ScopeEquals, err = clialert.SanitizeScope(*delFilter.ScopeEquals, *delFilter.IPEquals, *delFilter.RangeEquals)
if err != nil {
return err return err
} }

View file

@ -1,4 +1,4 @@
package main package clidecision
import ( import (
"bufio" "bufio"
@ -122,8 +122,8 @@ func (cli *cliDecisions) runImport(cmd *cobra.Command, args []string) error {
} }
var ( var (
content []byte content []byte
fin *os.File fin *os.File
) )
// set format if the file has a json or csv extension // set format if the file has a json or csv extension

View file

@ -1,4 +1,4 @@
package main package clidecision
import ( import (
"fmt" "fmt"

View file

@ -14,9 +14,11 @@ import (
"github.com/crowdsecurity/go-cs-lib/trace" "github.com/crowdsecurity/go-cs-lib/trace"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clialert"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clibouncer" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clibouncer"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clicapi" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clicapi"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cliconsole" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cliconsole"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clidecision"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cliexplain" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cliexplain"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clihub" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clihub"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clihubtest" "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clihubtest"
@ -257,8 +259,8 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
cmd.AddCommand(clihub.New(cli.cfg).NewCommand()) cmd.AddCommand(clihub.New(cli.cfg).NewCommand())
cmd.AddCommand(climetrics.New(cli.cfg).NewCommand()) cmd.AddCommand(climetrics.New(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand()) cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIDecisions(cli.cfg).NewCommand()) cmd.AddCommand(clidecision.New(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIAlerts(cli.cfg).NewCommand()) cmd.AddCommand(clialert.New(cli.cfg).NewCommand())
cmd.AddCommand(clisimulation.New(cli.cfg).NewCommand()) cmd.AddCommand(clisimulation.New(cli.cfg).NewCommand())
cmd.AddCommand(clibouncer.New(cli.cfg).NewCommand()) cmd.AddCommand(clibouncer.New(cli.cfg).NewCommand())
cmd.AddCommand(climachine.New(cli.cfg).NewCommand()) cmd.AddCommand(climachine.New(cli.cfg).NewCommand())

View file

@ -1,40 +0,0 @@
package main
import (
"fmt"
"net"
"strings"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
func manageCliDecisionAlerts(ip *string, ipRange *string, scope *string, value *string) error {
/*if a range is provided, change the scope*/
if *ipRange != "" {
_, _, err := net.ParseCIDR(*ipRange)
if err != nil {
return fmt.Errorf("%s isn't a valid range", *ipRange)
}
}
if *ip != "" {
ipRepr := net.ParseIP(*ip)
if ipRepr == nil {
return fmt.Errorf("%s isn't a valid ip", *ip)
}
}
// avoid confusion on scope (ip vs Ip and range vs Range)
switch strings.ToLower(*scope) {
case "ip":
*scope = types.Ip
case "range":
*scope = types.Range
case "country":
*scope = types.Country
case "as":
*scope = types.AS
}
return nil
}

View file

@ -6,7 +6,6 @@ import (
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -124,21 +123,6 @@ func (c *Controller) sendAlertToPluginChannel(alert *models.Alert, profileID uin
} }
} }
func normalizeScope(scope string) string {
switch strings.ToLower(scope) {
case "ip":
return types.Ip
case "range":
return types.Range
case "as":
return types.AS
case "country":
return types.Country
default:
return scope
}
}
// CreateAlert writes the alerts received in the body to the database // CreateAlert writes the alerts received in the body to the database
func (c *Controller) CreateAlert(gctx *gin.Context) { func (c *Controller) CreateAlert(gctx *gin.Context) {
var input models.AddAlertsRequest var input models.AddAlertsRequest
@ -160,12 +144,12 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
for _, alert := range input { for _, alert := range input {
// normalize scope for alert.Source and decisions // normalize scope for alert.Source and decisions
if alert.Source.Scope != nil { if alert.Source.Scope != nil {
*alert.Source.Scope = normalizeScope(*alert.Source.Scope) *alert.Source.Scope = types.NormalizeScope(*alert.Source.Scope)
} }
for _, decision := range alert.Decisions { for _, decision := range alert.Decisions {
if decision.Scope != nil { if decision.Scope != nil {
*decision.Scope = normalizeScope(*decision.Scope) *decision.Scope = types.NormalizeScope(*decision.Scope)
} }
} }
@ -296,8 +280,8 @@ func (c *Controller) FindAlerts(gctx *gin.Context) {
// FindAlertByID returns the alert associated with the ID // FindAlertByID returns the alert associated with the ID
func (c *Controller) FindAlertByID(gctx *gin.Context) { func (c *Controller) FindAlertByID(gctx *gin.Context) {
alertIDStr := gctx.Param("alert_id") alertIDStr := gctx.Param("alert_id")
alertID, err := strconv.Atoi(alertIDStr)
alertID, err := strconv.Atoi(alertIDStr)
if err != nil { if err != nil {
gctx.JSON(http.StatusBadRequest, gin.H{"message": "alert_id must be valid integer"}) gctx.JSON(http.StatusBadRequest, gin.H{"message": "alert_id must be valid integer"})
return return

View file

@ -2,6 +2,7 @@ package types
import ( import (
"net" "net"
"strings"
"time" "time"
"github.com/expr-lang/expr/vm" "github.com/expr-lang/expr/vm"
@ -19,11 +20,11 @@ const (
// Event is the structure representing a runtime event (log or overflow) // Event is the structure representing a runtime event (log or overflow)
type Event struct { type Event struct {
/* is it a log or an overflow */ /* is it a log or an overflow */
Type int `yaml:"Type,omitempty" json:"Type,omitempty"` //Can be types.LOG (0) or types.OVFLOW (1) Type int `yaml:"Type,omitempty" json:"Type,omitempty"` // Can be types.LOG (0) or types.OVFLOW (1)
ExpectMode int `yaml:"ExpectMode,omitempty" json:"ExpectMode,omitempty"` //how to buckets should handle event : types.TIMEMACHINE or types.LIVE ExpectMode int `yaml:"ExpectMode,omitempty" json:"ExpectMode,omitempty"` // how to buckets should handle event : types.TIMEMACHINE or types.LIVE
Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,omitempty"` Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,omitempty"`
WhitelistReason string `yaml:"WhitelistReason,omitempty" json:"whitelist_reason,omitempty"` WhitelistReason string `yaml:"WhitelistReason,omitempty" json:"whitelist_reason,omitempty"`
//should add whitelist reason ? // should add whitelist reason ?
/* the current stage of the line being parsed */ /* the current stage of the line being parsed */
Stage string `yaml:"Stage,omitempty" json:"Stage,omitempty"` Stage string `yaml:"Stage,omitempty" json:"Stage,omitempty"`
/* original line (produced by acquisition) */ /* original line (produced by acquisition) */
@ -36,11 +37,11 @@ type Event struct {
Unmarshaled map[string]interface{} `yaml:"Unmarshaled,omitempty" json:"Unmarshaled,omitempty"` Unmarshaled map[string]interface{} `yaml:"Unmarshaled,omitempty" json:"Unmarshaled,omitempty"`
/* Overflow */ /* Overflow */
Overflow RuntimeAlert `yaml:"Overflow,omitempty" json:"Alert,omitempty"` Overflow RuntimeAlert `yaml:"Overflow,omitempty" json:"Alert,omitempty"`
Time time.Time `yaml:"Time,omitempty" json:"Time,omitempty"` //parsed time `json:"-"` `` Time time.Time `yaml:"Time,omitempty" json:"Time,omitempty"` // parsed time `json:"-"` ``
StrTime string `yaml:"StrTime,omitempty" json:"StrTime,omitempty"` StrTime string `yaml:"StrTime,omitempty" json:"StrTime,omitempty"`
StrTimeFormat string `yaml:"StrTimeFormat,omitempty" json:"StrTimeFormat,omitempty"` StrTimeFormat string `yaml:"StrTimeFormat,omitempty" json:"StrTimeFormat,omitempty"`
MarshaledTime string `yaml:"MarshaledTime,omitempty" json:"MarshaledTime,omitempty"` MarshaledTime string `yaml:"MarshaledTime,omitempty" json:"MarshaledTime,omitempty"`
Process bool `yaml:"Process,omitempty" json:"Process,omitempty"` //can be set to false to avoid processing line Process bool `yaml:"Process,omitempty" json:"Process,omitempty"` // can be set to false to avoid processing line
Appsec AppsecEvent `yaml:"Appsec,omitempty" json:"Appsec,omitempty"` Appsec AppsecEvent `yaml:"Appsec,omitempty" json:"Appsec,omitempty"`
/* Meta is the only part that will make it to the API - it should be normalized */ /* Meta is the only part that will make it to the API - it should be normalized */
Meta map[string]string `yaml:"Meta,omitempty" json:"Meta,omitempty"` Meta map[string]string `yaml:"Meta,omitempty" json:"Meta,omitempty"`
@ -50,7 +51,9 @@ func (e *Event) SetMeta(key string, value string) bool {
if e.Meta == nil { if e.Meta == nil {
e.Meta = make(map[string]string) e.Meta = make(map[string]string)
} }
e.Meta[key] = value e.Meta[key] = value
return true return true
} }
@ -58,7 +61,9 @@ func (e *Event) SetParsed(key string, value string) bool {
if e.Parsed == nil { if e.Parsed == nil {
e.Parsed = make(map[string]string) e.Parsed = make(map[string]string)
} }
e.Parsed[key] = value e.Parsed[key] = value
return true return true
} }
@ -90,11 +95,13 @@ func (e *Event) GetMeta(key string) string {
} }
} }
} }
return "" return ""
} }
func (e *Event) ParseIPSources() []net.IP { func (e *Event) ParseIPSources() []net.IP {
var srcs []net.IP var srcs []net.IP
switch e.Type { switch e.Type {
case LOG: case LOG:
if _, ok := e.Meta["source_ip"]; ok { if _, ok := e.Meta["source_ip"]; ok {
@ -105,6 +112,7 @@ func (e *Event) ParseIPSources() []net.IP {
srcs = append(srcs, net.ParseIP(k)) srcs = append(srcs, net.ParseIP(k))
} }
} }
return srcs return srcs
} }
@ -131,8 +139,8 @@ type RuntimeAlert struct {
Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,omitempty"` Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,omitempty"`
Reprocess bool `yaml:"Reprocess,omitempty" json:"Reprocess,omitempty"` Reprocess bool `yaml:"Reprocess,omitempty" json:"Reprocess,omitempty"`
Sources map[string]models.Source `yaml:"Sources,omitempty" json:"Sources,omitempty"` Sources map[string]models.Source `yaml:"Sources,omitempty" json:"Sources,omitempty"`
Alert *models.Alert `yaml:"Alert,omitempty" json:"Alert,omitempty"` //this one is a pointer to APIAlerts[0] for convenience. Alert *models.Alert `yaml:"Alert,omitempty" json:"Alert,omitempty"` // this one is a pointer to APIAlerts[0] for convenience.
//APIAlerts will be populated at the end when there is more than one source // APIAlerts will be populated at the end when there is more than one source
APIAlerts []models.Alert `yaml:"APIAlerts,omitempty" json:"APIAlerts,omitempty"` APIAlerts []models.Alert `yaml:"APIAlerts,omitempty" json:"APIAlerts,omitempty"`
} }
@ -141,5 +149,21 @@ func (r RuntimeAlert) GetSources() []string {
for key := range r.Sources { for key := range r.Sources {
ret = append(ret, key) ret = append(ret, key)
} }
return ret return ret
} }
func NormalizeScope(scope string) string {
switch strings.ToLower(scope) {
case "ip":
return Ip
case "range":
return Range
case "as":
return AS
case "country":
return Country
default:
return scope
}
}

View file

@ -108,12 +108,12 @@ teardown() {
# invalid json # invalid json
rune -1 cscli decisions import -i - <<<'{"blah":"blah"}' --format json rune -1 cscli decisions import -i - <<<'{"blah":"blah"}' --format json
assert_stderr --partial 'Parsing json' assert_stderr --partial 'Parsing json'
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []main.decisionRaw' assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
# json with extra data # json with extra data
rune -1 cscli decisions import -i - <<<'{"values":"1.2.3.4","blah":"blah"}' --format json rune -1 cscli decisions import -i - <<<'{"values":"1.2.3.4","blah":"blah"}' --format json
assert_stderr --partial 'Parsing json' assert_stderr --partial 'Parsing json'
assert_stderr --partial 'json: cannot unmarshal object into Go value of type []main.decisionRaw' assert_stderr --partial 'json: cannot unmarshal object into Go value of type []clidecision.decisionRaw'
#---------- #----------
# CSV # CSV