simulation.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "slices"
  6. log "github.com/sirupsen/logrus"
  7. "github.com/spf13/cobra"
  8. "gopkg.in/yaml.v2"
  9. "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
  10. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  11. )
  12. type cliSimulation struct {
  13. cfg configGetter
  14. }
  15. func NewCLISimulation(cfg configGetter) *cliSimulation {
  16. return &cliSimulation{
  17. cfg: cfg,
  18. }
  19. }
  20. func (cli *cliSimulation) NewCommand() *cobra.Command {
  21. cmd := &cobra.Command{
  22. Use: "simulation [command]",
  23. Short: "Manage simulation status of scenarios",
  24. Example: `cscli simulation status
  25. cscli simulation enable crowdsecurity/ssh-bf
  26. cscli simulation disable crowdsecurity/ssh-bf`,
  27. DisableAutoGenTag: true,
  28. PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
  29. if err := cli.cfg().LoadSimulation(); err != nil {
  30. return err
  31. }
  32. if cli.cfg().Cscli.SimulationConfig == nil {
  33. return fmt.Errorf("no simulation configured")
  34. }
  35. return nil
  36. },
  37. PersistentPostRun: func(cmd *cobra.Command, _ []string) {
  38. if cmd.Name() != "status" {
  39. log.Infof(ReloadMessage())
  40. }
  41. },
  42. }
  43. cmd.Flags().SortFlags = false
  44. cmd.PersistentFlags().SortFlags = false
  45. cmd.AddCommand(cli.NewEnableCmd())
  46. cmd.AddCommand(cli.NewDisableCmd())
  47. cmd.AddCommand(cli.NewStatusCmd())
  48. return cmd
  49. }
  50. func (cli *cliSimulation) NewEnableCmd() *cobra.Command {
  51. var forceGlobalSimulation bool
  52. cmd := &cobra.Command{
  53. Use: "enable [scenario] [-global]",
  54. Short: "Enable the simulation, globally or on specified scenarios",
  55. Example: `cscli simulation enable`,
  56. DisableAutoGenTag: true,
  57. RunE: func(cmd *cobra.Command, args []string) error {
  58. hub, err := require.Hub(cli.cfg(), nil, nil)
  59. if err != nil {
  60. return err
  61. }
  62. if len(args) > 0 {
  63. for _, scenario := range args {
  64. var item = hub.GetItem(cwhub.SCENARIOS, scenario)
  65. if item == nil {
  66. log.Errorf("'%s' doesn't exist or is not a scenario", scenario)
  67. continue
  68. }
  69. if !item.State.Installed {
  70. log.Warningf("'%s' isn't enabled", scenario)
  71. }
  72. isExcluded := slices.Contains(cli.cfg().Cscli.SimulationConfig.Exclusions, scenario)
  73. if *cli.cfg().Cscli.SimulationConfig.Simulation && !isExcluded {
  74. log.Warning("global simulation is already enabled")
  75. continue
  76. }
  77. if !*cli.cfg().Cscli.SimulationConfig.Simulation && isExcluded {
  78. log.Warningf("simulation for '%s' already enabled", scenario)
  79. continue
  80. }
  81. if *cli.cfg().Cscli.SimulationConfig.Simulation && isExcluded {
  82. cli.removeFromExclusion(scenario)
  83. log.Printf("simulation enabled for '%s'", scenario)
  84. continue
  85. }
  86. cli.addToExclusion(scenario)
  87. log.Printf("simulation mode for '%s' enabled", scenario)
  88. }
  89. if err := cli.dumpSimulationFile(); err != nil {
  90. return fmt.Errorf("simulation enable: %s", err)
  91. }
  92. } else if forceGlobalSimulation {
  93. if err := cli.enableGlobalSimulation(); err != nil {
  94. return fmt.Errorf("unable to enable global simulation mode: %s", err)
  95. }
  96. } else {
  97. printHelp(cmd)
  98. }
  99. return nil
  100. },
  101. }
  102. cmd.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Enable global simulation (reverse mode)")
  103. return cmd
  104. }
  105. func (cli *cliSimulation) NewDisableCmd() *cobra.Command {
  106. var forceGlobalSimulation bool
  107. cmd := &cobra.Command{
  108. Use: "disable [scenario]",
  109. Short: "Disable the simulation mode. Disable only specified scenarios",
  110. Example: `cscli simulation disable`,
  111. DisableAutoGenTag: true,
  112. RunE: func(cmd *cobra.Command, args []string) error {
  113. if len(args) > 0 {
  114. for _, scenario := range args {
  115. isExcluded := slices.Contains(cli.cfg().Cscli.SimulationConfig.Exclusions, scenario)
  116. if !*cli.cfg().Cscli.SimulationConfig.Simulation && !isExcluded {
  117. log.Warningf("%s isn't in simulation mode", scenario)
  118. continue
  119. }
  120. if !*cli.cfg().Cscli.SimulationConfig.Simulation && isExcluded {
  121. cli.removeFromExclusion(scenario)
  122. log.Printf("simulation mode for '%s' disabled", scenario)
  123. continue
  124. }
  125. if isExcluded {
  126. log.Warningf("simulation mode is enabled but is already disable for '%s'", scenario)
  127. continue
  128. }
  129. cli.addToExclusion(scenario)
  130. log.Printf("simulation mode for '%s' disabled", scenario)
  131. }
  132. if err := cli.dumpSimulationFile(); err != nil {
  133. return fmt.Errorf("simulation disable: %s", err)
  134. }
  135. } else if forceGlobalSimulation {
  136. if err := cli.disableGlobalSimulation(); err != nil {
  137. return fmt.Errorf("unable to disable global simulation mode: %s", err)
  138. }
  139. } else {
  140. printHelp(cmd)
  141. }
  142. return nil
  143. },
  144. }
  145. cmd.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Disable global simulation (reverse mode)")
  146. return cmd
  147. }
  148. func (cli *cliSimulation) NewStatusCmd() *cobra.Command {
  149. cmd := &cobra.Command{
  150. Use: "status",
  151. Short: "Show simulation mode status",
  152. Example: `cscli simulation status`,
  153. DisableAutoGenTag: true,
  154. Run: func(_ *cobra.Command, _ []string) {
  155. cli.status()
  156. },
  157. PersistentPostRun: func(cmd *cobra.Command, args []string) {
  158. },
  159. }
  160. return cmd
  161. }
  162. func (cli *cliSimulation) addToExclusion(name string) {
  163. cfg := cli.cfg()
  164. cfg.Cscli.SimulationConfig.Exclusions = append(cfg.Cscli.SimulationConfig.Exclusions, name)
  165. }
  166. func (cli *cliSimulation) removeFromExclusion(name string) {
  167. cfg := cli.cfg()
  168. index := slices.Index(cfg.Cscli.SimulationConfig.Exclusions, name)
  169. // Remove element from the slice
  170. cfg.Cscli.SimulationConfig.Exclusions[index] = cfg.Cscli.SimulationConfig.Exclusions[len(cfg.Cscli.SimulationConfig.Exclusions)-1]
  171. cfg.Cscli.SimulationConfig.Exclusions[len(cfg.Cscli.SimulationConfig.Exclusions)-1] = ""
  172. cfg.Cscli.SimulationConfig.Exclusions = cfg.Cscli.SimulationConfig.Exclusions[:len(cfg.Cscli.SimulationConfig.Exclusions)-1]
  173. }
  174. func (cli *cliSimulation) enableGlobalSimulation() error {
  175. cfg := cli.cfg()
  176. cfg.Cscli.SimulationConfig.Simulation = new(bool)
  177. *cfg.Cscli.SimulationConfig.Simulation = true
  178. cfg.Cscli.SimulationConfig.Exclusions = []string{}
  179. if err := cli.dumpSimulationFile(); err != nil {
  180. return fmt.Errorf("unable to dump simulation file: %s", err)
  181. }
  182. log.Printf("global simulation: enabled")
  183. return nil
  184. }
  185. func (cli *cliSimulation) dumpSimulationFile() error {
  186. cfg := cli.cfg()
  187. newConfigSim, err := yaml.Marshal(cfg.Cscli.SimulationConfig)
  188. if err != nil {
  189. return fmt.Errorf("unable to marshal simulation configuration: %s", err)
  190. }
  191. err = os.WriteFile(cfg.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
  192. if err != nil {
  193. return fmt.Errorf("write simulation config in '%s' failed: %s", cfg.ConfigPaths.SimulationFilePath, err)
  194. }
  195. log.Debugf("updated simulation file %s", cfg.ConfigPaths.SimulationFilePath)
  196. return nil
  197. }
  198. func (cli *cliSimulation) disableGlobalSimulation() error {
  199. cfg := cli.cfg()
  200. cfg.Cscli.SimulationConfig.Simulation = new(bool)
  201. *cfg.Cscli.SimulationConfig.Simulation = false
  202. cfg.Cscli.SimulationConfig.Exclusions = []string{}
  203. newConfigSim, err := yaml.Marshal(cfg.Cscli.SimulationConfig)
  204. if err != nil {
  205. return fmt.Errorf("unable to marshal new simulation configuration: %s", err)
  206. }
  207. err = os.WriteFile(cfg.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
  208. if err != nil {
  209. return fmt.Errorf("unable to write new simulation config in '%s': %s", cfg.ConfigPaths.SimulationFilePath, err)
  210. }
  211. log.Printf("global simulation: disabled")
  212. return nil
  213. }
  214. func (cli *cliSimulation) status() {
  215. cfg := cli.cfg()
  216. if cfg.Cscli.SimulationConfig == nil {
  217. log.Printf("global simulation: disabled (configuration file is missing)")
  218. return
  219. }
  220. if *cfg.Cscli.SimulationConfig.Simulation {
  221. log.Println("global simulation: enabled")
  222. if len(cfg.Cscli.SimulationConfig.Exclusions) > 0 {
  223. log.Println("Scenarios not in simulation mode :")
  224. for _, scenario := range cfg.Cscli.SimulationConfig.Exclusions {
  225. log.Printf(" - %s", scenario)
  226. }
  227. }
  228. } else {
  229. log.Println("global simulation: disabled")
  230. if len(cfg.Cscli.SimulationConfig.Exclusions) > 0 {
  231. log.Println("Scenarios in simulation mode :")
  232. for _, scenario := range cfg.Cscli.SimulationConfig.Exclusions {
  233. log.Printf(" - %s", scenario)
  234. }
  235. }
  236. }
  237. }