merge
This commit is contained in:
commit
a2e6359880
11 changed files with 541 additions and 32 deletions
|
@ -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())
|
||||
|
|
|
@ -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
cmd/crowdsec-cli/waf_rules.go
Normal file
196
cmd/crowdsec-cli/waf_rules.go
Normal file
|
@ -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
|
||||
}
|
2
go.mod
2
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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
pkg/waf/waf.go
Normal file
196
pkg/waf/waf.go
Normal file
|
@ -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
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