From 45c6c11a1534958a56286efe7cc182dd20c6add7 Mon Sep 17 00:00:00 2001 From: marco Date: Tue, 29 Apr 2025 23:18:10 +0200 Subject: [PATCH] custom duration type for "cscli decisions list", "cscli alerts list" --- cmd/crowdsec-cli/clialert/alerts.go | 37 +++------------------- cmd/crowdsec-cli/clidecision/decisions.go | 38 +++++------------------ go.mod | 10 +++--- go.sum | 15 ++++----- pkg/apiclient/alerts_service.go | 10 +++--- pkg/database/alertfilter.go | 6 +++- test/bats/80_alerts.bats | 9 ++++++ test/bats/90_decisions.bats | 8 +++-- test/bats/cscli-allowlists.bats | 2 +- 9 files changed, 52 insertions(+), 83 deletions(-) diff --git a/cmd/crowdsec-cli/clialert/alerts.go b/cmd/crowdsec-cli/clialert/alerts.go index 3e69acfec..22c24828c 100644 --- a/cmd/crowdsec-cli/clialert/alerts.go +++ b/cmd/crowdsec-cli/clialert/alerts.go @@ -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 )") 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 )") diff --git a/cmd/crowdsec-cli/clidecision/decisions.go b/cmd/crowdsec-cli/clidecision/decisions.go index 91f39f421..42e9b71cd 100644 --- a/cmd/crowdsec-cli/clidecision/decisions.go +++ b/cmd/crowdsec-cli/clidecision/decisions.go @@ -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(), ","))) diff --git a/go.mod b/go.mod index 81c41acc9..375c80612 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 133e1e9e0..5bb274b7e 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/apiclient/alerts_service.go b/pkg/apiclient/alerts_service.go index 1f84862a8..85922063e 100644 --- a/pkg/apiclient/alerts_service.go +++ b/pkg/apiclient/alerts_service.go @@ -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"` diff --git a/pkg/database/alertfilter.go b/pkg/database/alertfilter.go index 6ff2ab99a..7bb00abcf 100644 --- a/pkg/database/alertfilter.go +++ b/pkg/database/alertfilter.go @@ -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) } diff --git a/test/bats/80_alerts.bats b/test/bats/80_alerts.bats index 78c4e67a7..0fadea984 100644 --- a/test/bats/80_alerts.bats +++ b/test/bats/80_alerts.bats @@ -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 diff --git a/test/bats/90_decisions.bats b/test/bats/90_decisions.bats index 549048965..5ba29659e 100644 --- a/test/bats/90_decisions.bats +++ b/test/bats/90_decisions.bats @@ -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" { diff --git a/test/bats/cscli-allowlists.bats b/test/bats/cscli-allowlists.bats index 24810110d..975dfe3cb 100644 --- a/test/bats/cscli-allowlists.bats +++ b/test/bats/cscli-allowlists.bats @@ -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'