parser_assert.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. package cstest
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "regexp"
  8. "sort"
  9. "strings"
  10. "time"
  11. "github.com/antonmedv/expr"
  12. "github.com/antonmedv/expr/vm"
  13. "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
  14. "github.com/crowdsecurity/crowdsec/pkg/types"
  15. "github.com/enescakir/emoji"
  16. "github.com/fatih/color"
  17. "github.com/pkg/errors"
  18. diff "github.com/r3labs/diff/v2"
  19. log "github.com/sirupsen/logrus"
  20. "gopkg.in/yaml.v2"
  21. )
  22. type AssertFail struct {
  23. File string
  24. Line int
  25. Expression string
  26. Debug map[string]string
  27. }
  28. type ParserAssert struct {
  29. File string
  30. AutoGenAssert bool
  31. AutoGenAssertData string
  32. NbAssert int
  33. Fails []AssertFail
  34. Success bool
  35. TestData *ParserResults
  36. }
  37. type ParserResult struct {
  38. Evt types.Event
  39. Success bool
  40. }
  41. type ParserResults map[string]map[string][]ParserResult
  42. func NewParserAssert(file string) *ParserAssert {
  43. ParserAssert := &ParserAssert{
  44. File: file,
  45. NbAssert: 0,
  46. Success: false,
  47. Fails: make([]AssertFail, 0),
  48. AutoGenAssert: false,
  49. TestData: &ParserResults{},
  50. }
  51. return ParserAssert
  52. }
  53. func (p *ParserAssert) AutoGenFromFile(filename string) (string, error) {
  54. err := p.LoadTest(filename)
  55. if err != nil {
  56. return "", err
  57. }
  58. ret := p.AutoGenParserAssert()
  59. return ret, nil
  60. }
  61. func (p *ParserAssert) LoadTest(filename string) error {
  62. var err error
  63. parserDump, err := LoadParserDump(filename)
  64. if err != nil {
  65. return fmt.Errorf("loading parser dump file: %+v", err)
  66. }
  67. p.TestData = parserDump
  68. return nil
  69. }
  70. func (p *ParserAssert) AssertFile(testFile string) error {
  71. file, err := os.Open(p.File)
  72. if err != nil {
  73. return fmt.Errorf("failed to open")
  74. }
  75. if err := p.LoadTest(testFile); err != nil {
  76. return fmt.Errorf("unable to load parser dump file '%s': %s", testFile, err)
  77. }
  78. scanner := bufio.NewScanner(file)
  79. scanner.Split(bufio.ScanLines)
  80. nbLine := 0
  81. for scanner.Scan() {
  82. nbLine += 1
  83. if scanner.Text() == "" {
  84. continue
  85. }
  86. ok, err := p.Run(scanner.Text())
  87. if err != nil {
  88. return fmt.Errorf("unable to run assert '%s': %+v", scanner.Text(), err)
  89. }
  90. p.NbAssert += 1
  91. if !ok {
  92. log.Debugf("%s is FALSE", scanner.Text())
  93. //fmt.SPrintf(" %s '%s'\n", emoji.RedSquare, scanner.Text())
  94. failedAssert := &AssertFail{
  95. File: p.File,
  96. Line: nbLine,
  97. Expression: scanner.Text(),
  98. Debug: make(map[string]string),
  99. }
  100. variableRE := regexp.MustCompile(`(?P<variable>[^ =]+) == .*`)
  101. match := variableRE.FindStringSubmatch(scanner.Text())
  102. if len(match) == 0 {
  103. log.Infof("Couldn't get variable of line '%s'", scanner.Text())
  104. }
  105. variable := match[1]
  106. result, err := p.EvalExpression(variable)
  107. if err != nil {
  108. log.Errorf("unable to evaluate variable '%s': %s", variable, err)
  109. continue
  110. }
  111. failedAssert.Debug[variable] = result
  112. p.Fails = append(p.Fails, *failedAssert)
  113. continue
  114. }
  115. //fmt.Printf(" %s '%s'\n", emoji.GreenSquare, scanner.Text())
  116. }
  117. file.Close()
  118. if p.NbAssert == 0 {
  119. assertData, err := p.AutoGenFromFile(testFile)
  120. if err != nil {
  121. return fmt.Errorf("couldn't generate assertion: %s", err.Error())
  122. }
  123. p.AutoGenAssertData = assertData
  124. p.AutoGenAssert = true
  125. }
  126. if len(p.Fails) == 0 {
  127. p.Success = true
  128. }
  129. return nil
  130. }
  131. func (p *ParserAssert) RunExpression(expression string) (interface{}, error) {
  132. var err error
  133. //debug doesn't make much sense with the ability to evaluate "on the fly"
  134. //var debugFilter *exprhelpers.ExprDebugger
  135. var runtimeFilter *vm.Program
  136. var output interface{}
  137. env := map[string]interface{}{"results": *p.TestData}
  138. if runtimeFilter, err = expr.Compile(expression, expr.Env(exprhelpers.GetExprEnv(env))); err != nil {
  139. return output, err
  140. }
  141. //dump opcode in trace level
  142. log.Tracef("%s", runtimeFilter.Disassemble())
  143. output, err = expr.Run(runtimeFilter, exprhelpers.GetExprEnv(map[string]interface{}{"results": *p.TestData}))
  144. if err != nil {
  145. log.Warningf("running : %s", expression)
  146. log.Warningf("runtime error : %s", err)
  147. return output, errors.Wrapf(err, "while running expression %s", expression)
  148. }
  149. return output, nil
  150. }
  151. func (p *ParserAssert) EvalExpression(expression string) (string, error) {
  152. output, err := p.RunExpression(expression)
  153. if err != nil {
  154. return "", err
  155. }
  156. ret, err := yaml.Marshal(output)
  157. if err != nil {
  158. return "", err
  159. }
  160. return string(ret), nil
  161. }
  162. func (p *ParserAssert) Run(assert string) (bool, error) {
  163. output, err := p.RunExpression(assert)
  164. if err != nil {
  165. return false, err
  166. }
  167. switch out := output.(type) {
  168. case bool:
  169. return out, nil
  170. default:
  171. return false, fmt.Errorf("assertion '%s' is not a condition", assert)
  172. }
  173. }
  174. func Escape(val string) string {
  175. val = strings.ReplaceAll(val, `\`, `\\`)
  176. val = strings.ReplaceAll(val, `"`, `\"`)
  177. return val
  178. }
  179. func (p *ParserAssert) AutoGenParserAssert() string {
  180. //attempt to autogen parser asserts
  181. var ret string
  182. //sort map keys for consistent ordre
  183. var stages []string
  184. for stage := range *p.TestData {
  185. stages = append(stages, stage)
  186. }
  187. sort.Strings(stages)
  188. ret += fmt.Sprintf("len(results) == %d\n", len(*p.TestData))
  189. for _, stage := range stages {
  190. parsers := (*p.TestData)[stage]
  191. //sort map keys for consistent ordre
  192. var pnames []string
  193. for pname := range parsers {
  194. pnames = append(pnames, pname)
  195. }
  196. sort.Strings(pnames)
  197. for _, parser := range pnames {
  198. presults := parsers[parser]
  199. ret += fmt.Sprintf(`len(results["%s"]["%s"]) == %d`+"\n", stage, parser, len(presults))
  200. for pidx, result := range presults {
  201. ret += fmt.Sprintf(`results["%s"]["%s"][%d].Success == %t`+"\n", stage, parser, pidx, result.Success)
  202. if !result.Success {
  203. continue
  204. }
  205. for pkey, pval := range result.Evt.Parsed {
  206. if pval == "" {
  207. continue
  208. }
  209. ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Parsed["%s"] == "%s"`+"\n", stage, parser, pidx, pkey, Escape(pval))
  210. }
  211. for mkey, mval := range result.Evt.Meta {
  212. if mval == "" {
  213. continue
  214. }
  215. ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Meta["%s"] == "%s"`+"\n", stage, parser, pidx, mkey, Escape(mval))
  216. }
  217. for ekey, eval := range result.Evt.Enriched {
  218. if eval == "" {
  219. continue
  220. }
  221. ret += fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Enriched["%s"] == "%s"`+"\n", stage, parser, pidx, ekey, Escape(eval))
  222. }
  223. }
  224. }
  225. }
  226. return ret
  227. }
  228. func LoadParserDump(filepath string) (*ParserResults, error) {
  229. var pdump ParserResults
  230. dumpData, err := os.Open(filepath)
  231. if err != nil {
  232. return nil, err
  233. }
  234. defer dumpData.Close()
  235. results, err := ioutil.ReadAll(dumpData)
  236. if err != nil {
  237. return nil, err
  238. }
  239. if err := yaml.Unmarshal(results, &pdump); err != nil {
  240. return nil, err
  241. }
  242. return &pdump, nil
  243. }
  244. func DumpTree(parser_results ParserResults, bucket_pour BucketPourInfo, details bool) error {
  245. //note : we can use line -> time as the unique identifier (of acquisition)
  246. state := make(map[time.Time]map[string]map[string]ParserResult)
  247. assoc := make(map[time.Time]string, 0)
  248. for stage, parsers := range parser_results {
  249. for parser, results := range parsers {
  250. for _, parser_res := range results {
  251. evt := parser_res.Evt
  252. if _, ok := state[evt.Line.Time]; !ok {
  253. state[evt.Line.Time] = make(map[string]map[string]ParserResult)
  254. assoc[evt.Line.Time] = evt.Line.Raw
  255. }
  256. if _, ok := state[evt.Line.Time][stage]; !ok {
  257. state[evt.Line.Time][stage] = make(map[string]ParserResult)
  258. }
  259. state[evt.Line.Time][stage][parser] = ParserResult{Evt: evt, Success: parser_res.Success}
  260. }
  261. }
  262. }
  263. for bname, evtlist := range bucket_pour {
  264. for _, evt := range evtlist {
  265. if evt.Line.Raw == "" {
  266. continue
  267. }
  268. //it might be bucket oveflow being reprocessed, skip this
  269. if _, ok := state[evt.Line.Time]; !ok {
  270. state[evt.Line.Time] = make(map[string]map[string]ParserResult)
  271. assoc[evt.Line.Time] = evt.Line.Raw
  272. }
  273. //there is a trick : to know if an event succesfully exit the parsers, we check if it reached the pour() phase
  274. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  275. if _, ok := state[evt.Line.Time]["buckets"]; !ok {
  276. state[evt.Line.Time]["buckets"] = make(map[string]ParserResult)
  277. }
  278. state[evt.Line.Time]["buckets"][bname] = ParserResult{Success: true}
  279. }
  280. }
  281. yellow := color.New(color.FgYellow).SprintFunc()
  282. red := color.New(color.FgRed).SprintFunc()
  283. green := color.New(color.FgGreen).SprintFunc()
  284. //get each line
  285. for tstamp, rawstr := range assoc {
  286. fmt.Printf("line: %s\n", rawstr)
  287. skeys := make([]string, 0, len(state[tstamp]))
  288. for k := range state[tstamp] {
  289. //there is a trick : to know if an event succesfully exit the parsers, we check if it reached the pour() phase
  290. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  291. if k == "buckets" {
  292. continue
  293. }
  294. skeys = append(skeys, k)
  295. }
  296. sort.Strings(skeys)
  297. //iterate stage
  298. var prev_item types.Event
  299. for _, stage := range skeys {
  300. parsers := state[tstamp][stage]
  301. sep := "├"
  302. presep := "|"
  303. fmt.Printf("\t%s %s\n", sep, stage)
  304. pkeys := make([]string, 0, len(parsers))
  305. for k := range parsers {
  306. pkeys = append(pkeys, k)
  307. }
  308. sort.Strings(pkeys)
  309. for idx, parser := range pkeys {
  310. res := parsers[parser].Success
  311. sep := "├"
  312. if idx == len(pkeys)-1 {
  313. sep = "└"
  314. }
  315. created := 0
  316. updated := 0
  317. deleted := 0
  318. whitelisted := false
  319. changeStr := ""
  320. detailsDisplay := ""
  321. if res {
  322. if prev_item.Stage == "" {
  323. changeStr = "first_parser"
  324. } else {
  325. changelog, _ := diff.Diff(prev_item, parsers[parser].Evt)
  326. for _, change := range changelog {
  327. switch change.Type {
  328. case "create":
  329. created++
  330. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s %s : %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), green(change.To))
  331. case "update":
  332. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s %s : %s -> %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), change.From, yellow(change.To))
  333. if change.Path[0] == "Whitelisted" && change.To == true {
  334. whitelisted = true
  335. }
  336. updated++
  337. case "delete":
  338. deleted++
  339. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s %s\n", presep, sep, change.Type, red(strings.Join(change.Path, ".")))
  340. }
  341. }
  342. }
  343. prev_item = parsers[parser].Evt
  344. }
  345. if created > 0 {
  346. changeStr += green(fmt.Sprintf("+%d", created))
  347. }
  348. if updated > 0 {
  349. if len(changeStr) > 0 {
  350. changeStr += " "
  351. }
  352. changeStr += yellow(fmt.Sprintf("~%d", updated))
  353. }
  354. if deleted > 0 {
  355. if len(changeStr) > 0 {
  356. changeStr += " "
  357. }
  358. changeStr += red(fmt.Sprintf("-%d", deleted))
  359. }
  360. if whitelisted {
  361. if len(changeStr) > 0 {
  362. changeStr += " "
  363. }
  364. changeStr += red("[whitelisted]")
  365. }
  366. if changeStr == "" {
  367. changeStr = yellow("unchanged")
  368. }
  369. if res {
  370. fmt.Printf("\t%s\t%s %s %s (%s)\n", presep, sep, emoji.GreenCircle, parser, changeStr)
  371. if details {
  372. fmt.Print(detailsDisplay)
  373. }
  374. } else {
  375. fmt.Printf("\t%s\t%s %s %s\n", presep, sep, emoji.RedCircle, parser)
  376. }
  377. }
  378. }
  379. sep := "└"
  380. if len(state[tstamp]["buckets"]) > 0 {
  381. sep = "├"
  382. }
  383. //did the event enter the bucket pour phase ?
  384. if _, ok := state[tstamp]["buckets"]["OK"]; ok {
  385. fmt.Printf("\t%s-------- parser success %s\n", sep, emoji.GreenCircle)
  386. } else {
  387. fmt.Printf("\t%s-------- parser failure %s\n", sep, emoji.RedCircle)
  388. }
  389. //now print bucket info
  390. if len(state[tstamp]["buckets"]) > 0 {
  391. fmt.Printf("\t├ Scenarios\n")
  392. }
  393. bnames := make([]string, 0, len(state[tstamp]["buckets"]))
  394. for k, _ := range state[tstamp]["buckets"] {
  395. //there is a trick : to know if an event succesfully exit the parsers, we check if it reached the pour() phase
  396. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  397. if k == "OK" {
  398. continue
  399. }
  400. bnames = append(bnames, k)
  401. }
  402. sort.Strings(bnames)
  403. for idx, bname := range bnames {
  404. sep := "├"
  405. if idx == len(bnames)-1 {
  406. sep = "└"
  407. }
  408. fmt.Printf("\t\t%s %s %s\n", sep, emoji.GreenCircle, bname)
  409. }
  410. fmt.Println()
  411. }
  412. return nil
  413. }