This commit is contained in:
Sebastien Blot 2023-06-15 22:51:57 +02:00
parent 805752dc62
commit 9180ac7be9
No known key found for this signature in database
GPG key ID: DFC2902F40449F6A
4 changed files with 117 additions and 90 deletions

View file

@ -117,24 +117,32 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
Handler: w.mux, Handler: w.mux,
} }
crowdsecWafConfig := waf.NewWafConfig() ruleLoader := waf.NewWafRuleLoader()
err = crowdsecWafConfig.LoadWafRules() rulesCollections, err := ruleLoader.LoadWafRules()
if err != nil { if err != nil {
return fmt.Errorf("cannot load WAF rules: %w", err) return fmt.Errorf("cannot load WAF rules: %w", err)
} }
var inBandRules string var inBandRules string
for _, rule := range crowdsecWafConfig.InbandRules { var outOfBandRules string
inBandRules += rule.String() + "\n" //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 %d in-band rules", len(strings.Split(inBandRules, "\n")))
//w.logger.Infof("Loading rules %+v", inBandRules) //w.logger.Infof("Loading rules %+v", inBandRules)
fs := os.DirFS(crowdsecWafConfig.Datadir) fs := os.DirFS(ruleLoader.Datadir)
//in-band waf : kill on sight //in-band waf : kill on sight
inbandwaf, err := coraza.NewWAF( inbandwaf, err := coraza.NewWAF(
@ -143,24 +151,11 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
WithDirectives(inBandRules).WithRootFS(fs), WithDirectives(inBandRules).WithRootFS(fs),
) )
//for _, rule := range inbandwaf.GetWAF().Rules.GetRules() {
// w.logger.Infof("Action for Rule %d: %+v ", rule.ID(), rule.GetActions())
//}
//betterwaf := experimental.ToBetterWAFEngine(inbandwaf)
//spew.Dump(betterwaf.Waf.Rules)
if err != nil { if err != nil {
return errors.Wrap(err, "Cannot create WAF") return errors.Wrap(err, "Cannot create WAF")
} }
w.inBandWaf = inbandwaf w.inBandWaf = inbandwaf
var outOfBandRules string
for _, rule := range crowdsecWafConfig.OutOfBandRules {
outOfBandRules += rule.String() + "\n"
}
w.logger.Infof("Loading %d out-of-band rules", len(strings.Split(outOfBandRules, "\n"))) w.logger.Infof("Loading %d out-of-band rules", len(strings.Split(outOfBandRules, "\n")))
//out-of-band waf : log only //out-of-band waf : log only
outofbandwaf, err := coraza.NewWAF( outofbandwaf, err := coraza.NewWAF(
@ -173,9 +168,6 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
return errors.Wrap(err, "Cannot create WAF") return errors.Wrap(err, "Cannot create WAF")
} }
w.outOfBandWaf = outofbandwaf w.outOfBandWaf = 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) w.mux.HandleFunc(w.config.Path, w.wafHandler)
return nil return nil

View file

