diff --git a/pkg/acquisition/modules/waf/waf.go b/pkg/acquisition/modules/waf/waf.go index e875e7435..e566e9c06 100644 --- a/pkg/acquisition/modules/waf/waf.go +++ b/pkg/acquisition/modules/waf/waf.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "os" "strings" "time" @@ -16,7 +17,6 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/types" "github.com/crowdsecurity/crowdsec/pkg/waf" "github.com/crowdsecurity/go-cs-lib/pkg/trace" - "github.com/davecgh/go-spew/spew" "github.com/google/uuid" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" @@ -80,6 +80,7 @@ func (w *WafSource) UnmarshalConfig(yamlConfig []byte) error { if w.config.Mode == "" { w.config.Mode = configuration.TAIL_MODE } + return nil } @@ -118,16 +119,21 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error { var inBandRules string for _, rule := range crowdsecWafConfig.InbandRules { + inBandRules += rule.String() + "\n" } - w.logger.Infof("Loading rules %+v", inBandRules) + w.logger.Infof("Loading %d in-band rules", len(strings.Split(inBandRules, "\n"))) + + //w.logger.Infof("Loading rules %+v", inBandRules) + + fs := os.DirFS(crowdsecWafConfig.Datadir) //in-band waf : kill on sight inbandwaf, err := coraza.NewWAF( coraza.NewWAFConfig(). WithErrorCallback(logError). - WithDirectives(inBandRules), + WithDirectives(inBandRules).WithRootFS(fs), ) if err != nil { @@ -145,8 +151,8 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error { return errors.Wrap(err, "Cannot create WAF") } w.outOfBandWaf = outofbandwaf - log.Printf("OOB -> %s", spew.Sdump(w.outOfBandWaf)) - log.Printf("IB -> %s", spew.Sdump(w.inBandWaf)) + //log.Printf("OOB -> %s", spew.Sdump(w.outOfBandWaf)) + //log.Printf("IB -> %s", spew.Sdump(w.inBandWaf)) //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) diff --git a/pkg/csconfig/crowdsec_service.go b/pkg/csconfig/crowdsec_service.go index 2642603cf..60484c916 100644 --- a/pkg/csconfig/crowdsec_service.go +++ b/pkg/csconfig/crowdsec_service.go @@ -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 } diff --git a/pkg/waf/waf.go b/pkg/waf/waf.go index f86a74199..a979d9d0a 100644 --- a/pkg/waf/waf.go +++ b/pkg/waf/waf.go @@ -2,21 +2,29 @@ package waf import ( "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" 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 + 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 WafRule struct { @@ -25,33 +33,43 @@ type WafRule struct { OnLoad []Hook `yaml:"on_load"` PreEval []Hook `yaml:"pre_eval"` OnMatch []Hook `yaml:"on_match"` - MergedRules []string `yaml:"-"` - OutOfBand bool `yaml:"-"` + + CompiledOnLoad []CompiledHook `yaml:"-"` + CompiledPreEval []CompiledHook `yaml:"-"` + CompiledOnMatch []CompiledHook `yaml:"-"` + + MergedRules []string `yaml:"-"` + OutOfBand bool `yaml:"-"` } type WafConfig struct { InbandRules []WafRule OutOfBandRules []WafRule + Datadir string + logger *log.Entry } -func buildHook(hook Hook) (Hook, error) { +func buildHook(hook Hook) (CompiledHook, error) { + compiledHook := CompiledHook{} if hook.Filter != "" { program, err := expr.Compile(hook.Filter) //FIXME: opts if err != nil { log.Errorf("unable to compile filter %s : %s", hook.Filter, err) - return Hook{}, err + return CompiledHook{}, err } - hook.FilterExpr = program + compiledHook.Filter = program } for _, apply := range hook.Apply { - program, err := expr.Compile(apply) //FIXME: opts + program, err := expr.Compile(apply, GetExprWAFOptions(map[string]interface{}{ + "WafRules": []WafRule{}, + })...) if err != nil { log.Errorf("unable to compile apply %s : %s", apply, err) - return Hook{}, err + return CompiledHook{}, err } - hook.ApplyExpr = append(hook.ApplyExpr, program) + compiledHook.Apply = append(compiledHook.Apply, program) } - return hook, nil + return compiledHook, nil } func (w *WafConfig) LoadWafRules() error { @@ -61,28 +79,37 @@ func (w *WafConfig) LoadWafRules() error { files = append(files, hubWafRuleItem.LocalPath) } } - log.Infof("Loading %d waf files", len(files)) + w.logger.Infof("Loading %d waf files", len(files)) for _, file := range files { fileContent, err := os.ReadFile(file) //FIXME: actually read from datadir if err != nil { - log.Errorf("unable to read file %s : %s", file, err) + w.logger.Errorf("unable to read file %s : %s", file, err) continue } wafRule := WafRule{} err = yaml.Unmarshal(fileContent, &wafRule) if err != nil { - log.Errorf("unable to unmarshal file %s : %s", file, err) + w.logger.Errorf("unable to unmarshal file %s : %s", file, err) continue } if wafRule.SecLangFilesRules != nil { for _, rulesFile := range wafRule.SecLangFilesRules { - c, err := os.ReadFile(rulesFile) + fullPath := filepath.Join(w.Datadir, rulesFile) + c, err := os.ReadFile(fullPath) if err != nil { - log.Errorf("unable to read file %s : %s", rulesFile, err) + w.logger.Errorf("unable to read file %s : %s", rulesFile, err) continue } - wafRule.MergedRules = append(wafRule.MergedRules, string(c)) + for _, line := range strings.Split(string(c), "\n") { + if strings.HasPrefix(line, "#") { + continue + } + if strings.TrimSpace(line) == "" { + continue + } + wafRule.MergedRules = append(wafRule.MergedRules, line) + } } } if wafRule.SecLangRules != nil { @@ -91,27 +118,48 @@ func (w *WafConfig) LoadWafRules() error { //compile hooks for _, hook := range wafRule.OnLoad { - hook, err = buildHook(hook) + compiledHook, err := buildHook(hook) if err != nil { - log.Errorf("unable to build hook %s : %s", hook.Filter, err) + w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) continue } + wafRule.CompiledOnLoad = append(wafRule.CompiledOnLoad, compiledHook) } for _, hook := range wafRule.PreEval { - hook, err = buildHook(hook) + compiledHook, err := buildHook(hook) if err != nil { - log.Errorf("unable to build hook %s : %s", hook.Filter, err) + w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) continue } + wafRule.CompiledPreEval = append(wafRule.CompiledPreEval, compiledHook) } for _, hook := range wafRule.OnMatch { - hook, err = buildHook(hook) + compiledHook, err := buildHook(hook) if err != nil { - log.Errorf("unable to build hook %s : %s", hook.Filter, err) + w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) continue } + wafRule.CompiledOnMatch = append(wafRule.CompiledOnMatch, compiledHook) + } + + //Run the on_load hooks + + if len(wafRule.CompiledOnLoad) > 0 { + w.logger.Infof("Running %d on_load hooks", len(wafRule.CompiledOnLoad)) + for hookIdx, onLoadHook := range wafRule.CompiledOnLoad { + //Ignore filter for on load ? + if onLoadHook.Apply != nil { + for exprIdx, applyExpr := range onLoadHook.Apply { + _, err := expr.Run(applyExpr, nil) //FIXME: give proper env + if err != nil { + w.logger.Errorf("unable to run apply for on_load rule %s : %s", wafRule.OnLoad[hookIdx].Apply[exprIdx], err) + continue + } + } + } + } } if wafRule.MergedRules != nil { @@ -121,14 +169,26 @@ func (w *WafConfig) LoadWafRules() error { w.InbandRules = append(w.InbandRules, wafRule) } } else { - log.Warnf("no rules found in file %s ??", file) + w.logger.Warnf("no rules found in file %s ??", file) } } return nil } func NewWafConfig() *WafConfig { - return &WafConfig{} + //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 &WafConfig{Datadir: csconfig.DataDir, logger: logger} } func (w *WafRule) String() string { diff --git a/pkg/waf/waf_expr_lib.go b/pkg/waf/waf_expr_lib.go new file mode 100644 index 000000000..79d693458 --- /dev/null +++ b/pkg/waf/waf_expr_lib.go @@ -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), + }, + }, +} diff --git a/pkg/waf/waf_helpers.go b/pkg/waf/waf_helpers.go new file mode 100644 index 000000000..b99732ca3 --- /dev/null +++ b/pkg/waf/waf_helpers.go @@ -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 +}