Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refact pkg/database: clean up code and error messages #3263

Merged
merged 3 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/apiserver/alerts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down
12 changes: 0 additions & 12 deletions pkg/apiserver/controllers/v1/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
258 changes: 258 additions & 0 deletions pkg/database/alertfilter.go
Original file line number Diff line number Diff line change
@@ -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"}
}

Check warning on line 29 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L28-L29

Added lines #L28 - L29 were not covered by tests
}

func handleScopeFilter(scope string, predicates *[]predicate.Alert) {
if strings.ToLower(scope) == "ip" {
scope = types.Ip
} else if strings.ToLower(scope) == "range" {
scope = types.Range
}

Check warning on line 37 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L36-L37

Added lines #L36 - L37 were not covered by tests

*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())
}

Check warning on line 51 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L50-L51

Added lines #L50 - L51 were not covered by tests

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)
}

Check warning on line 142 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L141-L142

Added lines #L141 - L142 were not covered by tests

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)
}

Check warning on line 168 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L167-L168

Added lines #L167 - L168 were not covered by tests

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)
}

Check warning on line 197 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L196-L197

Added lines #L196 - L197 were not covered by tests
case "scope":
handleScopeFilter(value[0], &predicates)
case "value":
predicates = append(predicates, alert.SourceValueEQ(value[0]))

Check warning on line 201 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L200-L201

Added lines #L200 - L201 were not covered by tests
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])))

Check warning on line 216 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L215-L216

Added lines #L215 - L216 were not covered by tests
case "include_capi": // allows to exclude one or more specific origins
if err = handleIncludeCapiFilter(value[0], &predicates); err != nil {
return nil, err
}

Check warning on line 220 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L219-L220

Added lines #L219 - L220 were not covered by tests
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
}

Check warning on line 246 in pkg/database/alertfilter.go

View check run for this annotation

Codecov / codecov/patch

pkg/database/alertfilter.go#L245-L246

Added lines #L245 - L246 were not covered by tests

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
}
Loading
Loading