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 }