Merge branch 'master' into login/cache

This commit is contained in:
marco 2025-05-06 16:01:02 +02:00
commit 8bf2f2873d
16 changed files with 318 additions and 240 deletions

View file

@ -184,7 +184,7 @@ linters:
maintidx: maintidx:
# raise this after refactoring # raise this after refactoring
under: 15 under: 18
misspell: misspell:
locale: US locale: US
@ -210,7 +210,7 @@ linters:
- name: cognitive-complexity - name: cognitive-complexity
arguments: arguments:
# lower this after refactoring # lower this after refactoring
- 119 - 113
- name: comment-spacings - name: comment-spacings
disabled: true disabled: true
- name: confusing-results - name: confusing-results
@ -235,8 +235,8 @@ linters:
- name: function-length - name: function-length
arguments: arguments:
# lower this after refactoring # lower this after refactoring
- 111 - 87
- 238 - 198
- name: get-return - name: get-return
disabled: true disabled: true
- name: increment-decrement - name: increment-decrement
@ -294,9 +294,7 @@ linters:
- -ST1003 - -ST1003
- -ST1005 - -ST1005
- -ST1012 - -ST1012
- -ST1022
- -QF1003 - -QF1003
- -QF1008
- -QF1012 - -QF1012
wsl: wsl:

View file

