diff --git a/cmd/crowdsec-cli/console.go b/cmd/crowdsec-cli/console.go index dcd6fb37f..b1912825c 100644 --- a/cmd/crowdsec-cli/console.go +++ b/cmd/crowdsec-cli/console.go @@ -25,32 +25,53 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/types" ) -func NewConsoleCmd() *cobra.Command { - var cmdConsole = &cobra.Command{ +type cliConsole struct { + cfg configGetter +} + +func NewCLIConsole(cfg configGetter) *cliConsole { + return &cliConsole{ + cfg: cfg, + } +} + +func (cli *cliConsole) NewCommand() *cobra.Command { + var cmd = &cobra.Command{ Use: "console [action]", Short: "Manage interaction with Crowdsec console (https://app.crowdsec.net)", Args: cobra.MinimumNArgs(1), DisableAutoGenTag: true, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - if err := require.LAPI(csConfig); err != nil { + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + cfg := cli.cfg() + if err := require.LAPI(cfg); err != nil { return err } - if err := require.CAPI(csConfig); err != nil { + if err := require.CAPI(cfg); err != nil { return err } - if err := require.CAPIRegistered(csConfig); err != nil { + if err := require.CAPIRegistered(cfg); err != nil { return err } + return nil }, } + cmd.AddCommand(cli.newEnrollCmd()) + cmd.AddCommand(cli.newEnableCmd()) + cmd.AddCommand(cli.newDisableCmd()) + cmd.AddCommand(cli.newStatusCmd()) + + return cmd +} + +func (cli *cliConsole) newEnrollCmd() *cobra.Command { name := "" overwrite := false tags := []string{} opts := []string{} - cmdEnroll := &cobra.Command{ + cmd := &cobra.Command{ Use: "enroll [enroll-key]", Short: "Enroll this instance to https://app.crowdsec.net [requires local API]", Long: ` @@ -66,96 +87,107 @@ After running this command your will need to validate the enrollment in the weba valid options are : %s,all (see 'cscli console status' for details)`, strings.Join(csconfig.CONSOLE_CONFIGS, ",")), Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - password := strfmt.Password(csConfig.API.Server.OnlineClient.Credentials.Password) - apiURL, err := url.Parse(csConfig.API.Server.OnlineClient.Credentials.URL) + RunE: func(_ *cobra.Command, args []string) error { + cfg := cli.cfg() + password := strfmt.Password(cfg.API.Server.OnlineClient.Credentials.Password) + + apiURL, err := url.Parse(cfg.API.Server.OnlineClient.Credentials.URL) if err != nil { - return fmt.Errorf("could not parse CAPI URL: %s", err) + return fmt.Errorf("could not parse CAPI URL: %w", err) } - hub, err := require.Hub(csConfig, nil, nil) + hub, err := require.Hub(cfg, nil, nil) if err != nil { return err } scenarios, err := hub.GetInstalledItemNames(cwhub.SCENARIOS) if err != nil { - return fmt.Errorf("failed to get installed scenarios: %s", err) + return fmt.Errorf("failed to get installed scenarios: %w", err) } if len(scenarios) == 0 { scenarios = make([]string, 0) } - enable_opts := []string{csconfig.SEND_MANUAL_SCENARIOS, csconfig.SEND_TAINTED_SCENARIOS} + enableOpts := []string{csconfig.SEND_MANUAL_SCENARIOS, csconfig.SEND_TAINTED_SCENARIOS} if len(opts) != 0 { for _, opt := range opts { valid := false if opt == "all" { - enable_opts = csconfig.CONSOLE_CONFIGS + enableOpts = csconfig.CONSOLE_CONFIGS break } - for _, available_opt := range csconfig.CONSOLE_CONFIGS { - if opt == available_opt { + for _, availableOpt := range csconfig.CONSOLE_CONFIGS { + if opt == availableOpt { valid = true enable := true - for _, enabled_opt := range enable_opts { - if opt == enabled_opt { + for _, enabledOpt := range enableOpts { + if opt == enabledOpt { enable = false continue } } if enable { - enable_opts = append(enable_opts, opt) + enableOpts = append(enableOpts, opt) } + break } } if !valid { return fmt.Errorf("option %s doesn't exist", opt) - } } } c, _ := apiclient.NewClient(&apiclient.Config{ - MachineID: csConfig.API.Server.OnlineClient.Credentials.Login, + MachineID: cli.cfg().API.Server.OnlineClient.Credentials.Login, Password: password, Scenarios: scenarios, UserAgent: fmt.Sprintf("crowdsec/%s", version.String()), URL: apiURL, VersionPrefix: "v3", }) + resp, err := c.Auth.EnrollWatcher(context.Background(), args[0], name, tags, overwrite) if err != nil { - return fmt.Errorf("could not enroll instance: %s", err) + return fmt.Errorf("could not enroll instance: %w", err) } + if resp.Response.StatusCode == 200 && !overwrite { log.Warning("Instance already enrolled. You can use '--overwrite' to force enroll") return nil } - if err := SetConsoleOpts(enable_opts, true); err != nil { + if err := cli.setConsoleOpts(enableOpts, true); err != nil { return err } - for _, opt := range enable_opts { + for _, opt := range enableOpts { log.Infof("Enabled %s : %s", opt, csconfig.CONSOLE_CONFIGS_HELP[opt]) } + log.Info("Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.") log.Info("Please restart crowdsec after accepting the enrollment.") + return nil }, } - cmdEnroll.Flags().StringVarP(&name, "name", "n", "", "Name to display in the console") - cmdEnroll.Flags().BoolVarP(&overwrite, "overwrite", "", false, "Force enroll the instance") - cmdEnroll.Flags().StringSliceVarP(&tags, "tags", "t", tags, "Tags to display in the console") - cmdEnroll.Flags().StringSliceVarP(&opts, "enable", "e", opts, "Enable console options") - cmdConsole.AddCommand(cmdEnroll) - var enableAll, disableAll bool + flags := cmd.Flags() + flags.StringVarP(&name, "name", "n", "", "Name to display in the console") + flags.BoolVarP(&overwrite, "overwrite", "", false, "Force enroll the instance") + flags.StringSliceVarP(&tags, "tags", "t", tags, "Tags to display in the console") + flags.StringSliceVarP(&opts, "enable", "e", opts, "Enable console options") - cmdEnable := &cobra.Command{ + return cmd +} + +func (cli *cliConsole) newEnableCmd() *cobra.Command { + var enableAll bool + + cmd := &cobra.Command{ Use: "enable [option]", Short: "Enable a console option", Example: "sudo cscli console enable tainted", @@ -163,9 +195,9 @@ After running this command your will need to validate the enrollment in the weba Enable given information push to the central API. Allows to empower the console`, ValidArgs: csconfig.CONSOLE_CONFIGS, DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { if enableAll { - if err := SetConsoleOpts(csconfig.CONSOLE_CONFIGS, true); err != nil { + if err := cli.setConsoleOpts(csconfig.CONSOLE_CONFIGS, true); err != nil { return err } log.Infof("All features have been enabled successfully") @@ -173,19 +205,26 @@ Enable given information push to the central API. Allows to empower the console` if len(args) == 0 { return fmt.Errorf("you must specify at least one feature to enable") } - if err := SetConsoleOpts(args, true); err != nil { + if err := cli.setConsoleOpts(args, true); err != nil { return err } log.Infof("%v have been enabled", args) } + log.Infof(ReloadMessage()) + return nil }, } - cmdEnable.Flags().BoolVarP(&enableAll, "all", "a", false, "Enable all console options") - cmdConsole.AddCommand(cmdEnable) + cmd.Flags().BoolVarP(&enableAll, "all", "a", false, "Enable all console options") - cmdDisable := &cobra.Command{ + return cmd +} + +func (cli *cliConsole) newDisableCmd() *cobra.Command { + var disableAll bool + + cmd := &cobra.Command{ Use: "disable [option]", Short: "Disable a console option", Example: "sudo cscli console disable tainted", @@ -193,47 +232,52 @@ Enable given information push to the central API. Allows to empower the console` Disable given information push to the central API.`, ValidArgs: csconfig.CONSOLE_CONFIGS, DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { if disableAll { - if err := SetConsoleOpts(csconfig.CONSOLE_CONFIGS, false); err != nil { + if err := cli.setConsoleOpts(csconfig.CONSOLE_CONFIGS, false); err != nil { return err } log.Infof("All features have been disabled") } else { - if err := SetConsoleOpts(args, false); err != nil { + if err := cli.setConsoleOpts(args, false); err != nil { return err } log.Infof("%v have been disabled", args) } log.Infof(ReloadMessage()) + return nil }, } - cmdDisable.Flags().BoolVarP(&disableAll, "all", "a", false, "Disable all console options") - cmdConsole.AddCommand(cmdDisable) + cmd.Flags().BoolVarP(&disableAll, "all", "a", false, "Disable all console options") - cmdConsoleStatus := &cobra.Command{ + return cmd +} + +func (cli *cliConsole) newStatusCmd() *cobra.Command { + cmd := &cobra.Command{ Use: "status", Short: "Shows status of the console options", Example: `sudo cscli console status`, DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { - switch csConfig.Cscli.Output { + RunE: func(_ *cobra.Command, _ []string) error { + cfg := cli.cfg() + consoleCfg := cfg.API.Server.ConsoleConfig + switch cfg.Cscli.Output { case "human": - cmdConsoleStatusTable(color.Output, *csConfig) + cmdConsoleStatusTable(color.Output, *consoleCfg) case "json": - c := csConfig.API.Server.ConsoleConfig out := map[string](*bool){ - csconfig.SEND_MANUAL_SCENARIOS: c.ShareManualDecisions, - csconfig.SEND_CUSTOM_SCENARIOS: c.ShareCustomScenarios, - csconfig.SEND_TAINTED_SCENARIOS: c.ShareTaintedScenarios, - csconfig.SEND_CONTEXT: c.ShareContext, - csconfig.CONSOLE_MANAGEMENT: c.ConsoleManagement, + csconfig.SEND_MANUAL_SCENARIOS: consoleCfg.ShareManualDecisions, + csconfig.SEND_CUSTOM_SCENARIOS: consoleCfg.ShareCustomScenarios, + csconfig.SEND_TAINTED_SCENARIOS: consoleCfg.ShareTaintedScenarios, + csconfig.SEND_CONTEXT: consoleCfg.ShareContext, + csconfig.CONSOLE_MANAGEMENT: consoleCfg.ConsoleManagement, } data, err := json.MarshalIndent(out, "", " ") if err != nil { - return fmt.Errorf("failed to marshal configuration: %s", err) + return fmt.Errorf("failed to marshal configuration: %w", err) } fmt.Println(string(data)) case "raw": @@ -244,11 +288,11 @@ Disable given information push to the central API.`, } rows := [][]string{ - {csconfig.SEND_MANUAL_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareManualDecisions)}, - {csconfig.SEND_CUSTOM_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios)}, - {csconfig.SEND_TAINTED_SCENARIOS, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios)}, - {csconfig.SEND_CONTEXT, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ShareContext)}, - {csconfig.CONSOLE_MANAGEMENT, fmt.Sprintf("%t", *csConfig.API.Server.ConsoleConfig.ConsoleManagement)}, + {csconfig.SEND_MANUAL_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareManualDecisions)}, + {csconfig.SEND_CUSTOM_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareCustomScenarios)}, + {csconfig.SEND_TAINTED_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareTaintedScenarios)}, + {csconfig.SEND_CONTEXT, fmt.Sprintf("%t", *consoleCfg.ShareContext)}, + {csconfig.CONSOLE_MANAGEMENT, fmt.Sprintf("%t", *consoleCfg.ConsoleManagement)}, } for _, row := range rows { err = csvwriter.Write(row) @@ -258,132 +302,137 @@ Disable given information push to the central API.`, } csvwriter.Flush() } + return nil }, } - cmdConsole.AddCommand(cmdConsoleStatus) - return cmdConsole + return cmd } -func dumpConsoleConfig(c *csconfig.LocalApiServerCfg) error { - out, err := yaml.Marshal(c.ConsoleConfig) +func (cli *cliConsole) dumpConfig() error { + serverCfg := cli.cfg().API.Server + + out, err := yaml.Marshal(serverCfg.ConsoleConfig) if err != nil { - return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", c.ConsoleConfigPath, err) + return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", serverCfg.ConsoleConfigPath, err) } - if c.ConsoleConfigPath == "" { - c.ConsoleConfigPath = csconfig.DefaultConsoleConfigFilePath - log.Debugf("Empty console_path, defaulting to %s", c.ConsoleConfigPath) + if serverCfg.ConsoleConfigPath == "" { + serverCfg.ConsoleConfigPath = csconfig.DefaultConsoleConfigFilePath + log.Debugf("Empty console_path, defaulting to %s", serverCfg.ConsoleConfigPath) } - if err := os.WriteFile(c.ConsoleConfigPath, out, 0o600); err != nil { - return fmt.Errorf("while dumping console config to %s: %w", c.ConsoleConfigPath, err) + if err := os.WriteFile(serverCfg.ConsoleConfigPath, out, 0o600); err != nil { + return fmt.Errorf("while dumping console config to %s: %w", serverCfg.ConsoleConfigPath, err) } return nil } -func SetConsoleOpts(args []string, wanted bool) error { +func (cli *cliConsole) setConsoleOpts(args []string, wanted bool) error { + cfg := cli.cfg() + consoleCfg := cfg.API.Server.ConsoleConfig + for _, arg := range args { switch arg { case csconfig.CONSOLE_MANAGEMENT: /*for each flag check if it's already set before setting it*/ - if csConfig.API.Server.ConsoleConfig.ConsoleManagement != nil { - if *csConfig.API.Server.ConsoleConfig.ConsoleManagement == wanted { + if consoleCfg.ConsoleManagement != nil { + if *consoleCfg.ConsoleManagement == wanted { log.Debugf("%s already set to %t", csconfig.CONSOLE_MANAGEMENT, wanted) } else { log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted) - *csConfig.API.Server.ConsoleConfig.ConsoleManagement = wanted + *consoleCfg.ConsoleManagement = wanted } } else { log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted) - csConfig.API.Server.ConsoleConfig.ConsoleManagement = ptr.Of(wanted) + consoleCfg.ConsoleManagement = ptr.Of(wanted) } - if csConfig.API.Server.OnlineClient.Credentials != nil { + if cfg.API.Server.OnlineClient.Credentials != nil { changed := false - if wanted && csConfig.API.Server.OnlineClient.Credentials.PapiURL == "" { + if wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL == "" { changed = true - csConfig.API.Server.OnlineClient.Credentials.PapiURL = types.PAPIBaseURL - } else if !wanted && csConfig.API.Server.OnlineClient.Credentials.PapiURL != "" { + cfg.API.Server.OnlineClient.Credentials.PapiURL = types.PAPIBaseURL + } else if !wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL != "" { changed = true - csConfig.API.Server.OnlineClient.Credentials.PapiURL = "" + cfg.API.Server.OnlineClient.Credentials.PapiURL = "" } if changed { - fileContent, err := yaml.Marshal(csConfig.API.Server.OnlineClient.Credentials) + fileContent, err := yaml.Marshal(cfg.API.Server.OnlineClient.Credentials) if err != nil { - return fmt.Errorf("cannot marshal credentials: %s", err) + return fmt.Errorf("cannot marshal credentials: %w", err) } - log.Infof("Updating credentials file: %s", csConfig.API.Server.OnlineClient.CredentialsFilePath) + log.Infof("Updating credentials file: %s", cfg.API.Server.OnlineClient.CredentialsFilePath) - err = os.WriteFile(csConfig.API.Server.OnlineClient.CredentialsFilePath, fileContent, 0o600) + err = os.WriteFile(cfg.API.Server.OnlineClient.CredentialsFilePath, fileContent, 0o600) if err != nil { - return fmt.Errorf("cannot write credentials file: %s", err) + return fmt.Errorf("cannot write credentials file: %w", err) } } } case csconfig.SEND_CUSTOM_SCENARIOS: /*for each flag check if it's already set before setting it*/ - if csConfig.API.Server.ConsoleConfig.ShareCustomScenarios != nil { - if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios == wanted { + if consoleCfg.ShareCustomScenarios != nil { + if *consoleCfg.ShareCustomScenarios == wanted { log.Debugf("%s already set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted) } else { log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted) - *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = wanted + *consoleCfg.ShareCustomScenarios = wanted } } else { log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted) - csConfig.API.Server.ConsoleConfig.ShareCustomScenarios = ptr.Of(wanted) + consoleCfg.ShareCustomScenarios = ptr.Of(wanted) } case csconfig.SEND_TAINTED_SCENARIOS: /*for each flag check if it's already set before setting it*/ - if csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios != nil { - if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios == wanted { + if consoleCfg.ShareTaintedScenarios != nil { + if *consoleCfg.ShareTaintedScenarios == wanted { log.Debugf("%s already set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted) } else { log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted) - *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = wanted + *consoleCfg.ShareTaintedScenarios = wanted } } else { log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted) - csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios = ptr.Of(wanted) + consoleCfg.ShareTaintedScenarios = ptr.Of(wanted) } case csconfig.SEND_MANUAL_SCENARIOS: /*for each flag check if it's already set before setting it*/ - if csConfig.API.Server.ConsoleConfig.ShareManualDecisions != nil { - if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions == wanted { + if consoleCfg.ShareManualDecisions != nil { + if *consoleCfg.ShareManualDecisions == wanted { log.Debugf("%s already set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted) } else { log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted) - *csConfig.API.Server.ConsoleConfig.ShareManualDecisions = wanted + *consoleCfg.ShareManualDecisions = wanted } } else { log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted) - csConfig.API.Server.ConsoleConfig.ShareManualDecisions = ptr.Of(wanted) + consoleCfg.ShareManualDecisions = ptr.Of(wanted) } case csconfig.SEND_CONTEXT: /*for each flag check if it's already set before setting it*/ - if csConfig.API.Server.ConsoleConfig.ShareContext != nil { - if *csConfig.API.Server.ConsoleConfig.ShareContext == wanted { + if consoleCfg.ShareContext != nil { + if *consoleCfg.ShareContext == wanted { log.Debugf("%s already set to %t", csconfig.SEND_CONTEXT, wanted) } else { log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted) - *csConfig.API.Server.ConsoleConfig.ShareContext = wanted + *consoleCfg.ShareContext = wanted } } else { log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted) - csConfig.API.Server.ConsoleConfig.ShareContext = ptr.Of(wanted) + consoleCfg.ShareContext = ptr.Of(wanted) } default: return fmt.Errorf("unknown flag %s", arg) } } - if err := dumpConsoleConfig(csConfig.API.Server); err != nil { - return fmt.Errorf("failed writing console config: %s", err) + if err := cli.dumpConfig(); err != nil { + return fmt.Errorf("failed writing console config: %w", err) } return nil diff --git a/cmd/crowdsec-cli/console_table.go b/cmd/crowdsec-cli/console_table.go index 2a221e36f..e71ea8113 100644 --- a/cmd/crowdsec-cli/console_table.go +++ b/cmd/crowdsec-cli/console_table.go @@ -9,7 +9,7 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/csconfig" ) -func cmdConsoleStatusTable(out io.Writer, csConfig csconfig.Config) { +func cmdConsoleStatusTable(out io.Writer, consoleCfg csconfig.ConsoleConfig) { t := newTable(out) t.SetRowLines(false) @@ -18,28 +18,30 @@ func cmdConsoleStatusTable(out io.Writer, csConfig csconfig.Config) { for _, option := range csconfig.CONSOLE_CONFIGS { activated := string(emoji.CrossMark) + switch option { case csconfig.SEND_CUSTOM_SCENARIOS: - if *csConfig.API.Server.ConsoleConfig.ShareCustomScenarios { + if *consoleCfg.ShareCustomScenarios { activated = string(emoji.CheckMarkButton) } case csconfig.SEND_MANUAL_SCENARIOS: - if *csConfig.API.Server.ConsoleConfig.ShareManualDecisions { + if *consoleCfg.ShareManualDecisions { activated = string(emoji.CheckMarkButton) } case csconfig.SEND_TAINTED_SCENARIOS: - if *csConfig.API.Server.ConsoleConfig.ShareTaintedScenarios { + if *consoleCfg.ShareTaintedScenarios { activated = string(emoji.CheckMarkButton) } case csconfig.SEND_CONTEXT: - if *csConfig.API.Server.ConsoleConfig.ShareContext { + if *consoleCfg.ShareContext { activated = string(emoji.CheckMarkButton) } case csconfig.CONSOLE_MANAGEMENT: - if *csConfig.API.Server.ConsoleConfig.ConsoleManagement { + if *consoleCfg.ConsoleManagement { activated = string(emoji.CheckMarkButton) } } + t.AddRow(option, activated, csconfig.CONSOLE_CONFIGS_HELP[option]) } diff --git a/cmd/crowdsec-cli/main.go b/cmd/crowdsec-cli/main.go index 63b7211b3..27ac17d55 100644 --- a/cmd/crowdsec-cli/main.go +++ b/cmd/crowdsec-cli/main.go @@ -243,7 +243,7 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall cmd.AddCommand(NewCLICapi().NewCommand()) cmd.AddCommand(NewCLILapi(cli.cfg).NewCommand()) cmd.AddCommand(NewCompletionCmd()) - cmd.AddCommand(NewConsoleCmd()) + cmd.AddCommand(NewCLIConsole(cli.cfg).NewCommand()) cmd.AddCommand(NewCLIExplain(cli.cfg).NewCommand()) cmd.AddCommand(NewCLIHubTest().NewCommand()) cmd.AddCommand(NewCLINotifications(cli.cfg).NewCommand())