Skip to content
Open
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 cmd/crowdsec-cli/cliitem/hubappsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ func NewAppsecRule(cfg csconfig.Getter) *cliItem {
for _, ruleType := range appsec_rule.SupportedTypes() {
fmt.Fprintf(os.Stdout, "\n%s format:\n", cases.Title(language.Und, cases.NoLower).String(ruleType))

for _, rule := range appsecRule.Rules {
convertedRule, _, err := rule.Convert(ruleType, appsecRule.Name, appsecRule.Description)
for i, rule := range appsecRule.Rules {
convertedRule, _, err := rule.Convert(ruleType, appsecRule.Name, appsecRule.Description, i)
if err != nil {
return fmt.Errorf("unable to convert rule %s: %w", rule.Name, err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/acquisition/modules/appsec/appsec_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func TestAppsecPreEvalHooks(t *testing.T) {
},
},
pre_eval: []appsec.Hook{
{Apply: []string{"RemoveInBandRuleByID(1516470898)"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
{Apply: []string{"RemoveInBandRuleByID(407789510)"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
},
input_request: appsec.ParsedRequest{
RemoteAddr: "1.2.3.4",
Expand Down Expand Up @@ -752,7 +752,7 @@ func TestAppsecPreEvalHooks(t *testing.T) {
},
},
pre_eval: []appsec.Hook{
{Apply: []string{"SetRemediationByID(1516470898, 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
{Apply: []string{"SetRemediationByID(407789510, 'foobar')"}}, //rule ID is generated at runtime. If you change rule, it will break the test (:
},
input_request: appsec.ParsedRequest{
RemoteAddr: "1.2.3.4",
Expand Down
12 changes: 9 additions & 3 deletions pkg/acquisition/modules/appsec/appsec_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"maps"
"os"
"regexp"
"slices"
"strings"
"time"
Expand Down Expand Up @@ -35,21 +36,26 @@ type AppsecRunner struct {
appsecAllowlistsClient *allowlists.AppsecAllowlist
}

// ruleIDDirective matches the generated id so dedup can ignore it: rules that
// differ only by id (same content, different position) are duplicates.
var ruleIDDirective = regexp.MustCompile(`"id:\d+,phase:`)

func (*AppsecRunner) MergeDedupRules(collections []appsec.AppsecCollection, logger *log.Entry) string {
var rulesArr []string
dedupRules := make(map[string]struct{})
discarded := 0

for _, collection := range collections {
// Dedup *our* rules
// Dedup *our* rules, ignoring the generated id.
for _, rule := range collection.Rules {
if _, ok := dedupRules[rule]; ok {
key := ruleIDDirective.ReplaceAllString(rule, `"id:,phase:`)
if _, ok := dedupRules[key]; ok {
discarded++
logger.Debugf("Discarding duplicate rule : %s", rule)
continue
}
rulesArr = append(rulesArr, rule)
dedupRules[rule] = struct{}{}
dedupRules[key] = struct{}{}
}
// Don't mess up with native modsec rules
rulesArr = append(rulesArr, collection.NativeRules...)
Expand Down
4 changes: 2 additions & 2 deletions pkg/acquisition/modules/appsec/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func testAppSecEngine(t *testing.T, test appsecRuleTest) {

//build rules
for ridx, rule := range test.inband_rules {
strRule, _, err := rule.Convert(appsec_rule.ModsecurityRuleType, rule.Name, "test-rule")
strRule, _, err := rule.Convert(appsec_rule.ModsecurityRuleType, rule.Name, "test-rule", ridx)
if err != nil {
t.Fatalf("failed compilation of rule %d/%d of %s : %s", ridx, len(test.inband_rules), test.name, err)
}
Expand All @@ -106,7 +106,7 @@ func testAppSecEngine(t *testing.T, test appsecRuleTest) {
nativeInbandRules = append(nativeInbandRules, test.inband_native_rules...)
nativeOutofbandRules = append(nativeOutofbandRules, test.outofband_native_rules...)
for ridx, rule := range test.outofband_rules {
strRule, _, err := rule.Convert(appsec_rule.ModsecurityRuleType, rule.Name, "test-rule")
strRule, _, err := rule.Convert(appsec_rule.ModsecurityRuleType, rule.Name, "test-rule", ridx)
if err != nil {
t.Fatalf("failed compilation of rule %d/%d of %s : %s", ridx, len(test.outofband_rules), test.name, err)
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/appsec/appsec_rule/appsec_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ type CustomRule struct {
BodyType string `yaml:"body_type,omitempty"`
}

func (v *CustomRule) Convert(ruleType string, appsecRuleName string, appsecRuleDescription string) (string, []uint32, error) {
// Convert renders the rule; ruleIndex is its position in the collection, used
// to keep ids unique across rules that share identical leaves.
func (v *CustomRule) Convert(ruleType string, appsecRuleName string, appsecRuleDescription string, ruleIndex int) (string, []uint32, error) {
if v.Zones == nil && v.And == nil && v.Or == nil {
return "", nil, errors.New("no zones defined")
}
Expand All @@ -62,7 +64,7 @@ func (v *CustomRule) Convert(ruleType string, appsecRuleName string, appsecRuleD
switch ruleType {
case ModsecurityRuleType:
r := ModsecurityRule{}
return r.Build(v, appsecRuleName, appsecRuleDescription)
return r.Build(v, appsecRuleName, appsecRuleDescription, ruleIndex)
default:
return "", nil, fmt.Errorf("unknown rule format '%s'", ruleType)
}
Expand Down
Loading
Loading