@ -104,15 +104,15 @@ func (cli *cliAlerts) alertsToTable(alerts *models.GetAlertsResponse, printMachi
if *alerts == nil { if *alerts == nil {
// avoid returning "null" in json // avoid returning "null" in json
// could be cleaner if we used slice of alerts directly // could be cleaner if we used slice of alerts directly
fmt.Println("[]") fmt.Fprintln(os.Stdout, "[]")
return nil return nil
} }
x, _ := json.MarshalIndent(alerts, "", " ") x, _ := json.MarshalIndent(alerts, "", " ")
fmt.Print(string(x)) fmt.Fprint(os.Stdout, string(x))
case "human": case "human":
if len(*alerts) == 0 { if len(*alerts) == 0 {
fmt.Println("No active alerts") fmt.Fprintln(os.Stdout, "No active alerts")
return nil return nil
} }
@ -156,7 +156,7 @@ func (cli *cliAlerts) displayOneAlert(alert *models.Alert, withDetail bool) erro
alertDecisionsTable(color.Output, cfg.Cscli.Color, alert) alertDecisionsTable(color.Output, cfg.Cscli.Color, alert)
if len(alert.Meta) > 0 { 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 { sort.Slice(alert.Meta, func(i, j int) bool {
return alert.Meta[i].Key < alert.Meta[j].Key return alert.Meta[i].Key < alert.Meta[j].Key
}) })
@ -183,7 +183,7 @@ func (cli *cliAlerts) displayOneAlert(alert *models.Alert, withDetail bool) erro
} }
if withDetail { if withDetail {
fmt.Printf("\n - Events :\n") fmt.Fprintf(os.Stdout, "\n - Events :\n")
for _, event := range alert.Events { for _, event := range alert.Events {
alertEventTable(color.Output, cfg.Cscli.Color, event) alertEventTable(color.Output, cfg.Cscli.Color, event)
@ -240,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 { func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.AlertsListOpts, limit *int, contained *bool, printMachine bool) error {
var err 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 { if err != nil {
return err return err
} }
@ -253,34 +253,6 @@ func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.Alerts
*alertListFilter.Limit = 0 *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 { if contained != nil && *contained {
alertListFilter.Contains = new(bool) alertListFilter.Contains = new(bool)
} }
@ -299,16 +271,16 @@ func (cli *cliAlerts) list(ctx context.Context, alertListFilter apiclient.Alerts
func (cli *cliAlerts) newListCmd() *cobra.Command { func (cli *cliAlerts) newListCmd() *cobra.Command {
alertListFilter := apiclient.AlertsListOpts{ alertListFilter := apiclient.AlertsListOpts{
ScopeEquals: new(string), ScopeEquals: "",
ValueEquals: new(string), ValueEquals: "",
ScenarioEquals: new(string), ScenarioEquals: "",
IPEquals: new(string), IPEquals: "",
RangeEquals: new(string), RangeEquals: "",
Since: cstime.DurationWithDays(0), Since: cstime.DurationWithDays(0),
Until: cstime.DurationWithDays(0), Until: cstime.DurationWithDays(0),
TypeEquals: new(string), TypeEquals: "",
IncludeCAPI: new(bool), IncludeCAPI: new(bool),
OriginEquals: new(string), OriginEquals: "",
} }
limit := new(int) limit := new(int)
@ -338,13 +310,13 @@ cscli alerts list --type ban`,
flags.BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API") flags.BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
flags.Var(&alertListFilter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)") flags.Var(&alertListFilter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)")
flags.Var(&alertListFilter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)") flags.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.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.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.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.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.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.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.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.BoolVar(contained, "contained", false, "query decisions contained by range")
flags.BoolVarP(&printMachine, "machine", "m", false, "print machines that sent alerts") 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)") flags.IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
@ -356,7 +328,7 @@ func (cli *cliAlerts) delete(ctx context.Context, delFilter apiclient.AlertsDele
var err error var err error
if !deleteAll { 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 { if err != nil {
return err return err
} }
@ -365,26 +337,6 @@ func (cli *cliAlerts) delete(ctx context.Context, delFilter apiclient.AlertsDele
delFilter.ActiveDecisionEquals = activeDecision 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 { if contained != nil && *contained {
delFilter.Contains = new(bool) delFilter.Contains = new(bool)
} }
@ -422,11 +374,11 @@ func (cli *cliAlerts) newDeleteCmd() *cobra.Command {
) )
delFilter := apiclient.AlertsDeleteOpts{ delFilter := apiclient.AlertsDeleteOpts{
ScopeEquals: new(string), ScopeEquals: "",
ValueEquals: new(string), ValueEquals: "",
ScenarioEquals: new(string), ScenarioEquals: "",
IPEquals: new(string), IPEquals: "",
RangeEquals: new(string), RangeEquals: "",
} }
contained := new(bool) contained := new(bool)
@ -445,9 +397,9 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
if deleteAll { if deleteAll {
return nil return nil
} }
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" && if delFilter.ScopeEquals == "" && delFilter.ValueEquals == "" &&
*delFilter.ScenarioEquals == "" && *delFilter.IPEquals == "" && delFilter.ScenarioEquals == "" && delFilter.IPEquals == "" &&
*delFilter.RangeEquals == "" && delAlertByID == "" { delFilter.RangeEquals == "" && delAlertByID == "" {
_ = cmd.Usage() _ = cmd.Usage()
return errors.New("at least one filter or --all must be specified") return errors.New("at least one filter or --all must be specified")
} }
@ -461,11 +413,11 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
flags := cmd.Flags() flags := cmd.Flags()
flags.SortFlags = false flags.SortFlags = false
flags.StringVar(delFilter.ScopeEquals, "scope", "", "the scope (ie. ip,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.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.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.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.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
flags.StringVar(&delAlertByID, "id", "", "alert ID") flags.StringVar(&delAlertByID, "id", "", "alert ID")
flags.BoolVarP(&deleteAll, "all", "a", false, "delete all alerts") flags.BoolVarP(&deleteAll, "all", "a", false, "delete all alerts")
flags.BoolVar(contained, "contained", false, "query decisions contained by range") flags.BoolVar(contained, "contained", false, "query decisions contained by range")
@ -499,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) 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": case "raw":
data, err := yaml.Marshal(alert) data, err := yaml.Marshal(alert)
if err != nil { if err != nil {
return fmt.Errorf("unable to serialize alert with id %s: %w", alertID, err) return fmt.Errorf("unable to serialize alert with id %s: %w", alertID, err)
} }
fmt.Println(string(data)) fmt.Fprintln(os.Stdout, string(data))
} }
} }
@ -536,7 +488,7 @@ func (cli *cliAlerts) newInspectCmd() *cobra.Command {
func (cli *cliAlerts) newFlushCmd() *cobra.Command { func (cli *cliAlerts) newFlushCmd() *cobra.Command {
var maxItems int var maxItems int
maxAge := cstime.DurationWithDays(7*24*time.Hour) maxAge := cstime.DurationWithDays(7 * 24 * time.Hour)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: `flush`, Use: `flush`,

View file

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"os"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@ -283,7 +284,7 @@ func (cli *cliAllowLists) create(ctx context.Context, db *database.Client, name
return err return err
} }
fmt.Printf("allowlist '%s' created successfully\n", name) fmt.Fprintf(os.Stdout, "allowlist '%s' created successfully\n", name)
return nil return nil
} }
@ -392,7 +393,7 @@ func (cli *cliAllowLists) delete(ctx context.Context, db *database.Client, name
return err return err
} }
fmt.Printf("allowlist '%s' deleted successfully\n", name) fmt.Fprintf(os.Stdout, "allowlist '%s' deleted successfully\n", name)
return nil return nil
} }
@ -475,7 +476,7 @@ func (cli *cliAllowLists) add(ctx context.Context, db *database.Client, name str
} }
if len(toAdd) == 0 { if len(toAdd) == 0 {
fmt.Println("no new values for allowlist") fmt.Fprintln(os.Stdout, "no new values for allowlist")
return nil return nil
} }
@ -485,7 +486,15 @@ func (cli *cliAllowLists) add(ctx context.Context, db *database.Client, name str
} }
if added > 0 { 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 return nil
@ -614,7 +623,7 @@ func (cli *cliAllowLists) remove(ctx context.Context, db *database.Client, name
} }
if len(toRemove) == 0 { if len(toRemove) == 0 {
fmt.Println("no value to remove from allowlist") fmt.Fprintln(os.Stdout, "no value to remove from allowlist")
return nil return nil
} }
@ -624,7 +633,7 @@ func (cli *cliAllowLists) remove(ctx context.Context, db *database.Client, name
} }
if deleted > 0 { 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 return nil

View file

@ -103,22 +103,22 @@ func (cli *cliDecisions) decisionsToTable(alerts *models.GetAlertsResponse, prin
if *alerts == nil { if *alerts == nil {
// avoid returning "null" in `json" // avoid returning "null" in `json"
// could be cleaner if we used slice of alerts directly // could be cleaner if we used slice of alerts directly
fmt.Println("[]") fmt.Fprintln(os.Stdout, "[]")
return nil return nil
} }
x, _ := json.MarshalIndent(alerts, "", " ") x, _ := json.MarshalIndent(alerts, "", " ")
fmt.Printf("%s", string(x)) fmt.Fprintln(os.Stdout, string(x))
case "human": case "human":
if len(*alerts) == 0 { if len(*alerts) == 0 {
fmt.Println("No active decisions") fmt.Fprintln(os.Stdout, "No active decisions")
return nil return nil
} }
cli.decisionsTable(color.Output, alerts, printMachine) cli.decisionsTable(color.Output, alerts, printMachine)
if skipped > 0 { if skipped > 0 {
fmt.Printf("%d duplicated entries skipped\n", skipped) fmt.Fprintf(os.Stdout, "%d duplicated entries skipped\n", skipped)
} }
} }
@ -175,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 { func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOpts, noSimu *bool, contained *bool, printMachine bool) error {
var err 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 { if err != nil {
return err return err
} }
@ -193,34 +193,6 @@ func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOp
*filter.Limit = 0 *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 { if contained != nil && *contained {
filter.Contains = new(bool) filter.Contains = new(bool)
} }
@ -240,15 +212,15 @@ func (cli *cliDecisions) list(ctx context.Context, filter apiclient.AlertsListOp
func (cli *cliDecisions) newListCmd() *cobra.Command { func (cli *cliDecisions) newListCmd() *cobra.Command {
filter := apiclient.AlertsListOpts{ filter := apiclient.AlertsListOpts{
ValueEquals: new(string), ValueEquals: "",
ScopeEquals: new(string), ScopeEquals: "",
ScenarioEquals: new(string), ScenarioEquals: "",
OriginEquals: new(string), OriginEquals: "",
IPEquals: new(string), IPEquals: "",
RangeEquals: new(string), RangeEquals: "",
Since: cstime.DurationWithDays(0), Since: cstime.DurationWithDays(0),
Until: cstime.DurationWithDays(0), Until: cstime.DurationWithDays(0),
TypeEquals: new(string), TypeEquals: "",
IncludeCAPI: new(bool), IncludeCAPI: new(bool),
Limit: new(int), Limit: new(int),
} }
@ -278,13 +250,13 @@ cscli decisions list --origin lists --scenario list_name
flags.BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API") flags.BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
flags.Var(&filter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)") flags.Var(&filter.Since, "since", "restrict to alerts newer than since (ie. 4h, 30d)")
flags.Var(&filter.Until, "until", "restrict to alerts older than until (ie. 4h, 30d)") flags.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.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.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.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.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.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.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.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.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.BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
flags.BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions") flags.BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions")
@ -428,39 +400,11 @@ func (cli *cliDecisions) delete(ctx context.Context, delFilter apiclient.Decisio
var err error var err error
/*take care of shorthand options*/ /*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 { if err != nil {
return err 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 { if contained != nil && *contained {
delFilter.Contains = new(bool) delFilter.Contains = new(bool)
} }
@ -490,13 +434,13 @@ func (cli *cliDecisions) delete(ctx context.Context, delFilter apiclient.Decisio
func (cli *cliDecisions) newDeleteCmd() *cobra.Command { func (cli *cliDecisions) newDeleteCmd() *cobra.Command {
delFilter := apiclient.DecisionsDeleteOpts{ delFilter := apiclient.DecisionsDeleteOpts{
ScopeEquals: new(string), ScopeEquals: "",
ValueEquals: new(string), ValueEquals: "",
TypeEquals: new(string), TypeEquals: "",
IPEquals: new(string), IPEquals: "",
RangeEquals: new(string), RangeEquals: "",
ScenarioEquals: new(string), ScenarioEquals: "",
OriginEquals: new(string), OriginEquals: "",
} }
var delDecisionID string var delDecisionID string
@ -522,10 +466,10 @@ cscli decisions delete --origin lists --scenario list_name
if delDecisionAll { if delDecisionAll {
return nil return nil
} }
if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" && if delFilter.ScopeEquals == "" && delFilter.ValueEquals == "" &&
*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" && delFilter.TypeEquals == "" && delFilter.IPEquals == "" &&
*delFilter.RangeEquals == "" && *delFilter.ScenarioEquals == "" && delFilter.RangeEquals == "" && delFilter.ScenarioEquals == "" &&
*delFilter.OriginEquals == "" && delDecisionID == "" { delFilter.OriginEquals == "" && delDecisionID == "" {
_ = cmd.Usage() _ = cmd.Usage()
return errors.New("at least one filter or --all must be specified") return errors.New("at least one filter or --all must be specified")
} }
@ -539,12 +483,12 @@ cscli decisions delete --origin lists --scenario list_name
flags := cmd.Flags() flags := cmd.Flags()
flags.SortFlags = false flags.SortFlags = false
flags.StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)") 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.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.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.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.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(&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.StringVar(&delDecisionID, "id", "", "decision id")
flags.BoolVar(&delDecisionAll, "all", false, "delete all decisions") flags.BoolVar(&delDecisionAll, "all", false, "delete all decisions")

View file

@ -15,14 +15,14 @@ import (
type AlertsService service type AlertsService service
type AlertsListOpts struct { type AlertsListOpts struct {
ScopeEquals *string `url:"scope,omitempty"` ScopeEquals string `url:"scope,omitempty"`
ValueEquals *string `url:"value,omitempty"` ValueEquals string `url:"value,omitempty"`
ScenarioEquals *string `url:"scenario,omitempty"` ScenarioEquals string `url:"scenario,omitempty"`
IPEquals *string `url:"ip,omitempty"` IPEquals string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"` RangeEquals string `url:"range,omitempty"`
OriginEquals *string `url:"origin,omitempty"` OriginEquals string `url:"origin,omitempty"`
Since cstime.DurationWithDays `url:"since,omitempty"` Since cstime.DurationWithDays `url:"since,omitempty"`
TypeEquals *string `url:"decision_type,omitempty"` TypeEquals string `url:"decision_type,omitempty"`
Until cstime.DurationWithDays `url:"until,omitempty"` Until cstime.DurationWithDays `url:"until,omitempty"`
IncludeSimulated *bool `url:"simulated,omitempty"` IncludeSimulated *bool `url:"simulated,omitempty"`
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"` ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
@ -33,16 +33,16 @@ type AlertsListOpts struct {
} }
type AlertsDeleteOpts struct { type AlertsDeleteOpts struct {
ScopeEquals *string `url:"scope,omitempty"` ScopeEquals string `url:"scope,omitempty"`
ValueEquals *string `url:"value,omitempty"` ValueEquals string `url:"value,omitempty"`
ScenarioEquals *string `url:"scenario,omitempty"` ScenarioEquals string `url:"scenario,omitempty"`
IPEquals *string `url:"ip,omitempty"` IPEquals string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"` RangeEquals string `url:"range,omitempty"`
Since cstime.DurationWithDays `url:"since,omitempty"` Since cstime.DurationWithDays `url:"since,omitempty"`
Until cstime.DurationWithDays `url:"until,omitempty"` Until cstime.DurationWithDays `url:"until,omitempty"`
OriginEquals *string `url:"origin,omitempty"` OriginEquals string `url:"origin,omitempty"`
ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"` ActiveDecisionEquals *bool `url:"has_active_decision,omitempty"`
SourceEquals *string `url:"alert_source,omitempty"` SourceEquals string `url:"alert_source,omitempty"`
Contains *bool `url:"contains,omitempty"` Contains *bool `url:"contains,omitempty"`
Limit *int `url:"limit,omitempty"` Limit *int `url:"limit,omitempty"`
ListOpts ListOpts

View file

@ -18,6 +18,7 @@ import (
func TestAlertsListAsMachine(t *testing.T) { func TestAlertsListAsMachine(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -189,7 +190,7 @@ func TestAlertsListAsMachine(t *testing.T) {
assert.Equal(t, expected, *alerts) assert.Equal(t, expected, *alerts)
// this one doesn't // 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) alerts, resp, err = client.Alerts.List(ctx, filter)
require.NoError(t, err) require.NoError(t, err)
@ -199,6 +200,7 @@ func TestAlertsListAsMachine(t *testing.T) {
func TestAlertsGetAsMachine(t *testing.T) { func TestAlertsGetAsMachine(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -367,6 +369,7 @@ func TestAlertsGetAsMachine(t *testing.T) {
func TestAlertsCreateAsMachine(t *testing.T) { func TestAlertsCreateAsMachine(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -410,6 +413,7 @@ func TestAlertsCreateAsMachine(t *testing.T) {
func TestAlertsDeleteAsMachine(t *testing.T) { func TestAlertsDeleteAsMachine(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -442,7 +446,7 @@ func TestAlertsDeleteAsMachine(t *testing.T) {
defer teardown() 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) alerts, resp, err := client.Alerts.Delete(ctx, alert)
require.NoError(t, err) require.NoError(t, err)

View file

@ -10,11 +10,11 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/crowdsecurity/go-cs-lib/cstest" "github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/crowdsecurity/go-cs-lib/ptr"
) )
func TestApiAuth(t *testing.T) { func TestApiAuth(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.TraceLevel) log.SetLevel(log.TraceLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -40,7 +40,7 @@ func TestApiAuth(t *testing.T) {
defer teardown() defer teardown()
//ok no answer // ok no answer
auth := &APIKeyTransport{ auth := &APIKeyTransport{
APIKey: "ixu", APIKey: "ixu",
} }
@ -48,12 +48,12 @@ func TestApiAuth(t *testing.T) {
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client()) newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
require.NoError(t, err) 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) _, resp, err := newcli.Decisions.List(ctx, alert)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Response.StatusCode) assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
//ko bad token // ko bad token
auth = &APIKeyTransport{ auth = &APIKeyTransport{
APIKey: "bad", APIKey: "bad",
} }
@ -69,7 +69,7 @@ func TestApiAuth(t *testing.T) {
cstest.RequireErrorMessage(t, err, "API error: access forbidden") cstest.RequireErrorMessage(t, err, "API error: access forbidden")
//ko empty token // ko empty token
auth = &APIKeyTransport{} auth = &APIKeyTransport{}
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client()) newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())

View file

@ -20,12 +20,12 @@ import (
type DecisionsService service type DecisionsService service
type DecisionsListOpts struct { type DecisionsListOpts struct {
ScopeEquals *string `url:"scope,omitempty"` ScopeEquals string `url:"scope,omitempty"`
ValueEquals *string `url:"value,omitempty"` ValueEquals string `url:"value,omitempty"`
TypeEquals *string `url:"type,omitempty"` TypeEquals string `url:"type,omitempty"`
IPEquals *string `url:"ip,omitempty"` IPEquals string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"` RangeEquals string `url:"range,omitempty"`
Contains *bool `url:"contains,omitempty"` Contains *bool `url:"contains,omitempty"`
ListOpts ListOpts
} }
@ -60,15 +60,15 @@ func (o *DecisionsStreamOpts) addQueryParamsToURL(url string) (string, error) {
} }
type DecisionsDeleteOpts struct { type DecisionsDeleteOpts struct {
ScopeEquals *string `url:"scope,omitempty"` ScopeEquals string `url:"scope,omitempty"`
ValueEquals *string `url:"value,omitempty"` ValueEquals string `url:"value,omitempty"`
TypeEquals *string `url:"type,omitempty"` TypeEquals string `url:"type,omitempty"`
IPEquals *string `url:"ip,omitempty"` IPEquals string `url:"ip,omitempty"`
RangeEquals *string `url:"range,omitempty"` RangeEquals string `url:"range,omitempty"`
Contains *bool `url:"contains,omitempty"` Contains *bool `url:"contains,omitempty"`
OriginEquals *string `url:"origin,omitempty"` OriginEquals string `url:"origin,omitempty"`
// //
ScenarioEquals *string `url:"scenario,omitempty"` ScenarioEquals string `url:"scenario,omitempty"`
ListOpts ListOpts
} }

View file

@ -19,6 +19,7 @@ import (
func TestDecisionsList(t *testing.T) { func TestDecisionsList(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -64,15 +65,13 @@ func TestDecisionsList(t *testing.T) {
} }
// OK decisions // OK decisions
decisionsFilter := DecisionsListOpts{IPEquals: ptr.Of("1.2.3.4")} decisions, resp, err := newcli.Decisions.List(ctx, DecisionsListOpts{IPEquals: "1.2.3.4"})
decisions, resp, err := newcli.Decisions.List(ctx, decisionsFilter)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Response.StatusCode) assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
assert.Equal(t, *expected, *decisions) assert.Equal(t, *expected, *decisions)
// Empty return // Empty return
decisionsFilter = DecisionsListOpts{IPEquals: ptr.Of("1.2.3.5")} decisions, resp, err = newcli.Decisions.List(ctx, DecisionsListOpts{IPEquals: "1.2.3.5"})
decisions, resp, err = newcli.Decisions.List(ctx, decisionsFilter)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.Response.StatusCode) assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
assert.Empty(t, *decisions) assert.Empty(t, *decisions)
@ -80,6 +79,7 @@ func TestDecisionsList(t *testing.T) {
func TestDecisionsStream(t *testing.T) { func TestDecisionsStream(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setup() mux, urlx, teardown := setup()
@ -156,6 +156,7 @@ func TestDecisionsStream(t *testing.T) {
func TestDecisionsStreamV3Compatibility(t *testing.T) { func TestDecisionsStreamV3Compatibility(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setupWithPrefix("v3") mux, urlx, teardown := setupWithPrefix("v3")
@ -224,6 +225,7 @@ func TestDecisionsStreamV3Compatibility(t *testing.T) {
func TestDecisionsStreamV3(t *testing.T) { func TestDecisionsStreamV3(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setupWithPrefix("v3") mux, urlx, teardown := setupWithPrefix("v3")
@ -297,6 +299,7 @@ func TestDecisionsStreamV3(t *testing.T) {
func TestDecisionsFromBlocklist(t *testing.T) { func TestDecisionsFromBlocklist(t *testing.T) {
ctx := t.Context() ctx := t.Context()
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
mux, urlx, teardown := setupWithPrefix("v3") mux, urlx, teardown := setupWithPrefix("v3")
@ -429,10 +432,7 @@ func TestDeleteDecisions(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
filters := DecisionsDeleteOpts{IPEquals: new(string)} deleted, _, err := client.Decisions.Delete(ctx, DecisionsDeleteOpts{IPEquals: "1.2.3.4"})
*filters.IPEquals = "1.2.3.4"
deleted, _, err := client.Decisions.Delete(ctx, filters)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "1", deleted.NbDeleted) assert.Equal(t, "1", deleted.NbDeleted)
} }

View file

@ -661,6 +661,8 @@ func fillAlertsWithDecisions(alerts []*models.Alert, decisions []*models.Decisio
func (a *apic) PullTop(ctx context.Context, forcePull bool) error { func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
var err error var err error
hasPulledAllowlists := false
// A mutex with TryLock would be a bit simpler // 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 // But go does not guarantee that TryLock will be able to acquire the lock even if it is available
select { select {
@ -722,7 +724,7 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
// process deleted decisions // process deleted decisions
nbDeleted, err := a.HandleDeletedDecisionsV3(ctx, data.Deleted, deleteCounters) nbDeleted, err := a.HandleDeletedDecisionsV3(ctx, data.Deleted, deleteCounters)
if err != nil { if err != nil {
return err log.Errorf("could not delete decisions from CAPI: %s", err)
} }
log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted) log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)
@ -730,8 +732,9 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
// Update allowlists before processing decisions // Update allowlists before processing decisions
if data.Links != nil { if data.Links != nil {
if len(data.Links.Allowlists) > 0 { if len(data.Links.Allowlists) > 0 {
hasPulledAllowlists = true
if err := a.UpdateAllowlists(ctx, data.Links.Allowlists, forcePull); err != nil { 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)
} }
} }
} }
@ -748,7 +751,7 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
err = a.SaveAlerts(ctx, alertsFromCapi, addCounters, deleteCounters) err = a.SaveAlerts(ctx, alertsFromCapi, addCounters, deleteCounters)
if err != nil { if err != nil {
return fmt.Errorf("while saving alerts: %w", err) log.Errorf("could not save alert for CAPI pull: %s", err)
} }
} else { } else {
if a.pullCommunity { if a.pullCommunity {
@ -762,11 +765,21 @@ func (a *apic) PullTop(ctx context.Context, forcePull bool) error {
if data.Links != nil { if data.Links != nil {
if len(data.Links.Blocklists) > 0 { if len(data.Links.Blocklists) > 0 {
if err := a.UpdateBlocklists(ctx, data.Links.Blocklists, addCounters, forcePull); err != nil { 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 return nil
} }

View file

@ -264,6 +264,14 @@ func ManagementCmd(message *Message, p *Papi, sync bool) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to force pull operation: %w", err) 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": case "allowlist_unsubscribe":
data, err := json.Marshal(message.Data) data, err := json.Marshal(message.Data)

View file

@ -12,6 +12,8 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/database/ent" "github.com/crowdsecurity/crowdsec/pkg/database/ent"
"github.com/crowdsecurity/crowdsec/pkg/database/ent/allowlist" "github.com/crowdsecurity/crowdsec/pkg/database/ent/allowlist"
"github.com/crowdsecurity/crowdsec/pkg/database/ent/allowlistitem" "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/models"
"github.com/crowdsecurity/crowdsec/pkg/types" "github.com/crowdsecurity/crowdsec/pkg/types"
) )
@ -389,3 +391,96 @@ func (c *Client) GetAllowlistsContentForAPIC(ctx context.Context) ([]net.IP, []*
return ips, nets, nil 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
}

View file

@ -246,3 +246,58 @@ teardown() {
rune -0 jq 'del(.created_at) | del(.updated_at) | del(.items.[].created_at) | del(.items.[].expiration)' <(output) 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"}' 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 []
}

View file

@ -116,7 +116,7 @@ load_init_data() {
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")" dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
if [[ "${DB_BACKEND}" != "${dump_backend}" ]]; then 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 fi
remove_init_data remove_init_data

View file

@ -168,7 +168,7 @@ load_init_data() {
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")" dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
if [[ "${DB_BACKEND}" != "${dump_backend}" ]]; then 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 fi
remove_init_data remove_init_data

View file

@ -26,7 +26,7 @@ fi
dump_backend="$(cat "$LOCAL_INIT_DIR/.backend")" dump_backend="$(cat "$LOCAL_INIT_DIR/.backend")"
if [[ "$DB_BACKEND" != "$dump_backend" ]]; then 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 fi
if [[ $# -ge 1 ]]; then if [[ $# -ge 1 ]]; then