This commit is contained in:
alteredCoder 2022-05-25 18:50:07 +02:00
parent 8ea9e83798
commit 772c53ebd0
4 changed files with 165 additions and 2 deletions

View file

@ -189,12 +189,17 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
if err != nil { if err != nil {
log.Errorf("running test '%s' failed: %+v", test.Name, err) log.Errorf("running test '%s' failed: %+v", test.Name, err)
} }
err = test.Lint()
if err != nil {
log.Errorf("lint error for '%s': %s", test.Name, err)
}
} }
}, },
PersistentPostRun: func(cmd *cobra.Command, args []string) { PersistentPostRun: func(cmd *cobra.Command, args []string) {
success := true success := true
testResult := make(map[string]bool) testResult := make(map[string]bool)
for _, test := range HubTest.Tests { for _, test := range HubTest.Tests {
if test.AutoGen { if test.AutoGen {
if test.ParserAssert.AutoGenAssert { if test.ParserAssert.AutoGenAssert {
@ -263,7 +268,6 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
} }
} }
} }
if cleanTestEnv || forceClean { if cleanTestEnv || forceClean {
if err := test.Clean(); err != nil { if err := test.Clean(); err != nil {
log.Fatalf("unable to clean test '%s' env: %s", test.Name, err) log.Fatalf("unable to clean test '%s' env: %s", test.Name, err)
@ -271,6 +275,10 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
} }
} }
} }
for _, test := range HubTest.Tests {
PrintLint(test.LintResult)
}
if csConfig.Cscli.Output == "human" { if csConfig.Cscli.Output == "human" {
table := tablewriter.NewWriter(os.Stdout) table := tablewriter.NewWriter(os.Stdout)
table.SetCenterSeparator("") table.SetCenterSeparator("")
@ -583,3 +591,24 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
return cmdHubTest return cmdHubTest
} }
func PrintLint(lintResult *cstest.Lint) {
if len(lintResult.Parser) > 0 {
for _, lint := range lintResult.Parser {
fmt.Printf("Parser '%s' (path: '%s'):\n", lint.ItemName, lint.ItemPath)
for _, warn := range lint.Warning {
fmt.Printf(" - %s\n", warn)
}
}
}
fmt.Println()
if len(lintResult.Bucket) > 0 {
for _, lint := range lintResult.Bucket {
fmt.Printf("Scenario '%s' (path: '%s'):\n", lint.ItemName, lint.ItemPath)
for _, warn := range lint.Warning {
fmt.Printf(" - %s\n", warn)
}
}
}
fmt.Println()
}

View file

