console.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. package main
  2. import (
  3. "context"
  4. "encoding/csv"
  5. "encoding/json"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "github.com/fatih/color"
  10. "github.com/go-openapi/strfmt"
  11. log "github.com/sirupsen/logrus"
  12. "github.com/spf13/cobra"
  13. "gopkg.in/yaml.v3"
  14. "github.com/crowdsecurity/go-cs-lib/ptr"
  15. "github.com/crowdsecurity/go-cs-lib/version"
  16. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  17. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  18. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  19. "github.com/crowdsecurity/crowdsec/pkg/fflag"
  20. "github.com/crowdsecurity/crowdsec/pkg/types"
  21. "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
  22. )
  23. func NewConsoleCmd() *cobra.Command {
  24. var cmdConsole = &cobra.Command{
  25. Use: "console [action]",
  26. Short: "Manage interaction with Crowdsec console (https://app.crowdsec.net)",
  27. Args: cobra.MinimumNArgs(1),
  28. DisableAutoGenTag: true,
  29. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  30. if err := require.LAPI(csConfig); err != nil {
  31. return err
  32. }
  33. if err := require.CAPI(csConfig); err != nil {
  34. return err
  35. }
  36. if err := require.CAPIRegistered(csConfig); err != nil {
  37. return err
  38. }
  39. return nil
  40. },
  41. }
  42. name := ""
  43. overwrite := false
  44. tags := []string{}
  45. cmdEnroll := &cobra.Command{
  46. Use: "enroll [enroll-key]",
  47. Short: "Enroll this instance to https://app.crowdsec.net [requires local API]",
  48. Long: `
  49. Enroll this instance to https://app.crowdsec.net
  50. You can get your enrollment key by creating an account on https://app.crowdsec.net.
  51. After running this command your will need to validate the enrollment in the webapp.`,
  52. Example: `cscli console enroll YOUR-ENROLL-KEY
  53. cscli console enroll --name [instance_name] YOUR-ENROLL-KEY
  54. cscli console enroll --name [instance_name] --tags [tag_1] --tags [tag_2] YOUR-ENROLL-KEY
  55. `,
  56. Args: cobra.ExactArgs(1),
  57. DisableAutoGenTag: true,
  58. RunE: func(cmd *cobra.Command, args []string) error {
  59. password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password)
  60. apiURL, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL)
  61. if err != nil {
  62. return fmt.Errorf("could not parse CAPI URL: %s", err)
  63. }
  64. hub, err := require.Hub(csConfig)
  65. if err != nil {
  66. return err
  67. }
  68. scenarios, err := hub.GetInstalledItemsAsString(cwhub.SCENARIOS)
  69. if err != nil {
  70. return fmt.Errorf("failed to get installed scenarios: %s", err)
  71. }
  72. if len(scenarios) == 0 {
  73. scenarios = make([]string, 0)
  74. }
  75. c, _ := apiclient.NewClient(&apiclient.Config{
  76. MachineID: csConfig.API.Server.OnlineClient.Credentials.Login,
  77. Password: password,
  78. Scenarios: scenarios,
  79. UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
  80. URL: apiURL,
  81. VersionPrefix: "v3",
  82. })
  83. resp, err := c.Auth.EnrollWatcher(context.Background(), args[0], name, tags, overwrite)
  84. if err != nil {
  85. return fmt.Errorf("could not enroll instance: %s", err)
  86. }
  87. if resp.Response.StatusCode == 200 && !overwrite {
  88. log.Warning("Instance already enrolled. You can use '--overwrite' to force enroll")
  89. return nil
  90. }
  91. if err := SetConsoleOpts([]string{csconfig.SEND_MANUAL_SCENARIOS, csconfig.SEND_TAINTED_SCENARIOS}, true); err != nil {
  92. return err
  93. }
  94. log.Info("Enabled tainted&manual alerts sharing, see 'cscli console status'.")
  95. log.Info("Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.")
  96. log.Info("Please restart crowdsec after accepting the enrollment.")
  97. return nil
  98. },
  99. }
  100. cmdEnroll.Flags().StringVarP(&name, "name", "n", "", "Name to display in the console")
  101. cmdEnroll.Flags().BoolVarP(&overwrite, "overwrite", "", false, "Force enroll the instance")
  102. cmdEnroll.Flags().StringSliceVarP(&tags, "tags", "t", tags, "Tags to display in the console")
  103. cmdConsole.AddCommand(cmdEnroll)
  104. var enableAll, disableAll bool
  105. cmdEnable := &cobra.Command{
  106. Use: "enable [option]",
  107. Short: "Enable a console option",
  108. Example: "sudo cscli console enable tainted",
  109. Long: `
  110. Enable given information push to the central API. Allows to empower the console`,
  111. ValidArgs: csconfig.CONSOLE_CONFIGS,
  112. DisableAutoGenTag: true,
  113. RunE: func(cmd *cobra.Command, args []string) error {
  114. if enableAll {
  115. if err := SetConsoleOpts(csconfig.CONSOLE_CONFIGS, true); err != nil {
  116. return err
  117. }
  118. log.Infof("All features have been enabled successfully")
  119. } else {
  120. if len(args) == 0 {
  121. return fmt.Errorf("you must specify at least one feature to enable")
  122. }
  123. if err := SetConsoleOpts(args, true); err != nil {
  124. return err
  125. }
  126. log.Infof("%v have been enabled", args)
  127. }
  128. log.Infof(ReloadMessage())
  129. return nil
  130. },
  131. }
  132. cmdEnable.Flags().BoolVarP(&enableAll, "all", "a", false, "Enable all console options")
  133. cmdConsole.AddCommand(cmdEnable)
  134. cmdDisable := &cobra.Command{
  135. Use: "disable [option]",
  136. Short: "Disable a console option",
  137. Example: "sudo cscli console disable tainted",
  138. Long: `
  139. Disable given information push to the central API.`,
  140. ValidArgs: csconfig.CONSOLE_CONFIGS,
  141. DisableAutoGenTag: true,
  142. RunE: func(cmd *cobra.Command, args []string) error {
  143. if disableAll {
  144. if err := SetConsoleOpts(csconfig.CONSOLE_CONFIGS, false); err != nil {
  145. return err
  146. }
  147. log.Infof("All features have been disabled")
  148. } else {
  149. if err := SetConsoleOpts(args, false); err != nil {
  150. return err
  151. }
  152. log.Infof("%v have been disabled", args)
  153. }
  154. log.Infof(ReloadMessage())
  155. return nil
  156. },
  157. }
  158. cmdDisable.Flags().BoolVarP(&disableAll, "all", "a", false, "Disable all console options")
  159. cmdConsole.AddCommand(cmdDisable)
  160. cmdConsoleStatus := &cobra.Command{
  161. Use: "status",
  162. Short: "Shows status of the console options",
  163. Example: `sudo cscli console status`,
  164. DisableAutoGenTag: true,
  165. RunE: func(cmd *cobra.Command, args []string) error {
  166. switch csConfig.Cscli.Output {
  167. case "human":
  168. cmdConsoleStatusTable(color.Output, *csConfig)
  169. case "json":
  170. c := csConfig.API.Server.ConsoleConfig
  171. out := map[string](*bool){
  172. csconfig.SEND_MANUAL_SCENARIOS: c.ShareManualDecisions,
  173. csconfig.SEND_CUSTOM_SCENARIOS: c.ShareCustomScenarios,
  174. csconfig.SEND_TAINTED_SCENARIOS: c.ShareTaintedScenarios,
  175. csconfig.SEND_CONTEXT: c.ShareContext,
  176. csconfig.CONSOLE_MANAGEMENT: c.ConsoleManagement,
  177. }
  178. data, err := json.MarshalIndent(out, "", " ")
  179. if err != nil {
  180. return fmt.Errorf("failed to marshal configuration: %s", err)
  181. }
  182. fmt.Println(string(data))
  183. case "raw":
  184. csvwriter := csv.NewWriter(os.Stdout)
  185. err := csvwriter.Write([]string{"option", "enabled"})
  186. if err != nil {
  187. return err
  188. }
  189. rows := [][]string{
  190. {csconfig.SEND_MANUAL_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareManualDecisions)},
  191. {csconfig.SEND_CUSTOM_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios)},
  192. {csconfig.SEND_TAINTED_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios)},
  193. {csconfig.SEND_CONTEXT, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareContext)},
  194. {csconfig.CONSOLE_MANAGEMENT, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ConsoleManagement)},
  195. }
  196. for _, row := range rows {
  197. err = csvwriter.Write(row)
  198. if err != nil {
  199. return err
  200. }
  201. }
  202. csvwriter.Flush()
  203. }
  204. return nil
  205. },
  206. }
  207. cmdConsole.AddCommand(cmdConsoleStatus)
  208. return cmdConsole
  209. }
  210. func dumpConsoleConfig(c *csconfig.LocalApiServerCfg) error {
  211. out, err := yaml.Marshal(c.ConsoleConfig)
  212. if err != nil {
  213. return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", c.ConsoleConfigPath, err)
  214. }
  215. if c.ConsoleConfigPath == "" {
  216. c.ConsoleConfigPath = csconfig.DefaultConsoleConfigFilePath
  217. log.Debugf("Empty console_path, defaulting to %s", c.ConsoleConfigPath)
  218. }
  219. if err := os.WriteFile(c.ConsoleConfigPath, out, 0600); err != nil {
  220. return fmt.Errorf("while dumping console config to %s: %w", c.ConsoleConfigPath, err)
  221. }
  222. return nil
  223. }
  224. func SetConsoleOpts(args []string, wanted bool) error {
  225. for _, arg := range args {
  226. switch arg {
  227. case csconfig.CONSOLE_MANAGEMENT:
  228. if !fflag.PapiClient.IsEnabled() {
  229. continue
  230. }
  231. /*for each flag check if it's already set before setting it*/
  232. if csConfig.API.Server.ConsoleConfig.ConsoleManagement != nil {
  233. if *csConfig.API.Server.ConsoleConfig.ConsoleManagement == wanted {
  234. log.Debugf("%s already set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
  235. } else {
  236. log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
  237. *csConfig.API.Server.ConsoleConfig.ConsoleManagement = wanted
  238. }
  239. } else {
  240. log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
  241. csConfig.API.Server.ConsoleConfig.ConsoleManagement = ptr.Of(wanted)
  242. }
  243. if csConfig.API.Server.OnlineClient.Credentials != nil {
  244. changed := false
  245. if wanted && csConfig.API.Server.OnlineClient.Credentials.PapiURL == "" {
  246. changed = true
  247. csConfig.API.Server.OnlineClient.Credentials.PapiURL = types.PAPIBaseURL
  248. } else if !wanted && csConfig.API.Server.OnlineClient.Credentials.PapiURL != "" {
  249. changed = true
  250. csConfig.API.Server.OnlineClient.Credentials.PapiURL = ""
  251. }
  252. if changed {
  253. fileContent, err := yaml.Marshal(csConfig.API.Server.OnlineClient.Credentials)
  254. if err != nil {
  255. return fmt.Errorf("cannot marshal credentials: %s", err)
  256. }
  257. log.Infof("Updating credentials file: %s", csConfig.API.Server.OnlineClient.CredentialsFilePath)
  258. err = os.WriteFile(csConfig.API.Server.OnlineClient.CredentialsFilePath, fileContent, 0600)
  259. if err != nil {
  260. return fmt.Errorf("cannot write credentials file: %s", err)
  261. }
  262. }
  263. }
  264. case csconfig.SEND_CUSTOM_SCENARIOS:
  265. /*for each flag check if it's already set before setting it*/
  266. if csConfig.API.Server.ConsoleConfig.ShareCustomScenarios != nil {
  267. if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios == wanted {
  268. log.Debugf("%s already set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
  269. } else {
  270. log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
  271. *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = wanted
  272. }
  273. } else {
  274. log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
  275. csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = ptr.Of(wanted)
  276. }
  277. case csconfig.SEND_TAINTED_SCENARIOS:
  278. /*for each flag check if it's already set before setting it*/
  279. if csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios != nil {
  280. if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios == wanted {
  281. log.Debugf("%s already set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
  282. } else {
  283. log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
  284. *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = wanted
  285. }
  286. } else {
  287. log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
  288. csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = ptr.Of(wanted)
  289. }
  290. case csconfig.SEND_MANUAL_SCENARIOS:
  291. /*for each flag check if it's already set before setting it*/
  292. if csConfig.API.Server.ConsoleConfig.ShareManualDecisions != nil {
  293. if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions == wanted {
  294. log.Debugf("%s already set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
  295. } else {
  296. log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
  297. *csConfig.API.Server.ConsoleConfig.ShareManualDecisions = wanted
  298. }
  299. } else {
  300. log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
  301. csConfig.API.Server.ConsoleConfig.ShareManualDecisions = ptr.Of(wanted)
  302. }
  303. case csconfig.SEND_CONTEXT:
  304. /*for each flag check if it's already set before setting it*/
  305. if csConfig.API.Server.ConsoleConfig.ShareContext != nil {
  306. if *csConfig.API.Server.ConsoleConfig.ShareContext == wanted {
  307. log.Debugf("%s already set to %t", csconfig.SEND_CONTEXT, wanted)
  308. } else {
  309. log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted)
  310. *csConfig.API.Server.ConsoleConfig.ShareContext = wanted
  311. }
  312. } else {
  313. log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted)
  314. csConfig.API.Server.ConsoleConfig.ShareContext = ptr.Of(wanted)
  315. }
  316. default:
  317. return fmt.Errorf("unknown flag %s", arg)
  318. }
  319. }
  320. if err := dumpConsoleConfig(csConfig.API.Server); err != nil {
  321. return fmt.Errorf("failed writing console config: %s", err)
  322. }
  323. return nil
  324. }