Sebastien Blot 2 years ago
parent
commit
a2e6359880

+ 1 - 0
cmd/crowdsec-cli/main.go

@@ -256,6 +256,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
 	rootCmd.AddCommand(NewHubTestCmd())
 	rootCmd.AddCommand(NewNotificationsCmd())
 	rootCmd.AddCommand(NewSupportCmd())
+	rootCmd.AddCommand(NewWafRulesCmd())
 
 	if fflag.CscliSetup.IsEnabled() {
 		rootCmd.AddCommand(NewSetupCmd())

+ 4 - 0
cmd/crowdsec-cli/utils.go

@@ -131,6 +131,8 @@ func compInstalledItems(itemType string, args []string, toComplete string) ([]st
 		items, err = cwhub.GetInstalledPostOverflowsAsString()
 	case cwhub.COLLECTIONS:
 		items, err = cwhub.GetInstalledCollectionsAsString()
+	case cwhub.WAF_RULES:
+		items, err = cwhub.GetInstalledWafRulesAsString()
 	default:
 		return nil, cobra.ShellCompDirectiveDefault
 	}
@@ -324,6 +326,8 @@ func ShowMetrics(hubItem *cwhub.Item) {
 			}
 			ShowMetrics(hubItem)
 		}
+	case cwhub.WAF_RULES:
+		log.Fatalf("FIXME: not implemented yet")
 	default:
 		log.Errorf("item of type '%s' is unknown", hubItem.Type)
 	}

+ 196 - 0
cmd/crowdsec-cli/waf_rules.go

