main.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package main
  2. import (
  3. "os"
  4. "slices"
  5. "time"
  6. "github.com/fatih/color"
  7. cc "github.com/ivanpirog/coloredcobra"
  8. log "github.com/sirupsen/logrus"
  9. "github.com/spf13/cobra"
  10. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  11. "github.com/crowdsecurity/crowdsec/pkg/database"
  12. "github.com/crowdsecurity/crowdsec/pkg/fflag"
  13. )
  14. var ConfigFilePath string
  15. var csConfig *csconfig.Config
  16. var dbClient *database.Client
  17. type configGetter func() *csconfig.Config
  18. var mergedConfig string
  19. type cliRoot struct {
  20. logTrace bool
  21. logDebug bool
  22. logInfo bool
  23. logWarn bool
  24. logErr bool
  25. outputColor string
  26. outputFormat string
  27. // flagBranch overrides the value in csConfig.Cscli.HubBranch
  28. flagBranch string
  29. }
  30. func newCliRoot() *cliRoot {
  31. return &cliRoot{}
  32. }
  33. // cfg() is a helper function to get the configuration loaded from config.yaml,
  34. // we pass it to subcommands because the file is not read until the Execute() call
  35. func (cli *cliRoot) cfg() *csconfig.Config {
  36. return csConfig
  37. }
  38. // wantedLogLevel returns the log level requested in the command line flags.
  39. func (cli *cliRoot) wantedLogLevel() log.Level {
  40. switch {
  41. case cli.logTrace:
  42. return log.TraceLevel
  43. case cli.logDebug:
  44. return log.DebugLevel
  45. case cli.logInfo:
  46. return log.InfoLevel
  47. case cli.logWarn:
  48. return log.WarnLevel
  49. case cli.logErr:
  50. return log.ErrorLevel
  51. default:
  52. return log.InfoLevel
  53. }
  54. }
  55. // loadConfigFor loads the configuration file for the given sub-command.
  56. // If the sub-command does not need it, it returns a default configuration.
  57. func loadConfigFor(command string) (*csconfig.Config, string, error) {
  58. noNeedConfig := []string{
  59. "doc",
  60. "help",
  61. "completion",
  62. "version",
  63. "hubtest",
  64. }
  65. if !slices.Contains(noNeedConfig, command) {
  66. log.Debugf("Using %s as configuration file", ConfigFilePath)
  67. config, merged, err := csconfig.NewConfig(ConfigFilePath, false, false, true)
  68. if err != nil {
  69. return nil, "", err
  70. }
  71. return config, merged, nil
  72. }
  73. return csconfig.NewDefaultConfig(), "", nil
  74. }
  75. // initialize is called before the subcommand is executed.
  76. func (cli *cliRoot) initialize() {
  77. var err error
  78. log.SetLevel(cli.wantedLogLevel())
  79. csConfig, mergedConfig, err = loadConfigFor(os.Args[1])
  80. if err != nil {
  81. log.Fatal(err)
  82. }
  83. // recap of the enabled feature flags, because logging
  84. // was not enabled when we set them from envvars
  85. if fflist := csconfig.ListFeatureFlags(); fflist != "" {
  86. log.Debugf("Enabled feature flags: %s", fflist)
  87. }
  88. if cli.flagBranch != "" {
  89. csConfig.Cscli.HubBranch = cli.flagBranch
  90. }
  91. if cli.outputFormat != "" {
  92. csConfig.Cscli.Output = cli.outputFormat
  93. }
  94. if csConfig.Cscli.Output == "" {
  95. csConfig.Cscli.Output = "human"
  96. }
  97. if csConfig.Cscli.Output != "human" && csConfig.Cscli.Output != "json" && csConfig.Cscli.Output != "raw" {
  98. log.Fatalf("output format '%s' not supported: must be one of human, json, raw", csConfig.Cscli.Output)
  99. }
  100. if csConfig.Cscli.Output == "json" {
  101. log.SetFormatter(&log.JSONFormatter{})
  102. log.SetLevel(log.ErrorLevel)
  103. } else if csConfig.Cscli.Output == "raw" {
  104. log.SetLevel(log.ErrorLevel)
  105. }
  106. if cli.outputColor != "" {
  107. csConfig.Cscli.Color = cli.outputColor
  108. if cli.outputColor != "yes" && cli.outputColor != "no" && cli.outputColor != "auto" {
  109. log.Fatalf("output color %s unknown", cli.outputColor)
  110. }
  111. }
  112. }
  113. // list of valid subcommands for the shell completion
  114. var validArgs = []string{
  115. "alerts", "appsec-configs", "appsec-rules", "bouncers", "capi", "collections",
  116. "completion", "config", "console", "contexts", "dashboard", "decisions", "explain",
  117. "hub", "hubtest", "lapi", "machines", "metrics", "notifications", "parsers",
  118. "postoverflows", "scenarios", "simulation", "support", "version",
  119. }
  120. func (cli *cliRoot) colorize(cmd *cobra.Command) {
  121. cc.Init(&cc.Config{
  122. RootCmd: cmd,
  123. Headings: cc.Yellow,
  124. Commands: cc.Green + cc.Bold,
  125. CmdShortDescr: cc.Cyan,
  126. Example: cc.Italic,
  127. ExecName: cc.Bold,
  128. Aliases: cc.Bold + cc.Italic,
  129. FlagsDataType: cc.White,
  130. Flags: cc.Green,
  131. FlagsDescr: cc.Cyan,
  132. NoExtraNewlines: true,
  133. NoBottomNewline: true,
  134. })
  135. cmd.SetOut(color.Output)
  136. }
  137. func (cli *cliRoot) NewCommand() *cobra.Command {
  138. // set the formatter asap and worry about level later
  139. logFormatter := &log.TextFormatter{TimestampFormat: time.RFC3339, FullTimestamp: true}
  140. log.SetFormatter(logFormatter)
  141. if err := fflag.RegisterAllFeatures(); err != nil {
  142. log.Fatalf("failed to register features: %s", err)
  143. }
  144. if err := csconfig.LoadFeatureFlagsEnv(log.StandardLogger()); err != nil {
  145. log.Fatalf("failed to set feature flags from env: %s", err)
  146. }
  147. cmd := &cobra.Command{
  148. Use: "cscli",
  149. Short: "cscli allows you to manage crowdsec",
  150. Long: `cscli is the main command to interact with your crowdsec service, scenarios & db.
  151. It is meant to allow you to manage bans, parsers/scenarios/etc, api and generally manage you crowdsec setup.`,
  152. ValidArgs: validArgs,
  153. DisableAutoGenTag: true,
  154. SilenceErrors: true,
  155. SilenceUsage: true,
  156. /*TBD examples*/
  157. }
  158. cli.colorize(cmd)
  159. /*don't sort flags so we can enforce order*/
  160. cmd.Flags().SortFlags = false
  161. pflags := cmd.PersistentFlags()
  162. pflags.SortFlags = false
  163. pflags.StringVarP(&ConfigFilePath, "config", "c", csconfig.DefaultConfigPath("config.yaml"), "path to crowdsec config file")
  164. pflags.StringVarP(&cli.outputFormat, "output", "o", "", "Output format: human, json, raw")
  165. pflags.StringVarP(&cli.outputColor, "color", "", "auto", "Output color: yes, no, auto")
  166. pflags.BoolVar(&cli.logDebug, "debug", false, "Set logging to debug")
  167. pflags.BoolVar(&cli.logInfo, "info", false, "Set logging to info")
  168. pflags.BoolVar(&cli.logWarn, "warning", false, "Set logging to warning")
  169. pflags.BoolVar(&cli.logErr, "error", false, "Set logging to error")
  170. pflags.BoolVar(&cli.logTrace, "trace", false, "Set logging to trace")
  171. pflags.StringVar(&cli.flagBranch, "branch", "", "Override hub branch on github")
  172. if err := pflags.MarkHidden("branch"); err != nil {
  173. log.Fatalf("failed to hide flag: %s", err)
  174. }
  175. // Look for "-c /path/to/config.yaml"
  176. // This duplicates the logic in cobra, but we need to do it before
  177. // because feature flags can change which subcommands are available.
  178. for i, arg := range os.Args {
  179. if arg == "-c" || arg == "--config" {
  180. if len(os.Args) > i+1 {
  181. ConfigFilePath = os.Args[i+1]
  182. }
  183. }
  184. }
  185. if err := csconfig.LoadFeatureFlagsFile(ConfigFilePath, log.StandardLogger()); err != nil {
  186. log.Fatal(err)
  187. }
  188. if len(os.Args) > 1 {
  189. cobra.OnInitialize(cli.initialize)
  190. }
  191. cmd.AddCommand(NewCLIDoc().NewCommand(cmd))
  192. cmd.AddCommand(NewCLIVersion().NewCommand())
  193. cmd.AddCommand(NewConfigCmd())
  194. cmd.AddCommand(NewCLIHub(cli.cfg).NewCommand())
  195. cmd.AddCommand(NewCLIMetrics(cli.cfg).NewCommand())
  196. cmd.AddCommand(NewCLIDashboard(cli.cfg).NewCommand())
  197. cmd.AddCommand(NewCLIDecisions(cli.cfg).NewCommand())
  198. cmd.AddCommand(NewCLIAlerts(cli.cfg).NewCommand())
  199. cmd.AddCommand(NewCLISimulation(cli.cfg).NewCommand())
  200. cmd.AddCommand(NewCLIBouncers(cli.cfg).NewCommand())
  201. cmd.AddCommand(NewCLIMachines(cli.cfg).NewCommand())
  202. cmd.AddCommand(NewCLICapi().NewCommand())
  203. cmd.AddCommand(NewCLILapi(cli.cfg).NewCommand())
  204. cmd.AddCommand(NewCompletionCmd())
  205. cmd.AddCommand(NewConsoleCmd())
  206. cmd.AddCommand(NewCLIExplain(cli.cfg).NewCommand())
  207. cmd.AddCommand(NewCLIHubTest().NewCommand())
  208. cmd.AddCommand(NewCLINotifications().NewCommand())
  209. cmd.AddCommand(NewCLISupport().NewCommand())
  210. cmd.AddCommand(NewCLIPapi(cli.cfg).NewCommand())
  211. cmd.AddCommand(NewCLICollection().NewCommand())
  212. cmd.AddCommand(NewCLIParser().NewCommand())
  213. cmd.AddCommand(NewCLIScenario().NewCommand())
  214. cmd.AddCommand(NewCLIPostOverflow().NewCommand())
  215. cmd.AddCommand(NewCLIContext().NewCommand())
  216. cmd.AddCommand(NewCLIAppsecConfig().NewCommand())
  217. cmd.AddCommand(NewCLIAppsecRule().NewCommand())
  218. if fflag.CscliSetup.IsEnabled() {
  219. cmd.AddCommand(NewSetupCmd())
  220. }
  221. return cmd
  222. }
  223. func main() {
  224. cmd := newCliRoot().NewCommand()
  225. if err := cmd.Execute(); err != nil {
  226. log.Fatal(err)
  227. }
  228. }