github.com/crowdsecurity/crowdsec@v1.6.1/cmd/crowdsec-cli/console.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/csv"
     6  	"encoding/json"
     7  	"fmt"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/fatih/color"
    13  	"github.com/go-openapi/strfmt"
    14  	log "github.com/sirupsen/logrus"
    15  	"github.com/spf13/cobra"
    16  	"gopkg.in/yaml.v3"
    17  
    18  	"github.com/crowdsecurity/go-cs-lib/ptr"
    19  	"github.com/crowdsecurity/go-cs-lib/version"
    20  
    21  	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
    22  	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
    23  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    24  	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
    25  	"github.com/crowdsecurity/crowdsec/pkg/types"
    26  )
    27  
    28  type cliConsole struct {
    29  	cfg configGetter
    30  }
    31  
    32  func NewCLIConsole(cfg configGetter) *cliConsole {
    33  	return &cliConsole{
    34  		cfg: cfg,
    35  	}
    36  }
    37  
    38  func (cli *cliConsole) NewCommand() *cobra.Command {
    39  	var cmd = &cobra.Command{
    40  		Use:               "console [action]",
    41  		Short:             "Manage interaction with Crowdsec console (https://app.crowdsec.net)",
    42  		Args:              cobra.MinimumNArgs(1),
    43  		DisableAutoGenTag: true,
    44  		PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
    45  			cfg := cli.cfg()
    46  			if err := require.LAPI(cfg); err != nil {
    47  				return err
    48  			}
    49  			if err := require.CAPI(cfg); err != nil {
    50  				return err
    51  			}
    52  			if err := require.CAPIRegistered(cfg); err != nil {
    53  				return err
    54  			}
    55  
    56  			return nil
    57  		},
    58  	}
    59  
    60  	cmd.AddCommand(cli.newEnrollCmd())
    61  	cmd.AddCommand(cli.newEnableCmd())
    62  	cmd.AddCommand(cli.newDisableCmd())
    63  	cmd.AddCommand(cli.newStatusCmd())
    64  
    65  	return cmd
    66  }
    67  
    68  func (cli *cliConsole) newEnrollCmd() *cobra.Command {
    69  	name := ""
    70  	overwrite := false
    71  	tags := []string{}
    72  	opts := []string{}
    73  
    74  	cmd := &cobra.Command{
    75  		Use:   "enroll [enroll-key]",
    76  		Short: "Enroll this instance to https://app.crowdsec.net [requires local API]",
    77  		Long: `
    78  Enroll this instance to https://app.crowdsec.net
    79  		
    80  You can get your enrollment key by creating an account on https://app.crowdsec.net.
    81  After running this command your will need to validate the enrollment in the webapp.`,
    82  		Example: fmt.Sprintf(`cscli console enroll YOUR-ENROLL-KEY
    83  		cscli console enroll --name [instance_name] YOUR-ENROLL-KEY
    84  		cscli console enroll --name [instance_name] --tags [tag_1] --tags [tag_2] YOUR-ENROLL-KEY
    85  		cscli console enroll --enable context,manual YOUR-ENROLL-KEY
    86  
    87  		valid options are : %s,all (see 'cscli console status' for details)`, strings.Join(csconfig.CONSOLE_CONFIGS, ",")),
    88  		Args:              cobra.ExactArgs(1),
    89  		DisableAutoGenTag: true,
    90  		RunE: func(_ *cobra.Command, args []string) error {
    91  			cfg := cli.cfg()
    92  			password := strfmt.Password(cfg.API.Server.OnlineClient.Credentials.Password)
    93  
    94  			apiURL, err := url.Parse(cfg.API.Server.OnlineClient.Credentials.URL)
    95  			if err != nil {
    96  				return fmt.Errorf("could not parse CAPI URL: %w", err)
    97  			}
    98  
    99  			hub, err := require.Hub(cfg, nil, nil)
   100  			if err != nil {
   101  				return err
   102  			}
   103  
   104  			scenarios, err := hub.GetInstalledItemNames(cwhub.SCENARIOS)
   105  			if err != nil {
   106  				return fmt.Errorf("failed to get installed scenarios: %w", err)
   107  			}
   108  
   109  			if len(scenarios) == 0 {
   110  				scenarios = make([]string, 0)
   111  			}
   112  
   113  			enableOpts := []string{csconfig.SEND_MANUAL_SCENARIOS, csconfig.SEND_TAINTED_SCENARIOS}
   114  			if len(opts) != 0 {
   115  				for _, opt := range opts {
   116  					valid := false
   117  					if opt == "all" {
   118  						enableOpts = csconfig.CONSOLE_CONFIGS
   119  						break
   120  					}
   121  					for _, availableOpt := range csconfig.CONSOLE_CONFIGS {
   122  						if opt == availableOpt {
   123  							valid = true
   124  							enable := true
   125  							for _, enabledOpt := range enableOpts {
   126  								if opt == enabledOpt {
   127  									enable = false
   128  									continue
   129  								}
   130  							}
   131  							if enable {
   132  								enableOpts = append(enableOpts, opt)
   133  							}
   134  
   135  							break
   136  						}
   137  					}
   138  					if !valid {
   139  						return fmt.Errorf("option %s doesn't exist", opt)
   140  					}
   141  				}
   142  			}
   143  
   144  			c, _ := apiclient.NewClient(&apiclient.Config{
   145  				MachineID:     cli.cfg().API.Server.OnlineClient.Credentials.Login,
   146  				Password:      password,
   147  				Scenarios:     scenarios,
   148  				UserAgent:     fmt.Sprintf("crowdsec/%s", version.String()),
   149  				URL:           apiURL,
   150  				VersionPrefix: "v3",
   151  			})
   152  
   153  			resp, err := c.Auth.EnrollWatcher(context.Background(), args[0], name, tags, overwrite)
   154  			if err != nil {
   155  				return fmt.Errorf("could not enroll instance: %w", err)
   156  			}
   157  
   158  			if resp.Response.StatusCode == 200 && !overwrite {
   159  				log.Warning("Instance already enrolled. You can use '--overwrite' to force enroll")
   160  				return nil
   161  			}
   162  
   163  			if err := cli.setConsoleOpts(enableOpts, true); err != nil {
   164  				return err
   165  			}
   166  
   167  			for _, opt := range enableOpts {
   168  				log.Infof("Enabled %s : %s", opt, csconfig.CONSOLE_CONFIGS_HELP[opt])
   169  			}
   170  
   171  			log.Info("Watcher successfully enrolled. Visit https://app.crowdsec.net to accept it.")
   172  			log.Info("Please restart crowdsec after accepting the enrollment.")
   173  
   174  			return nil
   175  		},
   176  	}
   177  
   178  	flags := cmd.Flags()
   179  	flags.StringVarP(&name, "name", "n", "", "Name to display in the console")
   180  	flags.BoolVarP(&overwrite, "overwrite", "", false, "Force enroll the instance")
   181  	flags.StringSliceVarP(&tags, "tags", "t", tags, "Tags to display in the console")
   182  	flags.StringSliceVarP(&opts, "enable", "e", opts, "Enable console options")
   183  
   184  	return cmd
   185  }
   186  
   187  func (cli *cliConsole) newEnableCmd() *cobra.Command {
   188  	var enableAll bool
   189  
   190  	cmd := &cobra.Command{
   191  		Use:     "enable [option]",
   192  		Short:   "Enable a console option",
   193  		Example: "sudo cscli console enable tainted",
   194  		Long: `
   195  Enable given information push to the central API. Allows to empower the console`,
   196  		ValidArgs:         csconfig.CONSOLE_CONFIGS,
   197  		DisableAutoGenTag: true,
   198  		RunE: func(_ *cobra.Command, args []string) error {
   199  			if enableAll {
   200  				if err := cli.setConsoleOpts(csconfig.CONSOLE_CONFIGS, true); err != nil {
   201  					return err
   202  				}
   203  				log.Infof("All features have been enabled successfully")
   204  			} else {
   205  				if len(args) == 0 {
   206  					return fmt.Errorf("you must specify at least one feature to enable")
   207  				}
   208  				if err := cli.setConsoleOpts(args, true); err != nil {
   209  					return err
   210  				}
   211  				log.Infof("%v have been enabled", args)
   212  			}
   213  
   214  			log.Infof(ReloadMessage())
   215  
   216  			return nil
   217  		},
   218  	}
   219  	cmd.Flags().BoolVarP(&enableAll, "all", "a", false, "Enable all console options")
   220  
   221  	return cmd
   222  }
   223  
   224  func (cli *cliConsole) newDisableCmd() *cobra.Command {
   225  	var disableAll bool
   226  
   227  	cmd := &cobra.Command{
   228  		Use:     "disable [option]",
   229  		Short:   "Disable a console option",
   230  		Example: "sudo cscli console disable tainted",
   231  		Long: `
   232  Disable given information push to the central API.`,
   233  		ValidArgs:         csconfig.CONSOLE_CONFIGS,
   234  		DisableAutoGenTag: true,
   235  		RunE: func(_ *cobra.Command, args []string) error {
   236  			if disableAll {
   237  				if err := cli.setConsoleOpts(csconfig.CONSOLE_CONFIGS, false); err != nil {
   238  					return err
   239  				}
   240  				log.Infof("All features have been disabled")
   241  			} else {
   242  				if err := cli.setConsoleOpts(args, false); err != nil {
   243  					return err
   244  				}
   245  				log.Infof("%v have been disabled", args)
   246  			}
   247  
   248  			log.Infof(ReloadMessage())
   249  
   250  			return nil
   251  		},
   252  	}
   253  	cmd.Flags().BoolVarP(&disableAll, "all", "a", false, "Disable all console options")
   254  
   255  	return cmd
   256  }
   257  
   258  func (cli *cliConsole) newStatusCmd() *cobra.Command {
   259  	cmd := &cobra.Command{
   260  		Use:               "status",
   261  		Short:             "Shows status of the console options",
   262  		Example:           `sudo cscli console status`,
   263  		DisableAutoGenTag: true,
   264  		RunE: func(_ *cobra.Command, _ []string) error {
   265  			cfg := cli.cfg()
   266  			consoleCfg := cfg.API.Server.ConsoleConfig
   267  			switch cfg.Cscli.Output {
   268  			case "human":
   269  				cmdConsoleStatusTable(color.Output, *consoleCfg)
   270  			case "json":
   271  				out := map[string](*bool){
   272  					csconfig.SEND_MANUAL_SCENARIOS:  consoleCfg.ShareManualDecisions,
   273  					csconfig.SEND_CUSTOM_SCENARIOS:  consoleCfg.ShareCustomScenarios,
   274  					csconfig.SEND_TAINTED_SCENARIOS: consoleCfg.ShareTaintedScenarios,
   275  					csconfig.SEND_CONTEXT:           consoleCfg.ShareContext,
   276  					csconfig.CONSOLE_MANAGEMENT:     consoleCfg.ConsoleManagement,
   277  				}
   278  				data, err := json.MarshalIndent(out, "", "  ")
   279  				if err != nil {
   280  					return fmt.Errorf("failed to marshal configuration: %w", err)
   281  				}
   282  				fmt.Println(string(data))
   283  			case "raw":
   284  				csvwriter := csv.NewWriter(os.Stdout)
   285  				err := csvwriter.Write([]string{"option", "enabled"})
   286  				if err != nil {
   287  					return err
   288  				}
   289  
   290  				rows := [][]string{
   291  					{csconfig.SEND_MANUAL_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareManualDecisions)},
   292  					{csconfig.SEND_CUSTOM_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareCustomScenarios)},
   293  					{csconfig.SEND_TAINTED_SCENARIOS, fmt.Sprintf("%t", *consoleCfg.ShareTaintedScenarios)},
   294  					{csconfig.SEND_CONTEXT, fmt.Sprintf("%t", *consoleCfg.ShareContext)},
   295  					{csconfig.CONSOLE_MANAGEMENT, fmt.Sprintf("%t", *consoleCfg.ConsoleManagement)},
   296  				}
   297  				for _, row := range rows {
   298  					err = csvwriter.Write(row)
   299  					if err != nil {
   300  						return err
   301  					}
   302  				}
   303  				csvwriter.Flush()
   304  			}
   305  
   306  			return nil
   307  		},
   308  	}
   309  
   310  	return cmd
   311  }
   312  
   313  func (cli *cliConsole) dumpConfig() error {
   314  	serverCfg := cli.cfg().API.Server
   315  
   316  	out, err := yaml.Marshal(serverCfg.ConsoleConfig)
   317  	if err != nil {
   318  		return fmt.Errorf("while marshaling ConsoleConfig (for %s): %w", serverCfg.ConsoleConfigPath, err)
   319  	}
   320  
   321  	if serverCfg.ConsoleConfigPath == "" {
   322  		serverCfg.ConsoleConfigPath = csconfig.DefaultConsoleConfigFilePath
   323  		log.Debugf("Empty console_path, defaulting to %s", serverCfg.ConsoleConfigPath)
   324  	}
   325  
   326  	if err := os.WriteFile(serverCfg.ConsoleConfigPath, out, 0o600); err != nil {
   327  		return fmt.Errorf("while dumping console config to %s: %w", serverCfg.ConsoleConfigPath, err)
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  func (cli *cliConsole) setConsoleOpts(args []string, wanted bool) error {
   334  	cfg := cli.cfg()
   335  	consoleCfg := cfg.API.Server.ConsoleConfig
   336  
   337  	for _, arg := range args {
   338  		switch arg {
   339  		case csconfig.CONSOLE_MANAGEMENT:
   340  			/*for each flag check if it's already set before setting it*/
   341  			if consoleCfg.ConsoleManagement != nil {
   342  				if *consoleCfg.ConsoleManagement == wanted {
   343  					log.Debugf("%s already set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
   344  				} else {
   345  					log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
   346  					*consoleCfg.ConsoleManagement = wanted
   347  				}
   348  			} else {
   349  				log.Infof("%s set to %t", csconfig.CONSOLE_MANAGEMENT, wanted)
   350  				consoleCfg.ConsoleManagement = ptr.Of(wanted)
   351  			}
   352  
   353  			if cfg.API.Server.OnlineClient.Credentials != nil {
   354  				changed := false
   355  				if wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL == "" {
   356  					changed = true
   357  					cfg.API.Server.OnlineClient.Credentials.PapiURL = types.PAPIBaseURL
   358  				} else if !wanted && cfg.API.Server.OnlineClient.Credentials.PapiURL != "" {
   359  					changed = true
   360  					cfg.API.Server.OnlineClient.Credentials.PapiURL = ""
   361  				}
   362  
   363  				if changed {
   364  					fileContent, err := yaml.Marshal(cfg.API.Server.OnlineClient.Credentials)
   365  					if err != nil {
   366  						return fmt.Errorf("cannot marshal credentials: %w", err)
   367  					}
   368  
   369  					log.Infof("Updating credentials file: %s", cfg.API.Server.OnlineClient.CredentialsFilePath)
   370  
   371  					err = os.WriteFile(cfg.API.Server.OnlineClient.CredentialsFilePath, fileContent, 0o600)
   372  					if err != nil {
   373  						return fmt.Errorf("cannot write credentials file: %w", err)
   374  					}
   375  				}
   376  			}
   377  		case csconfig.SEND_CUSTOM_SCENARIOS:
   378  			/*for each flag check if it's already set before setting it*/
   379  			if consoleCfg.ShareCustomScenarios != nil {
   380  				if *consoleCfg.ShareCustomScenarios == wanted {
   381  					log.Debugf("%s already set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
   382  				} else {
   383  					log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
   384  					*consoleCfg.ShareCustomScenarios = wanted
   385  				}
   386  			} else {
   387  				log.Infof("%s set to %t", csconfig.SEND_CUSTOM_SCENARIOS, wanted)
   388  				consoleCfg.ShareCustomScenarios = ptr.Of(wanted)
   389  			}
   390  		case csconfig.SEND_TAINTED_SCENARIOS:
   391  			/*for each flag check if it's already set before setting it*/
   392  			if consoleCfg.ShareTaintedScenarios != nil {
   393  				if *consoleCfg.ShareTaintedScenarios == wanted {
   394  					log.Debugf("%s already set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
   395  				} else {
   396  					log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
   397  					*consoleCfg.ShareTaintedScenarios = wanted
   398  				}
   399  			} else {
   400  				log.Infof("%s set to %t", csconfig.SEND_TAINTED_SCENARIOS, wanted)
   401  				consoleCfg.ShareTaintedScenarios = ptr.Of(wanted)
   402  			}
   403  		case csconfig.SEND_MANUAL_SCENARIOS:
   404  			/*for each flag check if it's already set before setting it*/
   405  			if consoleCfg.ShareManualDecisions != nil {
   406  				if *consoleCfg.ShareManualDecisions == wanted {
   407  					log.Debugf("%s already set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
   408  				} else {
   409  					log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
   410  					*consoleCfg.ShareManualDecisions = wanted
   411  				}
   412  			} else {
   413  				log.Infof("%s set to %t", csconfig.SEND_MANUAL_SCENARIOS, wanted)
   414  				consoleCfg.ShareManualDecisions = ptr.Of(wanted)
   415  			}
   416  		case csconfig.SEND_CONTEXT:
   417  			/*for each flag check if it's already set before setting it*/
   418  			if consoleCfg.ShareContext != nil {
   419  				if *consoleCfg.ShareContext == wanted {
   420  					log.Debugf("%s already set to %t", csconfig.SEND_CONTEXT, wanted)
   421  				} else {
   422  					log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted)
   423  					*consoleCfg.ShareContext = wanted
   424  				}
   425  			} else {
   426  				log.Infof("%s set to %t", csconfig.SEND_CONTEXT, wanted)
   427  				consoleCfg.ShareContext = ptr.Of(wanted)
   428  			}
   429  		default:
   430  			return fmt.Errorf("unknown flag %s", arg)
   431  		}
   432  	}
   433  
   434  	if err := cli.dumpConfig(); err != nil {
   435  		return fmt.Errorf("failed writing console config: %w", err)
   436  	}
   437  
   438  	return nil
   439  }