mirror of
https://github.com/crowdsecurity/crowdsec.git
synced 2025-05-10 20:05:55 +02:00
refact pkg/database: clean up code and error messages (#3263)
* refact pkg/database: extract alertfilter.go * refact pkg/database: extract function rollbackOnError(); dry error messages
This commit is contained in:
parent
ecf34c2fa1
commit
26c15a1267
8 changed files with 279 additions and 294 deletions
|
@ -242,7 +242,7 @@ func TestAlertListFilters(t *testing.T) {
|
|||
|
||||
w = lapi.RecordResponse(t, ctx, "GET", "/v1/alerts?ip=gruueq", emptyBody, "password")
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.JSONEq(t, `{"message":"unable to convert 'gruueq' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
||||
assert.JSONEq(t, `{"message":"invalid ip address 'gruueq'"}`, w.Body.String())
|
||||
|
||||
// test range (ok)
|
||||
|
||||
|
@ -261,7 +261,7 @@ func TestAlertListFilters(t *testing.T) {
|
|||
|
||||
w = lapi.RecordResponse(t, ctx, "GET", "/v1/alerts?range=ratata", emptyBody, "password")
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.JSONEq(t, `{"message":"unable to convert 'ratata' to int: invalid address: invalid ip address / range"}`, w.Body.String())
|
||||
assert.JSONEq(t, `{"message":"invalid ip address 'ratata'"}`, w.Body.String())
|
||||
|
||||
// test since (ok)
|
||||
|
||||
|
|
|
@ -21,18 +21,6 @@ func (c *Controller) HandleDBErrors(gctx *gin.Context, err error) {
|
|||
case errors.Is(err, database.HashError):
|
||||
gctx.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
|
||||
return
|
||||
case errors.Is(err, database.InsertFail):
|
||||
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
case errors.Is(err, database.QueryFail):
|
||||
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
case errors.Is(err, database.ParseTimeFail):
|
||||
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
case errors.Is(err, database.ParseDurationFail):
|
||||
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
default:
|
||||
gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
|
|
258
pkg/database/alertfilter.go
Normal file
258
pkg/database/alertfilter.go
Normal file
|
@ -0,0 +1,258 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/alert"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/predicate"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
)
|
||||
|
||||
func handleSimulatedFilter(filter map[string][]string, predicates *[]predicate.Alert) {
|
||||
/* the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */
|
||||
if v, ok := filter["simulated"]; ok && v[0] == "false" {
|
||||
*predicates = append(*predicates, alert.SimulatedEQ(false))
|
||||
}
|
||||
}
|
||||
|
||||
func handleOriginFilter(filter map[string][]string, predicates *[]predicate.Alert) {
|
||||
if _, ok := filter["origin"]; ok {
|
||||
filter["include_capi"] = []string{"true"}
|
||||
}
|
||||
}
|
||||
|
||||
func handleScopeFilter(scope string, predicates *[]predicate.Alert) {
|
||||
if strings.ToLower(scope) == "ip" {
|
||||
scope = types.Ip
|
||||
} else if strings.ToLower(scope) == "range" {
|
||||
scope = types.Range
|
||||
}
|
||||
|
||||
*predicates = append(*predicates, alert.SourceScopeEQ(scope))
|
||||
}
|
||||
|
||||
func handleTimeFilters(param, value string, predicates *[]predicate.Alert) error {
|
||||
duration, err := ParseDuration(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while parsing duration: %w", err)
|
||||
}
|
||||
|
||||
timePoint := time.Now().UTC().Add(-duration)
|
||||
if timePoint.IsZero() {
|
||||
return fmt.Errorf("empty time now() - %s", timePoint.String())
|
||||
}
|
||||
|
||||
switch param {
|
||||
case "since":
|
||||
*predicates = append(*predicates, alert.StartedAtGTE(timePoint))
|
||||
case "created_before":
|
||||
*predicates = append(*predicates, alert.CreatedAtLTE(timePoint))
|
||||
case "until":
|
||||
*predicates = append(*predicates, alert.StartedAtLTE(timePoint))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleIPv4Predicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) {
|
||||
if contains { // decision contains {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
alert.HasDecisionsWith(decision.StartIPLTE(start_ip)),
|
||||
alert.HasDecisionsWith(decision.EndIPGTE(end_ip)),
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
))
|
||||
} else { // decision is contained within {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
alert.HasDecisionsWith(decision.StartIPGTE(start_ip)),
|
||||
alert.HasDecisionsWith(decision.EndIPLTE(end_ip)),
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func handleIPv6Predicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) {
|
||||
if contains { // decision contains {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// matching addr size
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
alert.Or(
|
||||
// decision.start_ip < query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPLT(start_ip)),
|
||||
alert.And(
|
||||
// decision.start_ip == query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
|
||||
// decision.start_suffix <= query.start_suffix
|
||||
alert.HasDecisionsWith(decision.StartSuffixLTE(start_sfx)),
|
||||
),
|
||||
),
|
||||
alert.Or(
|
||||
// decision.end_ip > query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPGT(end_ip)),
|
||||
alert.And(
|
||||
// decision.end_ip == query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
|
||||
// decision.end_suffix >= query.end_suffix
|
||||
alert.HasDecisionsWith(decision.EndSuffixGTE(end_sfx)),
|
||||
),
|
||||
),
|
||||
))
|
||||
} else { // decision is contained within {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// matching addr size
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
alert.Or(
|
||||
// decision.start_ip > query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPGT(start_ip)),
|
||||
alert.And(
|
||||
// decision.start_ip == query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
|
||||
// decision.start_suffix >= query.start_suffix
|
||||
alert.HasDecisionsWith(decision.StartSuffixGTE(start_sfx)),
|
||||
),
|
||||
),
|
||||
alert.Or(
|
||||
// decision.end_ip < query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPLT(end_ip)),
|
||||
alert.And(
|
||||
// decision.end_ip == query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
|
||||
// decision.end_suffix <= query.end_suffix
|
||||
alert.HasDecisionsWith(decision.EndSuffixLTE(end_sfx)),
|
||||
),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func handleIPPredicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) error {
|
||||
if ip_sz == 4 {
|
||||
handleIPv4Predicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, predicates)
|
||||
} else if ip_sz == 16 {
|
||||
handleIPv6Predicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, predicates)
|
||||
} else if ip_sz != 0 {
|
||||
return errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleIncludeCapiFilter(value string, predicates *[]predicate.Alert) error {
|
||||
if value == "false" {
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// do not show alerts with active decisions having origin CAPI or lists
|
||||
alert.And(
|
||||
alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.CAPIOrigin))),
|
||||
alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.ListOrigin))),
|
||||
),
|
||||
alert.Not(
|
||||
alert.And(
|
||||
// do not show neither alerts with no decisions if the Source Scope is lists: or CAPI
|
||||
alert.Not(alert.HasDecisions()),
|
||||
alert.Or(
|
||||
alert.SourceScopeHasPrefix(types.ListOrigin+":"),
|
||||
alert.SourceScopeEQ(types.CommunityBlocklistPullSourceScope),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
} else if value != "true" {
|
||||
log.Errorf("invalid bool '%s' for include_capi", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AlertPredicatesFromFilter(filter map[string][]string) ([]predicate.Alert, error) {
|
||||
predicates := make([]predicate.Alert, 0)
|
||||
|
||||
var (
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
hasActiveDecision bool
|
||||
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)*/
|
||||
|
||||
handleSimulatedFilter(filter, &predicates)
|
||||
handleOriginFilter(filter, &predicates)
|
||||
|
||||
for param, value := range filter {
|
||||
switch param {
|
||||
case "contains":
|
||||
contains, err = strconv.ParseBool(value[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||
}
|
||||
case "scope":
|
||||
handleScopeFilter(value[0], &predicates)
|
||||
case "value":
|
||||
predicates = append(predicates, alert.SourceValueEQ(value[0]))
|
||||
case "scenario":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.ScenarioEQ(value[0])))
|
||||
case "ip", "range":
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "since", "created_before", "until":
|
||||
if err := handleTimeFilters(param, value[0], &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "decision_type":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.TypeEQ(value[0])))
|
||||
case "origin":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.OriginEQ(value[0])))
|
||||
case "include_capi": // allows to exclude one or more specific origins
|
||||
if err = handleIncludeCapiFilter(value[0], &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "has_active_decision":
|
||||
if hasActiveDecision, err = strconv.ParseBool(value[0]); err != nil {
|
||||
return nil, errors.Wrapf(ParseType, "'%s' is not a boolean: %s", value[0], err)
|
||||
}
|
||||
|
||||
if hasActiveDecision {
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.UntilGTE(time.Now().UTC())))
|
||||
} else {
|
||||
predicates = append(predicates, alert.Not(alert.HasDecisions()))
|
||||
}
|
||||
case "limit":
|
||||
continue
|
||||
case "sort":
|
||||
continue
|
||||
case "simulated":
|
||||
continue
|
||||
case "with_decisions":
|
||||
continue
|
||||
default:
|
||||
return nil, errors.Wrapf(InvalidFilter, "Filter parameter '%s' is unknown (=%s)", param, value[0])
|
||||
}
|
||||
}
|
||||
|
||||
if err := handleIPPredicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return predicates, nil
|
||||
}
|
||||
|
||||
func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]string) (*ent.AlertQuery, error) {
|
||||
preds, err := AlertPredicatesFromFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return alerts.Where(preds...), nil
|
||||
}
|
|
@ -20,7 +20,6 @@ import (
|
|||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/event"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/meta"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/database/ent/predicate"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
)
|
||||
|
@ -32,6 +31,14 @@ const (
|
|||
maxLockRetries = 10 // how many times to retry a bulk operation when sqlite3.ErrBusy is encountered
|
||||
)
|
||||
|
||||
func rollbackOnError(tx *ent.Tx, err error, msg string) error {
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
log.Errorf("rollback error: %v", rbErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %w", msg, err)
|
||||
}
|
||||
|
||||
// CreateOrUpdateAlert is specific to PAPI : It checks if alert already exists, otherwise inserts it
|
||||
// if alert already exists, it checks it associated decisions already exists
|
||||
// if some associated decisions are missing (ie. previous insert ended up in error) it inserts them
|
||||
|
@ -285,12 +292,7 @@ func (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models
|
|||
|
||||
duration, err := time.ParseDuration(*decisionItem.Duration)
|
||||
if err != nil {
|
||||
rollbackErr := txClient.Rollback()
|
||||
if rollbackErr != nil {
|
||||
log.Errorf("rollback error: %s", rollbackErr)
|
||||
}
|
||||
|
||||
return 0, 0, 0, errors.Wrapf(ParseDurationFail, "decision duration '%+v' : %s", *decisionItem.Duration, err)
|
||||
return 0,0,0, rollbackOnError(txClient, err, "parsing decision duration")
|
||||
}
|
||||
|
||||
if decisionItem.Scope == nil {
|
||||
|
@ -302,12 +304,7 @@ func (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models
|
|||
if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
|
||||
sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value)
|
||||
if err != nil {
|
||||
rollbackErr := txClient.Rollback()
|
||||
if rollbackErr != nil {
|
||||
log.Errorf("rollback error: %s", rollbackErr)
|
||||
}
|
||||
|
||||
return 0, 0, 0, errors.Wrapf(InvalidIPOrRange, "invalid addr/range %s : %s", *decisionItem.Value, err)
|
||||
return 0, 0, 0, rollbackOnError(txClient, err, "invalid ip addr/range")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -349,12 +346,7 @@ func (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models
|
|||
decision.ValueIn(deleteChunk...),
|
||||
)).Exec(ctx)
|
||||
if err != nil {
|
||||
rollbackErr := txClient.Rollback()
|
||||
if rollbackErr != nil {
|
||||
log.Errorf("rollback error: %s", rollbackErr)
|
||||
}
|
||||
|
||||
return 0, 0, 0, fmt.Errorf("while deleting older community blocklist decisions: %w", err)
|
||||
return 0, 0, 0, rollbackOnError(txClient, err, "deleting older community blocklist decisions")
|
||||
}
|
||||
|
||||
deleted += deletedDecisions
|
||||
|
@ -365,12 +357,7 @@ func (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models
|
|||
for _, builderChunk := range builderChunks {
|
||||
insertedDecisions, err := txClient.Decision.CreateBulk(builderChunk...).Save(ctx)
|
||||
if err != nil {
|
||||
rollbackErr := txClient.Rollback()
|
||||
if rollbackErr != nil {
|
||||
log.Errorf("rollback error: %s", rollbackErr)
|
||||
}
|
||||
|
||||
return 0, 0, 0, fmt.Errorf("while bulk creating decisions: %w", err)
|
||||
return 0, 0, 0, rollbackOnError(txClient, err, "bulk creating decisions")
|
||||
}
|
||||
|
||||
inserted += len(insertedDecisions)
|
||||
|
@ -380,12 +367,7 @@ func (c *Client) UpdateCommunityBlocklist(ctx context.Context, alertItem *models
|
|||
|
||||
err = txClient.Commit()
|
||||
if err != nil {
|
||||
rollbackErr := txClient.Rollback()
|
||||
if rollbackErr != nil {
|
||||
log.Errorf("rollback error: %s", rollbackErr)
|
||||
}
|
||||
|
||||
return 0, 0, 0, fmt.Errorf("error committing transaction: %w", err)
|
||||
return 0, 0, 0, rollbackOnError(txClient, err, "error committing transaction")
|
||||
}
|
||||
|
||||
return alertRef.ID, inserted, deleted, nil
|
||||
|
@ -727,247 +709,6 @@ func (c *Client) CreateAlert(ctx context.Context, machineID string, alertList []
|
|||
return alertIDs, nil
|
||||
}
|
||||
|
||||
func handleSimulatedFilter(filter map[string][]string, predicates *[]predicate.Alert) {
|
||||
/* the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */
|
||||
if v, ok := filter["simulated"]; ok && v[0] == "false" {
|
||||
*predicates = append(*predicates, alert.SimulatedEQ(false))
|
||||
}
|
||||
}
|
||||
|
||||
func handleOriginFilter(filter map[string][]string, predicates *[]predicate.Alert) {
|
||||
if _, ok := filter["origin"]; ok {
|
||||
filter["include_capi"] = []string{"true"}
|
||||
}
|
||||
}
|
||||
|
||||
func handleScopeFilter(scope string, predicates *[]predicate.Alert) {
|
||||
if strings.ToLower(scope) == "ip" {
|
||||
scope = types.Ip
|
||||
} else if strings.ToLower(scope) == "range" {
|
||||
scope = types.Range
|
||||
}
|
||||
|
||||
*predicates = append(*predicates, alert.SourceScopeEQ(scope))
|
||||
}
|
||||
|
||||
func handleTimeFilters(param, value string, predicates *[]predicate.Alert) error {
|
||||
duration, err := ParseDuration(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while parsing duration: %w", err)
|
||||
}
|
||||
|
||||
timePoint := time.Now().UTC().Add(-duration)
|
||||
if timePoint.IsZero() {
|
||||
return fmt.Errorf("empty time now() - %s", timePoint.String())
|
||||
}
|
||||
|
||||
switch param {
|
||||
case "since":
|
||||
*predicates = append(*predicates, alert.StartedAtGTE(timePoint))
|
||||
case "created_before":
|
||||
*predicates = append(*predicates, alert.CreatedAtLTE(timePoint))
|
||||
case "until":
|
||||
*predicates = append(*predicates, alert.StartedAtLTE(timePoint))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleIPv4Predicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) {
|
||||
if contains { // decision contains {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
alert.HasDecisionsWith(decision.StartIPLTE(start_ip)),
|
||||
alert.HasDecisionsWith(decision.EndIPGTE(end_ip)),
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
))
|
||||
} else { // decision is contained within {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
alert.HasDecisionsWith(decision.StartIPGTE(start_ip)),
|
||||
alert.HasDecisionsWith(decision.EndIPLTE(end_ip)),
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func handleIPv6Predicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) {
|
||||
if contains { // decision contains {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// matching addr size
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
alert.Or(
|
||||
// decision.start_ip < query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPLT(start_ip)),
|
||||
alert.And(
|
||||
// decision.start_ip == query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
|
||||
// decision.start_suffix <= query.start_suffix
|
||||
alert.HasDecisionsWith(decision.StartSuffixLTE(start_sfx)),
|
||||
),
|
||||
),
|
||||
alert.Or(
|
||||
// decision.end_ip > query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPGT(end_ip)),
|
||||
alert.And(
|
||||
// decision.end_ip == query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
|
||||
// decision.end_suffix >= query.end_suffix
|
||||
alert.HasDecisionsWith(decision.EndSuffixGTE(end_sfx)),
|
||||
),
|
||||
),
|
||||
))
|
||||
} else { // decision is contained within {start_ip,end_ip}
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// matching addr size
|
||||
alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))),
|
||||
alert.Or(
|
||||
// decision.start_ip > query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPGT(start_ip)),
|
||||
alert.And(
|
||||
// decision.start_ip == query.start_ip
|
||||
alert.HasDecisionsWith(decision.StartIPEQ(start_ip)),
|
||||
// decision.start_suffix >= query.start_suffix
|
||||
alert.HasDecisionsWith(decision.StartSuffixGTE(start_sfx)),
|
||||
),
|
||||
),
|
||||
alert.Or(
|
||||
// decision.end_ip < query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPLT(end_ip)),
|
||||
alert.And(
|
||||
// decision.end_ip == query.end_ip
|
||||
alert.HasDecisionsWith(decision.EndIPEQ(end_ip)),
|
||||
// decision.end_suffix <= query.end_suffix
|
||||
alert.HasDecisionsWith(decision.EndSuffixLTE(end_sfx)),
|
||||
),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func handleIPPredicates(ip_sz int, contains bool, start_ip, start_sfx, end_ip, end_sfx int64, predicates *[]predicate.Alert) error {
|
||||
if ip_sz == 4 {
|
||||
handleIPv4Predicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, predicates)
|
||||
} else if ip_sz == 16 {
|
||||
handleIPv6Predicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, predicates)
|
||||
} else if ip_sz != 0 {
|
||||
return errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleIncludeCapiFilter(value string, predicates *[]predicate.Alert) error {
|
||||
if value == "false" {
|
||||
*predicates = append(*predicates, alert.And(
|
||||
// do not show alerts with active decisions having origin CAPI or lists
|
||||
alert.And(
|
||||
alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.CAPIOrigin))),
|
||||
alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.ListOrigin))),
|
||||
),
|
||||
alert.Not(
|
||||
alert.And(
|
||||
// do not show neither alerts with no decisions if the Source Scope is lists: or CAPI
|
||||
alert.Not(alert.HasDecisions()),
|
||||
alert.Or(
|
||||
alert.SourceScopeHasPrefix(types.ListOrigin+":"),
|
||||
alert.SourceScopeEQ(types.CommunityBlocklistPullSourceScope),
|
||||
),
|
||||
),
|
||||
),
|
||||
))
|
||||
} else if value != "true" {
|
||||
log.Errorf("invalid bool '%s' for include_capi", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AlertPredicatesFromFilter(filter map[string][]string) ([]predicate.Alert, error) {
|
||||
predicates := make([]predicate.Alert, 0)
|
||||
|
||||
var (
|
||||
err error
|
||||
start_ip, start_sfx, end_ip, end_sfx int64
|
||||
hasActiveDecision bool
|
||||
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)*/
|
||||
|
||||
handleSimulatedFilter(filter, &predicates)
|
||||
handleOriginFilter(filter, &predicates)
|
||||
|
||||
for param, value := range filter {
|
||||
switch param {
|
||||
case "contains":
|
||||
contains, err = strconv.ParseBool(value[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err)
|
||||
}
|
||||
case "scope":
|
||||
handleScopeFilter(value[0], &predicates)
|
||||
case "value":
|
||||
predicates = append(predicates, alert.SourceValueEQ(value[0]))
|
||||
case "scenario":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.ScenarioEQ(value[0])))
|
||||
case "ip", "range":
|
||||
ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err)
|
||||
}
|
||||
case "since", "created_before", "until":
|
||||
if err := handleTimeFilters(param, value[0], &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "decision_type":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.TypeEQ(value[0])))
|
||||
case "origin":
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.OriginEQ(value[0])))
|
||||
case "include_capi": // allows to exclude one or more specific origins
|
||||
if err = handleIncludeCapiFilter(value[0], &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "has_active_decision":
|
||||
if hasActiveDecision, err = strconv.ParseBool(value[0]); err != nil {
|
||||
return nil, errors.Wrapf(ParseType, "'%s' is not a boolean: %s", value[0], err)
|
||||
}
|
||||
|
||||
if hasActiveDecision {
|
||||
predicates = append(predicates, alert.HasDecisionsWith(decision.UntilGTE(time.Now().UTC())))
|
||||
} else {
|
||||
predicates = append(predicates, alert.Not(alert.HasDecisions()))
|
||||
}
|
||||
case "limit":
|
||||
continue
|
||||
case "sort":
|
||||
continue
|
||||
case "simulated":
|
||||
continue
|
||||
case "with_decisions":
|
||||
continue
|
||||
default:
|
||||
return nil, errors.Wrapf(InvalidFilter, "Filter parameter '%s' is unknown (=%s)", param, value[0])
|
||||
}
|
||||
}
|
||||
|
||||
if err := handleIPPredicates(ip_sz, contains, start_ip, start_sfx, end_ip, end_sfx, &predicates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return predicates, nil
|
||||
}
|
||||
|
||||
func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]string) (*ent.AlertQuery, error) {
|
||||
preds, err := AlertPredicatesFromFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return alerts.Where(preds...), nil
|
||||
}
|
||||
|
||||
func (c *Client) AlertsCountPerScenario(ctx context.Context, filters map[string][]string) (map[string]int, error) {
|
||||
var res []struct {
|
||||
Scenario string
|
||||
|
|
|
@ -14,7 +14,6 @@ var (
|
|||
ParseTimeFail = errors.New("unable to parse time")
|
||||
ParseDurationFail = errors.New("unable to parse duration")
|
||||
MarshalFail = errors.New("unable to serialize")
|
||||
UnmarshalFail = errors.New("unable to parse")
|
||||
BulkError = errors.New("unable to insert bulk")
|
||||
ParseType = errors.New("unable to parse type")
|
||||
InvalidIPOrRange = errors.New("invalid ip address / range")
|
||||
|
|
|
@ -2,7 +2,6 @@ package types
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
|
@ -39,7 +38,7 @@ func Addr2Ints(anyIP string) (int, int64, int64, int64, int64, error) {
|
|||
if strings.Contains(anyIP, "/") {
|
||||
_, net, err := net.ParseCIDR(anyIP)
|
||||
if err != nil {
|
||||
return -1, 0, 0, 0, 0, fmt.Errorf("while parsing range %s: %w", anyIP, err)
|
||||
return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip range '%s': %w", anyIP, err)
|
||||
}
|
||||
|
||||
return Range2Ints(*net)
|
||||
|
@ -47,12 +46,12 @@ func Addr2Ints(anyIP string) (int, int64, int64, int64, int64, error) {
|
|||
|
||||
ip := net.ParseIP(anyIP)
|
||||
if ip == nil {
|
||||
return -1, 0, 0, 0, 0, errors.New("invalid address")
|
||||
return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip address '%s'", anyIP)
|
||||
}
|
||||
|
||||
sz, start, end, err := IP2Ints(ip)
|
||||
if err != nil {
|
||||
return -1, 0, 0, 0, 0, fmt.Errorf("while parsing ip %s: %w", anyIP, err)
|
||||
return -1, 0, 0, 0, 0, fmt.Errorf("invalid ip address '%s': %w", anyIP, err)
|
||||
}
|
||||
|
||||
return sz, start, end, start, end, nil
|
||||
|
|
|
@ -180,7 +180,7 @@ func TestAdd2Int(t *testing.T) {
|
|||
},
|
||||
{
|
||||
in_addr: "xxx2",
|
||||
exp_error: "invalid address",
|
||||
exp_error: "invalid ip address 'xxx2'",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ teardown() {
|
|||
EOT
|
||||
assert_stderr --partial 'Parsing values'
|
||||
assert_stderr --partial 'Imported 1 decisions'
|
||||
assert_file_contains "$LOGFILE" "invalid addr/range 'whatever': invalid address"
|
||||
assert_file_contains "$LOGFILE" "invalid addr/range 'whatever': invalid ip address 'whatever'"
|
||||
|
||||
rune -0 cscli decisions list -a -o json
|
||||
assert_json '[]'
|
||||
|
@ -182,7 +182,7 @@ teardown() {
|
|||
EOT
|
||||
assert_stderr --partial 'Parsing values'
|
||||
assert_stderr --partial 'Imported 3 decisions'
|
||||
assert_file_contains "$LOGFILE" "invalid addr/range 'bad-apple': invalid address"
|
||||
assert_file_contains "$LOGFILE" "invalid addr/range 'bad-apple': invalid ip address 'bad-apple'"
|
||||
|
||||
rune -0 cscli decisions list -a -o json
|
||||
rune -0 jq -r '.[0].decisions | length' <(output)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue