explain.go 5.6 KB

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