github.com/crowdsecurity/crowdsec@v1.6.1/pkg/hubtest/scenario_assert.go (about)

     1  package hubtest
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/antonmedv/expr"
    12  	log "github.com/sirupsen/logrus"
    13  	"gopkg.in/yaml.v2"
    14  
    15  	"github.com/crowdsecurity/crowdsec/pkg/dumps"
    16  	"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
    17  	"github.com/crowdsecurity/crowdsec/pkg/types"
    18  )
    19  
    20  type ScenarioAssert struct {
    21  	File              string
    22  	AutoGenAssert     bool
    23  	AutoGenAssertData string
    24  	NbAssert          int
    25  	Fails             []AssertFail
    26  	Success           bool
    27  	TestData          *BucketResults
    28  	PourData          *dumps.BucketPourInfo
    29  }
    30  
    31  type BucketResults []types.Event
    32  
    33  func NewScenarioAssert(file string) *ScenarioAssert {
    34  	ScenarioAssert := &ScenarioAssert{
    35  		File:          file,
    36  		NbAssert:      0,
    37  		Success:       false,
    38  		Fails:         make([]AssertFail, 0),
    39  		AutoGenAssert: false,
    40  		TestData:      &BucketResults{},
    41  		PourData:      &dumps.BucketPourInfo{},
    42  	}
    43  
    44  	return ScenarioAssert
    45  }
    46  
    47  func (s *ScenarioAssert) AutoGenFromFile(filename string) (string, error) {
    48  	err := s.LoadTest(filename, "")
    49  	if err != nil {
    50  		return "", err
    51  	}
    52  
    53  	ret := s.AutoGenScenarioAssert()
    54  
    55  	return ret, nil
    56  }
    57  
    58  func (s *ScenarioAssert) LoadTest(filename string, bucketpour string) error {
    59  	bucketDump, err := LoadScenarioDump(filename)
    60  	if err != nil {
    61  		return fmt.Errorf("loading scenario dump file '%s': %+v", filename, err)
    62  	}
    63  
    64  	s.TestData = bucketDump
    65  
    66  	if bucketpour != "" {
    67  		pourDump, err := dumps.LoadBucketPourDump(bucketpour)
    68  		if err != nil {
    69  			return fmt.Errorf("loading bucket pour dump file '%s': %+v", filename, err)
    70  		}
    71  
    72  		s.PourData = pourDump
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  func (s *ScenarioAssert) AssertFile(testFile string) error {
    79  	file, err := os.Open(s.File)
    80  
    81  	if err != nil {
    82  		return fmt.Errorf("failed to open")
    83  	}
    84  
    85  	if err := s.LoadTest(testFile, ""); err != nil {
    86  		return fmt.Errorf("unable to load parser dump file '%s': %s", testFile, err)
    87  	}
    88  
    89  	scanner := bufio.NewScanner(file)
    90  	scanner.Split(bufio.ScanLines)
    91  
    92  	nbLine := 0
    93  
    94  	for scanner.Scan() {
    95  		nbLine++
    96  
    97  		if scanner.Text() == "" {
    98  			continue
    99  		}
   100  
   101  		ok, err := s.Run(scanner.Text())
   102  		if err != nil {
   103  			return fmt.Errorf("unable to run assert '%s': %+v", scanner.Text(), err)
   104  		}
   105  
   106  		s.NbAssert++
   107  
   108  		if !ok {
   109  			log.Debugf("%s is FALSE", scanner.Text())
   110  			failedAssert := &AssertFail{
   111  				File:       s.File,
   112  				Line:       nbLine,
   113  				Expression: scanner.Text(),
   114  				Debug:      make(map[string]string),
   115  			}
   116  
   117  			match := variableRE.FindStringSubmatch(scanner.Text())
   118  
   119  			if len(match) == 0 {
   120  				log.Infof("Couldn't get variable of line '%s'", scanner.Text())
   121  				continue
   122  			}
   123  
   124  			variable := match[1]
   125  
   126  			result, err := s.EvalExpression(variable)
   127  			if err != nil {
   128  				log.Errorf("unable to evaluate variable '%s': %s", variable, err)
   129  				continue
   130  			}
   131  
   132  			failedAssert.Debug[variable] = result
   133  			s.Fails = append(s.Fails, *failedAssert)
   134  
   135  			continue
   136  		}
   137  		//fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text())
   138  	}
   139  
   140  	file.Close()
   141  
   142  	if s.NbAssert == 0 {
   143  		assertData, err := s.AutoGenFromFile(testFile)
   144  		if err != nil {
   145  			return fmt.Errorf("couldn't generate assertion: %s", err)
   146  		}
   147  
   148  		s.AutoGenAssertData = assertData
   149  		s.AutoGenAssert = true
   150  	}
   151  
   152  	if len(s.Fails) == 0 {
   153  		s.Success = true
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (s *ScenarioAssert) RunExpression(expression string) (interface{}, error) {
   160  	//debug doesn't make much sense with the ability to evaluate "on the fly"
   161  	//var debugFilter *exprhelpers.ExprDebugger
   162  	var output interface{}
   163  
   164  	env := map[string]interface{}{"results": *s.TestData}
   165  
   166  	runtimeFilter, err := expr.Compile(expression, exprhelpers.GetExprOptions(env)...)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	// if debugFilter, err = exprhelpers.NewDebugger(assert, expr.Env(env)); err != nil {
   171  	// 	log.Warningf("Failed building debugher for %s : %s", assert, err)
   172  	// }
   173  
   174  	//dump opcode in trace level
   175  	log.Tracef("%s", runtimeFilter.Disassemble())
   176  
   177  	output, err = expr.Run(runtimeFilter, map[string]interface{}{"results": *s.TestData})
   178  	if err != nil {
   179  		log.Warningf("running : %s", expression)
   180  		log.Warningf("runtime error : %s", err)
   181  
   182  		return nil, fmt.Errorf("while running expression %s: %w", expression, err)
   183  	}
   184  
   185  	return output, nil
   186  }
   187  
   188  func (s *ScenarioAssert) EvalExpression(expression string) (string, error) {
   189  	output, err := s.RunExpression(expression)
   190  	if err != nil {
   191  		return "", err
   192  	}
   193  
   194  	ret, err := yaml.Marshal(output)
   195  	if err != nil {
   196  		return "", err
   197  	}
   198  
   199  	return string(ret), nil
   200  }
   201  
   202  func (s *ScenarioAssert) Run(assert string) (bool, error) {
   203  	output, err := s.RunExpression(assert)
   204  	if err != nil {
   205  		return false, err
   206  	}
   207  
   208  	switch out := output.(type) {
   209  	case bool:
   210  		return out, nil
   211  	default:
   212  		return false, fmt.Errorf("assertion '%s' is not a condition", assert)
   213  	}
   214  }
   215  
   216  func (s *ScenarioAssert) AutoGenScenarioAssert() string {
   217  	// attempt to autogen scenario asserts
   218  	ret := fmt.Sprintf(`len(results) == %d`+"\n", len(*s.TestData))
   219  
   220  	for eventIndex, event := range *s.TestData {
   221  		for ipSrc, source := range event.Overflow.Sources {
   222  			ret += fmt.Sprintf(`"%s" in results[%d].Overflow.GetSources()`+"\n", ipSrc, eventIndex)
   223  			ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].IP == "%s"`+"\n", eventIndex, ipSrc, source.IP)
   224  			ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].Range == "%s"`+"\n", eventIndex, ipSrc, source.Range)
   225  			ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].GetScope() == "%s"`+"\n", eventIndex, ipSrc, *source.Scope)
   226  			ret += fmt.Sprintf(`results[%d].Overflow.Sources["%s"].GetValue() == "%s"`+"\n", eventIndex, ipSrc, *source.Value)
   227  		}
   228  
   229  		for evtIndex, evt := range event.Overflow.Alert.Events {
   230  			for _, meta := range evt.Meta {
   231  				ret += fmt.Sprintf(`results[%d].Overflow.Alert.Events[%d].GetMeta("%s") == "%s"`+"\n", eventIndex, evtIndex, meta.Key, Escape(meta.Value))
   232  			}
   233  		}
   234  
   235  		ret += fmt.Sprintf(`results[%d].Overflow.Alert.GetScenario() == "%s"`+"\n", eventIndex, *event.Overflow.Alert.Scenario)
   236  		ret += fmt.Sprintf(`results[%d].Overflow.Alert.Remediation == %t`+"\n", eventIndex, event.Overflow.Alert.Remediation)
   237  		ret += fmt.Sprintf(`results[%d].Overflow.Alert.GetEventsCount() == %d`+"\n", eventIndex, *event.Overflow.Alert.EventsCount)
   238  	}
   239  
   240  	return ret
   241  }
   242  
   243  func (b BucketResults) Len() int {
   244  	return len(b)
   245  }
   246  
   247  func (b BucketResults) Less(i, j int) bool {
   248  	return b[i].Overflow.Alert.GetScenario()+strings.Join(b[i].Overflow.GetSources(), "@") > b[j].Overflow.Alert.GetScenario()+strings.Join(b[j].Overflow.GetSources(), "@")
   249  }
   250  
   251  func (b BucketResults) Swap(i, j int) {
   252  	b[i], b[j] = b[j], b[i]
   253  }
   254  
   255  func LoadScenarioDump(filepath string) (*BucketResults, error) {
   256  	dumpData, err := os.Open(filepath)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	defer dumpData.Close()
   261  
   262  	results, err := io.ReadAll(dumpData)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	var bucketDump BucketResults
   268  
   269  	if err := yaml.Unmarshal(results, &bucketDump); err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	sort.Sort(bucketDump)
   274  
   275  	return &bucketDump, nil
   276  }