@ -2,14 +2,19 @@ package cstest
import ( import (
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/antonmedv/expr"
"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/exprhelpers"
"github.com/crowdsecurity/crowdsec/pkg/leakybucket"
"github.com/crowdsecurity/crowdsec/pkg/parser" "github.com/crowdsecurity/crowdsec/pkg/parser"
"github.com/crowdsecurity/crowdsec/pkg/types" "github.com/crowdsecurity/crowdsec/pkg/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -65,6 +70,8 @@ type HubTestItem struct {
Success bool Success bool
ErrorsList []string ErrorsList []string
LintResult *Lint
AutoGen bool AutoGen bool
ParserAssert *ParserAssert ParserAssert *ParserAssert
ScenarioAssert *ScenarioAssert ScenarioAssert *ScenarioAssert
@ -442,6 +449,127 @@ func (t *HubTestItem) Clean() error {
return os.RemoveAll(t.RuntimePath) return os.RemoveAll(t.RuntimePath)
} }
func (t *HubTestItem) Lint() error {
ret := &Lint{}
t.LintResult = &Lint{}
scenarios := make([]string, 0)
err := filepath.Walk(path.Join(t.RuntimePath, "scenarios"), func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return err
}
if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") {
scenarios = append(scenarios, path)
}
return nil
})
if err != nil {
return err
}
for _, scenarioFile := range scenarios {
//process the yaml
bucketConfigurationFile, err := os.Open(scenarioFile)
if err != nil {
log.Errorf("Can't access bucket configuration file %s", scenarioFile)
return err
}
dec := yaml.NewDecoder(bucketConfigurationFile)
dec.SetStrict(true)
for {
bucketFactory := leakybucket.BucketFactory{}
err = dec.Decode(&bucketFactory)
if err != nil {
if err != io.EOF {
log.Errorf("Bad yaml in %s : %v", scenarioFile, err)
return err
}
log.Tracef("End of yaml file")
break
}
lintObj := &BucketLint{
ItemName: bucketFactory.Name,
ItemPath: scenarioFile,
Warning: make([]string, 0),
TestName: t.Name,
}
if bucketFactory.Debug == true {
lintObj.Warning = append(lintObj.Warning, DEBUG_ENABLE)
}
if bucketFactory.Filter == "" {
lintObj.Warning = append(lintObj.Warning, NO_FILTER)
}
lintObj.Filters = append(lintObj.Filters, bucketFactory.Filter)
ret.Bucket = append(ret.Bucket, lintObj)
}
}
parserDump, err := LoadParserDump(t.ParserResultFile)
if err != nil {
return nil
}
if _, ok := (*parserDump)["s01-parse"]; !ok {
return nil
}
metaSourceIPFound := false
for parserName, parsers := range (*parserDump)["s01-parse"] {
lintObj := &ParserLint{
ItemName: parserName,
Warning: make([]string, 0),
TestName: t.Name,
Fields: make([]string, 0),
}
for _, result := range parsers {
for field := range result.Evt.Meta {
if field == "source_ip" {
metaSourceIPFound = true
}
lintObj.Fields = append(lintObj.Fields, fmt.Sprintf("evt.Meta.%s", field))
}
for field := range result.Evt.Parsed {
lintObj.Fields = append(lintObj.Fields, fmt.Sprintf("evt.Parsed.%s", field))
}
for field := range result.Evt.Enriched {
lintObj.Fields = append(lintObj.Fields, fmt.Sprintf("evt.Enriched.%s", field))
}
}
if !metaSourceIPFound {
lintObj.Warning = append(lintObj.Warning, NO_SOURCE_IP)
}
ret.Parser = append(ret.Parser, lintObj)
}
for _, bucket := range ret.Bucket {
for _, filter := range bucket.Filters {
exprDebugger, err := exprhelpers.NewDebugger(filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
if err != nil {
log.Errorf("unable to build debug filter for '%s' : %s", filter, err)
}
for _, variable := range exprDebugger.GetExpressions() {
variableStr := variable.Str
variableFound := false
for _, parserLint := range ret.Parser {
if types.InSlice(variableStr, parserLint.Fields) {
variableFound = true
break
}
}
if !variableFound {
warningMsg := fmt.Sprintf("filter '%s' not found in parsers: %+v", variableStr, t.Config.Parsers)
if !types.InSlice(warningMsg, bucket.Warning) {
bucket.Warning = append(bucket.Warning, warningMsg)
}
}
}
}
}
t.LintResult = ret
return nil
}
func (t *HubTestItem) Run() error { func (t *HubTestItem) Run() error {
t.Success = false t.Success = false
t.ErrorsList = make([]string, 0) t.ErrorsList = make([]string, 0)

View file

@ -157,7 +157,9 @@ func (s *ScenarioAssert) RunExpression(expression string) (interface{}, error) {
//dump opcode in trace level //dump opcode in trace level
log.Tracef("%s", runtimeFilter.Disassemble()) log.Tracef("%s", runtimeFilter.Disassemble())
if len(*s.TestData) == 0 {
return false, errors.Wrapf(err, "no result to run expression against")
}
output, err = expr.Run(runtimeFilter, exprhelpers.GetExprEnv(map[string]interface{}{"results": *s.TestData})) output, err = expr.Run(runtimeFilter, exprhelpers.GetExprEnv(map[string]interface{}{"results": *s.TestData}))
if err != nil { if err != nil {
log.Warningf("running : %s", expression) log.Warningf("running : %s", expression)

View file

@ -102,6 +102,10 @@ type ExprDebugger struct {
expression []*expression expression []*expression
} }
func (e *ExprDebugger) GetExpressions() []*expression {
return e.expression
}
// expression is the structure that represents the variable in string and compiled format // expression is the structure that represents the variable in string and compiled format
type expression struct { type expression struct {
Str string Str string