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