explain.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/spf13/cobra"
  11. "github.com/crowdsecurity/crowdsec/pkg/hubtest"
  12. "github.com/crowdsecurity/crowdsec/pkg/types"
  13. )
  14. func runExplain(cmd *cobra.Command, args []string) error {
  15. flags := cmd.Flags()
  16. logFile, err := flags.GetString("file")
  17. if err != nil {
  18. return err
  19. }
  20. dsn, err := flags.GetString("dsn")
  21. if err != nil {
  22. return err
  23. }
  24. logLine, err := flags.GetString("log")
  25. if err != nil {
  26. return err
  27. }
  28. logType, err := flags.GetString("type")
  29. if err != nil {
  30. return err
  31. }
  32. opts := hubtest.DumpOpts{}
  33. opts.Details, err = flags.GetBool("verbose")
  34. if err != nil {
  35. return err
  36. }
  37. opts.SkipOk, err = flags.GetBool("failures")
  38. if err != nil {
  39. return err
  40. }
  41. opts.ShowNotOkParsers, err = flags.GetBool("only-successful-parsers")
  42. opts.ShowNotOkParsers = !opts.ShowNotOkParsers
  43. if err != nil {
  44. return err
  45. }
  46. crowdsec, err := flags.GetString("crowdsec")
  47. if err != nil {
  48. return err
  49. }
  50. fileInfo, _ := os.Stdin.Stat()
  51. if logType == "" || (logLine == "" && logFile == "" && dsn == "") {
  52. printHelp(cmd)
  53. fmt.Println()
  54. fmt.Printf("Please provide --type flag\n")
  55. os.Exit(1)
  56. }
  57. if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) {
  58. return fmt.Errorf("the option -f - is intended to work with pipes")
  59. }
  60. var f *os.File
  61. // using empty string fallback to /tmp
  62. dir, err := os.MkdirTemp("", "cscli_explain")
  63. if err != nil {
  64. return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err)
  65. }
  66. tmpFile := ""
  67. // we create a temporary log file if a log line/stdin has been provided
  68. if logLine != "" || logFile == "-" {
  69. tmpFile = filepath.Join(dir, "cscli_test_tmp.log")
  70. f, err = os.Create(tmpFile)
  71. if err != nil {
  72. return err
  73. }
  74. if logLine != "" {
  75. _, err = f.WriteString(logLine)
  76. if err != nil {
  77. return err
  78. }
  79. } else if logFile == "-" {
  80. reader := bufio.NewReader(os.Stdin)
  81. errCount := 0
  82. for {
  83. input, err := reader.ReadBytes('\n')
  84. if err != nil && err == io.EOF {
  85. break
  86. }
  87. _, err = f.Write(input)
  88. if err != nil {
  89. errCount++
  90. }
  91. }
  92. if errCount > 0 {
  93. log.Warnf("Failed to write %d lines to tmp file", errCount)
  94. }
  95. }
  96. f.Close()
  97. // this is the file that was going to be read by crowdsec anyway
  98. logFile = tmpFile
  99. }
  100. if logFile != "" {
  101. absolutePath, err := filepath.Abs(logFile)
  102. if err != nil {
  103. return fmt.Errorf("unable to get absolute path of '%s', exiting", logFile)
  104. }
  105. dsn = fmt.Sprintf("file://%s", absolutePath)
  106. lineCount := types.GetLineCountForFile(absolutePath)
  107. if lineCount > 100 {
  108. log.Warnf("log file contains %d lines. This may take lot of resources.", lineCount)
  109. }
  110. }
  111. if dsn == "" {
  112. return fmt.Errorf("no acquisition (--file or --dsn) provided, can't run cscli test")
  113. }
  114. cmdArgs := []string{"-c", ConfigFilePath, "-type", logType, "-dsn", dsn, "-dump-data", dir, "-no-api"}
  115. crowdsecCmd := exec.Command(crowdsec, cmdArgs...)
  116. output, err := crowdsecCmd.CombinedOutput()
  117. if err != nil {
  118. fmt.Println(string(output))
  119. return fmt.Errorf("fail to run crowdsec for test: %v", err)
  120. }
  121. // rm the temporary log file if only a log line/stdin was provided
  122. if tmpFile != "" {
  123. if err := os.Remove(tmpFile); err != nil {
  124. return fmt.Errorf("unable to remove tmp log file '%s': %+v", tmpFile, err)
  125. }
  126. }
  127. parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
  128. bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName)
  129. parserDump, err := hubtest.LoadParserDump(parserDumpFile)
  130. if err != nil {
  131. return fmt.Errorf("unable to load parser dump result: %s", err)
  132. }
  133. bucketStateDump, err := hubtest.LoadBucketPourDump(bucketStateDumpFile)
  134. if err != nil {
  135. return fmt.Errorf("unable to load bucket dump result: %s", err)
  136. }
  137. hubtest.DumpTree(*parserDump, *bucketStateDump, opts)
  138. if err := os.RemoveAll(dir); err != nil {
  139. return fmt.Errorf("unable to delete temporary directory '%s': %s", dir, err)
  140. }
  141. return nil
  142. }
  143. func NewExplainCmd() *cobra.Command {
  144. cmdExplain := &cobra.Command{
  145. Use: "explain",
  146. Short: "Explain log pipeline",
  147. Long: `
  148. Explain log pipeline
  149. `,
  150. Example: `
  151. cscli explain --file ./myfile.log --type nginx
  152. cscli explain --log "Sep 19 18:33:22 scw-d95986 sshd[24347]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=1.2.3.4" --type syslog
  153. cscli explain --dsn "file://myfile.log" --type nginx
  154. tail -n 5 myfile.log | cscli explain --type nginx -f -
  155. `,
  156. Args: cobra.ExactArgs(0),
  157. DisableAutoGenTag: true,
  158. RunE: runExplain,
  159. }
  160. flags := cmdExplain.Flags()
  161. flags.StringP("file", "f", "", "Log file to test")
  162. flags.StringP("dsn", "d", "", "DSN to test")
  163. flags.StringP("log", "l", "", "Log line to test")
  164. flags.StringP("type", "t", "", "Type of the acquisition to test")
  165. flags.BoolP("verbose", "v", false, "Display individual changes")
  166. flags.Bool("failures", false, "Only show failed lines")
  167. flags.Bool("only-successful-parsers", false, "Only show successful parsers")
  168. flags.String("crowdsec", "crowdsec", "Path to crowdsec")
  169. return cmdExplain
  170. }