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

     1  package hubtest
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/antonmedv/expr"
    10  	log "github.com/sirupsen/logrus"
    11  	"gopkg.in/yaml.v2"
    12  
    13  	"github.com/crowdsecurity/crowdsec/pkg/dumps"
    14  	"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
    15  	"github.com/crowdsecurity/go-cs-lib/maptools"
    16  )
    17  
    18  type AssertFail struct {
    19  	File       string
    20  	Line       int
    21  	Expression string
    22  	Debug      map[string]string
    23  }
    24  
    25  type ParserAssert struct {
    26  	File              string
    27  	AutoGenAssert     bool
    28  	AutoGenAssertData string
    29  	NbAssert          int
    30  	Fails             []AssertFail
    31  	Success           bool
    32  	TestData          *dumps.ParserResults
    33  }
    34  
    35  func NewParserAssert(file string) *ParserAssert {
    36  	ParserAssert := &ParserAssert{
    37  		File:          file,
    38  		NbAssert:      0,
    39  		Success:       false,
    40  		Fails:         make([]AssertFail, 0),
    41  		AutoGenAssert: false,
    42  		TestData:      &dumps.ParserResults{},
    43  	}
    44  
    45  	return ParserAssert
    46  }
    47  
    48  func (p *ParserAssert) AutoGenFromFile(filename string) (string, error) {
    49  	err := p.LoadTest(filename)
    50  	if err != nil {
    51  		return "", err
    52  	}
    53  
    54  	ret := p.AutoGenParserAssert()
    55  
    56  	return ret, nil
    57  }
    58  
    59  func (p *ParserAssert) LoadTest(filename string) error {
    60  	parserDump, err := dumps.LoadParserDump(filename)
    61  	if err != nil {
    62  		return fmt.Errorf("loading parser dump file: %+v", err)
    63  	}
    64  
    65  	p.TestData = parserDump
    66  
    67  	return nil
    68  }
    69  
    70  func (p *ParserAssert) AssertFile(testFile string) error {
    71  	file, err := os.Open(p.File)
    72  
    73  	if err != nil {
    74  		return fmt.Errorf("failed to open")
    75  	}
    76  
    77  	if err := p.LoadTest(testFile); err != nil {
    78  		return fmt.Errorf("unable to load parser dump file '%s': %s", testFile, err)
    79  	}
    80  
    81  	scanner := bufio.NewScanner(file)
    82  	scanner.Split(bufio.ScanLines)
    83  
    84  	nbLine := 0
    85  
    86  	for scanner.Scan() {
    87  		nbLine++
    88  
    89  		if scanner.Text() == "" {
    90  			continue
    91  		}
    92  
    93  		ok, err := p.Run(scanner.Text())
    94  		if err != nil {
    95  			return fmt.Errorf("unable to run assert '%s': %+v", scanner.Text(), err)
    96  		}
    97  
    98  		p.NbAssert++
    99  
   100  		if !ok {
   101  			log.Debugf("%s is FALSE", scanner.Text())
   102  			failedAssert := &AssertFail{
   103  				File:       p.File,
   104  				Line:       nbLine,
   105  				Expression: scanner.Text(),
   106  				Debug:      make(map[string]string),
   107  			}
   108  
   109  			match := variableRE.FindStringSubmatch(scanner.Text())
   110  			var variable string
   111  
   112  			if len(match) == 0 {
   113  				log.Infof("Couldn't get variable of line '%s'", scanner.Text())
   114  				variable = scanner.Text()
   115  			} else {
   116  				variable = match[1]
   117  			}
   118  
   119  			result, err := p.EvalExpression(variable)
   120  			if err != nil {
   121  				log.Errorf("unable to evaluate variable '%s': %s", variable, err)
   122  				continue
   123  			}
   124  
   125  			failedAssert.Debug[variable] = result
   126  			p.Fails = append(p.Fails, *failedAssert)
   127  
   128  			continue
   129  		}
   130  		//fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text())
   131  	}
   132  
   133  	file.Close()
   134  
   135  	if p.NbAssert == 0 {
   136  		assertData, err := p.AutoGenFromFile(testFile)
   137  		if err != nil {
   138  			return fmt.Errorf("couldn't generate assertion: %s", err)
   139  		}
   140  
   141  		p.AutoGenAssertData = assertData
   142  		p.AutoGenAssert = true
   143  	}
   144  
   145  	if len(p.Fails) == 0 {
   146  		p.Success = true
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func (p *ParserAssert) RunExpression(expression string) (interface{}, error) {
   153  	//debug doesn't make much sense with the ability to evaluate "on the fly"
   154  	//var debugFilter *exprhelpers.ExprDebugger
   155  	var output interface{}
   156  
   157  	env := map[string]interface{}{"results": *p.TestData}
   158  
   159  	runtimeFilter, err := expr.Compile(expression, exprhelpers.GetExprOptions(env)...)
   160  	if err != nil {
   161  		log.Errorf("failed to compile '%s' : %s", expression, err)
   162  		return output, err
   163  	}
   164  
   165  	//dump opcode in trace level
   166  	log.Tracef("%s", runtimeFilter.Disassemble())
   167  
   168  	output, err = expr.Run(runtimeFilter, env)
   169  	if err != nil {
   170  		log.Warningf("running : %s", expression)
   171  		log.Warningf("runtime error : %s", err)
   172  
   173  		return output, fmt.Errorf("while running expression %s: %w", expression, err)
   174  	}
   175  
   176  	return output, nil
   177  }
   178  
   179  func (p *ParserAssert) EvalExpression(expression string) (string, error) {
   180  	output, err := p.RunExpression(expression)
   181  	if err != nil {
   182  		return "", err
   183  	}
   184  
   185  	ret, err := yaml.Marshal(output)
   186  
   187  	if err != nil {
   188  		return "", err
   189  	}
   190  
   191  	return string(ret), nil
   192  }
   193  
   194  func (p *ParserAssert) Run(assert string) (bool, error) {
   195  	output, err := p.RunExpression(assert)
   196  	if err != nil {
   197  		return false, err
   198  	}
   199  
   200  	switch out := output.(type) {
   201  	case bool:
   202  		return out, nil
   203  	default:
   204  		return false, fmt.Errorf("assertion '%s' is not a condition", assert)
   205  	}
   206  }
   207  
   208  func Escape(val string) string {
   209  	val = strings.ReplaceAll(val, `\`, `\\`)
   210  	val = strings.ReplaceAll(val, `"`, `\"`)
   211  
   212  	return val
   213  }
   214  
   215  func (p *ParserAssert) AutoGenParserAssert() string {
   216  	//attempt to autogen parser asserts
   217  	ret := fmt.Sprintf("len(results) == %d\n", len(*p.TestData))
   218  
   219  	//sort map keys for consistent order
   220  	stages := maptools.SortedKeys(*p.TestData)
   221  
   222  	for _, stage := range stages {
   223  		parsers := (*p.TestData)[stage]
   224  
   225  		//sort map keys for consistent order
   226  		pnames := maptools.SortedKeys(parsers)
   227  
   228  		for _, parser := range pnames {
   229  			presults := parsers[parser]
   230  			ret += fmt.Sprintf(`len(results["%s"]["%s"]) == %d`+"\n", stage, parser, len(presults))
   231  
   232  			for pidx, result := range presults {
   233  				ret += fmt.Sprintf(`results["%s"]["%s"][%d].Success == %t`+"\n", stage, parser, pidx, result.Success)
   234  
   235  				if !result.Success {
   236  					continue
   237  				}
   238  
   239  				for _, pkey := range maptools.SortedKeys(result.Evt.Parsed) {
   240  					pval := result.Evt.Parsed[pkey]
   241  					if pval == "" {
   242  						continue
   243  					}
   244  
   245  					ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s"`+"\n", stage, parser, pidx, pkey, Escape(pval))
   246  				}
   247  
   248  				for _, mkey := range maptools.SortedKeys(result.Evt.Meta) {
   249  					mval := result.Evt.Meta[mkey]
   250  					if mval == "" {
   251  						continue
   252  					}
   253  
   254  					ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s"`+"\n", stage, parser, pidx, mkey, Escape(mval))
   255  				}
   256  
   257  				for _, ekey := range maptools.SortedKeys(result.Evt.Enriched) {
   258  					eval := result.Evt.Enriched[ekey]
   259  					if eval == "" {
   260  						continue
   261  					}
   262  
   263  					ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s"`+"\n", stage, parser, pidx, ekey, Escape(eval))
   264  				}
   265  
   266  				for _, ukey := range maptools.SortedKeys(result.Evt.Unmarshaled) {
   267  					uval := result.Evt.Unmarshaled[ukey]
   268  					if uval == "" {
   269  						continue
   270  					}
   271  
   272  					base := fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Unmarshaled["%s"]`, stage, parser, pidx, ukey)
   273  
   274  					for _, line := range p.buildUnmarshaledAssert(base, uval) {
   275  						ret += line
   276  					}
   277  				}
   278  
   279  				ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Whitelisted == %t`+"\n", stage, parser, pidx, result.Evt.Whitelisted)
   280  
   281  				if result.Evt.WhitelistReason != "" {
   282  					ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.WhitelistReason == "%s"`+"\n", stage, parser, pidx, Escape(result.Evt.WhitelistReason))
   283  				}
   284  			}
   285  		}
   286  	}
   287  
   288  	return ret
   289  }
   290  
   291  func (p *ParserAssert) buildUnmarshaledAssert(ekey string, eval interface{}) []string {
   292  	ret := make([]string, 0)
   293  
   294  	switch val := eval.(type) {
   295  	case map[string]interface{}:
   296  		for k, v := range val {
   297  			ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...)
   298  		}
   299  	case map[interface{}]interface{}:
   300  		for k, v := range val {
   301  			ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...)
   302  		}
   303  	case []interface{}:
   304  	case string:
   305  		ret = append(ret, fmt.Sprintf(`%s == "%s"`+"\n", ekey, Escape(val)))
   306  	case bool:
   307  		ret = append(ret, fmt.Sprintf(`%s == %t`+"\n", ekey, val))
   308  	case int:
   309  		ret = append(ret, fmt.Sprintf(`%s == %d`+"\n", ekey, val))
   310  	case float64:
   311  		ret = append(ret, fmt.Sprintf(`FloatApproxEqual(%s, %f)`+"\n",
   312  			ekey, val))
   313  	default:
   314  		log.Warningf("unknown type '%T' for key '%s'", val, ekey)
   315  	}
   316  
   317  	return ret
   318  }