mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-10 20:05:55 +02:00
Merge branch 'master' into anomaly-detection
This commit is contained in:
commit
1300906ac7
37 changed files with 684 additions and 614 deletions
|
@ -184,7 +184,7 @@ linters:
|
|||
|
||||
maintidx:
|
||||
# raise this after refactoring
|
||||
under: 15
|
||||
under: 18
|
||||
|
||||
misspell:
|
||||
locale: US
|
||||
|
@ -210,7 +210,7 @@ linters:
|
|||
- name: cognitive-complexity
|
||||
arguments:
|
||||
# lower this after refactoring
|
||||
- 119
|
||||
- 113
|
||||
- name: comment-spacings
|
||||
disabled: true
|
||||
- name: confusing-results
|
||||
|
@ -235,8 +235,8 @@ linters:
|
|||
- name: function-length
|
||||
arguments:
|
||||
# lower this after refactoring
|
||||
- 111
|
||||
- 238
|
||||
- 87
|
||||
- 198
|
||||
- name: get-return
|
||||
disabled: true
|
||||
- name: increment-decrement
|
||||
|
@ -294,9 +294,7 @@ linters:
|
|||
- -ST1003
|
||||
- -ST1005
|
||||
- -ST1012
|
||||
- -ST1022
|
||||
- -QF1003
|
||||
- -QF1008
|
||||
- -QF1012
|
||||
|
||||
wsl:
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-openapi/strfmt"
|
||||
|
@ -19,6 +20,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"
|
||||
|
@ -102,15 +104,15 @@ func (cli *cliAlerts) alertsToTable(alerts *models.GetAlertsResponse, printMachi
|
|||
if *alerts == nil {
|
||||
// avoid returning "null" in json
|
||||
// could be cleaner if we used slice of alerts directly
|
||||
fmt.Println("[]")
|
||||
fmt.Fprintln(os.Stdout, "[]")
|
||||
return nil
|
||||
}
|
||||
|
||||
x, _ := json.MarshalIndent(alerts, "", " ")
|
||||
fmt.Print(string(x))
|
||||
fmt.Fprint(os.Stdout, string(x))
|
||||
case "human":
|
||||
if len(*alerts) == 0 {
|
||||
fmt.Println("No active alerts")
|
||||
fmt.Fprintln(os.Stdout, "No active alerts")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -154,7 +156,7 @@ func (cli *cliAlerts) displayOneAlert(alert *models.Alert, withDetail bool) erro
|
|||
alertDecisionsTable(color.Output, cfg.Cscli.Color, alert)
|
||||
|
||||
if len(alert.Meta) > 0 {
|
||||
fmt.Printf("\n - Context :\n")
|
||||
fmt.Fprintf(os.Stdout, "\n - Context :\n")
|
||||
sort.Slice(alert.Meta, func(i, j int) bool {
|
||||
return alert.Meta[i].Key < alert.Meta[j].Key
|
||||
})
|
||||
|
@ -181,7 +183,7 @@ func (cli *cliAlerts) displayOneAlert(alert *models.Alert, withDetail bool) erro
|
|||
}
|
||||
|
||||
if withDetail {
|
||||
fmt.Printf("\n - Events :\n")
|
||||
fmt.Fprintf(os.Stdout, "\n - Events :\n")
|
||||
|
||||
for _, event := range alert.Events {
|
||||
alertEventTable(color.Output, cfg.Cscli.Color, event)
|
||||
|
@ -238,7 +240,7 @@ func (cli *cliAlerts) NewCommand() *cobra.Command {
|
|||
func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.AlertsListOpts, limit *int, contained *bool, printMachine bool) error {
|
||||
var err error
|
||||
|
||||
*alertListFilter.ScopeEquals, err = SanitizeScope(*alertListFilter.ScopeEquals, *alertListFilter.IPEquals, *alertListFilter.RangeEquals)
|
||||
alertListFilter.ScopeEquals, err = SanitizeScope(alertListFilter.ScopeEquals, alertListFilter.IPEquals, alertListFilter.RangeEquals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -247,66 +249,10 @@ 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
|
||||
}
|
||||
|
||||
if *alertListFilter.TypeEquals == "" {
|
||||
alertListFilter.TypeEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.ScopeEquals == "" {
|
||||
alertListFilter.ScopeEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.ValueEquals == "" {
|
||||
alertListFilter.ValueEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.ScenarioEquals == "" {
|
||||
alertListFilter.ScenarioEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.IPEquals == "" {
|
||||
alertListFilter.IPEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.RangeEquals == "" {
|
||||
alertListFilter.RangeEquals = nil
|
||||
}
|
||||
|
||||
if *alertListFilter.OriginEquals == "" {
|
||||
alertListFilter.OriginEquals = nil
|
||||
}
|
||||
|
||||
if contained != nil && *contained {
|
||||
alertListFilter.Contains = new(bool)
|
||||
}
|
||||
|
@ -325,16 +271,16 @@ func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.Alerts
|
|||
|
||||
func (cli *cliAlerts) newListCmd() *cobra.Command {
|
||||
alertListFilter := apiclient.AlertsListOpts{
|
||||
ScopeEquals: new(string),
|
||||
ValueEquals: new(string),
|
||||
ScenarioEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
Since: new(string),
|
||||
Until: new(string),
|
||||
TypeEquals: new(string),
|
||||
ScopeEquals: "",
|
||||
ValueEquals: "",
|
||||
ScenarioEquals: "",
|
||||
IPEquals: "",
|
||||
RangeEquals: "",
|
||||
Since: cstime.DurationWithDays(0),
|
||||
Until: cstime.DurationWithDays(0),
|
||||
TypeEquals: "",
|
||||
IncludeCAPI: new(bool),
|
||||
OriginEquals: new(string),
|
||||
OriginEquals: "",
|
||||
}
|
||||
|
||||
limit := new(int)
|
||||
|
@ -362,15 +308,15 @@ 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.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>)")
|
||||
flags.StringVar(alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
|
||||
flags.StringVar(alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
|
||||
flags.StringVarP(alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVar(alertListFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
|
||||
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>)")
|
||||
flags.StringVar(&alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
|
||||
flags.StringVar(&alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
|
||||
flags.StringVarP(&alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVar(&alertListFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
|
||||
flags.BoolVar(contained, "contained", false, "query decisions contained by range")
|
||||
flags.BoolVarP(&printMachine, "machine", "m", false, "print machines that sent alerts")
|
||||
flags.IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
|
||||
|
@ -382,7 +328,7 @@ func (cli *cliAlerts) delete(ctx context.Context, delFilter apiclient.AlertsDele
|
|||
var err error
|
||||
|
||||
if !deleteAll {
|
||||
*delFilter.ScopeEquals, err = SanitizeScope(*delFilter.ScopeEquals, *delFilter.IPEquals, *delFilter.RangeEquals)
|
||||
delFilter.ScopeEquals, err = SanitizeScope(delFilter.ScopeEquals, delFilter.IPEquals, delFilter.RangeEquals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -391,26 +337,6 @@ func (cli *cliAlerts) delete(ctx context.Context, delFilter apiclient.AlertsDele
|
|||
delFilter.ActiveDecisionEquals = activeDecision
|
||||
}
|
||||
|
||||
if *delFilter.ScopeEquals == "" {
|
||||
delFilter.ScopeEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.ValueEquals == "" {
|
||||
delFilter.ValueEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.ScenarioEquals == "" {
|
||||
delFilter.ScenarioEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.IPEquals == "" {
|
||||
delFilter.IPEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.RangeEquals == "" {
|
||||
delFilter.RangeEquals = nil
|
||||
}
|
||||
|
||||
if contained != nil && *contained {
|
||||
delFilter.Contains = new(bool)
|
||||
}
|
||||
|
@ -448,11 +374,11 @@ func (cli *cliAlerts) newDeleteCmd() *cobra.Command {
|
|||
)
|
||||
|
||||
delFilter := apiclient.AlertsDeleteOpts{
|
||||
ScopeEquals: new(string),
|
||||
ValueEquals: new(string),
|
||||
ScenarioEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
ScopeEquals: "",
|
||||
ValueEquals: "",
|
||||
ScenarioEquals: "",
|
||||
IPEquals: "",
|
||||
RangeEquals: "",
|
||||
}
|
||||
|
||||
contained := new(bool)
|
||||
|
@ -471,9 +397,9 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
|
|||
if deleteAll {
|
||||
return nil
|
||||
}
|
||||
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
|
||||
*delFilter.ScenarioEquals == "" && *delFilter.IPEquals == "" &&
|
||||
*delFilter.RangeEquals == "" && delAlertByID == "" {
|
||||
if delFilter.ScopeEquals == "" && delFilter.ValueEquals == "" &&
|
||||
delFilter.ScenarioEquals == "" && delFilter.IPEquals == "" &&
|
||||
delFilter.RangeEquals == "" && delAlertByID == "" {
|
||||
_ = cmd.Usage()
|
||||
return errors.New("at least one filter or --all must be specified")
|
||||
}
|
||||
|
@ -487,11 +413,11 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
|
|||
|
||||
flags := cmd.Flags()
|
||||
flags.SortFlags = false
|
||||
flags.StringVar(delFilter.ScopeEquals, "scope", "", "the scope (ie. ip,range)")
|
||||
flags.StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVarP(delFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
||||
flags.StringVar(&delFilter.ScopeEquals, "scope", "", "the scope (ie. ip,range)")
|
||||
flags.StringVarP(&delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVarP(&delFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVarP(&delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(&delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
||||
flags.StringVar(&delAlertByID, "id", "", "alert ID")
|
||||
flags.BoolVarP(&deleteAll, "all", "a", false, "delete all alerts")
|
||||
flags.BoolVar(contained, "contained", false, "query decisions contained by range")
|
||||
|
@ -525,14 +451,14 @@ func (cli *cliAlerts) inspect(ctx context.Context, details bool, alertIDs ...str
|
|||
return fmt.Errorf("unable to serialize alert with id %s: %w", alertID, err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", string(data))
|
||||
fmt.Fprintln(os.Stdout, string(data))
|
||||
case "raw":
|
||||
data, err := yaml.Marshal(alert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to serialize alert with id %s: %w", alertID, err)
|
||||
}
|
||||
|
||||
fmt.Println(string(data))
|
||||
fmt.Fprintln(os.Stdout, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,10 +486,9 @@ func (cli *cliAlerts) newInspectCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
func (cli *cliAlerts) newFlushCmd() *cobra.Command {
|
||||
var (
|
||||
maxItems int
|
||||
maxAge string
|
||||
)
|
||||
var maxItems int
|
||||
|
||||
maxAge := cstime.DurationWithDays(7 * 24 * time.Hour)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: `flush`,
|
||||
|
@ -584,7 +509,7 @@ func (cli *cliAlerts) newFlushCmd() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
log.Info("Flushing alerts. !! This may take a long time !!")
|
||||
err = db.FlushAlerts(ctx, maxAge, maxItems)
|
||||
err = db.FlushAlerts(ctx, time.Duration(maxAge), maxItems)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to flush alerts: %w", err)
|
||||
}
|
||||
|
@ -596,7 +521,7 @@ func (cli *cliAlerts) newFlushCmd() *cobra.Command {
|
|||
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().IntVar(&maxItems, "max-items", 5000, "Maximum number of alert items to keep in the database")
|
||||
cmd.Flags().StringVar(&maxAge, "max-age", "7d", "Maximum age of alert items to keep in the database")
|
||||
cmd.Flags().Var(&maxAge, "max-age", "Maximum age of alert items to keep in the database")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -283,7 +284,7 @@ func (cli *cliAllowLists) create(ctx context.Context, db *database.Client, name
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("allowlist '%s' created successfully\n", name)
|
||||
fmt.Fprintf(os.Stdout, "allowlist '%s' created successfully\n", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -392,15 +393,15 @@ func (cli *cliAllowLists) delete(ctx context.Context, db *database.Client, name
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("allowlist '%s' deleted successfully\n", name)
|
||||
fmt.Fprintf(os.Stdout, "allowlist '%s' deleted successfully\n", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *cliAllowLists) newAddCmd() *cobra.Command {
|
||||
var (
|
||||
expirationStr string
|
||||
comment string
|
||||
expiration cstime.DurationWithDays
|
||||
comment string
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
@ -424,25 +425,16 @@ func (cli *cliAllowLists) newAddCmd() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
var expiration time.Duration
|
||||
|
||||
if expirationStr != "" {
|
||||
expiration, err = cstime.ParseDuration(expirationStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
values := args[1:]
|
||||
|
||||
return cli.add(ctx, db, name, values, expiration, comment)
|
||||
return cli.add(ctx, db, name, values, time.Duration(expiration), comment)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVarP(&expirationStr, "expiration", "e", "", "expiration duration")
|
||||
flags.VarP(&expiration, "expiration", "e", "expiration duration")
|
||||
flags.StringVarP(&comment, "comment", "d", "", "comment for the value")
|
||||
|
||||
return cmd
|
||||
|
@ -484,7 +476,7 @@ func (cli *cliAllowLists) add(ctx context.Context, db *database.Client, name str
|
|||
}
|
||||
|
||||
if len(toAdd) == 0 {
|
||||
fmt.Println("no new values for allowlist")
|
||||
fmt.Fprintln(os.Stdout, "no new values for allowlist")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -494,7 +486,15 @@ func (cli *cliAllowLists) add(ctx context.Context, db *database.Client, name str
|
|||
}
|
||||
|
||||
if added > 0 {
|
||||
fmt.Printf("added %d values to allowlist %s\n", added, name)
|
||||
fmt.Fprintf(os.Stdout, "added %d values to allowlist %s\n", added, name)
|
||||
}
|
||||
|
||||
deleted, err := db.ApplyAllowlistsToExistingDecisions(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to apply allowlists to existing decisions: %w", err)
|
||||
}
|
||||
if deleted > 0 {
|
||||
fmt.Printf("%d decisions deleted by allowlists\n", deleted)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -623,7 +623,7 @@ func (cli *cliAllowLists) remove(ctx context.Context, db *database.Client, name
|
|||
}
|
||||
|
||||
if len(toRemove) == 0 {
|
||||
fmt.Println("no value to remove from allowlist")
|
||||
fmt.Fprintln(os.Stdout, "no value to remove from allowlist")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -633,7 +633,7 @@ func (cli *cliAllowLists) remove(ctx context.Context, db *database.Client, name
|
|||
}
|
||||
|
||||
if deleted > 0 {
|
||||
fmt.Printf("removed %d values from allowlist %s", deleted, name)
|
||||
fmt.Fprintf(os.Stdout, "removed %d values from allowlist %s", deleted, name)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -9,10 +9,14 @@ import (
|
|||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
|
||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/ask"
|
||||
)
|
||||
|
||||
const defaultPruneDuration = 60 * time.Minute
|
||||
|
||||
func (cli *cliBouncers) prune(ctx context.Context, duration time.Duration, force bool) error {
|
||||
if duration < 2*time.Minute {
|
||||
if yes, err := ask.YesNo(
|
||||
|
@ -59,12 +63,9 @@ func (cli *cliBouncers) prune(ctx context.Context, duration time.Duration, force
|
|||
}
|
||||
|
||||
func (cli *cliBouncers) newPruneCmd() *cobra.Command {
|
||||
var (
|
||||
duration time.Duration
|
||||
force bool
|
||||
)
|
||||
var force bool
|
||||
|
||||
const defaultDuration = 60 * time.Minute
|
||||
duration := cstime.DurationWithDays(defaultPruneDuration)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "prune",
|
||||
|
@ -74,12 +75,12 @@ func (cli *cliBouncers) newPruneCmd() *cobra.Command {
|
|||
Example: `cscli bouncers prune -d 45m
|
||||
cscli bouncers prune -d 45m --force`,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cli.prune(cmd.Context(), duration, force)
|
||||
return cli.prune(cmd.Context(), time.Duration(duration), force)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.DurationVarP(&duration, "duration", "d", defaultDuration, "duration of time since last pull")
|
||||
flags.VarP(&duration, "duration", "d", "duration of time since last pull")
|
||||
flags.BoolVar(&force, "force", false, "force prune without asking for confirmation")
|
||||
|
||||
return cmd
|
||||
|
|
|
@ -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
|
||||
|
@ -101,22 +103,22 @@ func (cli *cliDecisions) decisionsToTable(alerts *models.GetAlertsResponse, prin
|
|||
if *alerts == nil {
|
||||
// avoid returning "null" in `json"
|
||||
// could be cleaner if we used slice of alerts directly
|
||||
fmt.Println("[]")
|
||||
fmt.Fprintln(os.Stdout, "[]")
|
||||
return nil
|
||||
}
|
||||
|
||||
x, _ := json.MarshalIndent(alerts, "", " ")
|
||||
fmt.Printf("%s", string(x))
|
||||
fmt.Fprintln(os.Stdout, string(x))
|
||||
case "human":
|
||||
if len(*alerts) == 0 {
|
||||
fmt.Println("No active decisions")
|
||||
fmt.Fprintln(os.Stdout, "No active decisions")
|
||||
return nil
|
||||
}
|
||||
|
||||
cli.decisionsTable(color.Output, alerts, printMachine)
|
||||
|
||||
if skipped > 0 {
|
||||
fmt.Printf("%d duplicated entries skipped\n", skipped)
|
||||
fmt.Fprintf(os.Stdout, "%d duplicated entries skipped\n", skipped)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +175,7 @@ func (cli *cliDecisions) NewCommand() *cobra.Command {
|
|||
func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOpts, noSimu *bool, contained *bool, printMachine bool) error {
|
||||
var err error
|
||||
|
||||
*filter.ScopeEquals, err = clialert.SanitizeScope(*filter.ScopeEquals, *filter.IPEquals, *filter.RangeEquals)
|
||||
filter.ScopeEquals, err = clialert.SanitizeScope(filter.ScopeEquals, filter.IPEquals, filter.RangeEquals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -184,67 +186,13 @@ 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
|
||||
}
|
||||
|
||||
if *filter.TypeEquals == "" {
|
||||
filter.TypeEquals = nil
|
||||
}
|
||||
|
||||
if *filter.ValueEquals == "" {
|
||||
filter.ValueEquals = nil
|
||||
}
|
||||
|
||||
if *filter.ScopeEquals == "" {
|
||||
filter.ScopeEquals = nil
|
||||
}
|
||||
|
||||
if *filter.ScenarioEquals == "" {
|
||||
filter.ScenarioEquals = nil
|
||||
}
|
||||
|
||||
if *filter.IPEquals == "" {
|
||||
filter.IPEquals = nil
|
||||
}
|
||||
|
||||
if *filter.RangeEquals == "" {
|
||||
filter.RangeEquals = nil
|
||||
}
|
||||
|
||||
if *filter.OriginEquals == "" {
|
||||
filter.OriginEquals = nil
|
||||
}
|
||||
|
||||
if contained != nil && *contained {
|
||||
filter.Contains = new(bool)
|
||||
}
|
||||
|
@ -264,15 +212,15 @@ func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOp
|
|||
|
||||
func (cli *cliDecisions) newListCmd() *cobra.Command {
|
||||
filter := apiclient.AlertsListOpts{
|
||||
ValueEquals: new(string),
|
||||
ScopeEquals: new(string),
|
||||
ScenarioEquals: new(string),
|
||||
OriginEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
Since: new(string),
|
||||
Until: new(string),
|
||||
TypeEquals: new(string),
|
||||
ValueEquals: "",
|
||||
ScopeEquals: "",
|
||||
ScenarioEquals: "",
|
||||
OriginEquals: "",
|
||||
IPEquals: "",
|
||||
RangeEquals: "",
|
||||
Since: cstime.DurationWithDays(0),
|
||||
Until: cstime.DurationWithDays(0),
|
||||
TypeEquals: "",
|
||||
IncludeCAPI: new(bool),
|
||||
Limit: new(int),
|
||||
}
|
||||
|
@ -300,15 +248,15 @@ 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.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(), ",")))
|
||||
flags.StringVarP(filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
|
||||
flags.StringVarP(filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
|
||||
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(), ",")))
|
||||
flags.StringVarP(&filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
|
||||
flags.StringVarP(&filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVarP(&filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(&filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
|
||||
flags.IntVarP(filter.Limit, "limit", "l", 100, "number of alerts to get (use 0 to remove the limit)")
|
||||
flags.BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
|
||||
flags.BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions")
|
||||
|
@ -452,39 +400,11 @@ func (cli *cliDecisions) delete(ctx context.Context, delFilter apiclient.Decisio
|
|||
var err error
|
||||
|
||||
/*take care of shorthand options*/
|
||||
*delFilter.ScopeEquals, err = clialert.SanitizeScope(*delFilter.ScopeEquals, *delFilter.IPEquals, *delFilter.RangeEquals)
|
||||
delFilter.ScopeEquals, err = clialert.SanitizeScope(delFilter.ScopeEquals, delFilter.IPEquals, delFilter.RangeEquals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *delFilter.ScopeEquals == "" {
|
||||
delFilter.ScopeEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.OriginEquals == "" {
|
||||
delFilter.OriginEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.ValueEquals == "" {
|
||||
delFilter.ValueEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.ScenarioEquals == "" {
|
||||
delFilter.ScenarioEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.TypeEquals == "" {
|
||||
delFilter.TypeEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.IPEquals == "" {
|
||||
delFilter.IPEquals = nil
|
||||
}
|
||||
|
||||
if *delFilter.RangeEquals == "" {
|
||||
delFilter.RangeEquals = nil
|
||||
}
|
||||
|
||||
if contained != nil && *contained {
|
||||
delFilter.Contains = new(bool)
|
||||
}
|
||||
|
@ -514,13 +434,13 @@ func (cli *cliDecisions) delete(ctx context.Context, delFilter apiclient.Decisio
|
|||
|
||||
func (cli *cliDecisions) newDeleteCmd() *cobra.Command {
|
||||
delFilter := apiclient.DecisionsDeleteOpts{
|
||||
ScopeEquals: new(string),
|
||||
ValueEquals: new(string),
|
||||
TypeEquals: new(string),
|
||||
IPEquals: new(string),
|
||||
RangeEquals: new(string),
|
||||
ScenarioEquals: new(string),
|
||||
OriginEquals: new(string),
|
||||
ScopeEquals: "",
|
||||
ValueEquals: "",
|
||||
TypeEquals: "",
|
||||
IPEquals: "",
|
||||
RangeEquals: "",
|
||||
ScenarioEquals: "",
|
||||
OriginEquals: "",
|
||||
}
|
||||
|
||||
var delDecisionID string
|
||||
|
@ -546,10 +466,10 @@ cscli decisions delete --origin lists --scenario list_name
|
|||
if delDecisionAll {
|
||||
return nil
|
||||
}
|
||||
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
|
||||
*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
|
||||
*delFilter.RangeEquals == "" && *delFilter.ScenarioEquals == "" &&
|
||||
*delFilter.OriginEquals == "" && delDecisionID == "" {
|
||||
if delFilter.ScopeEquals == "" && delFilter.ValueEquals == "" &&
|
||||
delFilter.TypeEquals == "" && delFilter.IPEquals == "" &&
|
||||
delFilter.RangeEquals == "" && delFilter.ScenarioEquals == "" &&
|
||||
delFilter.OriginEquals == "" && delDecisionID == "" {
|
||||
_ = cmd.Usage()
|
||||
return errors.New("at least one filter or --all must be specified")
|
||||
}
|
||||
|
@ -563,12 +483,12 @@ cscli decisions delete --origin lists --scenario list_name
|
|||
|
||||
flags := cmd.Flags()
|
||||
flags.SortFlags = false
|
||||
flags.StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
||||
flags.StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
|
||||
flags.StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVarP(delFilter.ScenarioEquals, "scenario", "s", "", "the scenario name (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVar(delFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
|
||||
flags.StringVarP(&delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
|
||||
flags.StringVarP(&delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
|
||||
flags.StringVarP(&delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
|
||||
flags.StringVarP(&delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
|
||||
flags.StringVarP(&delFilter.ScenarioEquals, "scenario", "s", "", "the scenario name (ie. crowdsecurity/ssh-bf)")
|
||||
flags.StringVar(&delFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
|
||||
|
||||
flags.StringVar(&delDecisionID, "id", "", "decision id")
|
||||
flags.BoolVar(&delDecisionAll, "all", false, "delete all decisions")
|
||||
|
|
|
@ -9,11 +9,15 @@ import (
|
|||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/args"
|
||||
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/ask"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||
)
|
||||
|
||||
const defaultPruneDuration = 10 * time.Minute
|
||||
|
||||
func (cli *cliMachines) prune(ctx context.Context, duration time.Duration, notValidOnly bool, force bool) error {
|
||||
if duration < 2*time.Minute && !notValidOnly {
|
||||
if yes, err := ask.YesNo(
|
||||
|
@ -67,12 +71,11 @@ func (cli *cliMachines) prune(ctx context.Context, duration time.Duration, notVa
|
|||
|
||||
func (cli *cliMachines) newPruneCmd() *cobra.Command {
|
||||
var (
|
||||
duration time.Duration
|
||||
notValidOnly bool
|
||||
force bool
|
||||
)
|
||||
|
||||
const defaultDuration = 10 * time.Minute
|
||||
duration := cstime.DurationWithDays(defaultPruneDuration)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "prune",
|
||||
|
@ -84,12 +87,12 @@ cscli machines prune --not-validated-only --force`,
|
|||
Args: args.NoArgs,
|
||||
DisableAutoGenTag: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return cli.prune(cmd.Context(), duration, notValidOnly, force)
|
||||
return cli.prune(cmd.Context(), time.Duration(duration), notValidOnly, force)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.DurationVarP(&duration, "duration", "d", defaultDuration, "duration of time since validated machine last heartbeat")
|
||||
flags.VarP(&duration, "duration", "d", "duration of time since validated machine last heartbeat")
|
||||
flags.BoolVar(¬ValidOnly, "not-validated-only", false, "only prune machines that are not validated")
|
||||
flags.BoolVar(&force, "force", false, "force prune without asking for confirmation")
|
||||
|
||||
|
|
8
go.mod
8
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
|
||||
github.com/crowdsecurity/go-onnxruntime v0.0.0-20240801073851-3fd7de0127b4
|
||||
github.com/crowdsecurity/grokky v0.2.2
|
||||
github.com/crowdsecurity/machineid v1.0.2
|
||||
|
@ -89,8 +89,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
|
||||
|
@ -133,7 +133,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
15
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 h1:wA4O8hGrEntTGn7eZTJqnQ3mrAje5JvQAj8DNbe5IZg=
|
||||
github.com/crowdsecurity/go-cs-lib v0.0.19/go.mod h1:hz2FOHFXc0vWzH78uxo2VebtPQ9Snkbdzy3TMA20tVQ=
|
||||
github.com/crowdsecurity/go-onnxruntime v0.0.0-20240801073851-3fd7de0127b4 h1:CwzISIxoKp0dJLrJJIlhvQPuzirpS9QH07guxK5LIeg=
|
||||
github.com/crowdsecurity/go-onnxruntime v0.0.0-20240801073851-3fd7de0127b4/go.mod h1:YfyL16lx2wA8Z6t/TG1x1/FBngOIpuCuo7nM/FSuP54=
|
||||
github.com/crowdsecurity/grokky v0.2.2 h1:yALsI9zqpDArYzmSSxfBq2dhYuGUTKMJq8KOEIAsuo4=
|
||||
|
@ -664,11 +664,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=
|
||||
|
|
|
@ -7,42 +7,44 @@ import (
|
|||
|
||||
qs "github.com/google/go-querystring/query"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
)
|
||||
|
||||
type AlertsService service
|
||||
|
||||
type AlertsListOpts struct {
|
||||
ScopeEquals *string `url:"scope,omitempty"`
|
||||
ValueEquals *string `url:"value,omitempty"`
|
||||
ScenarioEquals *string `url:"scenario,omitempty"`
|
||||
IPEquals *string `url:"ip,omitempty"`
|
||||
RangeEquals *string `url:"range,omitempty"`
|
||||
OriginEquals *string `url:"origin,omitempty"`
|
||||
Since *string `url:"since,omitempty"`
|
||||
TypeEquals *string `url:"decision_type,omitempty"`
|
||||
Until *string `url:"until,omitempty"`
|
||||
IncludeSimulated *bool `url:"simulated,omitempty"`
|
||||
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
|
||||
IncludeCAPI *bool `url:"include_capi,omitempty"`
|
||||
Limit *int `url:"limit,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
ScopeEquals string `url:"scope,omitempty"`
|
||||
ValueEquals string `url:"value,omitempty"`
|
||||
ScenarioEquals string `url:"scenario,omitempty"`
|
||||
IPEquals string `url:"ip,omitempty"`
|
||||
RangeEquals string `url:"range,omitempty"`
|
||||
OriginEquals string `url:"origin,omitempty"`
|
||||
Since cstime.DurationWithDays `url:"since,omitempty"`
|
||||
TypeEquals string `url:"decision_type,omitempty"`
|
||||
Until cstime.DurationWithDays `url:"until,omitempty"`
|
||||
IncludeSimulated *bool `url:"simulated,omitempty"`
|
||||
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
|
||||
IncludeCAPI *bool `url:"include_capi,omitempty"`
|
||||
Limit *int `url:"limit,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
ListOpts
|
||||
}
|
||||
|
||||
type AlertsDeleteOpts struct {
|
||||
ScopeEquals *string `url:"scope,omitempty"`
|
||||
ValueEquals *string `url:"value,omitempty"`
|
||||
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"`
|
||||
OriginEquals *string `url:"origin,omitempty"`
|
||||
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
|
||||
SourceEquals *string `url:"alert_source,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
Limit *int `url:"limit,omitempty"`
|
||||
ScopeEquals string `url:"scope,omitempty"`
|
||||
ValueEquals string `url:"value,omitempty"`
|
||||
ScenarioEquals string `url:"scenario,omitempty"`
|
||||
IPEquals string `url:"ip,omitempty"`
|
||||
RangeEquals string `url:"range,omitempty"`
|
||||
Since cstime.DurationWithDays `url:"since,omitempty"`
|
||||
Until cstime.DurationWithDays `url:"until,omitempty"`
|
||||
OriginEquals string `url:"origin,omitempty"`
|
||||
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
|
||||
SourceEquals string `url:"alert_source,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
Limit *int `url:"limit,omitempty"`
|
||||
ListOpts
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
func TestAlertsListAsMachine(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -189,7 +190,7 @@ func TestAlertsListAsMachine(t *testing.T) {
|
|||
assert.Equal(t, expected, *alerts)
|
||||
|
||||
// this one doesn't
|
||||
filter := AlertsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||
filter := AlertsListOpts{IPEquals: "1.2.3.4"}
|
||||
|
||||
alerts, resp, err = client.Alerts.List(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
|
@ -199,6 +200,7 @@ func TestAlertsListAsMachine(t *testing.T) {
|
|||
|
||||
func TestAlertsGetAsMachine(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -367,6 +369,7 @@ func TestAlertsGetAsMachine(t *testing.T) {
|
|||
|
||||
func TestAlertsCreateAsMachine(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -410,6 +413,7 @@ func TestAlertsCreateAsMachine(t *testing.T) {
|
|||
|
||||
func TestAlertsDeleteAsMachine(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -442,7 +446,7 @@ func TestAlertsDeleteAsMachine(t *testing.T) {
|
|||
|
||||
defer teardown()
|
||||
|
||||
alert := AlertsDeleteOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||
alert := AlertsDeleteOpts{IPEquals: "1.2.3.4"}
|
||||
alerts, resp, err := client.Alerts.Delete(ctx, alert)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
)
|
||||
|
||||
func TestApiAuth(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.TraceLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -40,7 +40,7 @@ func TestApiAuth(t *testing.T) {
|
|||
|
||||
defer teardown()
|
||||
|
||||
//ok no answer
|
||||
// ok no answer
|
||||
auth := &APIKeyTransport{
|
||||
APIKey: "ixu",
|
||||
}
|
||||
|
@ -48,12 +48,12 @@ func TestApiAuth(t *testing.T) {
|
|||
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||
require.NoError(t, err)
|
||||
|
||||
alert := DecisionsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||
alert := DecisionsListOpts{IPEquals: "1.2.3.4"}
|
||||
_, resp, err := newcli.Decisions.List(ctx, alert)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||
|
||||
//ko bad token
|
||||
// ko bad token
|
||||
auth = &APIKeyTransport{
|
||||
APIKey: "bad",
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func TestApiAuth(t *testing.T) {
|
|||
|
||||
cstest.RequireErrorMessage(t, err, "API error: access forbidden")
|
||||
|
||||
//ko empty token
|
||||
// ko empty token
|
||||
auth = &APIKeyTransport{}
|
||||
|
||||
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||
|
|
|
@ -20,12 +20,12 @@ import (
|
|||
type DecisionsService service
|
||||
|
||||
type DecisionsListOpts struct {
|
||||
ScopeEquals *string `url:"scope,omitempty"`
|
||||
ValueEquals *string `url:"value,omitempty"`
|
||||
TypeEquals *string `url:"type,omitempty"`
|
||||
IPEquals *string `url:"ip,omitempty"`
|
||||
RangeEquals *string `url:"range,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
ScopeEquals string `url:"scope,omitempty"`
|
||||
ValueEquals string `url:"value,omitempty"`
|
||||
TypeEquals string `url:"type,omitempty"`
|
||||
IPEquals string `url:"ip,omitempty"`
|
||||
RangeEquals string `url:"range,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
ListOpts
|
||||
}
|
||||
|
||||
|
@ -60,15 +60,15 @@ func (o *DecisionsStreamOpts) addQueryParamsToURL(url string) (string, error) {
|
|||
}
|
||||
|
||||
type DecisionsDeleteOpts struct {
|
||||
ScopeEquals *string `url:"scope,omitempty"`
|
||||
ValueEquals *string `url:"value,omitempty"`
|
||||
TypeEquals *string `url:"type,omitempty"`
|
||||
IPEquals *string `url:"ip,omitempty"`
|
||||
RangeEquals *string `url:"range,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
OriginEquals *string `url:"origin,omitempty"`
|
||||
ScopeEquals string `url:"scope,omitempty"`
|
||||
ValueEquals string `url:"value,omitempty"`
|
||||
TypeEquals string `url:"type,omitempty"`
|
||||
IPEquals string `url:"ip,omitempty"`
|
||||
RangeEquals string `url:"range,omitempty"`
|
||||
Contains *bool `url:"contains,omitempty"`
|
||||
OriginEquals string `url:"origin,omitempty"`
|
||||
//
|
||||
ScenarioEquals *string `url:"scenario,omitempty"`
|
||||
ScenarioEquals string `url:"scenario,omitempty"`
|
||||
ListOpts
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
func TestDecisionsList(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -64,15 +65,13 @@ func TestDecisionsList(t *testing.T) {
|
|||
}
|
||||
|
||||
// OK decisions
|
||||
decisionsFilter := DecisionsListOpts{IPEquals: ptr.Of("1.2.3.4")}
|
||||
decisions, resp, err := newcli.Decisions.List(ctx, decisionsFilter)
|
||||
decisions, resp, err := newcli.Decisions.List(ctx, DecisionsListOpts{IPEquals: "1.2.3.4"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||
assert.Equal(t, *expected, *decisions)
|
||||
|
||||
// Empty return
|
||||
decisionsFilter = DecisionsListOpts{IPEquals: ptr.Of("1.2.3.5")}
|
||||
decisions, resp, err = newcli.Decisions.List(ctx, decisionsFilter)
|
||||
decisions, resp, err = newcli.Decisions.List(ctx, DecisionsListOpts{IPEquals: "1.2.3.5"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
||||
assert.Empty(t, *decisions)
|
||||
|
@ -80,6 +79,7 @@ func TestDecisionsList(t *testing.T) {
|
|||
|
||||
func TestDecisionsStream(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setup()
|
||||
|
@ -156,6 +156,7 @@ func TestDecisionsStream(t *testing.T) {
|
|||
|
||||
func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setupWithPrefix("v3")
|
||||
|
@ -224,6 +225,7 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
|
|||
|
||||
func TestDecisionsStreamV3(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setupWithPrefix("v3")
|
||||
|
@ -297,6 +299,7 @@ func TestDecisionsStreamV3(t *testing.T) {
|
|||
|
||||
func TestDecisionsFromBlocklist(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
mux, urlx, teardown := setupWithPrefix("v3")
|
||||
|
@ -429,10 +432,7 @@ func TestDeleteDecisions(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
filters := DecisionsDeleteOpts{IPEquals: new(string)}
|
||||
*filters.IPEquals = "1.2.3.4"
|
||||
|
||||
deleted, _, err := client.Decisions.Delete(ctx, filters)
|
||||
deleted, _, err := client.Decisions.Delete(ctx, DecisionsDeleteOpts{IPEquals: "1.2.3.4"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "1", deleted.NbDeleted)
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/tomb.v2"
|
||||
|
||||
|
@ -214,8 +214,6 @@ func NewAPIC(ctx context.Context, config *csconfig.OnlineApiClientCfg, dbClient
|
|||
shareSignals: *config.Sharing,
|
||||
}
|
||||
|
||||
password := strfmt.Password(config.Credentials.Password)
|
||||
|
||||
apiURL, err := url.Parse(config.Credentials.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while parsing '%s': %w", config.Credentials.URL, err)
|
||||
|
@ -233,7 +231,7 @@ func NewAPIC(ctx context.Context, config *csconfig.OnlineApiClientCfg, dbClient
|
|||
|
||||
ret.apiClient, err = apiclient.NewClient(&apiclient.Config{
|
||||
MachineID: config.Credentials.Login,
|
||||
Password: password,
|
||||
Password: strfmt.Password(config.Credentials.Password),
|
||||
URL: apiURL,
|
||||
PapiURL: papiURL,
|
||||
VersionPrefix: "v3",
|
||||
|
@ -244,29 +242,103 @@ func NewAPIC(ctx context.Context, config *csconfig.OnlineApiClientCfg, dbClient
|
|||
return nil, fmt.Errorf("while creating api client: %w", err)
|
||||
}
|
||||
|
||||
// The watcher will be authenticated by the RoundTripper the first time it will call CAPI
|
||||
// Explicit authentication will provoke a useless supplementary call to CAPI
|
||||
scenarios, err := ret.FetchScenariosListFromDB(ctx)
|
||||
err = ret.Authenticate(ctx, config)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// loadAPICToken attempts to retrieve and validate a JWT token from the local database.
|
||||
// It returns the token string, its expiration time, and a boolean indicating whether the token is valid.
|
||||
//
|
||||
// A token is considered valid if:
|
||||
// - it exists in the database,
|
||||
// - it is a properly formatted JWT with an "exp" claim,
|
||||
// - it is not expired or near expiry.
|
||||
func loadAPICToken(ctx context.Context, db *database.Client) (string, time.Time, bool) {
|
||||
token, err := db.GetConfigItem(ctx, "apic_token")
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("get scenario in db: %w", err)
|
||||
log.Debugf("error fetching token from DB: %s", err)
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
authResp, _, err := ret.apiClient.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{
|
||||
if token == nil {
|
||||
log.Debug("no token found in DB")
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
parser := new(jwt.Parser)
|
||||
tok, _, err := parser.ParseUnverified(*token, jwt.MapClaims{})
|
||||
if err != nil {
|
||||
log.Debugf("error parsing token: %s", err)
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
claims, ok := tok.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
log.Debugf("error parsing token claims: %s", err)
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
expFloat, ok := claims["exp"].(float64)
|
||||
if !ok {
|
||||
log.Debug("token missing 'exp' claim")
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
exp := time.Unix(int64(expFloat), 0)
|
||||
if time.Now().UTC().After(exp.Add(-1*time.Minute)) {
|
||||
log.Debug("auth token expired")
|
||||
return "", time.Time{}, false
|
||||
}
|
||||
|
||||
return *token, exp, true
|
||||
}
|
||||
|
||||
// saveAPICToken stores the given JWT token in the local database under the "apic_token" config item.
|
||||
func saveAPICToken(ctx context.Context, db *database.Client, token string) error {
|
||||
if err := db.SetConfigItem(ctx, "apic_token", token); err != nil {
|
||||
return fmt.Errorf("saving token to db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate ensures the API client is authorized to communicate with the CAPI.
|
||||
// It attempts to reuse a previously saved JWT token from the database, falling back to
|
||||
// an authentication request if the token is missing, invalid, or expired.
|
||||
//
|
||||
// If a new token is obtained, it is saved back to the database for caching.
|
||||
func (a *apic) Authenticate(ctx context.Context, config *csconfig.OnlineApiClientCfg) error {
|
||||
if token, exp, valid := loadAPICToken(ctx, a.dbClient); valid {
|
||||
log.Debug("using valid token from DB")
|
||||
a.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Token = token
|
||||
a.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Expiration = exp
|
||||
}
|
||||
|
||||
log.Debug("No token found, authenticating")
|
||||
|
||||
scenarios, err := a.FetchScenariosListFromDB(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get scenario in db: %w", err)
|
||||
}
|
||||
|
||||
password := strfmt.Password(config.Credentials.Password)
|
||||
|
||||
authResp, _, err := a.apiClient.Auth.AuthenticateWatcher(ctx, models.WatcherAuthRequest{
|
||||
MachineID: &config.Credentials.Login,
|
||||
Password: &password,
|
||||
Scenarios: scenarios,
|
||||
})
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("authenticate watcher (%s): %w", config.Credentials.Login, err)
|
||||
return fmt.Errorf("authenticate watcher (%s): %w", config.Credentials.Login, err)
|
||||
}
|
||||
|
||||
if err = ret.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Expiration.UnmarshalText([]byte(authResp.Expire)); err != nil {
|
||||
return ret, fmt.Errorf("unable to parse jwt expiration: %w", err)
|
||||
if err = a.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Expiration.UnmarshalText([]byte(authResp.Expire)); err != nil {
|
||||
return fmt.Errorf("unable to parse jwt expiration: %w", err)
|
||||
}
|
||||
|
||||
ret.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Token = authResp.Token
|
||||
a.apiClient.GetClient().Transport.(*apiclient.JWTTransport).Token = authResp.Token
|
||||
|
||||
return ret, err
|
||||
return saveAPICToken(ctx, a.dbClient, authResp.Token)
|
||||
}
|
||||
|
||||
// keep track of all alerts in cache and push it to CAPI every PushInterval.
|
||||
|
@ -439,16 +511,11 @@ func (a *apic) HandleDeletedDecisionsV3(ctx context.Context, deletedDecisions []
|
|||
filter["scopes"] = []string{*scope}
|
||||
}
|
||||
|
||||
dbCliRet, _, err := a.dbClient.ExpireDecisionsWithFilter(ctx, filter)
|
||||
dbCliDel, _, err := a.dbClient.ExpireDecisionsWithFilter(ctx, filter)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("expiring decisions error: %w", err)
|
||||
}
|
||||
|
||||
dbCliDel, err := strconv.Atoi(dbCliRet)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("converting db ret %d: %w", dbCliDel, err)
|
||||
}
|
||||
|
||||
updateCounterForDecision(deleteCounters, ptr.Of(types.CAPIOrigin), nil, dbCliDel)
|
||||
nbDeleted += dbCliDel
|
||||
}
|
||||
|
@ -588,6 +655,8 @@ func fillAlertsWithDecisions(alerts []*models.Alert, decisions []*models.Decisio
|
|||
func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
|
||||
var err error
|
||||
|
||||
hasPulledAllowlists := false
|
||||
|
||||
// A mutex with TryLock would be a bit simpler
|
||||
// But go does not guarantee that TryLock will be able to acquire the lock even if it is available
|
||||
select {
|
||||
|
@ -649,7 +718,7 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
|
|||
// process deleted decisions
|
||||
nbDeleted, err := a.HandleDeletedDecisionsV3(ctx, data.Deleted, deleteCounters)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("could not delete decisions from CAPI: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)
|
||||
|
@ -657,8 +726,10 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
|
|||
// Update allowlists before processing decisions
|
||||
if data.Links != nil {
|
||||
if len(data.Links.Allowlists) > 0 {
|
||||
hasPulledAllowlists = true
|
||||
|
||||
if err := a.UpdateAllowlists(ctx, data.Links.Allowlists, forcePull); err != nil {
|
||||
return fmt.Errorf("while updating allowlists: %w", err)
|
||||
log.Errorf("could not update allowlists from CAPI: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -675,7 +746,7 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
|
|||
|
||||
err = a.SaveAlerts(ctx, alertsFromCapi, addCounters, deleteCounters)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while saving alerts: %w", err)
|
||||
log.Errorf("could not save alert for CAPI pull: %s", err)
|
||||
}
|
||||
} else {
|
||||
if a.pullCommunity {
|
||||
|
@ -689,11 +760,22 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
|
|||
if data.Links != nil {
|
||||
if len(data.Links.Blocklists) > 0 {
|
||||
if err := a.UpdateBlocklists(ctx, data.Links.Blocklists, addCounters, forcePull); err != nil {
|
||||
return fmt.Errorf("while updating blocklists: %w", err)
|
||||
log.Errorf("could not update blocklists from CAPI: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasPulledAllowlists {
|
||||
deleted, err := a.dbClient.ApplyAllowlistsToExistingDecisions(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("could not apply allowlists to existing decisions: %s", err)
|
||||
}
|
||||
|
||||
if deleted > 0 {
|
||||
log.Infof("deleted %d decisions from allowlists", deleted)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstest"
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
"github.com/crowdsecurity/go-cs-lib/version"
|
||||
|
||||
|
@ -47,9 +48,9 @@ var (
|
|||
|
||||
func LoadTestConfig(t *testing.T) csconfig.Config {
|
||||
config := csconfig.Config{}
|
||||
maxAge := "1h"
|
||||
maxAge := cstime.DurationWithDays(1*time.Hour)
|
||||
flushConfig := csconfig.FlushDBCfg{
|
||||
MaxAge: &maxAge,
|
||||
MaxAge: maxAge,
|
||||
}
|
||||
|
||||
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
|
||||
|
@ -97,9 +98,9 @@ func LoadTestConfig(t *testing.T) csconfig.Config {
|
|||
|
||||
func LoadTestConfigForwardedFor(t *testing.T) csconfig.Config {
|
||||
config := csconfig.Config{}
|
||||
maxAge := "1h"
|
||||
maxAge := cstime.DurationWithDays(1*time.Hour)
|
||||
flushConfig := csconfig.FlushDBCfg{
|
||||
MaxAge: &maxAge,
|
||||
MaxAge: maxAge,
|
||||
}
|
||||
|
||||
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
|
||||
|
@ -363,9 +364,9 @@ func TestLoggingDebugToFileConfig(t *testing.T) {
|
|||
ctx := t.Context()
|
||||
|
||||
/*declare settings*/
|
||||
maxAge := "1h"
|
||||
maxAge := cstime.DurationWithDays(1*time.Hour)
|
||||
flushConfig := csconfig.FlushDBCfg{
|
||||
MaxAge: &maxAge,
|
||||
MaxAge: maxAge,
|
||||
}
|
||||
|
||||
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
|
||||
|
@ -416,9 +417,9 @@ func TestLoggingErrorToFileConfig(t *testing.T) {
|
|||
ctx := t.Context()
|
||||
|
||||
/*declare settings*/
|
||||
maxAge := "1h"
|
||||
maxAge := cstime.DurationWithDays(1*time.Hour)
|
||||
flushConfig := csconfig.FlushDBCfg{
|
||||
MaxAge: &maxAge,
|
||||
MaxAge: maxAge,
|
||||
}
|
||||
|
||||
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
|
||||
|
|
|
@ -134,7 +134,7 @@ func (c *Controller) DeleteDecisions(gctx *gin.Context) {
|
|||
}
|
||||
|
||||
deleteDecisionResp := models.DeleteDecisionResponse{
|
||||
NbDeleted: nbDeleted,
|
||||
NbDeleted: strconv.Itoa(nbDeleted),
|
||||
}
|
||||
|
||||
gctx.JSON(http.StatusOK, deleteDecisionResp)
|
||||
|
|
|
@ -264,6 +264,14 @@ func ManagementCmd(message *Message, p *Papi, sync bool) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to force pull operation: %w", err)
|
||||
}
|
||||
|
||||
deleted, err := p.DBClient.ApplyAllowlistsToExistingDecisions(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("could not apply allowlists to existing decisions: %s", err)
|
||||
}
|
||||
if deleted > 0 {
|
||||
log.Infof("deleted %d decisions from allowlists", deleted)
|
||||
}
|
||||
}
|
||||
case "allowlist_unsubscribe":
|
||||
data, err := json.Marshal(message.Data)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/go-sql-driver/mysql"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
|
@ -58,10 +59,10 @@ type AuthGCCfg struct {
|
|||
type FlushDBCfg struct {
|
||||
MaxItems *int `yaml:"max_items,omitempty"`
|
||||
// We could unmarshal as time.Duration, but alert filters right now are a map of strings
|
||||
MaxAge *string `yaml:"max_age,omitempty"`
|
||||
BouncersGC *AuthGCCfg `yaml:"bouncers_autodelete,omitempty"`
|
||||
AgentsGC *AuthGCCfg `yaml:"agents_autodelete,omitempty"`
|
||||
MetricsMaxAge *time.Duration `yaml:"metrics_max_age,omitempty"`
|
||||
MaxAge cstime.DurationWithDays `yaml:"max_age,omitempty"`
|
||||
BouncersGC *AuthGCCfg `yaml:"bouncers_autodelete,omitempty"`
|
||||
AgentsGC *AuthGCCfg `yaml:"agents_autodelete,omitempty"`
|
||||
MetricsMaxAge cstime.DurationWithDays `yaml:"metrics_max_age,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) LoadDBConfig(inCli bool) error {
|
||||
|
|
|
@ -7,12 +7,13 @@ import (
|
|||
"github.com/expr-lang/expr/vm"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
utils "github.com/crowdsecurity/crowdsec/pkg/database"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
|
@ -84,7 +85,7 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) {
|
|||
duration = defaultDuration
|
||||
}
|
||||
|
||||
if _, err := utils.ParseDuration(duration); err != nil {
|
||||
if _, err := cstime.ParseDurationWithDays(duration); err != nil {
|
||||
return nil, fmt.Errorf("error parsing duration '%s' of %s: %w", duration, profile.Name, err)
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +137,7 @@ func (profile *Runtime) GenerateDecisionFromProfile(alert *models.Alert) ([]*mod
|
|||
profile.Logger.Warningf("Failed to run duration_expr : %v", err)
|
||||
} else {
|
||||
durationStr := fmt.Sprint(duration)
|
||||
if _, err := utils.ParseDuration(durationStr); err != nil {
|
||||
if _, err := cstime.ParseDurationWithDays(durationStr); err != nil {
|
||||
profile.Logger.Warningf("Failed to parse expr duration result '%s'", duration)
|
||||
} else {
|
||||
*decision.Duration = durationStr
|
||||
|
|
|
@ -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.ParseDurationWithDays(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while parsing duration: %w", err)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
"github.com/crowdsecurity/go-cs-lib/slicetools"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||
|
@ -382,7 +383,7 @@ func (c *Client) createDecisionChunk(ctx context.Context, simulated bool, stopAt
|
|||
sz int
|
||||
)
|
||||
|
||||
duration, err := ParseDuration(*decisionItem.Duration)
|
||||
duration, err := cstime.ParseDurationWithDays(*decisionItem.Duration)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(ParseDurationFail, "decision duration '%+v' : %s", *decisionItem.Duration, err)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/allowlist"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/allowlistitem"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
)
|
||||
|
@ -389,3 +391,96 @@ func (c *Client) GetAllowlistsContentForAPIC(ctx context.Context) ([]net.IP, []*
|
|||
|
||||
return ips, nets, nil
|
||||
}
|
||||
|
||||
func (c *Client) ApplyAllowlistsToExistingDecisions(ctx context.Context) (int, error) {
|
||||
// Soft delete (set expiration to now) all decisions that matches any allowlist
|
||||
|
||||
totalCount := 0
|
||||
|
||||
// Get all non-expired allowlist items
|
||||
// We will match them one by one against all decisions
|
||||
allowlistItems, err := c.Ent.AllowListItem.Query().
|
||||
Where(
|
||||
allowlistitem.Or(
|
||||
allowlistitem.ExpiresAtGTE(time.Now().UTC()),
|
||||
allowlistitem.ExpiresAtIsNil(),
|
||||
),
|
||||
).All(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get allowlist items: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
for _, item := range allowlistItems {
|
||||
updateQuery := c.Ent.Decision.Update().SetUntil(now).Where(decision.UntilGTE(now))
|
||||
switch item.IPSize {
|
||||
case 4:
|
||||
updateQuery = updateQuery.Where(
|
||||
decision.And(
|
||||
decision.IPSizeEQ(4),
|
||||
decision.Or(
|
||||
decision.And(
|
||||
decision.StartIPLTE(item.StartIP),
|
||||
decision.EndIPGTE(item.EndIP),
|
||||
),
|
||||
decision.And(
|
||||
decision.StartIPGTE(item.StartIP),
|
||||
decision.EndIPLTE(item.EndIP),
|
||||
),
|
||||
)))
|
||||
case 16:
|
||||
updateQuery = updateQuery.Where(
|
||||
decision.And(
|
||||
decision.IPSizeEQ(16),
|
||||
decision.Or(
|
||||
decision.And(
|
||||
decision.Or(
|
||||
decision.StartIPLT(item.StartIP),
|
||||
decision.And(
|
||||
decision.StartIPEQ(item.StartIP),
|
||||
decision.StartSuffixLTE(item.StartSuffix),
|
||||
)),
|
||||
decision.Or(
|
||||
decision.EndIPGT(item.EndIP),
|
||||
decision.And(
|
||||
decision.EndIPEQ(item.EndIP),
|
||||
decision.EndSuffixGTE(item.EndSuffix),
|
||||
),
|
||||
),
|
||||
),
|
||||
decision.And(
|
||||
decision.Or(
|
||||
decision.StartIPGT(item.StartIP),
|
||||
decision.And(
|
||||
decision.StartIPEQ(item.StartIP),
|
||||
decision.StartSuffixGTE(item.StartSuffix),
|
||||
)),
|
||||
decision.Or(
|
||||
decision.EndIPLT(item.EndIP),
|
||||
decision.And(
|
||||
decision.EndIPEQ(item.EndIP),
|
||||
decision.EndSuffixLTE(item.EndSuffix),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
default:
|
||||
// This should never happen
|
||||
// But better safe than sorry and just skip it instead of expiring all decisions
|
||||
c.Log.Errorf("unexpected IP size %d for allowlist item %s", item.IPSize, item.Value)
|
||||
continue
|
||||
}
|
||||
// Update the decisions
|
||||
count, err := updateQuery.Save(ctx)
|
||||
if err != nil {
|
||||
c.Log.Errorf("unable to expire existing decisions: %s", err)
|
||||
continue
|
||||
}
|
||||
totalCount += count
|
||||
}
|
||||
|
||||
return totalCount, nil
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@ type DecisionsByScenario struct {
|
|||
|
||||
func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string][]string) (*ent.DecisionQuery, error) {
|
||||
var (
|
||||
err error
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
ip_sz int
|
||||
ip_sz int
|
||||
)
|
||||
|
||||
contains := true
|
||||
|
@ -100,18 +100,21 @@ func BuildDecisionRequestWithFilter(query *ent.DecisionQuery, filter map[string]
|
|||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidFilter, "invalid limit value : %s", err)
|
||||
}
|
||||
|
||||
query = query.Limit(limit)
|
||||
case "offset":
|
||||
offset, err := strconv.Atoi(value[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidFilter, "invalid offset value : %s", err)
|
||||
}
|
||||
|
||||
query = query.Offset(offset)
|
||||
case "id_gt":
|
||||
id, err := strconv.Atoi(value[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidFilter, "invalid id_gt value : %s", err)
|
||||
}
|
||||
|
||||
query = query.Where(decision.IDGT(id))
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +204,7 @@ func (c *Client) QueryDecisionCountByScenario(ctx context.Context) ([]*Decisions
|
|||
|
||||
func (c *Client) QueryDecisionWithFilter(ctx context.Context, filter map[string][]string) ([]*ent.Decision, error) {
|
||||
var (
|
||||
err error
|
||||
err error
|
||||
data []*ent.Decision
|
||||
)
|
||||
|
||||
|
@ -322,70 +325,12 @@ func (c *Client) QueryNewDecisionsSinceWithFilters(ctx context.Context, since *t
|
|||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteDecisionsWithFilter(ctx context.Context, filter map[string][]string) (string, []*ent.Decision, error) {
|
||||
var (
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
ip_sz int
|
||||
)
|
||||
|
||||
contains := true
|
||||
/*if contains is true, return bans that *contains* the given value (value is the inner)
|
||||
else, return bans that are *contained* by the given value (value is the outer) */
|
||||
|
||||
decisions := c.Ent.Decision.Query()
|
||||
|
||||
for param, value := range filter {
|
||||
switch param {
|
||||
case "contains":
|
||||
contains, err = strconv.ParseBool(value[0])
|
||||
if err != nil {
|
||||
return "0", nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||
}
|
||||
case "scope":
|
||||
decisions = decisions.Where(decision.ScopeEQ(value[0]))
|
||||
case "value":
|
||||
decisions = decisions.Where(decision.ValueEQ(value[0]))
|
||||
case "type":
|
||||
decisions = decisions.Where(decision.TypeEQ(value[0]))
|
||||
case "ip", "range":
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
||||
if err != nil {
|
||||
return "0", nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||
}
|
||||
case "scenario":
|
||||
decisions = decisions.Where(decision.ScenarioEQ(value[0]))
|
||||
default:
|
||||
return "0", nil, errors.Wrap(InvalidFilter, fmt.Sprintf("'%s' doesn't exist", param))
|
||||
}
|
||||
}
|
||||
|
||||
decisions, err = decisionIPFilter(decisions, contains, ip_sz, start_ip, start_sfx, end_ip, end_sfx)
|
||||
if err != nil {
|
||||
return "0", nil, err
|
||||
}
|
||||
|
||||
toDelete, err := decisions.All(ctx)
|
||||
if err != nil {
|
||||
c.Log.Warningf("DeleteDecisionsWithFilter : %s", err)
|
||||
return "0", nil, errors.Wrap(DeleteFail, "decisions with provided filter")
|
||||
}
|
||||
|
||||
count, err := c.DeleteDecisions(ctx, toDelete)
|
||||
if err != nil {
|
||||
c.Log.Warningf("While deleting decisions : %s", err)
|
||||
return "0", nil, errors.Wrap(DeleteFail, "decisions with provided filter")
|
||||
}
|
||||
|
||||
return strconv.Itoa(count), toDelete, nil
|
||||
}
|
||||
|
||||
// ExpireDecisionsWithFilter updates the expiration time to now() for the decisions matching the filter, and returns the updated items
|
||||
func (c *Client) ExpireDecisionsWithFilter(ctx context.Context, filter map[string][]string) (string, []*ent.Decision, error) {
|
||||
func (c *Client) ExpireDecisionsWithFilter(ctx context.Context, filter map[string][]string) (int, []*ent.Decision, error) {
|
||||
var (
|
||||
err error
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
ip_sz int
|
||||
ip_sz int
|
||||
)
|
||||
|
||||
contains := true
|
||||
|
@ -398,7 +343,7 @@ func (c *Client) ExpireDecisionsWithFilter(ctx context.Context, filter map[strin
|
|||
case "contains":
|
||||
contains, err = strconv.ParseBool(value[0])
|
||||
if err != nil {
|
||||
return "0", nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||
return 0, nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||
}
|
||||
case "scopes":
|
||||
decisions = decisions.Where(decision.ScopeEQ(value[0]))
|
||||
|
@ -413,32 +358,32 @@ func (c *Client) ExpireDecisionsWithFilter(ctx context.Context, filter map[strin
|
|||
case "ip", "range":
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
||||
if err != nil {
|
||||
return "0", nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||
return 0, nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||
}
|
||||
case "scenario":
|
||||
decisions = decisions.Where(decision.ScenarioEQ(value[0]))
|
||||
default:
|
||||
return "0", nil, errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
|
||||
return 0, nil, errors.Wrapf(InvalidFilter, "'%s' doesn't exist", param)
|
||||
}
|
||||
}
|
||||
|
||||
decisions, err = decisionIPFilter(decisions, contains, ip_sz, start_ip, start_sfx, end_ip, end_sfx)
|
||||
if err != nil {
|
||||
return "0", nil, err
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
DecisionsToDelete, err := decisions.All(ctx)
|
||||
if err != nil {
|
||||
c.Log.Warningf("ExpireDecisionsWithFilter : %s", err)
|
||||
return "0", nil, errors.Wrap(DeleteFail, "expire decisions with provided filter")
|
||||
return 0, nil, errors.Wrap(DeleteFail, "expire decisions with provided filter")
|
||||
}
|
||||
|
||||
count, err := c.ExpireDecisions(ctx, DecisionsToDelete)
|
||||
if err != nil {
|
||||
return "0", nil, errors.Wrapf(DeleteFail, "expire decisions with provided filter : %s", err)
|
||||
return 0, nil, errors.Wrapf(DeleteFail, "expire decisions with provided filter : %s", err)
|
||||
}
|
||||
|
||||
return strconv.Itoa(count), DecisionsToDelete, err
|
||||
return count, DecisionsToDelete, err
|
||||
}
|
||||
|
||||
func decisionIDs(decisions []*ent.Decision) []int {
|
||||
|
@ -564,13 +509,7 @@ func (c *Client) CountDecisionsByValue(ctx context.Context, value string, since
|
|||
}
|
||||
|
||||
func (c *Client) GetActiveDecisionsTimeLeftByValue(ctx context.Context, decisionValue string) (time.Duration, error) {
|
||||
var (
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
ip_sz int
|
||||
)
|
||||
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(decisionValue)
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err := types.Addr2Ints(decisionValue)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to convert '%s' to int: %w", decisionValue, err)
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ var (
|
|||
{Name: "created_at", Type: field.TypeTime},
|
||||
{Name: "updated_at", Type: field.TypeTime},
|
||||
{Name: "name", Type: field.TypeString, Unique: true},
|
||||
{Name: "value", Type: field.TypeString},
|
||||
{Name: "value", Type: field.TypeString, SchemaType: map[string]string{"mysql": "longtext", "postgres": "text"}},
|
||||
}
|
||||
// ConfigItemsTable holds the schema information for the "config_items" table.
|
||||
ConfigItemsTable = &schema.Table{
|
||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
|||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/schema/field"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
|
@ -22,7 +23,10 @@ func (ConfigItem) Fields() []ent.Field {
|
|||
Default(types.UtcNow).
|
||||
UpdateDefault(types.UtcNow).StructTag(`json:"updated_at"`),
|
||||
field.String("name").Unique().StructTag(`json:"name"`).Immutable(),
|
||||
field.String("value").StructTag(`json:"value"`), // a json object
|
||||
field.String("value").SchemaType(map[string]string{
|
||||
dialect.MySQL: "longtext",
|
||||
dialect.Postgres: "text",
|
||||
}).StructTag(`json:"value"`), // a json object
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/go-co-op/gocron"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/ptr"
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/alert"
|
||||
|
@ -30,7 +30,6 @@ const (
|
|||
|
||||
func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.FlushDBCfg) (*gocron.Scheduler, error) {
|
||||
maxItems := 0
|
||||
maxAge := ""
|
||||
|
||||
if config.MaxItems != nil && *config.MaxItems <= 0 {
|
||||
return nil, errors.New("max_items can't be zero or negative")
|
||||
|
@ -40,14 +39,10 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
maxItems = *config.MaxItems
|
||||
}
|
||||
|
||||
if config.MaxAge != nil && *config.MaxAge != "" {
|
||||
maxAge = *config.MaxAge
|
||||
}
|
||||
|
||||
// Init & Start cronjob every minute for alerts
|
||||
scheduler := gocron.NewScheduler(time.UTC)
|
||||
|
||||
job, err := scheduler.Every(1).Minute().Do(c.FlushAlerts, ctx, maxAge, maxItems)
|
||||
job, err := scheduler.Every(1).Minute().Do(c.FlushAlerts, ctx, time.Duration(config.MaxAge), maxItems)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while starting FlushAlerts scheduler: %w", err)
|
||||
}
|
||||
|
@ -56,7 +51,7 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
// Init & Start cronjob every hour for bouncers/agents
|
||||
if config.AgentsGC != nil {
|
||||
if config.AgentsGC.Cert != nil {
|
||||
duration, err := ParseDuration(*config.AgentsGC.Cert)
|
||||
duration, err := cstime.ParseDurationWithDays(*config.AgentsGC.Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while parsing agents cert auto-delete duration: %w", err)
|
||||
}
|
||||
|
@ -65,7 +60,7 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
}
|
||||
|
||||
if config.AgentsGC.LoginPassword != nil {
|
||||
duration, err := ParseDuration(*config.AgentsGC.LoginPassword)
|
||||
duration, err := cstime.ParseDurationWithDays(*config.AgentsGC.LoginPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while parsing agents login/password auto-delete duration: %w", err)
|
||||
}
|
||||
|
@ -80,7 +75,7 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
|
||||
if config.BouncersGC != nil {
|
||||
if config.BouncersGC.Cert != nil {
|
||||
duration, err := ParseDuration(*config.BouncersGC.Cert)
|
||||
duration, err := cstime.ParseDurationWithDays(*config.BouncersGC.Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while parsing bouncers cert auto-delete duration: %w", err)
|
||||
}
|
||||
|
@ -89,7 +84,7 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
}
|
||||
|
||||
if config.BouncersGC.Api != nil {
|
||||
duration, err := ParseDuration(*config.BouncersGC.Api)
|
||||
duration, err := cstime.ParseDurationWithDays(*config.BouncersGC.Api)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while parsing bouncers api auto-delete duration: %w", err)
|
||||
}
|
||||
|
@ -109,7 +104,7 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
|
||||
baJob.SingletonMode()
|
||||
|
||||
metricsJob, err := scheduler.Every(flushInterval).Do(c.flushMetrics, ctx, config.MetricsMaxAge)
|
||||
metricsJob, err := scheduler.Every(flushInterval).Do(c.flushMetrics, ctx, time.Duration(config.MetricsMaxAge))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("while starting flushMetrics scheduler: %w", err)
|
||||
}
|
||||
|
@ -129,15 +124,15 @@ func (c *Client) StartFlushScheduler(ctx context.Context, config *csconfig.Flush
|
|||
}
|
||||
|
||||
// flushMetrics deletes metrics older than maxAge, regardless if they have been pushed to CAPI or not
|
||||
func (c *Client) flushMetrics(ctx context.Context, maxAge *time.Duration) {
|
||||
if maxAge == nil {
|
||||
maxAge = ptr.Of(defaultMetricsMaxAge)
|
||||
func (c *Client) flushMetrics(ctx context.Context, maxAge time.Duration) {
|
||||
if maxAge == 0 {
|
||||
maxAge = defaultMetricsMaxAge
|
||||
}
|
||||
|
||||
c.Log.Debugf("flushing metrics older than %s", maxAge)
|
||||
|
||||
deleted, err := c.Ent.Metric.Delete().Where(
|
||||
metric.ReceivedAtLTE(time.Now().UTC().Add(-*maxAge)),
|
||||
metric.ReceivedAtLTE(time.Now().UTC().Add(-maxAge)),
|
||||
).Exec(ctx)
|
||||
if err != nil {
|
||||
c.Log.Errorf("while flushing metrics: %s", err)
|
||||
|
@ -230,7 +225,7 @@ func (c *Client) FlushAgentsAndBouncers(ctx context.Context, agentsCfg *csconfig
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) FlushAlerts(ctx context.Context, maxAge string, maxItems int) error {
|
||||
func (c *Client) FlushAlerts(ctx context.Context, maxAge time.Duration, maxItems int) error {
|
||||
var (
|
||||
deletedByAge int
|
||||
deletedByNbItem int
|
||||
|
@ -255,9 +250,9 @@ func (c *Client) FlushAlerts(ctx context.Context, maxAge string, maxItems int) e
|
|||
|
||||
c.Log.Debugf("FlushAlerts (Total alerts): %d", totalAlerts)
|
||||
|
||||
if maxAge != "" {
|
||||
if maxAge != 0 {
|
||||
filter := map[string][]string{
|
||||
"created_before": {maxAge},
|
||||
"created_before": {maxAge.String()},
|
||||
}
|
||||
|
||||
nbDeleted, err := c.DeleteAlertWithFilter(ctx, filter)
|
||||
|
|
|
@ -4,9 +4,6 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func IP2Int(ip net.IP) uint32 {
|
||||
|
@ -69,28 +66,3 @@ func GetIpsFromIpRange(host string) (int64, int64, error) {
|
|||
|
||||
return ipStart, ipEnd, nil
|
||||
}
|
||||
|
||||
func ParseDuration(d string) (time.Duration, error) {
|
||||
durationStr := d
|
||||
|
||||
if strings.HasSuffix(d, "d") {
|
||||
days := strings.Split(d, "d")[0]
|
||||
if days == "" {
|
||||
return 0, fmt.Errorf("'%s' can't be parsed as duration", d)
|
||||
}
|
||||
|
||||
daysInt, err := strconv.Atoi(days)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
durationStr = strconv.Itoa(daysInt*24) + "h"
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(durationStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return duration, nil
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import (
|
|||
"github.com/umahmood/haversine"
|
||||
"github.com/wasilibs/go-re2"
|
||||
|
||||
"github.com/crowdsecurity/go-cs-lib/cstime"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cache"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/fflag"
|
||||
|
@ -675,7 +677,7 @@ func GetDecisionsSinceCount(params ...any) (any, error) {
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
sinceDuration, err := time.ParseDuration(since)
|
||||
sinceDuration, err := cstime.ParseDurationWithDays(since)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse since parameter '%s' : %s", since, err)
|
||||
return 0, nil
|
||||
|
|
|
@ -37,103 +37,132 @@ type Stagefile struct {
|
|||
}
|
||||
|
||||
func LoadStages(stageFiles []Stagefile, pctx *UnixParserCtx, ectx EnricherCtx) ([]Node, error) {
|
||||
var nodes []Node
|
||||
tmpstages := make(map[string]bool)
|
||||
var allNodes []Node
|
||||
|
||||
tmpStages := make(map[string]bool)
|
||||
pctx.Stages = []string{}
|
||||
|
||||
for _, stageFile := range stageFiles {
|
||||
if !strings.HasSuffix(stageFile.Filename, ".yaml") && !strings.HasSuffix(stageFile.Filename, ".yml") {
|
||||
log.Warningf("skip non yaml : %s", stageFile.Filename)
|
||||
continue
|
||||
}
|
||||
log.Debugf("loading parser file '%s'", stageFile)
|
||||
st, err := os.Stat(stageFile.Filename)
|
||||
for _, sf := range stageFiles {
|
||||
nodes, err := processStageFile(sf, pctx, ectx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat %s : %v", stageFile, err)
|
||||
return nil, err
|
||||
}
|
||||
if st.IsDir() {
|
||||
continue
|
||||
|
||||
for _, n := range nodes { //nolint:gocritic // rangeValCopy
|
||||
allNodes = append(allNodes, n)
|
||||
tmpStages[n.Stage] = true
|
||||
}
|
||||
yamlFile, err := os.Open(stageFile.Filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't access parsing configuration file %s : %s", stageFile.Filename, err)
|
||||
}
|
||||
defer yamlFile.Close()
|
||||
//process the yaml
|
||||
dec := yaml.NewDecoder(yamlFile)
|
||||
dec.SetStrict(true)
|
||||
nodesCount := 0
|
||||
for {
|
||||
node := Node{}
|
||||
node.OnSuccess = "continue" //default behavior is to continue
|
||||
err = dec.Decode(&node)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
log.Tracef("End of yaml file")
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("error decoding parsing configuration file '%s': %v", stageFile.Filename, err)
|
||||
}
|
||||
|
||||
//check for empty bucket
|
||||
if node.Name == "" && node.Description == "" && node.Author == "" {
|
||||
log.Infof("Node in %s has no name, author or description. Skipping.", stageFile.Filename)
|
||||
continue
|
||||
}
|
||||
//check compat
|
||||
if node.FormatVersion == "" {
|
||||
log.Tracef("no version in %s, assuming '1.0'", node.Name)
|
||||
node.FormatVersion = "1.0"
|
||||
}
|
||||
ok, err := constraint.Satisfies(node.FormatVersion, constraint.Parser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check version : %s", err)
|
||||
}
|
||||
if !ok {
|
||||
log.Errorf("%s : %s doesn't satisfy parser format %s, skip", node.Name, node.FormatVersion, constraint.Parser)
|
||||
continue
|
||||
}
|
||||
|
||||
node.Stage = stageFile.Stage
|
||||
if _, ok := tmpstages[stageFile.Stage]; !ok {
|
||||
tmpstages[stageFile.Stage] = true
|
||||
}
|
||||
//compile the node : grok pattern and expression
|
||||
err = node.compile(pctx, ectx)
|
||||
if err != nil {
|
||||
if node.Name != "" {
|
||||
return nil, fmt.Errorf("failed to compile node '%s' in '%s' : %s", node.Name, stageFile.Filename, err)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to compile node in '%s' : %s", stageFile.Filename, err)
|
||||
}
|
||||
/* if the stage is empty, the node is empty, it's a trailing entry in users yaml file */
|
||||
if node.Stage == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, data := range node.Data {
|
||||
err = exprhelpers.FileInit(pctx.DataFolder, data.DestPath, data.Type)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
if data.Type == "regexp" { //cache only makes sense for regexp
|
||||
if err = exprhelpers.RegexpCacheInit(data.DestPath, *data); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes = append(nodes, node)
|
||||
nodesCount++
|
||||
}
|
||||
log.WithFields(log.Fields{"file": stageFile.Filename, "stage": stageFile.Stage}).Infof("Loaded %d parser nodes", nodesCount)
|
||||
}
|
||||
|
||||
for k := range tmpstages {
|
||||
for k := range tmpStages {
|
||||
pctx.Stages = append(pctx.Stages, k)
|
||||
}
|
||||
|
||||
sort.Strings(pctx.Stages)
|
||||
log.Infof("Loaded %d nodes from %d stages", len(nodes), len(pctx.Stages))
|
||||
log.Infof("Loaded %d nodes from %d stages", len(allNodes), len(pctx.Stages))
|
||||
|
||||
return allNodes, nil
|
||||
}
|
||||
|
||||
func processStageFile(stageFile Stagefile, pctx *UnixParserCtx, ectx EnricherCtx) ([]Node, error) {
|
||||
if !strings.HasSuffix(stageFile.Filename, ".yaml") && !strings.HasSuffix(stageFile.Filename, ".yml") {
|
||||
log.Warningf("skip non yaml : %s", stageFile.Filename)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Debugf("loading parser file '%s'", stageFile)
|
||||
|
||||
st, err := os.Stat(stageFile.Filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to stat %s : %v", stageFile, err)
|
||||
}
|
||||
|
||||
if st.IsDir() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
yamlFile, err := os.Open(stageFile.Filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't access parsing configuration file %s : %s", stageFile.Filename, err)
|
||||
}
|
||||
defer yamlFile.Close()
|
||||
// process the yaml
|
||||
dec := yaml.NewDecoder(yamlFile)
|
||||
dec.SetStrict(true)
|
||||
|
||||
var nodes []Node
|
||||
|
||||
nodesCount := 0
|
||||
|
||||
for {
|
||||
node := Node{}
|
||||
node.OnSuccess = "continue" // default behavior is to continue
|
||||
|
||||
if err = dec.Decode(&node); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
log.Tracef("End of yaml file")
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error decoding parsing configuration file '%s': %v", stageFile.Filename, err)
|
||||
}
|
||||
|
||||
// check for empty bucket
|
||||
if node.Name == "" && node.Description == "" && node.Author == "" {
|
||||
log.Infof("Node in %s has no name, author or description. Skipping.", stageFile.Filename)
|
||||
continue
|
||||
}
|
||||
|
||||
// check compat
|
||||
if node.FormatVersion == "" {
|
||||
log.Tracef("no version in %s, assuming '1.0'", node.Name)
|
||||
node.FormatVersion = "1.0"
|
||||
}
|
||||
|
||||
ok, err := constraint.Satisfies(node.FormatVersion, constraint.Parser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check version : %s", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Errorf("%s : %s doesn't satisfy parser format %s, skip", node.Name, node.FormatVersion, constraint.Parser)
|
||||
continue
|
||||
}
|
||||
|
||||
node.Stage = stageFile.Stage
|
||||
// compile the node : grok pattern and expression
|
||||
|
||||
err = node.compile(pctx, ectx)
|
||||
if err != nil {
|
||||
if node.Name != "" {
|
||||
return nil, fmt.Errorf("failed to compile node '%s' in '%s' : %s", node.Name, stageFile.Filename, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to compile node in '%s' : %s", stageFile.Filename, err)
|
||||
}
|
||||
/* if the stage is empty, the node is empty, it's a trailing entry in users yaml file */
|
||||
if node.Stage == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, data := range node.Data {
|
||||
err = exprhelpers.FileInit(pctx.DataFolder, data.DestPath, data.Type)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
if data.Type == "regexp" { // cache only makes sense for regexp
|
||||
if err = exprhelpers.RegexpCacheInit(data.DestPath, *data); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes = append(nodes, node)
|
||||
nodesCount++
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{"file": stageFile.Filename, "stage": stageFile.Stage}).Infof("Loaded %d parser nodes", nodesCount)
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
|
|
@ -133,6 +133,13 @@ teardown() {
|
|||
}
|
||||
|
||||
@test "cscli bouncers prune" {
|
||||
rune -1 cscli bouncers prune --duration foobar
|
||||
assert_stderr 'Error: invalid argument "foobar" for "-d, --duration" flag: time: invalid duration "foobar"'
|
||||
|
||||
# duration takes days as well
|
||||
rune -0 cscli bouncers prune --duration 1d30m
|
||||
assert_output 'No bouncers to prune.'
|
||||
|
||||
rune -0 cscli bouncers prune
|
||||
assert_output 'No bouncers to prune.'
|
||||
rune -0 cscli bouncers add ciTestBouncer
|
||||
|
|
|
@ -124,12 +124,19 @@ teardown() {
|
|||
@test "cscli machines prune" {
|
||||
rune -0 cscli metrics
|
||||
|
||||
rune -1 cscli machines prune --duration foobar
|
||||
assert_stderr 'Error: invalid argument "foobar" for "-d, --duration" flag: time: invalid duration "foobar"'
|
||||
|
||||
# if the fixture has been created some time ago,
|
||||
# the machines may be old enough to trigger a user prompt.
|
||||
# make sure the prune duration is high enough.
|
||||
rune -0 cscli machines prune --duration 1000000h
|
||||
assert_output 'No machines to prune.'
|
||||
|
||||
# duration takes days as well
|
||||
rune -0 cscli machines prune --duration 1000d30m
|
||||
assert_output 'No machines to prune.'
|
||||
|
||||
rune -0 cscli machines list -o json
|
||||
rune -0 jq -r '.[-1].machineId' <(output)
|
||||
rune -0 cscli machines delete "$output"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -119,14 +119,14 @@ teardown() {
|
|||
|
||||
# comment and expiration are applied to all values
|
||||
rune -1 cscli allowlist add foo 10.10.10.10 10.20.30.40 -d comment -e toto
|
||||
assert_stderr 'Error: time: invalid duration "toto"'
|
||||
assert_stderr 'Error: invalid argument "toto" for "-e, --expiration" flag: time: invalid duration "toto"'
|
||||
refute_output
|
||||
|
||||
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 argument "1 day" for "-e, --expiration" flag: invalid day value in duration "1 day"'
|
||||
|
||||
rune -0 cscli allowlist add foo 10.10.10.10 -d comment -e '1d'
|
||||
rune -0 cscli allowlist add foo 10.10.10.10 -d comment -e '1d12h'
|
||||
assert_output 'added 1 values to allowlist foo'
|
||||
refute_stderr
|
||||
|
||||
|
@ -246,3 +246,58 @@ teardown() {
|
|||
rune -0 jq 'del(.created_at) | del(.updated_at) | del(.items.[].created_at) | del(.items.[].expiration)' <(output)
|
||||
assert_json '{"description":"a foo","items":[],"name":"foo"}'
|
||||
}
|
||||
|
||||
@test "allowlists expire active decisions" {
|
||||
rune -0 cscli decisions add -i 1.2.3.4
|
||||
rune -0 cscli decisions add -r 2.3.4.0/24
|
||||
rune -0 cscli decisions add -i 5.4.3.42
|
||||
rune -0 cscli decisions add -r 6.5.4.0/24
|
||||
rune -0 cscli decisions add -r 10.0.0.0/23
|
||||
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -r 'sort_by(.decisions[].value) | .[].decisions[0].value' <(output)
|
||||
assert_output - <<-EOT
|
||||
1.2.3.4
|
||||
10.0.0.0/23
|
||||
2.3.4.0/24
|
||||
5.4.3.42
|
||||
6.5.4.0/24
|
||||
EOT
|
||||
|
||||
rune -0 cscli allowlists create foo -d "foo"
|
||||
|
||||
# add an allowlist that matches exactly
|
||||
rune -0 cscli allowlists add foo 1.2.3.4
|
||||
if is_db_mysql; then sleep 2; fi
|
||||
# it should not be here anymore
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -e 'any(.[].decisions[]; .value == "1.2.3.4") | not' <(output)
|
||||
|
||||
# allowlist an IP belonging to a range
|
||||
rune -0 cscli allowlist add foo 2.3.4.42
|
||||
if is_db_mysql; then sleep 2; fi
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -e 'any(.[].decisions[]; .value == "2.3.4.0/24") | not' <(output)
|
||||
|
||||
# allowlist a range with an active decision inside
|
||||
rune -0 cscli allowlist add foo 5.4.3.0/24
|
||||
if is_db_mysql; then sleep 2; fi
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -e 'any(.[].decisions[]; .value == "5.4.3.42") | not' <(output)
|
||||
|
||||
# allowlist a range inside a range for which we have a decision
|
||||
rune -0 cscli allowlist add foo 6.5.4.0/25
|
||||
if is_db_mysql; then sleep 2; fi
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -e 'any(.[].decisions[]; .value == "6.5.4.0/24") | not' <(output)
|
||||
|
||||
# allowlist a range bigger than a range for which we have a decision
|
||||
rune -0 cscli allowlist add foo 10.0.0.0/24
|
||||
if is_db_mysql; then sleep 2; fi
|
||||
rune -0 cscli decisions list -o json
|
||||
rune -0 jq -e 'any(.[].decisions[]; .value == "10.0.0.0/24") | not' <(output)
|
||||
|
||||
# sanity check no more active decisions
|
||||
rune -0 cscli decisions list -o json
|
||||
assert_json []
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ load_init_data() {
|
|||
|
||||
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
|
||||
if [[ "${DB_BACKEND}" != "${dump_backend}" ]]; then
|
||||
die "Can't run with backend '${DB_BACKEND}' because the test data was built with '${dump_backend}'"
|
||||
die "Can't run with backend '${DB_BACKEND}' because 'make bats-fixture' was ran with '${dump_backend}'"
|
||||
fi
|
||||
|
||||
remove_init_data
|
||||
|
|
|
@ -168,7 +168,7 @@ load_init_data() {
|
|||
|
||||
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
|
||||
if [[ "${DB_BACKEND}" != "${dump_backend}" ]]; then
|
||||
die "Can't run with backend '${DB_BACKEND}' because the test data was built with '${dump_backend}'"
|
||||
die "Can't run with backend '${DB_BACKEND}' because 'make bats-fixture' was ran with '${dump_backend}'"
|
||||
fi
|
||||
|
||||
remove_init_data
|
||||
|
|
|
@ -26,7 +26,7 @@ fi
|
|||
|
||||
dump_backend="$(cat "$LOCAL_INIT_DIR/.backend")"
|
||||
if [[ "$DB_BACKEND" != "$dump_backend" ]]; then
|
||||
die "Can't run with backend '$DB_BACKEND' because the test data was build with '$dump_backend'"
|
||||
die "Can't run with backend '$DB_BACKEND' because 'make bats-fixture' was ran with '$dump_backend'"
|
||||
fi
|
||||
|
||||
if [[ $# -ge 1 ]]; then
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue