up
This commit is contained in:
parent
415e2dc68d
commit
c46e2ccdad
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -16,7 +17,6 @@ import (
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/waf"
|
"github.com/crowdsecurity/crowdsec/pkg/waf"
|
||||||
"github.com/crowdsecurity/go-cs-lib/pkg/trace"
|
"github.com/crowdsecurity/go-cs-lib/pkg/trace"
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
@ -80,6 +80,7 @@ func (w *WafSource) UnmarshalConfig(yamlConfig []byte) error {
|
||||||
if w.config.Mode == "" {
|
if w.config.Mode == "" {
|
||||||
w.config.Mode = configuration.TAIL_MODE
|
w.config.Mode = configuration.TAIL_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,16 +119,21 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
|
||||||
var inBandRules string
|
var inBandRules string
|
||||||
|
|
||||||
for _, rule := range crowdsecWafConfig.InbandRules {
|
for _, rule := range crowdsecWafConfig.InbandRules {
|
||||||
|
|
||||||
inBandRules += rule.String() + "\n"
|
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
|
//in-band waf : kill on sight
|
||||||
inbandwaf, err := coraza.NewWAF(
|
inbandwaf, err := coraza.NewWAF(
|
||||||
coraza.NewWAFConfig().
|
coraza.NewWAFConfig().
|
||||||
WithErrorCallback(logError).
|
WithErrorCallback(logError).
|
||||||
WithDirectives(inBandRules),
|
WithDirectives(inBandRules).WithRootFS(fs),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -145,8 +151,8 @@ 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("OOB -> %s", spew.Sdump(w.outOfBandWaf))
|
//log.Printf("OOB -> %s", spew.Sdump(w.outOfBandWaf))
|
||||||
log.Printf("IB -> %s", spew.Sdump(w.inBandWaf))
|
//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
|
//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)
|
||||||
|
|
|
@ -11,6 +11,8 @@ import (
|
||||||
"github.com/crowdsecurity/go-cs-lib/pkg/ptr"
|
"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
|
// CrowdsecServiceCfg contains the location of parsers/scenarios/... and acquisition files
|
||||||
type CrowdsecServiceCfg struct {
|
type CrowdsecServiceCfg struct {
|
||||||
Enable *bool `yaml:"enable"`
|
Enable *bool `yaml:"enable"`
|
||||||
|
@ -106,6 +108,8 @@ func (c *Config) LoadCrowdsec() error {
|
||||||
c.Crowdsec.HubDir = c.ConfigPaths.HubDir
|
c.Crowdsec.HubDir = c.ConfigPaths.HubDir
|
||||||
c.Crowdsec.HubIndexFile = c.ConfigPaths.HubIndexFile
|
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 {
|
if c.Crowdsec.ParserRoutinesCount <= 0 {
|
||||||
c.Crowdsec.ParserRoutinesCount = 1
|
c.Crowdsec.ParserRoutinesCount = 1
|
||||||
}
|
}
|
||||||
|
|
116
pkg/waf/waf.go
116
pkg/waf/waf.go
|
@ -2,21 +2,29 @@ package waf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/antonmedv/expr"
|
"github.com/antonmedv/expr"
|
||||||
"github.com/antonmedv/expr/vm"
|
"github.com/antonmedv/expr/vm"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
Filter string `yaml:"filter"`
|
Filter string `yaml:"filter"`
|
||||||
FilterExpr *vm.Program `yaml:"-"`
|
FilterExpr *vm.Program `yaml:"-"`
|
||||||
OnSuccess string `yaml:"on_success"`
|
OnSuccess string `yaml:"on_success"`
|
||||||
Apply []string `yaml:"apply"`
|
Apply []string `yaml:"apply"`
|
||||||
ApplyExpr []*vm.Program
|
ApplyExpr []*vm.Program `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompiledHook struct {
|
||||||
|
Filter *vm.Program `yaml:"-"`
|
||||||
|
Apply []*vm.Program `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WafRule struct {
|
type WafRule struct {
|
||||||
|
@ -25,33 +33,43 @@ type WafRule struct {
|
||||||
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"`
|
||||||
MergedRules []string `yaml:"-"`
|
|
||||||
OutOfBand bool `yaml:"-"`
|
CompiledOnLoad []CompiledHook `yaml:"-"`
|
||||||
|
CompiledPreEval []CompiledHook `yaml:"-"`
|
||||||
|
CompiledOnMatch []CompiledHook `yaml:"-"`
|
||||||
|
|
||||||
|
MergedRules []string `yaml:"-"`
|
||||||
|
OutOfBand bool `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WafConfig struct {
|
type WafConfig struct {
|
||||||
InbandRules []WafRule
|
InbandRules []WafRule
|
||||||
OutOfBandRules []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 != "" {
|
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)
|
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 {
|
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 {
|
if err != nil {
|
||||||
log.Errorf("unable to compile apply %s : %s", apply, err)
|
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 {
|
func (w *WafConfig) LoadWafRules() error {
|
||||||
|
@ -61,28 +79,37 @@ func (w *WafConfig) LoadWafRules() error {
|
||||||
files = append(files, hubWafRuleItem.LocalPath)
|
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 {
|
for _, file := range files {
|
||||||
|
|
||||||
fileContent, err := os.ReadFile(file) //FIXME: actually read from datadir
|
fileContent, err := os.ReadFile(file) //FIXME: actually read from datadir
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
wafRule := WafRule{}
|
wafRule := WafRule{}
|
||||||
err = yaml.Unmarshal(fileContent, &wafRule)
|
err = yaml.Unmarshal(fileContent, &wafRule)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if wafRule.SecLangFilesRules != nil {
|
if wafRule.SecLangFilesRules != nil {
|
||||||
for _, rulesFile := range wafRule.SecLangFilesRules {
|
for _, rulesFile := range wafRule.SecLangFilesRules {
|
||||||
c, err := os.ReadFile(rulesFile)
|
fullPath := filepath.Join(w.Datadir, rulesFile)
|
||||||
|
c, err := os.ReadFile(fullPath)
|
||||||
if err != nil {
|
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
|
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 {
|
if wafRule.SecLangRules != nil {
|
||||||
|
@ -91,27 +118,48 @@ func (w *WafConfig) LoadWafRules() error {
|
||||||
|
|
||||||
//compile hooks
|
//compile hooks
|
||||||
for _, hook := range wafRule.OnLoad {
|
for _, hook := range wafRule.OnLoad {
|
||||||
hook, err = buildHook(hook)
|
compiledHook, err := buildHook(hook)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
wafRule.CompiledOnLoad = append(wafRule.CompiledOnLoad, compiledHook)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, hook := range wafRule.PreEval {
|
for _, hook := range wafRule.PreEval {
|
||||||
hook, err = buildHook(hook)
|
compiledHook, err := buildHook(hook)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
wafRule.CompiledPreEval = append(wafRule.CompiledPreEval, compiledHook)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, hook := range wafRule.OnMatch {
|
for _, hook := range wafRule.OnMatch {
|
||||||
hook, err = buildHook(hook)
|
compiledHook, err := buildHook(hook)
|
||||||
if err != nil {
|
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
|
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 {
|
if wafRule.MergedRules != nil {
|
||||||
|
@ -121,14 +169,26 @@ func (w *WafConfig) LoadWafRules() error {
|
||||||
w.InbandRules = append(w.InbandRules, wafRule)
|
w.InbandRules = append(w.InbandRules, wafRule)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("no rules found in file %s ??", file)
|
w.logger.Warnf("no rules found in file %s ??", file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWafConfig() *WafConfig {
|
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 {
|
func (w *WafRule) String() string {
|
||||||
|
|
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
|
||||||
|
}
|
Loading…
Reference in a new issue