install.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package setup
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. goccyyaml "github.com/goccy/go-yaml"
  9. "gopkg.in/yaml.v3"
  10. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  11. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  12. )
  13. // AcquisDocument is created from a SetupItem. It represents a single YAML document, and can be part of a multi-document file.
  14. type AcquisDocument struct {
  15. AcquisFilename string
  16. DataSource map[string]interface{}
  17. }
  18. func decodeSetup(input []byte, fancyErrors bool) (Setup, error) {
  19. ret := Setup{}
  20. // parse with goccy to have better error messages in many cases
  21. dec := goccyyaml.NewDecoder(bytes.NewBuffer(input), goccyyaml.Strict())
  22. if err := dec.Decode(&ret); err != nil {
  23. if fancyErrors {
  24. return ret, fmt.Errorf("%v", goccyyaml.FormatError(err, true, true))
  25. }
  26. // XXX errors here are multiline, should we just print them to stderr instead of logging?
  27. return ret, fmt.Errorf("%v", err)
  28. }
  29. // parse again because goccy is not strict enough anyway
  30. dec2 := yaml.NewDecoder(bytes.NewBuffer(input))
  31. dec2.KnownFields(true)
  32. if err := dec2.Decode(&ret); err != nil {
  33. return ret, fmt.Errorf("while unmarshaling setup file: %w", err)
  34. }
  35. return ret, nil
  36. }
  37. // InstallHubItems installs the objects recommended in a setup file.
  38. func InstallHubItems(csConfig *csconfig.Config, input []byte, dryRun bool) error {
  39. setupEnvelope, err := decodeSetup(input, false)
  40. if err != nil {
  41. return err
  42. }
  43. if err := csConfig.LoadHub(); err != nil {
  44. return fmt.Errorf("loading hub: %w", err)
  45. }
  46. cwhub.SetHubBranch()
  47. if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
  48. return fmt.Errorf("getting hub index: %w", err)
  49. }
  50. for _, setupItem := range setupEnvelope.Setup {
  51. forceAction := false
  52. downloadOnly := false
  53. install := setupItem.Install
  54. if install == nil {
  55. continue
  56. }
  57. if len(install.Collections) > 0 {
  58. for _, collection := range setupItem.Install.Collections {
  59. if dryRun {
  60. fmt.Println("dry-run: would install collection", collection)
  61. continue
  62. }
  63. if err := cwhub.InstallItem(csConfig, collection, cwhub.COLLECTIONS, forceAction, downloadOnly); err != nil {
  64. return fmt.Errorf("while installing collection %s: %w", collection, err)
  65. }
  66. }
  67. }
  68. if len(install.Parsers) > 0 {
  69. for _, parser := range setupItem.Install.Parsers {
  70. if dryRun {
  71. fmt.Println("dry-run: would install parser", parser)
  72. continue
  73. }
  74. if err := cwhub.InstallItem(csConfig, parser, cwhub.PARSERS, forceAction, downloadOnly); err != nil {
  75. return fmt.Errorf("while installing parser %s: %w", parser, err)
  76. }
  77. }
  78. }
  79. if len(install.Scenarios) > 0 {
  80. for _, scenario := range setupItem.Install.Scenarios {
  81. if dryRun {
  82. fmt.Println("dry-run: would install scenario", scenario)
  83. continue
  84. }
  85. if err := cwhub.InstallItem(csConfig, scenario, cwhub.SCENARIOS, forceAction, downloadOnly); err != nil {
  86. return fmt.Errorf("while installing scenario %s: %w", scenario, err)
  87. }
  88. }
  89. }
  90. if len(install.PostOverflows) > 0 {
  91. for _, postoverflow := range setupItem.Install.PostOverflows {
  92. if dryRun {
  93. fmt.Println("dry-run: would install postoverflow", postoverflow)
  94. continue
  95. }
  96. if err := cwhub.InstallItem(csConfig, postoverflow, cwhub.POSTOVERFLOWS, forceAction, downloadOnly); err != nil {
  97. return fmt.Errorf("while installing postoverflow %s: %w", postoverflow, err)
  98. }
  99. }
  100. }
  101. }
  102. return nil
  103. }
  104. // marshalAcquisDocuments creates the monolithic file, or itemized files (if a directory is provided) with the acquisition documents.
  105. func marshalAcquisDocuments(ads []AcquisDocument, toDir string) (string, error) {
  106. var sb strings.Builder
  107. dashTerminator := false
  108. disclaimer := `
  109. #
  110. # This file was automatically generated by "cscli setup datasources".
  111. # You can modify it by hand, but will be responsible for its maintenance.
  112. # To add datasources or logfiles, you can instead write a new configuration
  113. # in the directory defined by acquisition_dir.
  114. #
  115. `
  116. if toDir == "" {
  117. sb.WriteString(disclaimer)
  118. } else {
  119. _, err := os.Stat(toDir)
  120. if os.IsNotExist(err) {
  121. return "", fmt.Errorf("directory %s does not exist", toDir)
  122. }
  123. }
  124. for _, ad := range ads {
  125. out, err := goccyyaml.MarshalWithOptions(ad.DataSource, goccyyaml.IndentSequence(true))
  126. if err != nil {
  127. return "", fmt.Errorf("while encoding datasource: %w", err)
  128. }
  129. if toDir != "" {
  130. if ad.AcquisFilename == "" {
  131. return "", fmt.Errorf("empty acquis filename")
  132. }
  133. fname := filepath.Join(toDir, ad.AcquisFilename)
  134. fmt.Println("creating", fname)
  135. f, err := os.Create(fname)
  136. if err != nil {
  137. return "", fmt.Errorf("creating acquisition file: %w", err)
  138. }
  139. defer f.Close()
  140. _, err = f.WriteString(disclaimer)
  141. if err != nil {
  142. return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
  143. }
  144. _, err = f.Write(out)
  145. if err != nil {
  146. return "", fmt.Errorf("while writing to %s: %w", ad.AcquisFilename, err)
  147. }
  148. f.Sync()
  149. continue
  150. }
  151. if dashTerminator {
  152. sb.WriteString("---\n")
  153. }
  154. sb.Write(out)
  155. dashTerminator = true
  156. }
  157. return sb.String(), nil
  158. }
  159. // Validate checks the validity of a setup file.
  160. func Validate(input []byte) error {
  161. _, err := decodeSetup(input, true)
  162. if err != nil {
  163. return err
  164. }
  165. return nil
  166. }
  167. // DataSources generates the acquisition documents from a setup file.
  168. func DataSources(input []byte, toDir string) (string, error) {
  169. setupEnvelope, err := decodeSetup(input, false)
  170. if err != nil {
  171. return "", err
  172. }
  173. ads := make([]AcquisDocument, 0)
  174. filename := func(basename string, ext string) string {
  175. if basename == "" {
  176. return basename
  177. }
  178. return basename + ext
  179. }
  180. for _, setupItem := range setupEnvelope.Setup {
  181. datasource := setupItem.DataSource
  182. basename := ""
  183. if toDir != "" {
  184. basename = "setup." + setupItem.DetectedService
  185. }
  186. if datasource == nil {
  187. continue
  188. }
  189. ad := AcquisDocument{
  190. AcquisFilename: filename(basename, ".yaml"),
  191. DataSource: datasource,
  192. }
  193. ads = append(ads, ad)
  194. }
  195. return marshalAcquisDocuments(ads, toDir)
  196. }