@ -1,6 +1,7 @@
package waf package waf
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -10,6 +11,7 @@ import (
"github.com/crowdsecurity/crowdsec/pkg/csconfig" "github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/cwhub" "github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/types" "github.com/crowdsecurity/crowdsec/pkg/types"
"github.com/davecgh/go-spew/spew"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -27,26 +29,25 @@ type CompiledHook struct {
Apply []*vm.Program `yaml:"-"` Apply []*vm.Program `yaml:"-"`
} }
type WafRule struct { /*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"` SecLangFilesRules []string `yaml:"seclang_files_rules"`
SecLangRules []string `yaml:"seclang_rules"` SecLangRules []string `yaml:"seclang_rules"`
OnLoad []Hook `yaml:"on_load"` OnLoad []Hook `yaml:"on_load"`
PreEval []Hook `yaml:"pre_eval"` PreEval []Hook `yaml:"pre_eval"`
OnMatch []Hook `yaml:"on_match"` OnMatch []Hook `yaml:"on_match"`
CompiledOnLoad []CompiledHook `yaml:"-"`
CompiledPreEval []CompiledHook `yaml:"-"`
CompiledOnMatch []CompiledHook `yaml:"-"`
MergedRules []string `yaml:"-"`
OutOfBand bool `yaml:"-"`
} }
type WafConfig struct { type WafRuleLoader struct {
InbandRules []WafRule logger *log.Entry
OutOfBandRules []WafRule Datadir string
Datadir string
logger *log.Entry
} }
func buildHook(hook Hook) (CompiledHook, error) { func buildHook(hook Hook) (CompiledHook, error) {
@ -54,48 +55,56 @@ func buildHook(hook Hook) (CompiledHook, error) {
if hook.Filter != "" { if hook.Filter != "" {
program, err := expr.Compile(hook.Filter) //FIXME: opts program, err := expr.Compile(hook.Filter) //FIXME: opts
if err != nil { if err != nil {
log.Errorf("unable to compile filter %s : %s", hook.Filter, err) return CompiledHook{}, fmt.Errorf("unable to compile filter %s : %w", hook.Filter, err)
return CompiledHook{}, err
} }
compiledHook.Filter = program compiledHook.Filter = program
} }
for _, apply := range hook.Apply { for _, apply := range hook.Apply {
program, err := expr.Compile(apply, GetExprWAFOptions(map[string]interface{}{ program, err := expr.Compile(apply, GetExprWAFOptions(map[string]interface{}{
"InBandRules": []WafRule{}, "rules": &WafRulesCollection{},
"OutOfBandRules": []WafRule{},
})...) })...)
if err != nil { if err != nil {
log.Errorf("unable to compile apply %s : %s", apply, err) return CompiledHook{}, fmt.Errorf("unable to compile apply %s : %w", apply, err)
return CompiledHook{}, err
} }
compiledHook.Apply = append(compiledHook.Apply, program) compiledHook.Apply = append(compiledHook.Apply, program)
} }
return compiledHook, nil return compiledHook, nil
} }
func (w *WafConfig) LoadWafRules() error { func (w *WafRuleLoader) LoadWafRules() ([]*WafRulesCollection, error) {
var files []string var wafRulesFiles []string
for _, hubWafRuleItem := range cwhub.GetItemMap(cwhub.WAF_RULES) { for _, hubWafRuleItem := range cwhub.GetItemMap(cwhub.WAF_RULES) {
if hubWafRuleItem.Installed { if hubWafRuleItem.Installed {
files = append(files, hubWafRuleItem.LocalPath) wafRulesFiles = append(wafRulesFiles, hubWafRuleItem.LocalPath)
} }
} }
w.logger.Infof("Loading %d waf files", len(files))
for _, file := range files {
fileContent, err := os.ReadFile(file) //FIXME: actually read from datadir 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 { if err != nil {
w.logger.Errorf("unable to read file %s : %s", file, err) w.logger.Errorf("unable to read file %s : %s", wafRulesFile, err)
continue continue
} }
wafRule := WafRule{} wafConfig := WafConfig{}
err = yaml.Unmarshal(fileContent, &wafRule) err = yaml.Unmarshal(fileContent, &wafConfig)
if err != nil { if err != nil {
w.logger.Errorf("unable to unmarshal file %s : %s", file, err) w.logger.Errorf("unable to unmarshal file %s : %s", wafRulesFile, err)
continue continue
} }
if wafRule.SecLangFilesRules != nil {
for _, rulesFile := range wafRule.SecLangFilesRules { spew.Dump(wafConfig)
collection := &WafRulesCollection{}
if wafConfig.SecLangFilesRules != nil {
for _, rulesFile := range wafConfig.SecLangFilesRules {
fullPath := filepath.Join(w.Datadir, rulesFile) fullPath := filepath.Join(w.Datadir, rulesFile)
c, err := os.ReadFile(fullPath) c, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
@ -109,77 +118,72 @@ func (w *WafConfig) LoadWafRules() error {
if strings.TrimSpace(line) == "" { if strings.TrimSpace(line) == "" {
continue continue
} }
wafRule.MergedRules = append(wafRule.MergedRules, line) collection.Rules = append(collection.Rules, WafRule{RawRule: line})
} }
} }
} }
if wafRule.SecLangRules != nil {
wafRule.MergedRules = append(wafRule.MergedRules, wafRule.SecLangRules...) if wafConfig.SecLangRules != nil {
for _, rule := range wafConfig.SecLangRules {
collection.Rules = append(collection.Rules, WafRule{RawRule: rule})
}
} }
//TODO: add our own format
//compile hooks //compile hooks
for _, hook := range wafRule.OnLoad { for _, hook := range wafConfig.OnLoad {
compiledHook, err := buildHook(hook) compiledHook, err := buildHook(hook)
if err != nil { if err != nil {
w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) w.logger.Errorf("unable to build on_load hook %s : %s", hook.Filter, err)
continue continue
} }
wafRule.CompiledOnLoad = append(wafRule.CompiledOnLoad, compiledHook) collection.CompiledOnLoad = append(collection.CompiledOnLoad, compiledHook)
} }
for _, hook := range wafRule.PreEval { for _, hook := range wafConfig.PreEval {
compiledHook, err := buildHook(hook) compiledHook, err := buildHook(hook)
if err != nil { if err != nil {
w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) w.logger.Errorf("unable to build pre_eval hook %s : %s", hook.Filter, err)
continue continue
} }
wafRule.CompiledPreEval = append(wafRule.CompiledPreEval, compiledHook) collection.CompiledPreEval = append(collection.CompiledPreEval, compiledHook)
} }
for _, hook := range wafRule.OnMatch { for _, hook := range wafConfig.OnMatch {
compiledHook, err := buildHook(hook) compiledHook, err := buildHook(hook)
if err != nil { if err != nil {
w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err) w.logger.Errorf("unable to build on_match hook %s : %s", hook.Filter, err)
continue continue
} }
wafRule.CompiledOnMatch = append(wafRule.CompiledOnMatch, compiledHook) collection.CompiledOnMatch = append(collection.CompiledOnMatch, compiledHook)
} }
//Run the on_load hooks //Run the on_load hooks
if len(collection.CompiledOnLoad) > 0 {
if len(wafRule.CompiledOnLoad) > 0 { w.logger.Infof("Running %d on_load hooks", len(collection.CompiledOnLoad))
w.logger.Infof("Running %d on_load hooks", len(wafRule.CompiledOnLoad)) for hookIdx, onLoadHook := range collection.CompiledOnLoad {
for hookIdx, onLoadHook := range wafRule.CompiledOnLoad {
//Ignore filter for on load ? //Ignore filter for on load ?
if onLoadHook.Apply != nil { if onLoadHook.Apply != nil {
for exprIdx, applyExpr := range onLoadHook.Apply { for exprIdx, applyExpr := range onLoadHook.Apply {
_, err := expr.Run(applyExpr, map[string]interface{}{ _, err := expr.Run(applyExpr, map[string]interface{}{
"InBandRules": []WafRule{}, "rules": collection,
"OutOfBandRules": []WafRule{},
}) })
if err != nil { if err != nil {
w.logger.Errorf("unable to run apply for on_load rule %s : %s", wafRule.OnLoad[hookIdx].Apply[exprIdx], err) w.logger.Errorf("unable to run apply for on_load rule %s : %s", wafConfig.OnLoad[hookIdx].Apply[exprIdx], err)
continue continue
} }
} }
} }
} }
} }
wafRulesCollections = append(wafRulesCollections, collection)
if wafRule.MergedRules != nil {
if wafRule.OutOfBand {
w.OutOfBandRules = append(w.OutOfBandRules, wafRule)
} else {
w.InbandRules = append(w.InbandRules, wafRule)
}
} else {
w.logger.Warnf("no rules found in file %s ??", file)
}
} }
return nil
return wafRulesCollections, nil
} }
func NewWafConfig() *WafConfig { func NewWafRuleLoader() *WafRuleLoader {
//FIXME: find a better way to get the datadir //FIXME: find a better way to get the datadir
clog := log.New() clog := log.New()
if err := types.ConfigureLogger(clog); err != nil { if err := types.ConfigureLogger(clog); err != nil {
@ -192,9 +196,5 @@ func NewWafConfig() *WafConfig {
initWafHelpers() initWafHelpers()
return &WafConfig{Datadir: csconfig.DataDir, logger: logger} return &WafRuleLoader{Datadir: csconfig.DataDir, logger: logger}
}
func (w *WafRule) String() string {
return strings.Join(w.MergedRules, "\n")
} }

View file

@ -9,7 +9,7 @@ type exprCustomFunc struct {
} }
var exprFuncs = []exprCustomFunc{ var exprFuncs = []exprCustomFunc{
{ /*{
name: "SetRulesToInband", name: "SetRulesToInband",
function: SetRulesToInband, function: SetRulesToInband,
signature: []interface{}{ signature: []interface{}{
@ -22,5 +22,5 @@ var exprFuncs = []exprCustomFunc{
signature: []interface{}{ signature: []interface{}{
new(func() error), new(func() error),
}, },
}, },*/
} }

View file

@ -0,0 +1,35 @@
package waf
import "strings"
type WafRule struct {
RawRule string
}
// 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")
}