custom duration type for "cscli decisions list", "cscli alerts list"

This commit is contained in:
marco 2025-04-29 23:18:10 +02:00
parent d10067e772
commit 45c6c11a15
9 changed files with 52 additions and 83 deletions

View file

@ -19,6 +19,7 @@ import (
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"github.com/crowdsecurity/go-cs-lib/cstime"
"github.com/crowdsecurity/go-cs-lib/maptools"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
@ -247,34 +248,6 @@ func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.Alerts
alertListFilter.Limit = limit
}
if *alertListFilter.Until == "" {
alertListFilter.Until = nil
} else if strings.HasSuffix(*alertListFilter.Until, "d") {
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
realDuration := strings.TrimSuffix(*alertListFilter.Until, "d")
days, err := strconv.Atoi(realDuration)
if err != nil {
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Until)
}
*alertListFilter.Until = fmt.Sprintf("%d%s", days*24, "h")
}
if *alertListFilter.Since == "" {
alertListFilter.Since = nil
} else if strings.HasSuffix(*alertListFilter.Since, "d") {
// time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier
realDuration := strings.TrimSuffix(*alertListFilter.Since, "d")
days, err := strconv.Atoi(realDuration)
if err != nil {
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Since)
}
*alertListFilter.Since = fmt.Sprintf("%d%s", days*24, "h")
}
if *alertListFilter.IncludeCAPI {
*alertListFilter.Limit = 0
}
@ -330,8 +303,8 @@ func (cli *cliAlerts) newListCmd() *cobra.Command {
ScenarioEquals: new(string),
IPEquals: new(string),
RangeEquals: new(string),
Since: new(string),
Until: new(string),
Since: cstime.Duration(0),
Until: cstime.Duration(0),
TypeEquals: new(string),
IncludeCAPI: new(bool),
OriginEquals: new(string),
@ -362,8 +335,8 @@ cscli alerts list --type ban`,
flags := cmd.Flags()
flags.SortFlags = false
flags.BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
flags.StringVar(alertListFilter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
flags.StringVar(alertListFilter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
flags.Var(&alertListFilter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)")
flags.Var(&alertListFilter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)")
flags.StringVarP(alertListFilter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
flags.StringVarP(alertListFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
flags.StringVarP(alertListFilter.RangeEquals, "range", "r", "", "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)")

View file

@ -23,6 +23,8 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/crowdsecurity/go-cs-lib/cstime"
)
type configGetter func() *csconfig.Config
@ -184,34 +186,8 @@ func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOp
if noSimu != nil && *noSimu {
filter.IncludeSimulated = new(bool)
}
/* nullify the empty entries to avoid bad filter */
if *filter.Until == "" {
filter.Until = nil
} else if strings.HasSuffix(*filter.Until, "d") {
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
realDuration := strings.TrimSuffix(*filter.Until, "d")
days, err := strconv.Atoi(realDuration)
if err != nil {
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
}
*filter.Until = fmt.Sprintf("%d%s", days*24, "h")
}
if *filter.Since == "" {
filter.Since = nil
} else if strings.HasSuffix(*filter.Since, "d") {
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
realDuration := strings.TrimSuffix(*filter.Since, "d")
days, err := strconv.Atoi(realDuration)
if err != nil {
return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Since)
}
*filter.Since = fmt.Sprintf("%d%s", days*24, "h")
}
if *filter.IncludeCAPI {
*filter.Limit = 0
@ -270,8 +246,8 @@ func (cli *cliDecisions) newListCmd() *cobra.Command {
OriginEquals: new(string),
IPEquals: new(string),
RangeEquals: new(string),
Since: new(string),
Until: new(string),
Since: cstime.Duration(0),
Until: cstime.Duration(0),
TypeEquals: new(string),
IncludeCAPI: new(bool),
Limit: new(int),
@ -300,8 +276,8 @@ cscli decisions list --origin lists --scenario list_name
flags := cmd.Flags()
flags.SortFlags = false
flags.BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
flags.StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
flags.StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
flags.Var(&filter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)")
flags.Var(&filter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)")
flags.StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
flags.StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
flags.StringVar(filter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))

10
go.mod
View file

@ -24,7 +24,7 @@ require (
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/creack/pty v1.1.21 // indirect
github.com/crowdsecurity/dlog v0.0.0-20170105205344-4fb5f8204f26
github.com/crowdsecurity/go-cs-lib v0.0.18
github.com/crowdsecurity/go-cs-lib v0.0.19-0.20250429135122-36305bb35807
github.com/crowdsecurity/grokky v0.2.2
github.com/crowdsecurity/machineid v1.0.2
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@ -87,8 +87,8 @@ require (
github.com/shirou/gopsutil/v3 v3.23.5
github.com/sirupsen/logrus v1.9.3
github.com/slack-go/slack v0.16.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/testify v1.10.0
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
github.com/wasilibs/go-re2 v1.7.0
@ -100,7 +100,7 @@ require (
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.36.0
golang.org/x/mod v0.23.0
golang.org/x/net v0.38.0 // indirect
golang.org/x/net v0.38.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
@ -131,7 +131,7 @@ require (
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect

15
go.sum
View file

@ -100,8 +100,8 @@ github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -111,8 +111,8 @@ github.com/crowdsecurity/coraza/v3 v3.0.0-20250320231801-749b8bded21a h1:2Nyr+47
github.com/crowdsecurity/coraza/v3 v3.0.0-20250320231801-749b8bded21a/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA=
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/go-cs-lib v0.0.18 h1:GNyvaag5MXfuapIy4E30pIOvIE5AyHoanJBNSMA1cmE=
github.com/crowdsecurity/go-cs-lib v0.0.18/go.mod h1:XwGcvTt4lMq4Tm1IRMSKMDf0CVrnytTU8Uoofa7AR+g=
github.com/crowdsecurity/go-cs-lib v0.0.19-0.20250429135122-36305bb35807 h1:vle0PUZT8/S+PBA+QjV47RzjyoDtY3jLOtO/RZW1JTc=
github.com/crowdsecurity/go-cs-lib v0.0.19-0.20250429135122-36305bb35807/go.mod h1:vQljl8j38dF6QKjlxXCjIBHCADzYt3PcBTN6REPH42g=
github.com/crowdsecurity/grokky v0.2.2 h1:yALsI9zqpDArYzmSSxfBq2dhYuGUTKMJq8KOEIAsuo4=
github.com/crowdsecurity/grokky v0.2.2/go.mod h1:33usDIYzGDsgX1kHAThCbseso6JuWNJXOzRQDGXHtWM=
github.com/crowdsecurity/machineid v1.0.2 h1:wpkpsUghJF8Khtmn/tg6GxgdhLA1Xflerh5lirI+bdc=
@ -660,11 +660,12 @@ github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=

View file

@ -7,6 +7,8 @@ import (
qs "github.com/google/go-querystring/query"
"github.com/crowdsecurity/go-cs-lib/cstime"
"github.com/crowdsecurity/crowdsec/pkg/models"
)
@ -19,9 +21,9 @@ type AlertsListOpts struct {
IPEquals *string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"`
OriginEquals *string `url:"origin,omitempty"`
Since *string `url:"since,omitempty"`
Since cstime.Duration `url:"since,omitempty"`
TypeEquals *string `url:"decision_type,omitempty"`
Until *string `url:"until,omitempty"`
Until cstime.Duration `url:"until,omitempty"`
IncludeSimulated *bool `url:"simulated,omitempty"`
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
IncludeCAPI *bool `url:"include_capi,omitempty"`
@ -36,8 +38,8 @@ type AlertsDeleteOpts struct {
ScenarioEquals *string `url:"scenario,omitempty"`
IPEquals *string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"`
Since *string `url:"since,omitempty"`
Until *string `url:"until,omitempty"`
Since cstime.Duration `url:"since,omitempty"`
Until cstime.Duration `url:"until,omitempty"`
OriginEquals *string `url:"origin,omitempty"`
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
SourceEquals *string `url:"alert_source,omitempty"`

View file

@ -9,6 +9,8 @@ import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/crowdsecurity/go-cs-lib/cstime"
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
"github.com/crowdsecurity/crowdsec/pkg/database/ent/alert"
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
@ -40,7 +42,9 @@ func handleScopeFilter(scope string, predicates *[]predicate.Alert) {
}
func handleTimeFilters(param, value string, predicates *[]predicate.Alert) error {
duration, err := ParseDuration(value)
// crowsdec now always sends duration without days, but we allow them for
// compatibility with other tools
duration, err := cstime.ParseDuration(value)
if err != nil {
return fmt.Errorf("while parsing duration: %w", err)
}

View file

@ -43,6 +43,15 @@ teardown() {
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
}
@test "cscli alerts list, accept duration parameters with days" {
rune -1 cscli alerts list --until toto
assert_stderr 'Error: invalid argument "toto" for "--until" flag: time: invalid duration "toto"'
rune -0 cscli alerts list --until 2d12h --debug
assert_stderr --partial "until=60h0m0s"
rune -0 cscli alerts list --since 2d12h --debug
assert_stderr --partial "since=60h0m0s"
}
@test "cscli alerts list, human/json/raw" {
rune -0 cscli decisions add -i 10.20.30.40 -t ban

View file

@ -54,9 +54,13 @@ teardown() {
assert_output --regexp " githubciXXXXXXXXXXXXXXXXXXXXXXXX([a-zA-Z0-9]{16})? "
}
@test "cscli decisions list, incorrect parameters" {
@test "cscli decisions list, accept duration parameters with days" {
rune -1 cscli decisions list --until toto
assert_stderr 'Error: unable to retrieve decisions: performing request: API error: while parsing duration: time: invalid duration "toto"'
assert_stderr 'Error: invalid argument "toto" for "--until" flag: time: invalid duration "toto"'
rune -0 cscli decisions list --until 2d12h --debug
assert_stderr --partial "until=60h0m0s"
rune -0 cscli decisions list --since 2d12h --debug
assert_stderr --partial "since=60h0m0s"
}
@test "cscli decisions import" {

View file

@ -124,7 +124,7 @@ teardown() {
rune -1 cscli allowlist add foo 10.10.10.10 10.20.30.40 -d comment -e '1 day'
refute_output
assert_stderr 'Error: strconv.Atoi: parsing "1 ": invalid syntax'
assert_stderr 'Error: invalid day value in duration "1 day"'
rune -0 cscli allowlist add foo 10.10.10.10 -d comment -e '1d'
assert_output 'added 1 values to allowlist foo'