wip
This commit is contained in:
parent
805752dc62
commit
9180ac7be9
|
@ -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
|
||||||
|
|
134
pkg/waf/waf.go
134
pkg/waf/waf.go
|
@ -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")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
},
|
},
|
||||||
},
|
},*/
|
||||||
}
|
}
|
||||||
|
|
35
pkg/waf/waf_rules_collection.go
Normal file
35
pkg/waf/waf_rules_collection.go
Normal 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")
|
||||||
|
}
|
Loading…
Reference in a new issue