203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
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}
|
|
}
|