parser_assert.go 15 KB

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