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

View file

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

View file

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

View file

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

View file

@ -14,9 +14,11 @@ import (
"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/clicapi"
"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/clihub"
"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(climetrics.New(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIDecisions(cli.cfg).NewCommand())
cmd.AddCommand(NewCLIAlerts(cli.cfg).NewCommand())
cmd.AddCommand(clidecision.New(cli.cfg).NewCommand())
cmd.AddCommand(clialert.New(cli.cfg).NewCommand())
cmd.AddCommand(clisimulation.New(cli.cfg).NewCommand())
cmd.AddCommand(clibouncer.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/http"
"strconv"
"strings"
"time"
"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
func (c *Controller) CreateAlert(gctx *gin.Context) {
var input models.AddAlertsRequest
@ -160,12 +144,12 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
for _, alert := range input {
// normalize scope for alert.Source and decisions
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 {
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
func (c *Controller) FindAlertByID(gctx *gin.Context) {
alertIDStr := gctx.Param("alert_id")
alertID, err := strconv.Atoi(alertIDStr)
alertID, err := strconv.Atoi(alertIDStr)
if err != nil {
gctx.JSON(http.StatusBadRequest, gin.H{"message": "alert_id must be valid integer"})
return

View file

@ -2,6 +2,7 @@ package types
import (
"net"
"strings"
"time"
"github.com/expr-lang/expr/vm"
@ -19,11 +20,11 @@ const (
// Event is the structure representing a runtime event (log or overflow)
type Event struct {
/* is it a log or an overflow */
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
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
Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,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 */
Stage string `yaml:"Stage,omitempty" json:"Stage,omitempty"`
/* original line (produced by acquisition) */
@ -36,11 +37,11 @@ type Event struct {
Unmarshaled map[string]interface{} `yaml:"Unmarshaled,omitempty" json:"Unmarshaled,omitempty"`
/* Overflow */
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"`
StrTimeFormat string `yaml:"StrTimeFormat,omitempty" json:"StrTimeFormat,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"`
/* 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"`
@ -50,7 +51,9 @@ func (e *Event) SetMeta(key string, value string) bool {
if e.Meta == nil {
e.Meta = make(map[string]string)
}
e.Meta[key] = value
return true
}
@ -58,7 +61,9 @@ func (e *Event) SetParsed(key string, value string) bool {
if e.Parsed == nil {
e.Parsed = make(map[string]string)
}
e.Parsed[key] = value
return true
}
@ -90,11 +95,13 @@ func (e *Event) GetMeta(key string) string {
}
}
}
return ""
}
func (e *Event) ParseIPSources() []net.IP {
var srcs []net.IP
switch e.Type {
case LOG:
if _, ok := e.Meta["source_ip"]; ok {
@ -105,6 +112,7 @@ func (e *Event) ParseIPSources() []net.IP {
srcs = append(srcs, net.ParseIP(k))
}
}
return srcs
}
@ -131,8 +139,8 @@ type RuntimeAlert struct {
Whitelisted bool `yaml:"Whitelisted,omitempty" json:"Whitelisted,omitempty"`
Reprocess bool `yaml:"Reprocess,omitempty" json:"Reprocess,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.
//APIAlerts will be populated at the end when there is more than one source
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 []models.Alert `yaml:"APIAlerts,omitempty" json:"APIAlerts,omitempty"`
}
@ -141,5 +149,21 @@ func (r RuntimeAlert) GetSources() []string {
for key := range r.Sources {
ret = append(ret, key)
}
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
rune -1 cscli decisions import -i - <<<'{"blah":"blah"}' --format 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
rune -1 cscli decisions import -i - <<<'{"values":"1.2.3.4","blah":"blah"}' --format 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