parser_assert.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. package hubtest
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  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)
  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, exprhelpers.GetExprOptions(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, 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. for ekey, eval := range result.Evt.Unmarshaled {
  224. if eval == "" {
  225. continue
  226. }
  227. base := fmt.Sprintf(`results["%s"]["%s"][%d].Evt.Unmarshaled["%s"]`, stage, parser, pidx, ekey)
  228. for _, line := range p.buildUnmarshaledAssert("", eval) {
  229. ret += base + line
  230. }
  231. }
  232. }
  233. }
  234. }
  235. return ret
  236. }
  237. func (p *ParserAssert) buildUnmarshaledAssert(ekey string, eval interface{}) []string {
  238. ret := make([]string, 0)
  239. switch val := eval.(type) {
  240. case map[string]interface{}:
  241. for k, v := range val {
  242. ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...)
  243. }
  244. case map[interface{}]interface{}:
  245. for k, v := range val {
  246. ret = append(ret, p.buildUnmarshaledAssert(fmt.Sprintf(`%s["%s"]`, ekey, k), v)...)
  247. }
  248. case []interface{}:
  249. case string:
  250. ret = append(ret, fmt.Sprintf(`%s == "%s"`+"\n", ekey, Escape(val)))
  251. case bool:
  252. ret = append(ret, fmt.Sprintf(`%s == %t`+"\n", ekey, val))
  253. case int:
  254. ret = append(ret, fmt.Sprintf(`%s == %d`+"\n", ekey, val))
  255. case float64:
  256. ret = append(ret, fmt.Sprintf(`%s == %f`+"\n", ekey, val))
  257. default:
  258. log.Warningf("unknown type '%T' for key '%s'", val, ekey)
  259. }
  260. return ret
  261. }
  262. func LoadParserDump(filepath string) (*ParserResults, error) {
  263. var pdump ParserResults
  264. dumpData, err := os.Open(filepath)
  265. if err != nil {
  266. return nil, err
  267. }
  268. defer dumpData.Close()
  269. results, err := io.ReadAll(dumpData)
  270. if err != nil {
  271. return nil, err
  272. }
  273. if err := yaml.Unmarshal(results, &pdump); err != nil {
  274. return nil, err
  275. }
  276. /* we know that some variables should always be set,
  277. let's check if they're present in last parser output of last stage */
  278. stages := make([]string, 0, len(pdump))
  279. for k := range pdump {
  280. stages = append(stages, k)
  281. }
  282. sort.Strings(stages)
  283. /*the very last one is set to 'success' which is just a bool indicating if the line was successfully parsed*/
  284. lastStage := stages[len(stages)-2]
  285. parsers := make([]string, 0, len(pdump[lastStage]))
  286. for k := range pdump[lastStage] {
  287. parsers = append(parsers, k)
  288. }
  289. sort.Strings(parsers)
  290. lastParser := parsers[len(parsers)-1]
  291. for idx, result := range pdump[lastStage][lastParser] {
  292. if result.Evt.StrTime == "" {
  293. log.Warningf("Line %d/%d is missing evt.StrTime. It is most likely a mistake as it will prevent your logs to be processed in time-machine/forensic mode.", idx, len(pdump[lastStage][lastParser]))
  294. } else {
  295. log.Debugf("Line %d/%d has evt.StrTime set to '%s'", idx, len(pdump[lastStage][lastParser]), result.Evt.StrTime)
  296. }
  297. }
  298. return &pdump, nil
  299. }
  300. type DumpOpts struct {
  301. Details bool
  302. SkipOk bool
  303. ShowNotOkParsers bool
  304. }
  305. func DumpTree(parser_results ParserResults, bucket_pour BucketPourInfo, opts DumpOpts) {
  306. //note : we can use line -> time as the unique identifier (of acquisition)
  307. state := make(map[time.Time]map[string]map[string]ParserResult)
  308. assoc := make(map[time.Time]string, 0)
  309. for stage, parsers := range parser_results {
  310. for parser, results := range parsers {
  311. for _, parser_res := range results {
  312. evt := parser_res.Evt
  313. if _, ok := state[evt.Line.Time]; !ok {
  314. state[evt.Line.Time] = make(map[string]map[string]ParserResult)
  315. assoc[evt.Line.Time] = evt.Line.Raw
  316. }
  317. if _, ok := state[evt.Line.Time][stage]; !ok {
  318. state[evt.Line.Time][stage] = make(map[string]ParserResult)
  319. }
  320. state[evt.Line.Time][stage][parser] = ParserResult{Evt: evt, Success: parser_res.Success}
  321. }
  322. }
  323. }
  324. for bname, evtlist := range bucket_pour {
  325. for _, evt := range evtlist {
  326. if evt.Line.Raw == "" {
  327. continue
  328. }
  329. //it might be bucket overflow being reprocessed, skip this
  330. if _, ok := state[evt.Line.Time]; !ok {
  331. state[evt.Line.Time] = make(map[string]map[string]ParserResult)
  332. assoc[evt.Line.Time] = evt.Line.Raw
  333. }
  334. //there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
  335. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  336. if _, ok := state[evt.Line.Time]["buckets"]; !ok {
  337. state[evt.Line.Time]["buckets"] = make(map[string]ParserResult)
  338. }
  339. state[evt.Line.Time]["buckets"][bname] = ParserResult{Success: true}
  340. }
  341. }
  342. yellow := color.New(color.FgYellow).SprintFunc()
  343. red := color.New(color.FgRed).SprintFunc()
  344. green := color.New(color.FgGreen).SprintFunc()
  345. whitelistReason := ""
  346. //get each line
  347. for tstamp, rawstr := range assoc {
  348. if opts.SkipOk {
  349. if _, ok := state[tstamp]["buckets"]["OK"]; ok {
  350. continue
  351. }
  352. }
  353. fmt.Printf("line: %s\n", rawstr)
  354. skeys := make([]string, 0, len(state[tstamp]))
  355. for k := range state[tstamp] {
  356. //there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
  357. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  358. if k == "buckets" {
  359. continue
  360. }
  361. skeys = append(skeys, k)
  362. }
  363. sort.Strings(skeys)
  364. //iterate stage
  365. var prev_item types.Event
  366. for _, stage := range skeys {
  367. parsers := state[tstamp][stage]
  368. sep := "├"
  369. presep := "|"
  370. fmt.Printf("\t%s %s\n", sep, stage)
  371. pkeys := make([]string, 0, len(parsers))
  372. for k := range parsers {
  373. pkeys = append(pkeys, k)
  374. }
  375. sort.Strings(pkeys)
  376. for idx, parser := range pkeys {
  377. res := parsers[parser].Success
  378. sep := "├"
  379. if idx == len(pkeys)-1 {
  380. sep = "└"
  381. }
  382. created := 0
  383. updated := 0
  384. deleted := 0
  385. whitelisted := false
  386. changeStr := ""
  387. detailsDisplay := ""
  388. if res {
  389. changelog, _ := diff.Diff(prev_item, parsers[parser].Evt)
  390. for _, change := range changelog {
  391. switch change.Type {
  392. case "create":
  393. created++
  394. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), green(change.To))
  395. case "update":
  396. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s : %s -> %s\n", presep, sep, change.Type, strings.Join(change.Path, "."), change.From, yellow(change.To))
  397. if change.Path[0] == "Whitelisted" && change.To == true {
  398. whitelisted = true
  399. if whitelistReason == "" {
  400. whitelistReason = parsers[parser].Evt.WhitelistReason
  401. }
  402. }
  403. updated++
  404. case "delete":
  405. deleted++
  406. detailsDisplay += fmt.Sprintf("\t%s\t\t%s %s evt.%s\n", presep, sep, change.Type, red(strings.Join(change.Path, ".")))
  407. }
  408. }
  409. prev_item = parsers[parser].Evt
  410. }
  411. if created > 0 {
  412. changeStr += green(fmt.Sprintf("+%d", created))
  413. }
  414. if updated > 0 {
  415. if len(changeStr) > 0 {
  416. changeStr += " "
  417. }
  418. changeStr += yellow(fmt.Sprintf("~%d", updated))
  419. }
  420. if deleted > 0 {
  421. if len(changeStr) > 0 {
  422. changeStr += " "
  423. }
  424. changeStr += red(fmt.Sprintf("-%d", deleted))
  425. }
  426. if whitelisted {
  427. if len(changeStr) > 0 {
  428. changeStr += " "
  429. }
  430. changeStr += red("[whitelisted]")
  431. }
  432. if changeStr == "" {
  433. changeStr = yellow("unchanged")
  434. }
  435. if res {
  436. fmt.Printf("\t%s\t%s %s %s (%s)\n", presep, sep, emoji.GreenCircle, parser, changeStr)
  437. if opts.Details {
  438. fmt.Print(detailsDisplay)
  439. }
  440. } else if opts.ShowNotOkParsers {
  441. fmt.Printf("\t%s\t%s %s %s\n", presep, sep, emoji.RedCircle, parser)
  442. }
  443. }
  444. }
  445. sep := "└"
  446. if len(state[tstamp]["buckets"]) > 0 {
  447. sep = "├"
  448. }
  449. //did the event enter the bucket pour phase ?
  450. if _, ok := state[tstamp]["buckets"]["OK"]; ok {
  451. fmt.Printf("\t%s-------- parser success %s\n", sep, emoji.GreenCircle)
  452. } else if whitelistReason != "" {
  453. fmt.Printf("\t%s-------- parser success, ignored by whitelist (%s) %s\n", sep, whitelistReason, emoji.GreenCircle)
  454. } else {
  455. fmt.Printf("\t%s-------- parser failure %s\n", sep, emoji.RedCircle)
  456. }
  457. //now print bucket info
  458. if len(state[tstamp]["buckets"]) > 0 {
  459. fmt.Printf("\t├ Scenarios\n")
  460. }
  461. bnames := make([]string, 0, len(state[tstamp]["buckets"]))
  462. for k := range state[tstamp]["buckets"] {
  463. //there is a trick : to know if an event successfully exit the parsers, we check if it reached the pour() phase
  464. //we thus use a fake stage "buckets" and a fake parser "OK" to know if it entered
  465. if k == "OK" {
  466. continue
  467. }
  468. bnames = append(bnames, k)
  469. }
  470. sort.Strings(bnames)
  471. for idx, bname := range bnames {
  472. sep := "├"
  473. if idx == len(bnames)-1 {
  474. sep = "└"
  475. }
  476. fmt.Printf("\t\t%s %s %s\n", sep, emoji.GreenCircle, bname)
  477. }
  478. fmt.Println()
  479. }
  480. }