@@ -0,0 +1,196 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/fatih/color"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
+)
+
+func NewWafRulesCmd() *cobra.Command {
+	var cmdWafRules = &cobra.Command{
+		Use:   "waf-rules [action] [config]",
+		Short: "Install/Remove/Upgrade/Inspect waf-rule(s) from hub",
+		Example: `cscli waf-rules install crowdsecurity/core-rule-set
+cscli waf-rules inspect crowdsecurity/core-rule-set
+cscli waf-rules upgrade crowdsecurity/core-rule-set
+cscli waf-rules list
+cscli waf-rules remove crowdsecurity/core-rule-set
+`,
+		Args:              cobra.MinimumNArgs(1),
+		Aliases:           []string{"waf-rule"},
+		DisableAutoGenTag: true,
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := csConfig.LoadHub(); err != nil {
+				log.Fatal(err)
+			}
+			if csConfig.Hub == nil {
+				return fmt.Errorf("you must configure cli before interacting with hub")
+			}
+
+			if err := cwhub.SetHubBranch(); err != nil {
+				return fmt.Errorf("error while setting hub branch: %s", err)
+			}
+
+			if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
+				log.Info("Run 'sudo cscli hub update' to get the hub index")
+				log.Fatalf("Failed to get Hub index : %v", err)
+			}
+			return nil
+		},
+		PersistentPostRun: func(cmd *cobra.Command, args []string) {
+			if cmd.Name() == "inspect" || cmd.Name() == "list" {
+				return
+			}
+			log.Infof(ReloadMessage())
+		},
+	}
+
+	cmdWafRules.AddCommand(NewWafRulesInstallCmd())
+	cmdWafRules.AddCommand(NewWafRulesRemoveCmd())
+	cmdWafRules.AddCommand(NewWafRulesUpgradeCmd())
+	cmdWafRules.AddCommand(NewWafRulesInspectCmd())
+	cmdWafRules.AddCommand(NewWafRulesListCmd())
+
+	return cmdWafRules
+}
+
+func NewWafRulesInstallCmd() *cobra.Command {
+	var ignoreError bool
+
+	var cmdWafRulesInstall = &cobra.Command{
+		Use:               "install [config]",
+		Short:             "Install given waf-rule(s)",
+		Long:              `Fetch and install given waf-rule(s) from hub`,
+		Example:           `cscli waf-rules install crowdsec/xxx crowdsec/xyz`,
+		Args:              cobra.MinimumNArgs(1),
+		DisableAutoGenTag: true,
+		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return compAllItems(cwhub.WAF_RULES, args, toComplete)
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			for _, name := range args {
+				t := cwhub.GetItem(cwhub.WAF_RULES, name)
+				if t == nil {
+					nearestItem, score := GetDistance(cwhub.WAF_RULES, name)
+					Suggest(cwhub.WAF_RULES, name, nearestItem.Name, score, ignoreError)
+					continue
+				}
+				if err := cwhub.InstallItem(csConfig, name, cwhub.WAF_RULES, forceAction, downloadOnly); err != nil {
+					if ignoreError {
+						log.Errorf("Error while installing '%s': %s", name, err)
+					} else {
+						log.Fatalf("Error while installing '%s': %s", name, err)
+					}
+				}
+			}
+		},
+	}
+	cmdWafRulesInstall.PersistentFlags().BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable")
+	cmdWafRulesInstall.PersistentFlags().BoolVar(&forceAction, "force", false, "Force install : Overwrite tainted and outdated files")
+	cmdWafRulesInstall.PersistentFlags().BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple waf rules")
+
+	return cmdWafRulesInstall
+}
+
+func NewWafRulesRemoveCmd() *cobra.Command {
+	var cmdWafRulesRemove = &cobra.Command{
+		Use:               "remove [config]",
+		Short:             "Remove given waf-rule(s)",
+		Long:              `Remove given waf-rule(s) from hub`,
+		Aliases:           []string{"delete"},
+		Example:           `cscli waf-rules remove crowdsec/xxx crowdsec/xyz`,
+		DisableAutoGenTag: true,
+		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			if all {
+				cwhub.RemoveMany(csConfig, cwhub.WAF_RULES, "", all, purge, forceAction)
+				return
+			}
+
+			if len(args) == 0 {
+				log.Fatalf("Specify at least one waf rule to remove or '--all' flag.")
+			}
+
+			for _, name := range args {
+				cwhub.RemoveMany(csConfig, cwhub.WAF_RULES, name, all, purge, forceAction)
+			}
+		},
+	}
+	cmdWafRulesRemove.PersistentFlags().BoolVar(&purge, "purge", false, "Delete source file too")
+	cmdWafRulesRemove.PersistentFlags().BoolVar(&forceAction, "force", false, "Force remove : Remove tainted and outdated files")
+	cmdWafRulesRemove.PersistentFlags().BoolVar(&all, "all", false, "Delete all the waf rules")
+
+	return cmdWafRulesRemove
+}
+
+func NewWafRulesUpgradeCmd() *cobra.Command {
+	var cmdWafRulesUpgrade = &cobra.Command{
+		Use:               "upgrade [config]",
+		Short:             "Upgrade given waf-rule(s)",
+		Long:              `Fetch and upgrade given waf-rule(s) from hub`,
+		Example:           `cscli waf-rules upgrade crowdsec/xxx crowdsec/xyz`,
+		DisableAutoGenTag: true,
+		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			if all {
+				cwhub.UpgradeConfig(csConfig, cwhub.WAF_RULES, "", forceAction)
+			} else {
+				if len(args) == 0 {
+					log.Fatalf("no target waf rule to upgrade")
+				}
+				for _, name := range args {
+					cwhub.UpgradeConfig(csConfig, cwhub.WAF_RULES, name, forceAction)
+				}
+			}
+		},
+	}
+	cmdWafRulesUpgrade.PersistentFlags().BoolVar(&all, "all", false, "Upgrade all the waf rules")
+	cmdWafRulesUpgrade.PersistentFlags().BoolVar(&forceAction, "force", false, "Force upgrade : Overwrite tainted and outdated files")
+
+	return cmdWafRulesUpgrade
+}
+
+func NewWafRulesInspectCmd() *cobra.Command {
+	var cmdWafRulesInspect = &cobra.Command{
+		Use:               "inspect [name]",
+		Short:             "Inspect given waf rule",
+		Long:              `Inspect given waf rule`,
+		Example:           `cscli waf-rules inspect crowdsec/xxx`,
+		DisableAutoGenTag: true,
+		Args:              cobra.MinimumNArgs(1),
+		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return compInstalledItems(cwhub.WAF_RULES, args, toComplete)
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			InspectItem(args[0], cwhub.WAF_RULES)
+		},
+	}
+	cmdWafRulesInspect.PersistentFlags().StringVarP(&prometheusURL, "url", "u", "", "Prometheus url")
+
+	return cmdWafRulesInspect
+}
+
+func NewWafRulesListCmd() *cobra.Command {
+	var cmdWafRulesList = &cobra.Command{
+		Use:   "list [name]",
+		Short: "List all waf rules or given one",
+		Long:  `List all waf rules or given one`,
+		Example: `cscli waf-rules list
+cscli waf-rules list crowdsecurity/xxx`,
+		DisableAutoGenTag: true,
+		Run: func(cmd *cobra.Command, args []string) {
+			ListItems(color.Output, []string{cwhub.WAF_RULES}, args, false, true, all)
+		},
+	}
+	cmdWafRulesList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well")
+
+	return cmdWafRulesList
+}

