Compare commits
33 commits
master
...
coraza_met
Author | SHA1 | Date | |
---|---|---|---|
|
4c0c5e3e9b | ||
|
0a00b5ba5e | ||
|
7c5a00b23e | ||
|
edc28142ff | ||
|
3fe6e3be14 | ||
|
877d4fc32d | ||
|
07b60233db | ||
|
9180ac7be9 | ||
|
805752dc62 | ||
|
40f65de7b9 | ||
|
fa172bed56 | ||
|
a2e6359880 | ||
|
c46e2ccdad | ||
|
61e1cc29d5 | ||
|
415e2dc68d | ||
|
739d086325 | ||
|
30455a8eb6 | ||
|
d123254949 | ||
|
ee8b31348b | ||
|
4a7e26af02 | ||
|
a7d80aacd6 | ||
|
7078d79ce4 | ||
|
65884fb4be | ||
|
44a5c81199 | ||
|
abaa6a5c56 | ||
|
6d3b2b354b | ||
|
6ac0a9ef9d | ||
|
cacdcd75b6 | ||
|
53c73a5e05 | ||
|
1e94b24a74 | ||
|
d335e74c81 | ||
|
1973aa1a56 | ||
|
1d9891a244 |
32 changed files with 1536 additions and 55 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -14,3 +14,7 @@
|
|||
[submodule "tests/lib/bats-mock"]
|
||||
path = test/lib/bats-mock
|
||||
url = https://github.com/crowdsecurity/bats-mock.git
|
||||
[submodule "coraza"]
|
||||
path = coraza
|
||||
url = http://github.com/buixor/coraza
|
||||
branch = testing
|
||||
|
|
|
@ -256,6 +256,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
|
|||
rootCmd.AddCommand(NewHubTestCmd())
|
||||
rootCmd.AddCommand(NewNotificationsCmd())
|
||||
rootCmd.AddCommand(NewSupportCmd())
|
||||
rootCmd.AddCommand(NewWafRulesCmd())
|
||||
|
||||
if fflag.CscliSetup.IsEnabled() {
|
||||
rootCmd.AddCommand(NewSetupCmd())
|
||||
|
|
|
@ -131,6 +131,8 @@ func compInstalledItems(itemType string, args []string, toComplete string) ([]st
|
|||
items, err = cwhub.GetInstalledPostOverflowsAsString()
|
||||
case cwhub.COLLECTIONS:
|
||||
items, err = cwhub.GetInstalledCollectionsAsString()
|
||||
case cwhub.WAF_RULES:
|
||||
items, err = cwhub.GetInstalledWafRulesAsString()
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
@ -324,6 +326,8 @@ func ShowMetrics(hubItem *cwhub.Item) {
|
|||
}
|
||||
ShowMetrics(hubItem)
|
||||
}
|
||||
case cwhub.WAF_RULES:
|
||||
log.Fatalf("FIXME: not implemented yet")
|
||||
default:
|
||||
log.Errorf("item of type '%s' is unknown", hubItem.Type)
|
||||
}
|
||||
|
|
196
cmd/crowdsec-cli/waf_rules.go
Normal file
196
cmd/crowdsec-cli/waf_rules.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||
)
|
||||
|
||||
func NewWafRulesCmd() *cobra.Command {
|
||||
var cmdWafRules = &cobra.Command{
|
||||
Use: "waf-rules [action] [config]",
|
||||
Short: "Install/Remove/Upgrade/Inspect waf-rule(s) from hub",
|
||||
Example: `cscli waf-rules install crowdsecurity/core-rule-set
|
||||
cscli waf-rules inspect crowdsecurity/core-rule-set
|
||||
cscli waf-rules upgrade crowdsecurity/core-rule-set
|
||||
cscli waf-rules list
|
||||
cscli waf-rules remove crowdsecurity/core-rule-set
|
||||
`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Aliases: []string{"waf-rule"},
|
||||
DisableAutoGenTag: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := csConfig.LoadHub(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if csConfig.Hub == nil {
|
||||
return fmt.Errorf("you must configure cli before interacting with hub")
|
||||
}
|
||||
|
||||
if err := cwhub.SetHubBranch(); err != nil {
|
||||
return fmt.Errorf("error while setting hub branch: %s", err)
|
||||
}
|
||||
|
||||
if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
|
||||
log.Info("Run 'sudo cscli hub update' to get the hub index")
|
||||
log.Fatalf("Failed to get Hub index : %v", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
if cmd.Name() == "inspect" || cmd.Name() == "list" {
|
||||
return
|
||||
}
|
||||
log.Infof(ReloadMessage())
|
||||
},
|
||||
}
|
||||
|
||||
cmdWafRules.AddCommand(NewWafRulesInstallCmd())
|
||||
cmdWafRules.AddCommand(NewWafRulesRemoveCmd())
|
||||
cmdWafRules.AddCommand(NewWafRulesUpgradeCmd())
|
||||
cmdWafRules.AddCommand(NewWafRulesInspectCmd())
|
||||
cmdWafRules.AddCommand(NewWafRulesListCmd())
|
||||
|
||||
return cmdWafRules
|
||||
}
|
||||
|
||||
func NewWafRulesInstallCmd() *cobra.Command {
|
||||
var ignoreError bool
|
||||
|
||||
var cmdWafRulesInstall = &cobra.Command{
|
||||
Use: "install [config]",
|
||||
Short: "Install given waf-rule(s)",
|
||||
Long: `Fetch and install given waf-rule(s) from hub`,
|
||||
Example: `cscli waf-rules install crowdsec/xxx crowdsec/xyz`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
DisableAutoGenTag: true,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return compAllItems(cwhub.WAF_RULES, args, toComplete)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
for _, name := range args {
|
||||
t := cwhub.GetItem(cwhub.WAF_RULES, name)
|
||||
if t == nil {
|
||||
nearestItem, score := GetDistance(cwhub.WAF_RULES, name)
|
||||
Suggest(cwhub.WAF_RULES, name, nearestItem.Name, score, ignoreError)
|
||||
continue
|
||||
}
|
||||
if err := cwhub.InstallItem(csConfig, name, cwhub.WAF_RULES, forceAction, downloadOnly); err != nil {
|
||||
if ignoreError {
|
||||
log.Errorf("Error while installing '%s': %s", name, err)
|
||||
} else {
|
||||
log.Fatalf("Error while installing '%s': %s", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdWafRulesInstall.PersistentFlags().BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable")
|
||||
cmdWafRulesInstall.PersistentFlags().BoolVar(&forceAction, "force", false, "Force install : Overwrite tainted and outdated files")
|
||||
cmdWafRulesInstall.PersistentFlags().BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple waf rules")
|
||||
|
||||
return cmdWafRulesInstall
|
||||
}
|
||||
|
||||
func NewWafRulesRemoveCmd() *cobra.Command {
|
||||
var cmdWafRulesRemove = &cobra.Command{
|
||||
Use: "remove [config]",
|
||||
Short: "Remove given waf-rule(s)",
|
||||
Long: `Remove given waf-rule(s) from hub`,
|
||||
Aliases: []string{"delete"},
|
||||
Example: `cscli waf-rules remove crowdsec/xxx crowdsec/xyz`,
|
||||
DisableAutoGenTag: true,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if all {
|
||||
cwhub.RemoveMany(csConfig, cwhub.WAF_RULES, "", all, purge, forceAction)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
log.Fatalf("Specify at least one waf rule to remove or '--all' flag.")
|
||||
}
|
||||
|
||||
for _, name := range args {
|
||||
cwhub.RemoveMany(csConfig, cwhub.WAF_RULES, name, all, purge, forceAction)
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdWafRulesRemove.PersistentFlags().BoolVar(&purge, "purge", false, "Delete source file too")
|
||||
cmdWafRulesRemove.PersistentFlags().BoolVar(&forceAction, "force", false, "Force remove : Remove tainted and outdated files")
|
||||
cmdWafRulesRemove.PersistentFlags().BoolVar(&all, "all", false, "Delete all the waf rules")
|
||||
|
||||
return cmdWafRulesRemove
|
||||
}
|
||||
|
||||
func NewWafRulesUpgradeCmd() *cobra.Command {
|
||||
var cmdWafRulesUpgrade = &cobra.Command{
|
||||
Use: "upgrade [config]",
|
||||
Short: "Upgrade given waf-rule(s)",
|
||||
Long: `Fetch and upgrade given waf-rule(s) from hub`,
|
||||
Example: `cscli waf-rules upgrade crowdsec/xxx crowdsec/xyz`,
|
||||
DisableAutoGenTag: true,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if all {
|
||||
cwhub.UpgradeConfig(csConfig, cwhub.WAF_RULES, "", forceAction)
|
||||
} else {
|
||||
if len(args) == 0 {
|
||||
log.Fatalf("no target waf rule to upgrade")
|
||||
}
|
||||
for _, name := range args {
|
||||
cwhub.UpgradeConfig(csConfig, cwhub.WAF_RULES, name, forceAction)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
cmdWafRulesUpgrade.PersistentFlags().BoolVar(&all, "all", false, "Upgrade all the waf rules")
|
||||
cmdWafRulesUpgrade.PersistentFlags().BoolVar(&forceAction, "force", false, "Force upgrade : Overwrite tainted and outdated files")
|
||||
|
||||
return cmdWafRulesUpgrade
|
||||
}
|
||||
|
||||
func NewWafRulesInspectCmd() *cobra.Command {
|
||||
var cmdWafRulesInspect = &cobra.Command{
|
||||
Use: "inspect [name]",
|
||||
Short: "Inspect given waf rule",
|
||||
Long: `Inspect given waf rule`,
|
||||
Example: `cscli waf-rules inspect crowdsec/xxx`,
|
||||
DisableAutoGenTag: true,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
InspectItem(args[0], cwhub.WAF_RULES)
|
||||
},
|
||||
}
|
||||
cmdWafRulesInspect.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url")
|
||||
|
||||
return cmdWafRulesInspect
|
||||
}
|
||||
|
||||
func NewWafRulesListCmd() *cobra.Command {
|
||||
var cmdWafRulesList = &cobra.Command{
|
||||
Use: "list [name]",
|
||||
Short: "List all waf rules or given one",
|
||||
Long: `List all waf rules or given one`,
|
||||
Example: `cscli waf-rules list
|
||||
cscli waf-rules list crowdsecurity/xxx`,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListItems(color.Output, []string{cwhub.WAF_RULES}, args, false, true, all)
|
||||
},
|
||||
}
|
||||
cmdWafRulesList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
|
||||
|
||||
return cmdWafRulesList
|
||||
}
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/crowdsecurity/go-cs-lib/pkg/trace"
|
||||
"github.com/crowdsecurity/go-cs-lib/pkg/version"
|
||||
|
||||
waf "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/waf"
|
||||
v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers/v1"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cache"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
|
@ -169,7 +170,8 @@ func registerPrometheus(config *csconfig.PrometheusCfg) {
|
|||
leaky.BucketsUnderflow, leaky.BucketsCanceled, leaky.BucketsInstantiation, leaky.BucketsOverflow,
|
||||
v1.LapiRouteHits,
|
||||
leaky.BucketsCurrentCount,
|
||||
cache.CacheMetrics, exprhelpers.RegexpCacheMetrics)
|
||||
cache.CacheMetrics, exprhelpers.RegexpCacheMetrics,
|
||||
waf.WafParsingHistogram, waf.WafReqCounter, waf.WafRuleHits)
|
||||
} else {
|
||||
log.Infof("Loading prometheus collectors")
|
||||
prometheus.MustRegister(globalParserHits, globalParserHitsOk, globalParserHitsKo,
|
||||
|
@ -178,7 +180,8 @@ func registerPrometheus(config *csconfig.PrometheusCfg) {
|
|||
v1.LapiRouteHits, v1.LapiMachineHits, v1.LapiBouncerHits, v1.LapiNilDecisions, v1.LapiNonNilDecisions, v1.LapiResponseTime,
|
||||
leaky.BucketsPour, leaky.BucketsUnderflow, leaky.BucketsCanceled, leaky.BucketsInstantiation, leaky.BucketsOverflow, leaky.BucketsCurrentCount,
|
||||
globalActiveDecisions, globalAlerts,
|
||||
cache.CacheMetrics, exprhelpers.RegexpCacheMetrics)
|
||||
cache.CacheMetrics, exprhelpers.RegexpCacheMetrics,
|
||||
waf.WafParsingHistogram, waf.WafReqCounter, waf.WafRuleHits)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
1
coraza
Submodule
1
coraza
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 063bbc322dc2ad389c354e7703638104412e197a
|
18
go.mod
18
go.mod
|
@ -52,7 +52,7 @@ require (
|
|||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.3
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/mod v0.6.0
|
||||
golang.org/x/mod v0.8.0
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
|
@ -70,6 +70,7 @@ require (
|
|||
github.com/blackfireio/osinfo v1.0.3
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/cespare/xxhash/v2 v2.1.2
|
||||
github.com/corazawaf/coraza/v3 v3.0.0-00010101000000-000000000000
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/crowdsecurity/go-cs-lib v0.0.0-20230531105801-4c1535c2b3bd
|
||||
github.com/goccy/go-yaml v1.9.7
|
||||
|
@ -102,6 +103,7 @@ require (
|
|||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/corazawaf/libinjection-go v0.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
|
@ -160,6 +162,7 @@ require (
|
|||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
|
@ -172,7 +175,9 @@ require (
|
|||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tetratelabs/wazero v1.0.0-rc.2 // indirect
|
||||
github.com/tidwall/gjson v1.13.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||
|
@ -180,10 +185,10 @@ require (
|
|||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.9.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
|
@ -193,8 +198,11 @@ require (
|
|||
k8s.io/apimachinery v0.25.2 // indirect
|
||||
k8s.io/klog/v2 v2.70.1 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
rsc.io/binaryregexp v0.2.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
)
|
||||
|
||||
replace golang.org/x/time/rate => github.com/crowdsecurity/crowdsec/pkg/time/rate v0.0.0
|
||||
|
||||
replace github.com/corazawaf/coraza/v3 => ./coraza
|
||||
|
|
31
go.sum
31
go.sum
|
@ -151,6 +151,8 @@ github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMe
|
|||
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
|
||||
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
|
||||
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
|
||||
github.com/corazawaf/libinjection-go v0.1.2 h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM=
|
||||
github.com/corazawaf/libinjection-go v0.1.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
|
@ -216,6 +218,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
|||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
|
@ -680,6 +683,7 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex
|
|||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
|
@ -754,6 +758,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
|
|||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
|
@ -891,13 +897,14 @@ github.com/tetratelabs/wazero v1.0.0-rc.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+Gk
|
|||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
|
||||
github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||
|
@ -1048,8 +1055,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -1100,8 +1107,8 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1209,8 +1216,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
|
|||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1220,8 +1227,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1289,6 +1296,7 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1459,6 +1467,7 @@ k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2R
|
|||
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
k8sauditacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/kubernetesaudit"
|
||||
s3acquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/s3"
|
||||
syslogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog"
|
||||
wafacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/waf"
|
||||
wineventlogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/wineventlog"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||
|
||||
|
@ -61,6 +62,7 @@ var AcquisitionSources = map[string]func() DataSource{
|
|||
"kafka": func() DataSource { return &kafkaacquisition.KafkaSource{} },
|
||||
"k8s-audit": func() DataSource { return &k8sauditacquisition.KubernetesAuditSource{} },
|
||||
"s3": func() DataSource { return &s3acquisition.S3Source{} },
|
||||
"waf": func() DataSource { return &wafacquisition.WafSource{} },
|
||||
}
|
||||
|
||||
var transformRuntimes = map[string]*vm.Program{}
|
||||
|
|
104
pkg/acquisition/modules/waf/README.md
Normal file
104
pkg/acquisition/modules/waf/README.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
Ongoing poc for Coraza
|
||||
|
||||
For config:
|
||||
|
||||
coraza_inband.conf:
|
||||
```shell
|
||||
SecRuleEngine On
|
||||
SecRule ARGS:id "@eq 0" "id:1, phase:1,deny, status:403,msg:'Invalid id',log,auditlog"
|
||||
SecRequestBodyAccess On
|
||||
SecRule REQUEST_BODY "@contains password" "id:2, phase:2,deny, status:403,msg:'Invalid request body',log,auditlog"
|
||||
```
|
||||
|
||||
|
||||
coraza_outofband.conf:
|
||||
```shell
|
||||
SecRuleEngine On
|
||||
SecRule ARGS:id "@eq 1" "id:3,phase:1,log,msg:'Invalid id',log,auditlog"
|
||||
SecRule ARGS:idd "@eq 2" "id:4,phase:1,log,msg:'Invalid id',log,auditlog"
|
||||
SecRequestBodyAccess On
|
||||
#We know that because we are not cloning the body in waf.go, the outofband rules cannot access body as it has been consumed.
|
||||
#We are finding a way around this
|
||||
#SecRule REQUEST_BODY "@contains totolol" "id:4, phase:2,deny,msg:'Invalid request body',log,auditlog"
|
||||
#SecRule REQUEST_BODY "@contains password" "id:2, phase:2,deny, status:403,msg:'Invalid request body',log,auditlog"
|
||||
|
||||
```
|
||||
|
||||
|
||||
acquis.yaml :
|
||||
|
||||
```yaml
|
||||
listen_addr: 127.0.0.1
|
||||
listen_port: 4241
|
||||
path: /
|
||||
source: waf
|
||||
labels:
|
||||
type: waf
|
||||
```
|
||||
|
||||
Coraza parser:
|
||||
|
||||
```yaml
|
||||
onsuccess: next_stage
|
||||
debug: true
|
||||
filter: "evt.Parsed.program == 'waf'"
|
||||
name: crowdsecurity/waf-logs
|
||||
description: "Parse WAF logs"
|
||||
statics:
|
||||
- parsed: cloudtrail_parsed
|
||||
expression: UnmarshalJSON(evt.Line.Raw, evt.Unmarshaled, 'waf')
|
||||
- meta: req_uuid
|
||||
expression: evt.Unmarshaled.waf.req_uuid
|
||||
- meta: source_ip
|
||||
expression: evt.Unmarshaled.waf.source_ip
|
||||
- meta: rule_id
|
||||
expression: evt.Unmarshaled.waf.rule_id
|
||||
- meta: action
|
||||
expression: evt.Unmarshaled.waf.rule_action
|
||||
- meta: service
|
||||
value: waf
|
||||
- parsed: event_type
|
||||
value: waf_match
|
||||
|
||||
```
|
||||
|
||||
Coraza trigger scenario:
|
||||
|
||||
```yaml
|
||||
type: trigger
|
||||
filter: evt.Parsed.event_type == "waf_match" && evt.Unmarshaled.waf.rule_type == "inband"
|
||||
debug: true
|
||||
name: coroza-triggger
|
||||
description: here we go
|
||||
blackhole: 2m
|
||||
labels:
|
||||
type: exploit
|
||||
remediation: true
|
||||
groupby: "evt.Meta.source_ip"
|
||||
```
|
||||
|
||||
Coraza leaky scenario:
|
||||
|
||||
```yaml
|
||||
type: leaky
|
||||
filter: evt.Parsed.event_type == "waf_match" && evt.Unmarshaled.waf.rule_type == "outofband"
|
||||
debug: true
|
||||
name: coroza-leaky
|
||||
description: here we go
|
||||
blackhole: 2m
|
||||
leakspeed: 30s
|
||||
capacity: 1
|
||||
labels:
|
||||
type: exploit
|
||||
remediation: true
|
||||
groupby: "evt.Meta.source_ip"
|
||||
distinct: evt.Meta.rule_id
|
||||
```
|
||||
|
||||
|
||||
|
||||
To be solved:
|
||||
- We need to solve the body cloning issue
|
||||
- Merge w/ hub
|
||||
|
||||
|
88
pkg/acquisition/modules/waf/utils.go
Normal file
88
pkg/acquisition/modules/waf/utils.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package wafacquisition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corazatypes "github.com/corazawaf/coraza/v3/types"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/waf"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TxToEvents(r waf.ParsedRequest, kind string) ([]types.Event, error) {
|
||||
evts := []types.Event{}
|
||||
if r.Tx == nil {
|
||||
return nil, fmt.Errorf("tx is nil")
|
||||
}
|
||||
for _, rule := range r.Tx.MatchedRules() {
|
||||
//we're discarding rules that don't have a message. They are not relevant for us
|
||||
if rule.Message() == "" {
|
||||
continue
|
||||
}
|
||||
WafRuleHits.With(prometheus.Labels{"rule_id": fmt.Sprintf("%d", rule.Rule().ID()), "type": kind}).Inc()
|
||||
evt, err := RuleMatchToEvent(rule, r.Tx, r, kind)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Cannot convert rule match to event")
|
||||
}
|
||||
evts = append(evts, evt)
|
||||
}
|
||||
|
||||
return evts, nil
|
||||
}
|
||||
|
||||
// Transforms a coraza interruption to a crowdsec event
|
||||
func RuleMatchToEvent(rule corazatypes.MatchedRule, tx corazatypes.Transaction, r waf.ParsedRequest, kind string) (types.Event, error) {
|
||||
evt := types.Event{}
|
||||
//we might want to change this based on in-band vs out-of-band ?
|
||||
evt.Type = types.LOG
|
||||
evt.ExpectMode = types.LIVE
|
||||
//def needs fixing
|
||||
evt.Stage = "s00-raw"
|
||||
evt.Process = true
|
||||
log.Infof("SOURCE IP: %+v", rule)
|
||||
//we build a big-ass object that is going to be marshaled in line.raw and unmarshaled later.
|
||||
//why ? because it's more consistent with the other data-sources etc. and it provides users with flexibility to alter our parsers
|
||||
CorazaEvent := map[string]interface{}{
|
||||
//core rule info
|
||||
"rule_type": kind,
|
||||
"rule_id": rule.Rule().ID(),
|
||||
//"rule_action": tx.Interruption().Action,
|
||||
"rule_disruptive": rule.Disruptive(),
|
||||
"rule_tags": rule.Rule().Tags(),
|
||||
"rule_file": rule.Rule().File(),
|
||||
"rule_file_line": rule.Rule().Line(),
|
||||
"rule_revision": rule.Rule().Revision(),
|
||||
"rule_secmark": rule.Rule().SecMark(),
|
||||
"rule_accuracy": rule.Rule().Accuracy(),
|
||||
|
||||
//http contextual infos
|
||||
"upstream_addr": r.RemoteAddr,
|
||||
"req_uuid": tx.ID(),
|
||||
"source_ip": strings.Split(rule.ClientIPAddress(), ":")[0],
|
||||
"uri": rule.URI(),
|
||||
}
|
||||
|
||||
if tx.Interruption() != nil {
|
||||
CorazaEvent["rule_action"] = tx.Interruption().Action
|
||||
}
|
||||
corazaEventB, err := json.Marshal(CorazaEvent)
|
||||
if err != nil {
|
||||
return evt, fmt.Errorf("Unable to marshal coraza alert: %w", err)
|
||||
}
|
||||
evt.Line = types.Line{
|
||||
Time: time.Now(),
|
||||
//should we add some info like listen addr/port/path ?
|
||||
Labels: map[string]string{"type": "waf"},
|
||||
Process: true,
|
||||
Module: "waf",
|
||||
Src: "waf",
|
||||
Raw: string(corazaEventB),
|
||||
}
|
||||
|
||||
return evt, nil
|
||||
}
|
574
pkg/acquisition/modules/waf/waf.go
Normal file
574
pkg/acquisition/modules/waf/waf.go
Normal file
|
@ -0,0 +1,574 @@
|
|||
package wafacquisition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/corazawaf/coraza/v3"
|
||||
"github.com/corazawaf/coraza/v3/experimental"
|
||||
corazatypes "github.com/corazawaf/coraza/v3/types"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/waf"
|
||||
"github.com/crowdsecurity/go-cs-lib/pkg/trace"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/tomb.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var WafParsingHistogram = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Help: "Time spent processing a request by the WAF.",
|
||||
Name: "cs_waf_parsing_time_seconds",
|
||||
Buckets: []float64{0.0005, 0.001, 0.0015, 0.002, 0.0025, 0.003, 0.004, 0.005, 0.0075, 0.01},
|
||||
},
|
||||
[]string{"source"},
|
||||
)
|
||||
|
||||
var WafReqCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "cs_waf_reqs_total",
|
||||
Help: "Total events processed by the WAF.",
|
||||
},
|
||||
[]string{"source"},
|
||||
)
|
||||
|
||||
var WafRuleHits = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "cs_waf_rule_hits",
|
||||
Help: "Count of triggered rule, by rule_id and type (inband/outofband).",
|
||||
},
|
||||
[]string{"rule_id", "type"},
|
||||
)
|
||||
|
||||
const (
|
||||
InBand = "inband"
|
||||
OutOfBand = "outofband"
|
||||
)
|
||||
|
||||
type WafRunner struct {
|
||||
outChan chan types.Event
|
||||
inChan chan waf.ParsedRequest
|
||||
inBandWaf coraza.WAF
|
||||
outOfBandWaf coraza.WAF
|
||||
UUID string
|
||||
RulesCollections []*waf.WafRulesCollection
|
||||
}
|
||||
|
||||
type WafSourceConfig struct {
|
||||
ListenAddr string `yaml:"listen_addr"`
|
||||
ListenPort int `yaml:"listen_port"`
|
||||
Path string `yaml:"path"`
|
||||
WafRoutines int `yaml:"waf_routines"`
|
||||
configuration.DataSourceCommonCfg `yaml:",inline"`
|
||||
}
|
||||
|
||||
type WafSource struct {
|
||||
config WafSourceConfig
|
||||
logger *log.Entry
|
||||
mux *http.ServeMux
|
||||
server *http.Server
|
||||
addr string
|
||||
outChan chan types.Event
|
||||
InChan chan waf.ParsedRequest
|
||||
|
||||
inBandWaf coraza.WAF
|
||||
outOfBandWaf coraza.WAF
|
||||
RulesCollections []*waf.WafRulesCollection
|
||||
|
||||
WafRunners []WafRunner
|
||||
}
|
||||
|
||||
func (w *WafSource) GetMetrics() []prometheus.Collector {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafSource) GetAggregMetrics() []prometheus.Collector {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafSource) UnmarshalConfig(yamlConfig []byte) error {
|
||||
wafConfig := WafSourceConfig{}
|
||||
err := yaml.UnmarshalStrict(yamlConfig, &wafConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot parse waf configuration")
|
||||
}
|
||||
|
||||
w.config = wafConfig
|
||||
|
||||
if w.config.ListenAddr == "" {
|
||||
return fmt.Errorf("listen_addr cannot be empty")
|
||||
}
|
||||
|
||||
if w.config.ListenPort == 0 {
|
||||
return fmt.Errorf("listen_port cannot be empty")
|
||||
}
|
||||
|
||||
//FIXME: is that really needed ?
|
||||
if w.config.Path == "" {
|
||||
return fmt.Errorf("path cannot be empty")
|
||||
}
|
||||
|
||||
if w.config.Path[0] != '/' {
|
||||
w.config.Path = "/" + w.config.Path
|
||||
}
|
||||
|
||||
if w.config.Mode == "" {
|
||||
w.config.Mode = configuration.TAIL_MODE
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func logError(error corazatypes.MatchedRule) {
|
||||
msg := error.ErrorLog(0)
|
||||
log.Infof("[logError][%s] %s", error.Rule().Severity(), msg)
|
||||
}
|
||||
|
||||
func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
|
||||
err := w.UnmarshalConfig(yamlConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot parse waf configuration")
|
||||
}
|
||||
|
||||
w.logger = logger
|
||||
|
||||
w.logger.Tracef("WAF configuration: %+v", w.config)
|
||||
|
||||
w.addr = fmt.Sprintf("%s:%d", w.config.ListenAddr, w.config.ListenPort)
|
||||
|
||||
w.mux = http.NewServeMux()
|
||||
|
||||
w.server = &http.Server{
|
||||
Addr: w.addr,
|
||||
Handler: w.mux,
|
||||
}
|
||||
|
||||
ruleLoader := waf.NewWafRuleLoader()
|
||||
|
||||
rulesCollections, err := ruleLoader.LoadWafRules()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load WAF rules: %w", err)
|
||||
}
|
||||
|
||||
w.RulesCollections = rulesCollections
|
||||
|
||||
var inBandRules string
|
||||
var outOfBandRules string
|
||||
|
||||
//spew.Dump(rulesCollections)
|
||||
|
||||
for _, collection := range rulesCollections {
|
||||
if !collection.OutOfBand {
|
||||
inBandRules += collection.String() + "\n"
|
||||
} else {
|
||||
outOfBandRules += collection.String() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
w.logger.Infof("Loading %d in-band rules", len(strings.Split(inBandRules, "\n")))
|
||||
|
||||
//w.logger.Infof("Loading rules %+v", inBandRules)
|
||||
|
||||
fs := os.DirFS(ruleLoader.Datadir)
|
||||
// always have at least one waf routine
|
||||
if w.config.WafRoutines == 0 {
|
||||
w.config.WafRoutines = 1
|
||||
}
|
||||
|
||||
w.InChan = make(chan waf.ParsedRequest)
|
||||
w.WafRunners = make([]WafRunner, w.config.WafRoutines)
|
||||
for nbRoutine := 0; nbRoutine < w.config.WafRoutines; nbRoutine++ {
|
||||
w.logger.Infof("Loading %d in-band rules", len(strings.Split(inBandRules, "\n")))
|
||||
|
||||
//in-band waf : kill on sight
|
||||
inbandwaf, err := coraza.NewWAF(
|
||||
coraza.NewWAFConfig().
|
||||
//WithErrorCallback(logError).
|
||||
WithDirectives(inBandRules).WithRootFS(fs),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot create WAF")
|
||||
}
|
||||
w.logger.Infof("Loading %d out-of-band rules", len(strings.Split(outOfBandRules, "\n")))
|
||||
//out-of-band waf : log only
|
||||
outofbandwaf, err := coraza.NewWAF(
|
||||
coraza.NewWAFConfig().
|
||||
//WithErrorCallback(logError).
|
||||
WithDirectives(outOfBandRules).WithRootFS(fs),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot create WAF")
|
||||
}
|
||||
|
||||
runner := WafRunner{
|
||||
outOfBandWaf: outofbandwaf,
|
||||
inBandWaf: inbandwaf,
|
||||
inChan: w.InChan,
|
||||
UUID: uuid.New().String(),
|
||||
RulesCollections: rulesCollections,
|
||||
}
|
||||
w.WafRunners[nbRoutine] = runner
|
||||
}
|
||||
|
||||
w.logger.Infof("Loading %d out-of-band rules", len(strings.Split(outOfBandRules, "\n")))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot create WAF")
|
||||
}
|
||||
|
||||
//We don´t use the wrapper provided by coraza because we want to fully control what happens when a rule match to send the information in crowdsec
|
||||
w.mux.HandleFunc(w.config.Path, w.wafHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafSource) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error {
|
||||
return fmt.Errorf("WAF datasource does not support command line acquisition")
|
||||
}
|
||||
|
||||
func (w *WafSource) GetMode() string {
|
||||
return w.config.Mode
|
||||
}
|
||||
|
||||
func (w *WafSource) GetName() string {
|
||||
return "waf"
|
||||
}
|
||||
|
||||
func (w *WafSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
|
||||
return fmt.Errorf("WAF datasource does not support command line acquisition")
|
||||
}
|
||||
|
||||
func (w *WafSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
|
||||
w.outChan = out
|
||||
t.Go(func() error {
|
||||
defer trace.CatchPanic("crowdsec/acquis/waf/live")
|
||||
|
||||
w.logger.Infof("%d waf runner to start", len(w.WafRunners))
|
||||
for _, runner := range w.WafRunners {
|
||||
w.logger.Infof("Running waf runner: %s", runner.UUID)
|
||||
runner.outChan = out
|
||||
t.Go(func() error {
|
||||
return runner.Run(t)
|
||||
})
|
||||
}
|
||||
|
||||
w.logger.Infof("Starting WAF server on %s:%d%s", w.config.ListenAddr, w.config.ListenPort, w.config.Path)
|
||||
t.Go(func() error {
|
||||
err := w.server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
return errors.Wrap(err, "WAF server failed")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
<-t.Dying()
|
||||
w.logger.Infof("Stopping WAF server on %s:%d%s", w.config.ListenAddr, w.config.ListenPort, w.config.Path)
|
||||
w.server.Shutdown(context.TODO())
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafSource) CanRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafSource) GetUuid() string {
|
||||
return w.config.UniqueId
|
||||
}
|
||||
|
||||
func (w *WafSource) Dump() interface{} {
|
||||
return w
|
||||
}
|
||||
|
||||
func processReqWithEngine(tx experimental.FullTransaction, r waf.ParsedRequest, wafType string) (*corazatypes.Interruption, experimental.FullTransaction, error) {
|
||||
var in *corazatypes.Interruption
|
||||
if tx.IsRuleEngineOff() {
|
||||
log.Printf("engine is off")
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
tx.ProcessLogging()
|
||||
tx.Close()
|
||||
}()
|
||||
|
||||
//this method is not exported by coraza, so we have to do it ourselves.
|
||||
//ideally, this would be dealt with by expr code, and we provide helpers to manipulate the transaction object?\
|
||||
//var txx experimental.FullTransaction
|
||||
|
||||
//txx := experimental.ToFullInterface(tx)
|
||||
//txx = tx.(experimental.FullTransaction)
|
||||
//txx.RemoveRuleByID(1)
|
||||
tx.ProcessConnection(r.ClientIP, 0, "", 0)
|
||||
|
||||
//tx.ProcessURI(r.URL.String(), r.Method, r.Proto) //FIXME: get it from the headers
|
||||
tx.ProcessURI(r.URI, r.Method, r.Proto) //FIXME: get it from the headers
|
||||
|
||||
for k, vr := range r.Headers {
|
||||
for _, v := range vr {
|
||||
tx.AddRequestHeader(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if r.ClientHost != "" {
|
||||
tx.AddRequestHeader("Host", r.ClientHost)
|
||||
// This connector relies on the host header (now host field) to populate ServerName
|
||||
tx.SetServerName(r.ClientHost)
|
||||
}
|
||||
|
||||
if r.TransferEncoding != nil {
|
||||
tx.AddRequestHeader("Transfer-Encoding", r.TransferEncoding[0])
|
||||
}
|
||||
|
||||
in = tx.ProcessRequestHeaders()
|
||||
|
||||
//spew.Dump(in)
|
||||
//spew.Dump(tx.MatchedRules())
|
||||
|
||||
for _, rule := range tx.MatchedRules() {
|
||||
if rule.Message() == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//if we're inband, we should stop here, but for outofband go to the end
|
||||
if in != nil && wafType == InBand {
|
||||
return in, tx, nil
|
||||
}
|
||||
|
||||
ct := r.Headers.Get("content-type")
|
||||
if r.Body != nil && len(r.Body) != 0 {
|
||||
it, _, err := tx.WriteRequestBody(r.Body)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Cannot read request body")
|
||||
}
|
||||
|
||||
if it != nil {
|
||||
return it, nil, nil
|
||||
}
|
||||
// from https://github.com/corazawaf/coraza/blob/main/internal/corazawaf/transaction.go#L419
|
||||
// urlencoded cannot end with CRLF
|
||||
if ct != "application/x-www-form-urlencoded" {
|
||||
it, _, err := tx.WriteRequestBody([]byte{'\r', '\n'})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot write to request body to buffer: %s", err.Error())
|
||||
}
|
||||
|
||||
if it != nil {
|
||||
return it, nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
in, err := tx.ProcessRequestBody()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Cannot process request body")
|
||||
}
|
||||
if in != nil && wafType == InBand {
|
||||
return in, tx, nil
|
||||
}
|
||||
|
||||
return nil, tx, nil
|
||||
}
|
||||
|
||||
func (r *WafRunner) Run(t *tomb.Tomb) error {
|
||||
for {
|
||||
select {
|
||||
case <-t.Dying():
|
||||
log.Infof("Waf Runner is dying")
|
||||
return nil
|
||||
case request := <-r.inChan:
|
||||
WafReqCounter.With(prometheus.Labels{"source": request.RemoteAddr}).Inc()
|
||||
//measure the time spent in the WAF
|
||||
startParsing := time.Now()
|
||||
inBoundTx := r.inBandWaf.NewTransactionWithID(request.UUID)
|
||||
expTx := inBoundTx.(experimental.FullTransaction)
|
||||
// we use this internal transaction for the expr helpers
|
||||
tx := waf.NewTransaction(expTx)
|
||||
|
||||
//Run the pre_eval hooks
|
||||
for _, rules := range r.RulesCollections {
|
||||
if len(rules.CompiledPreEval) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, compiledHook := range rules.CompiledPreEval {
|
||||
if compiledHook.Filter != nil {
|
||||
res, err := expr.Run(compiledHook.Filter, map[string]interface{}{
|
||||
"rules": rules,
|
||||
"req": request,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("unable to run PreEval filter: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch t := res.(type) {
|
||||
case bool:
|
||||
if t == false {
|
||||
log.Infof("filter didnt match")
|
||||
continue
|
||||
}
|
||||
default:
|
||||
log.Errorf("Filter must return a boolean, can't filter")
|
||||
continue
|
||||
}
|
||||
}
|
||||
// here means there is no filter or the filter matched
|
||||
for _, applyExpr := range compiledHook.Apply {
|
||||
_, err := expr.Run(applyExpr, map[string]interface{}{
|
||||
"rules": rules,
|
||||
"req": request,
|
||||
"RemoveRuleByID": tx.RemoveRuleByIDWithError,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("unable to apply filter: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in, expTx, err := processReqWithEngine(expTx, request, InBand)
|
||||
request.Tx = expTx
|
||||
|
||||
response := waf.NewResponseRequest(expTx, in, request.UUID, err)
|
||||
|
||||
// run the on_match hooks
|
||||
for _, rules := range r.RulesCollections {
|
||||
if len(rules.CompiledOnMatch) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, compiledHook := range rules.CompiledOnMatch {
|
||||
if compiledHook.Filter != nil {
|
||||
res, err := expr.Run(compiledHook.Filter, map[string]interface{}{
|
||||
"rules": rules,
|
||||
"req": request,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("unable to run PreEval filter: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch t := res.(type) {
|
||||
case bool:
|
||||
if t == false {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
log.Errorf("Filter must return a boolean, can't filter")
|
||||
continue
|
||||
}
|
||||
}
|
||||
// here means there is no filter or the filter matched
|
||||
for _, applyExpr := range compiledHook.Apply {
|
||||
_, err := expr.Run(applyExpr, map[string]interface{}{
|
||||
"rules": rules,
|
||||
"req": request,
|
||||
"RemoveRuleByID": tx.RemoveRuleByIDWithError,
|
||||
"SetRemediation": response.SetRemediation,
|
||||
"SetRemediationByID": response.SetRemediationByID,
|
||||
"CancelEvent": response.CancelEvent,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("unable to apply filter: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send back the result to the HTTP handler for the InBand part
|
||||
request.ResponseChannel <- response
|
||||
if in != nil && response.SendEvents {
|
||||
// Generate the events for InBand channel
|
||||
events, err := TxToEvents(request, InBand)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot convert transaction to events : %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, evt := range events {
|
||||
r.outChan <- evt
|
||||
}
|
||||
}
|
||||
|
||||
// Process outBand
|
||||
outBandTx := r.outOfBandWaf.NewTransactionWithID(request.UUID)
|
||||
expTx = outBandTx.(experimental.FullTransaction)
|
||||
in, expTx, err = processReqWithEngine(expTx, request, OutOfBand)
|
||||
if err != nil { //things went south
|
||||
log.Errorf("Error while processing request : %s", err)
|
||||
continue
|
||||
}
|
||||
request.Tx = expTx
|
||||
if expTx != nil && len(expTx.MatchedRules()) > 0 {
|
||||
events, err := TxToEvents(request, OutOfBand)
|
||||
log.Infof("Request triggered by WAF, %d events to send", len(events))
|
||||
for _, evt := range events {
|
||||
r.outChan <- evt
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Cannot convert transaction to events : %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
//measure the full time spent in the WAF
|
||||
elapsed := time.Since(startParsing)
|
||||
WafParsingHistogram.With(prometheus.Labels{"source": request.RemoteAddr}).Observe(elapsed.Seconds())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BodyResponse struct {
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
func (w *WafSource) wafHandler(rw http.ResponseWriter, r *http.Request) {
|
||||
// parse the request only once
|
||||
parsedRequest, err := waf.NewParsedRequestFromRequest(r)
|
||||
if err != nil {
|
||||
log.Errorf("%s", err)
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
w.InChan <- parsedRequest
|
||||
|
||||
message := <-parsedRequest.ResponseChannel
|
||||
|
||||
if message.Err != nil {
|
||||
log.Errorf("Error while processing InBAND: %s", err)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
if message.Interruption != nil {
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
body, err := json.Marshal(BodyResponse{Action: message.Interruption.Action})
|
||||
if err != nil {
|
||||
log.Errorf("unable to build response: %s", err)
|
||||
} else {
|
||||
rw.Write(body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
body, err := json.Marshal(BodyResponse{Action: "allow"})
|
||||
if err != nil {
|
||||
log.Errorf("unable to build response: %s", err)
|
||||
} else {
|
||||
rw.Write(body)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -11,6 +11,8 @@ import (
|
|||
"github.com/crowdsecurity/go-cs-lib/pkg/ptr"
|
||||
)
|
||||
|
||||
var DataDir string // FIXME: find a better way to pass this to the waf
|
||||
|
||||
// CrowdsecServiceCfg contains the location of parsers/scenarios/... and acquisition files
|
||||
type CrowdsecServiceCfg struct {
|
||||
Enable *bool `yaml:"enable"`
|
||||
|
@ -106,6 +108,8 @@ func (c *Config) LoadCrowdsec() error {
|
|||
c.Crowdsec.HubDir = c.ConfigPaths.HubDir
|
||||
c.Crowdsec.HubIndexFile = c.ConfigPaths.HubIndexFile
|
||||
|
||||
DataDir = c.Crowdsec.DataDir // FIXME: find a better way to give it to the waf
|
||||
|
||||
if c.Crowdsec.ParserRoutinesCount <= 0 {
|
||||
c.Crowdsec.ParserRoutinesCount = 1
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ var PARSERS = "parsers"
|
|||
var PARSERS_OVFLW = "postoverflows"
|
||||
var SCENARIOS = "scenarios"
|
||||
var COLLECTIONS = "collections"
|
||||
var ItemTypes = []string{PARSERS, PARSERS_OVFLW, SCENARIOS, COLLECTIONS}
|
||||
var WAF_RULES = "waf-rules"
|
||||
var ItemTypes = []string{PARSERS, PARSERS_OVFLW, SCENARIOS, COLLECTIONS, WAF_RULES}
|
||||
|
||||
var hubIdx map[string]map[string]Item
|
||||
|
||||
|
@ -42,7 +43,7 @@ type ItemHubStatus struct {
|
|||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
//Item can be : parsed, scenario, collection
|
||||
// Item can be : parsed, scenario, collection
|
||||
type Item struct {
|
||||
/*descriptive info*/
|
||||
Type string `yaml:"type,omitempty" json:"type,omitempty"` //parser|postoverflows|scenario|collection(|enrich)
|
||||
|
@ -77,6 +78,7 @@ type Item struct {
|
|||
PostOverflows []string `yaml:"postoverflows,omitempty" json:"postoverflows,omitempty"`
|
||||
Scenarios []string `yaml:"scenarios,omitempty" json:"scenarios,omitempty"`
|
||||
Collections []string `yaml:"collections,omitempty" json:"collections,omitempty"`
|
||||
WafRules []string `yaml:"waf_rules,omitempty" json:"waf_rules,omitempty"`
|
||||
}
|
||||
|
||||
func (i *Item) toHubStatus() ItemHubStatus {
|
||||
|
@ -107,7 +109,7 @@ var skippedTainted = 0
|
|||
var ReferenceMissingError = errors.New("Reference(s) missing in collection")
|
||||
var MissingHubIndex = errors.New("hub index can't be found")
|
||||
|
||||
//GetVersionStatus : semver requires 'v' prefix
|
||||
// GetVersionStatus : semver requires 'v' prefix
|
||||
func GetVersionStatus(v *Item) int {
|
||||
return semver.Compare("v"+v.Version, "v"+v.LocalVersion)
|
||||
}
|
||||
|
@ -140,7 +142,7 @@ func GetItemMap(itemType string) map[string]Item {
|
|||
return m
|
||||
}
|
||||
|
||||
//GetItemByPath retrieves the item from hubIdx based on the path. To achieve this it will resolve symlink to find associated hub item.
|
||||
// GetItemByPath retrieves the item from hubIdx based on the path. To achieve this it will resolve symlink to find associated hub item.
|
||||
func GetItemByPath(itemType string, itemPath string) (*Item, error) {
|
||||
/*try to resolve symlink*/
|
||||
finalName := ""
|
||||
|
@ -199,14 +201,14 @@ func AddItem(itemType string, item Item) error {
|
|||
}
|
||||
|
||||
func DisplaySummary() {
|
||||
log.Printf("Loaded %d collecs, %d parsers, %d scenarios, %d post-overflow parsers", len(hubIdx[COLLECTIONS]),
|
||||
len(hubIdx[PARSERS]), len(hubIdx[SCENARIOS]), len(hubIdx[PARSERS_OVFLW]))
|
||||
log.Printf("Loaded %d collecs, %d parsers, %d scenarios, %d post-overflow parsers, %d waf rules", len(hubIdx[COLLECTIONS]),
|
||||
len(hubIdx[PARSERS]), len(hubIdx[SCENARIOS]), len(hubIdx[PARSERS_OVFLW]), len(hubIdx[WAF_RULES]))
|
||||
if skippedLocal > 0 || skippedTainted > 0 {
|
||||
log.Printf("unmanaged items : %d local, %d tainted", skippedLocal, skippedTainted)
|
||||
}
|
||||
}
|
||||
|
||||
//returns: human-text, Enabled, Warning, Unmanaged
|
||||
// returns: human-text, Enabled, Warning, Unmanaged
|
||||
func ItemStatus(v Item) (string, bool, bool, bool) {
|
||||
strret := "disabled"
|
||||
Ok := false
|
||||
|
@ -341,7 +343,34 @@ func GetInstalledCollections() ([]Item, error) {
|
|||
return retItems, nil
|
||||
}
|
||||
|
||||
//Returns a list of entries for packages : name, status, local_path, local_version, utf8_status (fancy)
|
||||
func GetInstalledWafRules() ([]Item, error) {
|
||||
var retItems []Item
|
||||
|
||||
if _, ok := hubIdx[WAF_RULES]; !ok {
|
||||
return nil, fmt.Errorf("no waf rules in hubIdx")
|
||||
}
|
||||
for _, item := range hubIdx[WAF_RULES] {
|
||||
if item.Installed {
|
||||
retItems = append(retItems, item)
|
||||
}
|
||||
}
|
||||
return retItems, nil
|
||||
}
|
||||
|
||||
func GetInstalledWafRulesAsString() ([]string, error) {
|
||||
var retStr []string
|
||||
|
||||
items, err := GetInstalledWafRules()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "while fetching waf rules")
|
||||
}
|
||||
for _, it := range items {
|
||||
retStr = append(retStr, it.Name)
|
||||
}
|
||||
return retStr, nil
|
||||
}
|
||||
|
||||
// Returns a list of entries for packages : name, status, local_path, local_version, utf8_status (fancy)
|
||||
func GetHubStatusForItemType(itemType string, name string, all bool) []ItemHubStatus {
|
||||
if _, ok := hubIdx[itemType]; !ok {
|
||||
log.Errorf("type %s doesn't exist", itemType)
|
||||
|
|
|
@ -90,8 +90,11 @@ func parser_visit(path string, f os.DirEntry, err error) error {
|
|||
} else if stage == COLLECTIONS {
|
||||
ftype = COLLECTIONS
|
||||
stage = ""
|
||||
} else if stage == WAF_RULES {
|
||||
ftype = WAF_RULES
|
||||
stage = ""
|
||||
} else if ftype != PARSERS && ftype != PARSERS_OVFLW /*its a PARSER / PARSER_OVFLW with a stage */ {
|
||||
return fmt.Errorf("unknown configuration type for file '%s'", path)
|
||||
return fmt.Errorf("unknown configuration type %s for file '%s'", ftype, path)
|
||||
}
|
||||
|
||||
log.Tracef("CORRECTED [%s] by [%s] in stage [%s] of type [%s]", fname, fauthor, stage, ftype)
|
||||
|
@ -102,7 +105,7 @@ func parser_visit(path string, f os.DirEntry, err error) error {
|
|||
when the collection is installed, both files are created
|
||||
*/
|
||||
//non symlinks are local user files or hub files
|
||||
if f.Type() & os.ModeSymlink == 0 {
|
||||
if f.Type()&os.ModeSymlink == 0 {
|
||||
local = true
|
||||
log.Tracef("%s isn't a symlink", path)
|
||||
} else {
|
||||
|
@ -406,7 +409,7 @@ func LoadPkgIndex(buff []byte) (map[string]map[string]Item, error) {
|
|||
/*if it's a collection, check its sub-items are present*/
|
||||
//XX should be done later
|
||||
if itemType == COLLECTIONS {
|
||||
var tmp = [][]string{item.Parsers, item.PostOverflows, item.Scenarios, item.Collections}
|
||||
var tmp = [][]string{item.Parsers, item.PostOverflows, item.Scenarios, item.Collections, item.WafRules}
|
||||
for idx, ptr := range tmp {
|
||||
ptrtype := ItemTypes[idx]
|
||||
for _, p := range ptr {
|
||||
|
|
|
@ -274,6 +274,10 @@ func (n *Node) process(p *types.Event, ctx UnixParserCtx, expressionEnv map[stri
|
|||
switch out := output.(type) {
|
||||
case string:
|
||||
gstr = out
|
||||
case int:
|
||||
gstr = fmt.Sprintf("%d", out)
|
||||
case float64, float32:
|
||||
gstr = fmt.Sprintf("%f", out)
|
||||
default:
|
||||
clog.Errorf("unexpected return type for RunTimeValue : %T", output)
|
||||
}
|
||||
|
|
|
@ -132,6 +132,8 @@ func (n *Node) ProcessStatics(statics []types.ExtraField, event *types.Event) er
|
|||
value = out
|
||||
case int:
|
||||
value = strconv.Itoa(out)
|
||||
case float64, float32:
|
||||
value = fmt.Sprintf("%f", out)
|
||||
case map[string]interface{}:
|
||||
clog.Warnf("Expression '%s' returned a map, please use ToJsonString() to convert it to string if you want to keep it as is, or refine your expression to extract a string", static.ExpValue)
|
||||
case []interface{}:
|
||||
|
@ -139,7 +141,7 @@ func (n *Node) ProcessStatics(statics []types.ExtraField, event *types.Event) er
|
|||
case nil:
|
||||
clog.Debugf("Expression '%s' returned nil, skipping", static.ExpValue)
|
||||
default:
|
||||
clog.Errorf("unexpected return type for RunTimeValue : %T", output)
|
||||
clog.Errorf("unexpected return type for '%s' : %T", static.ExpValue, output)
|
||||
return errors.New("unexpected return type for RunTimeValue")
|
||||
}
|
||||
}
|
||||
|
|
32
pkg/waf/env.go
Normal file
32
pkg/waf/env.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package waf
|
||||
|
||||
import "github.com/corazawaf/coraza/v3/experimental"
|
||||
|
||||
type Transaction struct {
|
||||
Tx experimental.FullTransaction
|
||||
}
|
||||
|
||||
func NewTransaction(tx experimental.FullTransaction) Transaction {
|
||||
return Transaction{Tx: tx}
|
||||
}
|
||||
|
||||
func (t *Transaction) RemoveRuleByIDWithError(id int) error {
|
||||
t.Tx.RemoveRuleByID(id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetEnv() map[string]interface{} {
|
||||
ResponseRequest := ResponseRequest{}
|
||||
ParsedRequest := ParsedRequest{}
|
||||
Rules := &WafRulesCollection{}
|
||||
Tx := Transaction{}
|
||||
|
||||
return map[string]interface{}{
|
||||
"rules": Rules,
|
||||
"req": ParsedRequest,
|
||||
"SetRemediation": ResponseRequest.SetRemediation,
|
||||
"SetRemediationByID": ResponseRequest.SetRemediationByID,
|
||||
"CancelEvent": ResponseRequest.CancelEvent,
|
||||
"RemoveRuleByID": Tx.RemoveRuleByIDWithError,
|
||||
}
|
||||
}
|
106
pkg/waf/request.go
Normal file
106
pkg/waf/request.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package waf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/corazawaf/coraza/v3/experimental"
|
||||
corazatypes "github.com/corazawaf/coraza/v3/types"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ResponseRequest struct {
|
||||
UUID string
|
||||
Tx corazatypes.Transaction
|
||||
Interruption *corazatypes.Interruption
|
||||
Err error
|
||||
SendEvents bool
|
||||
}
|
||||
|
||||
func NewResponseRequest(Tx experimental.FullTransaction, in *corazatypes.Interruption, UUID string, err error) ResponseRequest {
|
||||
return ResponseRequest{
|
||||
UUID: UUID,
|
||||
Tx: Tx,
|
||||
Interruption: in,
|
||||
Err: err,
|
||||
SendEvents: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ResponseRequest) SetRemediation(remediation string) error {
|
||||
r.Interruption.Action = remediation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ResponseRequest) SetRemediationByID(ID int, remediation string) error {
|
||||
if r.Interruption.RuleID == ID {
|
||||
r.Interruption.Action = remediation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ResponseRequest) CancelEvent() error {
|
||||
// true by default
|
||||
r.SendEvents = false
|
||||
return nil
|
||||
}
|
||||
|
||||
type ParsedRequest struct {
|
||||
RemoteAddr string
|
||||
Host string
|
||||
ClientIP string
|
||||
URI string
|
||||
ClientHost string
|
||||
Headers http.Header
|
||||
URL *url.URL
|
||||
Method string
|
||||
Proto string
|
||||
Body []byte
|
||||
TransferEncoding []string
|
||||
UUID string
|
||||
Tx experimental.FullTransaction
|
||||
ResponseChannel chan ResponseRequest
|
||||
}
|
||||
|
||||
func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) {
|
||||
var body []byte
|
||||
var err error
|
||||
|
||||
if r.Body != nil {
|
||||
body = make([]byte, 0)
|
||||
body, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return ParsedRequest{}, fmt.Errorf("unable to read body: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// the real source of the request is set in 'x-client-ip'
|
||||
clientIP := r.Header.Get("X-Client-Ip")
|
||||
// the real target Host of the request is set in 'x-client-host'
|
||||
clientHost := r.Header.Get("X-Client-Host")
|
||||
// the real URI of the request is set in 'x-client-uri'
|
||||
clientURI := r.Header.Get("X-Client-Uri")
|
||||
|
||||
// delete those headers before coraza process the request
|
||||
delete(r.Header, "x-client-ip")
|
||||
delete(r.Header, "x-client-host")
|
||||
delete(r.Header, "x-client-uri")
|
||||
|
||||
return ParsedRequest{
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
UUID: uuid.New().String(),
|
||||
ClientHost: clientHost,
|
||||
ClientIP: clientIP,
|
||||
URI: clientURI,
|
||||
Host: r.Host,
|
||||
Headers: r.Header,
|
||||
URL: r.URL,
|
||||
Method: r.Method,
|
||||
Proto: r.Proto,
|
||||
Body: body,
|
||||
TransferEncoding: r.TransferEncoding,
|
||||
ResponseChannel: make(chan ResponseRequest),
|
||||
}, nil
|
||||
}
|
203
pkg/waf/waf.go
Normal file
203
pkg/waf/waf.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package waf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Hook struct {
|
||||
Filter string `yaml:"filter"`
|
||||
FilterExpr *vm.Program `yaml:"-"`
|
||||
OnSuccess string `yaml:"on_success"`
|
||||
Apply []string `yaml:"apply"`
|
||||
ApplyExpr []*vm.Program `yaml:"-"`
|
||||
}
|
||||
|
||||
type CompiledHook struct {
|
||||
Filter *vm.Program `yaml:"-"`
|
||||
Apply []*vm.Program `yaml:"-"`
|
||||
}
|
||||
|
||||
/*type WafConfig struct {
|
||||
InbandRules []WafRule
|
||||
OutOfBandRules []WafRule
|
||||
Datadir string
|
||||
logger *log.Entry
|
||||
}*/
|
||||
|
||||
// This represents one "waf-rule" config
|
||||
type WafConfig struct {
|
||||
SecLangFilesRules []string `yaml:"seclang_files_rules"`
|
||||
SecLangRules []string `yaml:"seclang_rules"`
|
||||
OnLoad []Hook `yaml:"on_load"`
|
||||
PreEval []Hook `yaml:"pre_eval"`
|
||||
OnMatch []Hook `yaml:"on_match"`
|
||||
|
||||
CompiledOnLoad []CompiledHook `yaml:"-"`
|
||||
CompiledPreEval []CompiledHook `yaml:"-"`
|
||||
CompiledOnMatch []CompiledHook `yaml:"-"`
|
||||
|
||||
MergedRules []string `yaml:"-"`
|
||||
OutOfBand bool `yaml:"outofband"`
|
||||
}
|
||||
|
||||
type WafRuleLoader struct {
|
||||
logger *log.Entry
|
||||
Datadir string
|
||||
}
|
||||
|
||||
func buildHook(hook Hook) (CompiledHook, error) {
|
||||
compiledHook := CompiledHook{}
|
||||
if hook.Filter != "" {
|
||||
program, err := expr.Compile(hook.Filter) //FIXME: opts
|
||||
if err != nil {
|
||||
return CompiledHook{}, fmt.Errorf("unable to compile filter %s : %w", hook.Filter, err)
|
||||
}
|
||||
compiledHook.Filter = program
|
||||
}
|
||||
for _, apply := range hook.Apply {
|
||||
program, err := expr.Compile(apply, GetExprWAFOptions(GetEnv())...)
|
||||
if err != nil {
|
||||
return CompiledHook{}, fmt.Errorf("unable to compile apply %s : %w", apply, err)
|
||||
}
|
||||
compiledHook.Apply = append(compiledHook.Apply, program)
|
||||
}
|
||||
return compiledHook, nil
|
||||
}
|
||||
|
||||
func (w *WafRuleLoader) LoadWafRules() ([]*WafRulesCollection, error) {
|
||||
var wafRulesFiles []string
|
||||
for _, hubWafRuleItem := range cwhub.GetItemMap(cwhub.WAF_RULES) {
|
||||
if hubWafRuleItem.Installed {
|
||||
wafRulesFiles = append(wafRulesFiles, hubWafRuleItem.LocalPath)
|
||||
}
|
||||
}
|
||||
|
||||
if len(wafRulesFiles) == 0 {
|
||||
return nil, fmt.Errorf("no waf rules found in hub")
|
||||
}
|
||||
|
||||
w.logger.Infof("Loading %d waf files", len(wafRulesFiles))
|
||||
wafRulesCollections := []*WafRulesCollection{}
|
||||
for _, wafRulesFile := range wafRulesFiles {
|
||||
|
||||
fileContent, err := os.ReadFile(wafRulesFile)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to read file %s : %s", wafRulesFile, err)
|
||||
continue
|
||||
}
|
||||
wafConfig := WafConfig{}
|
||||
err = yaml.Unmarshal(fileContent, &wafConfig)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to unmarshal file %s : %s", wafRulesFile, err)
|
||||
continue
|
||||
}
|
||||
|
||||
spew.Dump(wafConfig)
|
||||
|
||||
collection := &WafRulesCollection{}
|
||||
|
||||
if wafConfig.SecLangFilesRules != nil {
|
||||
for _, rulesFile := range wafConfig.SecLangFilesRules {
|
||||
fullPath := filepath.Join(w.Datadir, rulesFile)
|
||||
c, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to read file %s : %s", rulesFile, err)
|
||||
continue
|
||||
}
|
||||
for _, line := range strings.Split(string(c), "\n") {
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
collection.Rules = append(collection.Rules, WafRule{RawRule: line})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wafConfig.SecLangRules != nil {
|
||||
for _, rule := range wafConfig.SecLangRules {
|
||||
collection.Rules = append(collection.Rules, WafRule{RawRule: rule})
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: add our own format
|
||||
|
||||
//compile hooks
|
||||
for _, hook := range wafConfig.OnLoad {
|
||||
compiledHook, err := buildHook(hook)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to build on_load hook %s : %s", hook.Filter, err)
|
||||
continue
|
||||
}
|
||||
collection.CompiledOnLoad = append(collection.CompiledOnLoad, compiledHook)
|
||||
}
|
||||
|
||||
for _, hook := range wafConfig.PreEval {
|
||||
compiledHook, err := buildHook(hook)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to build pre_eval hook %s : %s", hook.Filter, err)
|
||||
continue
|
||||
}
|
||||
collection.CompiledPreEval = append(collection.CompiledPreEval, compiledHook)
|
||||
}
|
||||
|
||||
for _, hook := range wafConfig.OnMatch {
|
||||
compiledHook, err := buildHook(hook)
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to build on_match hook %s : %s", hook.Filter, err)
|
||||
continue
|
||||
}
|
||||
collection.CompiledOnMatch = append(collection.CompiledOnMatch, compiledHook)
|
||||
}
|
||||
|
||||
//Run the on_load hooks
|
||||
if len(collection.CompiledOnLoad) > 0 {
|
||||
w.logger.Infof("Running %d on_load hooks", len(collection.CompiledOnLoad))
|
||||
for hookIdx, onLoadHook := range collection.CompiledOnLoad {
|
||||
//Ignore filter for on load ?
|
||||
if onLoadHook.Apply != nil {
|
||||
for exprIdx, applyExpr := range onLoadHook.Apply {
|
||||
_, err := expr.Run(applyExpr, GetEnv())
|
||||
if err != nil {
|
||||
w.logger.Errorf("unable to run apply for on_load rule %s : %s", wafConfig.OnLoad[hookIdx].Apply[exprIdx], err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wafRulesCollections = append(wafRulesCollections, collection)
|
||||
}
|
||||
|
||||
return wafRulesCollections, nil
|
||||
}
|
||||
|
||||
func NewWafRuleLoader() *WafRuleLoader {
|
||||
//FIXME: find a better way to get the datadir
|
||||
clog := log.New()
|
||||
if err := types.ConfigureLogger(clog); err != nil {
|
||||
//return nil, fmt.Errorf("while configuring datasource logger: %w", err)
|
||||
return nil
|
||||
}
|
||||
logger := clog.WithFields(log.Fields{
|
||||
"type": "waf-config",
|
||||
})
|
||||
|
||||
initWafHelpers()
|
||||
|
||||
return &WafRuleLoader{Datadir: csconfig.DataDir, logger: logger}
|
||||
}
|
26
pkg/waf/waf_expr_lib.go
Normal file
26
pkg/waf/waf_expr_lib.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package waf
|
||||
|
||||
//This is a copy paste from expr_lib.go, we probably want to only have one ?
|
||||
|
||||
type exprCustomFunc struct {
|
||||
name string
|
||||
function func(params ...any) (any, error)
|
||||
signature []interface{}
|
||||
}
|
||||
|
||||
var exprFuncs = []exprCustomFunc{
|
||||
/*{
|
||||
name: "SetRulesToInband",
|
||||
function: SetRulesToInband,
|
||||
signature: []interface{}{
|
||||
new(func() error),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SetRulesToOutOfBand",
|
||||
function: SetRulesToOutOfBand,
|
||||
signature: []interface{}{
|
||||
new(func() error),
|
||||
},
|
||||
},*/
|
||||
}
|
41
pkg/waf/waf_helpers.go
Normal file
41
pkg/waf/waf_helpers.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package waf
|
||||
|
||||
import (
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
|
||||
)
|
||||
|
||||
var exprFunctionOptions []expr.Option
|
||||
|
||||
func initWafHelpers() {
|
||||
exprFunctionOptions = []expr.Option{}
|
||||
for _, function := range exprFuncs {
|
||||
exprFunctionOptions = append(exprFunctionOptions,
|
||||
expr.Function(function.name,
|
||||
function.function,
|
||||
function.signature...,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func GetExprWAFOptions(ctx map[string]interface{}) []expr.Option {
|
||||
baseHelpers := exprhelpers.GetExprOptions(ctx)
|
||||
|
||||
for _, function := range exprFuncs {
|
||||
baseHelpers = append(baseHelpers,
|
||||
expr.Function(function.name,
|
||||
function.function,
|
||||
function.signature...,
|
||||
))
|
||||
}
|
||||
return baseHelpers
|
||||
}
|
||||
|
||||
func SetRulesToInband(params ...any) (any, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func SetRulesToOutOfBand(params ...any) (any, error) {
|
||||
return nil, nil
|
||||
}
|
37
pkg/waf/waf_rules_collection.go
Normal file
37
pkg/waf/waf_rules_collection.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package waf
|
||||
|
||||
import "strings"
|
||||
|
||||
type WafRule struct {
|
||||
RawRule string
|
||||
RuleID string
|
||||
InBand bool
|
||||
}
|
||||
|
||||
// This is the "compiled" state of a WafConfig
|
||||
type WafRulesCollection struct {
|
||||
Rules []WafRule
|
||||
CompiledOnLoad []CompiledHook `yaml:"-"`
|
||||
CompiledPreEval []CompiledHook `yaml:"-"`
|
||||
CompiledOnMatch []CompiledHook `yaml:"-"`
|
||||
OutOfBand bool
|
||||
}
|
||||
|
||||
func (w *WafRulesCollection) SetInBand() error {
|
||||
w.OutOfBand = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafRulesCollection) SetOutOfBand() error {
|
||||
w.OutOfBand = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WafRulesCollection) String() string {
|
||||
//return strings.Join(w.Rules, "\n")
|
||||
var rules []string
|
||||
for _, rule := range w.Rules {
|
||||
rules = append(rules, rule.RawRule)
|
||||
}
|
||||
return strings.Join(rules, "\n")
|
||||
}
|
|
@ -20,9 +20,9 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
|
@ -104,8 +104,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -133,8 +133,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
@ -19,9 +19,9 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
|
@ -102,8 +102,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -131,8 +131,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
@ -22,9 +22,9 @@ require (
|
|||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
|
@ -111,8 +111,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -140,8 +140,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
@ -19,9 +19,9 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
|
@ -102,8 +102,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -131,8 +131,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 397c735212bf1a06cfdd0cb7806c5a6ea79582bf
|
||||
Subproject commit 44913ffe6020d1561c4c4d1e26cda8e07a1f374f
|
Loading…
Reference in a new issue