+ 1 - 1
go.mod

@@ -70,7 +70,7 @@ require (
 	github.com/blackfireio/osinfo v1.0.3
 	github.com/bluele/gcache v0.0.2
 	github.com/cespare/xxhash/v2 v2.1.2
-	github.com/corazawaf/coraza/v3 v3.0.0-rc.2
+	github.com/corazawaf/coraza/v3 v3.0.0-00010101000000-000000000000
 	github.com/coreos/go-systemd/v22 v22.5.0
 	github.com/crowdsecurity/go-cs-lib v0.0.0-20230531105801-4c1535c2b3bd
 	github.com/goccy/go-yaml v1.9.7

+ 29 - 20
pkg/acquisition/modules/waf/waf.go

@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"os"
 	"strings"
 	"time"
 
@@ -14,6 +15,7 @@ import (
 	corazatypes "github.com/corazawaf/coraza/v3/types"
 	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+	"github.com/crowdsecurity/crowdsec/pkg/waf"
 	"github.com/crowdsecurity/go-cs-lib/pkg/trace"
 	"github.com/google/uuid"
 	"github.com/pkg/errors"
@@ -48,23 +50,6 @@ type WafSourceConfig struct {
 	configuration.DataSourceCommonCfg `yaml:",inline"`
 }
 
-/*
-type DataSource interface {
-	GetMetrics() []prometheus.Collector                                 // Returns pointers to metrics that are managed by the module
-	GetAggregMetrics() []prometheus.Collector                           // Returns pointers to metrics that are managed by the module (aggregated mode, limits cardinality)
-	UnmarshalConfig([]byte) error                                       // Decode and pre-validate the YAML datasource - anything that can be checked before runtime
-	Configure([]byte, *log.Entry) error                                 // Complete the YAML datasource configuration and perform runtime checks.
-	ConfigureByDSN(string, map[string]string, *log.Entry, string) error // Configure the datasource
-	GetMode() string                                                    // Get the mode (TAIL, CAT or SERVER)
-	GetName() string                                                    // Get the name of the module
-	OneShotAcquisition(chan types.Event, *tomb.Tomb) error              // Start one shot acquisition(eg, cat a file)
-	StreamingAcquisition(chan types.Event, *tomb.Tomb) error            // Start live acquisition (eg, tail a file)
-	CanRun() error                                                      // Whether the datasource can run or not (eg, journalctl on BSD is a non-sense)
-	GetUuid() string                                                    // Get the unique identifier of the datasource
-	Dump() interface{}
-}
-*/
-
 func (w *WafSource) GetMetrics() []prometheus.Collector {
 	return nil
 }
@@ -102,6 +87,7 @@ func (w *WafSource) UnmarshalConfig(yamlConfig []byte) error {
 	if w.config.Mode == "" {
 		w.config.Mode = configuration.TAIL_MODE
 	}
+
 	return nil
 }
 
@@ -129,11 +115,32 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
 		Handler: w.mux,
 	}
 
+	crowdsecWafConfig := waf.NewWafConfig()
+
+	err = crowdsecWafConfig.LoadWafRules()
+
+	if err != nil {
+		return fmt.Errorf("cannot load WAF rules: %w", err)
+	}
+
+	var inBandRules string
+
+	for _, rule := range crowdsecWafConfig.InbandRules {
+
+		inBandRules += rule.String() + "\n"
+	}
+
+	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).
-			WithDirectivesFromFile("coraza_inband.conf"),
+			WithDirectives(inBandRules).WithRootFS(fs),
 	)
 
 	if err != nil {
@@ -144,8 +151,8 @@ func (w *WafSource) Configure(yamlConfig []byte, logger *log.Entry) error {
 	//out-of-band waf : log only
 	outofbandwaf, err := coraza.NewWAF(
 		coraza.NewWAFConfig().
-			WithErrorCallback(logError).
-			WithDirectivesFromFile("coraza_outofband.conf"),
+			WithErrorCallback(logError), //.
+		//WithDirectivesFromFile("coraza_outofband.conf"),
 	)
 	if err != nil {
 		return errors.Wrap(err, "Cannot create WAF")
@@ -269,6 +276,8 @@ func processReqWithEngine(waf coraza.WAF, r ParsedRequest, uuid string, wafType
 	//this method is not exported by coraza, so we have to do it ourselves.
 	//ideally, this would be dealt with by expr code, and we provide helpers to manipulate the transaction object?\
 	//var txx experimental.FullTransaction
+
+	//txx := experimental.ToFullInterface(tx)
 	//txx = tx.(experimental.FullTransaction)
 	//txx.RemoveRuleByID(1)
 

+ 4 - 0
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
 	}

+ 37 - 8
pkg/cwhub/cwhub.go

@@ -20,7 +20,8 @@ var PARSERS = "parsers"
 var PARSERS_OVFLW = "postoverflows"
 var SCENARIOS = "scenarios"
 var COLLECTIONS = "collections"
-var ItemTypes = []string{PARSERS, PARSERS_OVFLW, SCENARIOS, COLLECTIONS}
+var WAF_RULES = "waf-rules"
+var ItemTypes = []string{PARSERS, PARSERS_OVFLW, SCENARIOS, COLLECTIONS, WAF_RULES}
 
 var hubIdx map[string]map[string]Item
 
@@ -42,7 +43,7 @@ type ItemHubStatus struct {
 	Status       string `json:"status"`
 }
 
-//Item can be : parsed, scenario, collection
+// Item can be : parsed, scenario, collection
 type Item struct {
 	/*descriptive info*/
 	Type                 string   `yaml:"type,omitempty" json:"type,omitempty"`                                     //parser|postoverflows|scenario|collection(|enrich)
@@ -77,6 +78,7 @@ type Item struct {
 	PostOverflows []string `yaml:"postoverflows,omitempty" json:"postoverflows,omitempty"`
 	Scenarios     []string `yaml:"scenarios,omitempty" json:"scenarios,omitempty"`
 	Collections   []string `yaml:"collections,omitempty" json:"collections,omitempty"`
+	WafRules      []string `yaml:"waf_rules,omitempty" json:"waf_rules,omitempty"`
 }
 
 func (i *Item) toHubStatus() ItemHubStatus {
@@ -107,7 +109,7 @@ var skippedTainted = 0
 var ReferenceMissingError = errors.New("Reference(s) missing in collection")
 var MissingHubIndex = errors.New("hub index can't be found")
 
-//GetVersionStatus : semver requires 'v' prefix
+// GetVersionStatus : semver requires 'v' prefix
 func GetVersionStatus(v *Item) int {
 	return semver.Compare("v"+v.Version, "v"+v.LocalVersion)
 }
@@ -140,7 +142,7 @@ func GetItemMap(itemType string) map[string]Item {
 	return m
 }
 
-//GetItemByPath retrieves the item from hubIdx based on the path. To achieve this it will resolve symlink to find associated hub item.
+// GetItemByPath retrieves the item from hubIdx based on the path. To achieve this it will resolve symlink to find associated hub item.
 func GetItemByPath(itemType string, itemPath string) (*Item, error) {
 	/*try to resolve symlink*/
 	finalName := ""
@@ -199,14 +201,14 @@ func AddItem(itemType string, item Item) error {
 }
 
 func DisplaySummary() {
-	log.Printf("Loaded %d collecs, %d parsers, %d scenarios, %d post-overflow parsers", len(hubIdx[COLLECTIONS]),
-		len(hubIdx[PARSERS]), len(hubIdx[SCENARIOS]), len(hubIdx[PARSERS_OVFLW]))
+	log.Printf("Loaded %d collecs, %d parsers, %d scenarios, %d post-overflow parsers, %d waf rules", len(hubIdx[COLLECTIONS]),
+		len(hubIdx[PARSERS]), len(hubIdx[SCENARIOS]), len(hubIdx[PARSERS_OVFLW]), len(hubIdx[WAF_RULES]))
 	if skippedLocal > 0 || skippedTainted > 0 {
 		log.Printf("unmanaged items : %d local, %d tainted", skippedLocal, skippedTainted)
 	}
 }
 
-//returns: human-text, Enabled, Warning, Unmanaged
+// returns: human-text, Enabled, Warning, Unmanaged
 func ItemStatus(v Item) (string, bool, bool, bool) {
 	strret := "disabled"
 	Ok := false
@@ -341,7 +343,34 @@ func GetInstalledCollections() ([]Item, error) {
 	return retItems, nil
 }
 
-//Returns a list of entries for packages : name, status, local_path, local_version, utf8_status (fancy)
+func GetInstalledWafRules() ([]Item, error) {
+	var retItems []Item
+
+	if _, ok := hubIdx[WAF_RULES]; !ok {
+		return nil, fmt.Errorf("no waf rules in hubIdx")
+	}
+	for _, item := range hubIdx[WAF_RULES] {
+		if item.Installed {
+			retItems = append(retItems, item)
+		}
+	}
+	return retItems, nil
+}
+
+func GetInstalledWafRulesAsString() ([]string, error) {
+	var retStr []string
+
+	items, err := GetInstalledWafRules()
+	if err != nil {
+		return nil, errors.Wrap(err, "while fetching waf rules")
+	}
+	for _, it := range items {
+		retStr = append(retStr, it.Name)
+	}
+	return retStr, nil
+}
+
+// Returns a list of entries for packages : name, status, local_path, local_version, utf8_status (fancy)
 func GetHubStatusForItemType(itemType string, name string, all bool) []ItemHubStatus {
 	if _, ok := hubIdx[itemType]; !ok {
 		log.Errorf("type %s doesn't exist", itemType)

+ 6 - 3
pkg/cwhub/loader.go

@@ -90,8 +90,11 @@ func parser_visit(path string, f os.DirEntry, err error) error {
 	} else if stage == COLLECTIONS {
 		ftype = COLLECTIONS
 		stage = ""
+	} else if stage == WAF_RULES {
+		ftype = WAF_RULES
+		stage = ""
 	} else if ftype != PARSERS && ftype != PARSERS_OVFLW /*its a PARSER / PARSER_OVFLW with a stage */ {
-		return fmt.Errorf("unknown configuration type for file '%s'", path)
+		return fmt.Errorf("unknown configuration type %s for file '%s'", ftype, path)
 	}
 
 	log.Tracef("CORRECTED [%s] by [%s] in stage [%s] of type [%s]", fname, fauthor, stage, ftype)
@@ -102,7 +105,7 @@ func parser_visit(path string, f os.DirEntry, err error) error {
 		when the collection is installed, both files are created
 	*/
 	//non symlinks are local user files or hub files
-	if f.Type() & os.ModeSymlink == 0 {
+	if f.Type()&os.ModeSymlink == 0 {
 		local = true
 		log.Tracef("%s isn't a symlink", path)
 	} else {
@@ -406,7 +409,7 @@ func LoadPkgIndex(buff []byte) (map[string]map[string]Item, error) {
 			/*if it's a collection, check its sub-items are present*/
 			//XX should be done later
 			if itemType == COLLECTIONS {
-				var tmp = [][]string{item.Parsers, item.PostOverflows, item.Scenarios, item.Collections}
+				var tmp = [][]string{item.Parsers, item.PostOverflows, item.Scenarios, item.Collections, item.WafRules}
 				for idx, ptr := range tmp {
 					ptrtype := ItemTypes[idx]
 					for _, p := range ptr {

+ 196 - 0
pkg/waf/waf.go

@@ -0,0 +1,196 @@
+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 `yaml:"-"`
+}
+
+type CompiledHook struct {
+	Filter *vm.Program   `yaml:"-"`
+	Apply  []*vm.Program `yaml:"-"`
+}
+
+type WafRule 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:"-"`
+}
+
+type WafConfig struct {
+	InbandRules    []WafRule
+	OutOfBandRules []WafRule
+	Datadir        string
+	logger         *log.Entry
+}
+
+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 CompiledHook{}, err
+		}
+		compiledHook.Filter = program
+	}
+	for _, apply := range hook.Apply {
+		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 CompiledHook{}, err
+		}
+		compiledHook.Apply = append(compiledHook.Apply, program)
+	}
+	return compiledHook, nil
+}
+
+func (w *WafConfig) LoadWafRules() error {
+	var files []string
+	for _, hubWafRuleItem := range cwhub.GetItemMap(cwhub.WAF_RULES) {
+		if hubWafRuleItem.Installed {
+			files = append(files, 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 err != nil {
+			w.logger.Errorf("unable to read file %s : %s", file, err)
+			continue
+		}
+		wafRule := WafRule{}
+		err = yaml.Unmarshal(fileContent, &wafRule)
+		if err != nil {
+			w.logger.Errorf("unable to unmarshal file %s : %s", file, err)
+			continue
+		}
+		if wafRule.SecLangFilesRules != nil {
+			for _, rulesFile := range wafRule.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
+					}
+					wafRule.MergedRules = append(wafRule.MergedRules, line)
+				}
+			}
+		}
+		if wafRule.SecLangRules != nil {
+			wafRule.MergedRules = append(wafRule.MergedRules, wafRule.SecLangRules...)
+		}
+
+		//compile hooks
+		for _, hook := range wafRule.OnLoad {
+			compiledHook, err := buildHook(hook)
+			if err != nil {
+				w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err)
+				continue
+			}
+			wafRule.CompiledOnLoad = append(wafRule.CompiledOnLoad, compiledHook)
+		}
+
+		for _, hook := range wafRule.PreEval {
+			compiledHook, err := buildHook(hook)
+			if err != nil {
+				w.logger.Errorf("unable to build hook %s : %s", hook.Filter, err)
+				continue
+			}
+			wafRule.CompiledPreEval = append(wafRule.CompiledPreEval, compiledHook)
+		}
+
+		for _, hook := range wafRule.OnMatch {
+			compiledHook, err := buildHook(hook)
+			if err != nil {
+				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 {
+			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
+}
+
+func NewWafConfig() *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 {
+	return strings.Join(w.MergedRules, "\n")
+}

+ 26 - 0
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),
+		},
+	},
+}

+ 41 - 0
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
+}