From 59f6610721a36f2c740451ef49e554803aa87dae Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:13:52 +0100 Subject: [PATCH] separate cscli cobra constructors: lapi, machines, bouncers, postoverflows (#1945) --- cmd/crowdsec-cli/bouncers.go | 240 ++++++++++++---------- cmd/crowdsec-cli/capi.go | 1 + cmd/crowdsec-cli/lapi.go | 307 ++++++++++++++++------------ cmd/crowdsec-cli/machines.go | 322 +++++++++++++++++------------- cmd/crowdsec-cli/postoverflows.go | 268 ++++++++++++++----------- 5 files changed, 648 insertions(+), 490 deletions(-) diff --git a/cmd/crowdsec-cli/bouncers.go b/cmd/crowdsec-cli/bouncers.go index eadf245b9..3c2dcec34 100644 --- a/cmd/crowdsec-cli/bouncers.go +++ b/cmd/crowdsec-cli/bouncers.go @@ -18,10 +18,6 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/types" ) -var keyIP string -var keyLength int -var key string - func getBouncers(out io.Writer, dbClient *database.Client) error { bouncers, err := dbClient.ListBouncers() if err != nil { @@ -59,6 +55,141 @@ func getBouncers(out io.Writer, dbClient *database.Client) error { return nil } +func NewBouncersListCmd() *cobra.Command { + cmdBouncersList := &cobra.Command{ + Use: "list", + Short: "List bouncers", + Long: `List bouncers`, + Example: `cscli bouncers list`, + Args: cobra.ExactArgs(0), + DisableAutoGenTag: true, + Run: func(cmd *cobra.Command, arg []string) { + err := getBouncers(color.Output, dbClient) + if err != nil { + log.Fatalf("unable to list bouncers: %s", err) + } + }, + } + + return cmdBouncersList +} + +func runBouncersAdd(cmd *cobra.Command, args []string) error { + flags := cmd.Flags() + + keyLength, err := flags.GetInt("length") + if err != nil { + return err + } + + key, err := flags.GetString("key") + if err != nil { + return err + } + + keyName := args[0] + var apiKey string + + if keyName == "" { + log.Fatalf("Please provide a name for the api key") + } + apiKey = key + if key == "" { + apiKey, err = middlewares.GenerateAPIKey(keyLength) + } + if err != nil { + log.Fatalf("unable to generate api key: %s", err) + } + _, err = dbClient.CreateBouncer(keyName, "", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType) + if err != nil { + log.Fatalf("unable to create bouncer: %s", err) + } + + if csConfig.Cscli.Output == "human" { + fmt.Printf("Api key for '%s':\n\n", keyName) + fmt.Printf(" %s\n\n", apiKey) + fmt.Print("Please keep this key since you will not be able to retrieve it!\n") + } else if csConfig.Cscli.Output == "raw" { + fmt.Printf("%s", apiKey) + } else if csConfig.Cscli.Output == "json" { + j, err := json.Marshal(apiKey) + if err != nil { + log.Fatalf("unable to marshal api key") + } + fmt.Printf("%s", string(j)) + } + + return nil +} + + +func NewBouncersAddCmd() *cobra.Command { + cmdBouncersAdd := &cobra.Command{ + Use: "add MyBouncerName [--length 16]", + Short: "add bouncer", + Long: `add bouncer`, + Example: fmt.Sprintf(`cscli bouncers add MyBouncerName +cscli bouncers add MyBouncerName -l 24 +cscli bouncers add MyBouncerName -k %s`, generatePassword(32)), + Args: cobra.ExactArgs(1), + DisableAutoGenTag: true, + RunE: runBouncersAdd, + } + + flags := cmdBouncersAdd.Flags() + + flags.IntP("length", "l", 16, "length of the api key") + flags.StringP("key", "k", "", "api key for the bouncer") + + return cmdBouncersAdd +} + + +func runBouncersDelete(cmd *cobra.Command, args []string) error { + for _, bouncerID := range args { + err := dbClient.DeleteBouncer(bouncerID) + if err != nil { + log.Fatalf("unable to delete bouncer '%s': %s", bouncerID, err) + } + log.Infof("bouncer '%s' deleted successfully", bouncerID) + } + + return nil +} + + +func NewBouncersDeleteCmd() *cobra.Command { + cmdBouncersDelete := &cobra.Command{ + Use: "delete MyBouncerName", + Short: "delete bouncer", + Args: cobra.MinimumNArgs(1), + Aliases: []string{"remove"}, + DisableAutoGenTag: true, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + var err error + dbClient, err = getDBClient() + if err != nil { + cobra.CompError("unable to create new database client: " + err.Error()) + return nil, cobra.ShellCompDirectiveNoFileComp + } + bouncers, err := dbClient.ListBouncers() + if err != nil { + cobra.CompError("unable to list bouncers " + err.Error()) + } + ret := make([]string, 0) + for _, bouncer := range bouncers { + if strings.Contains(bouncer.Name, toComplete) && !inSlice(bouncer.Name, args) { + ret = append(ret, bouncer.Name) + } + } + return ret, cobra.ShellCompDirectiveNoFileComp + }, + RunE: runBouncersDelete, + } + + return cmdBouncersDelete +} + func NewBouncersCmd() *cobra.Command { /* ---- DECISIONS COMMAND */ var cmdBouncers = &cobra.Command{ @@ -85,104 +216,9 @@ Note: This command requires database direct access, so is intended to be run on }, } - var cmdBouncersList = &cobra.Command{ - Use: "list", - Short: "List bouncers", - Long: `List bouncers`, - Example: `cscli bouncers list`, - Args: cobra.ExactArgs(0), - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, arg []string) { - err := getBouncers(color.Output, dbClient) - if err != nil { - log.Fatalf("unable to list bouncers: %s", err) - } - }, - } - cmdBouncers.AddCommand(cmdBouncersList) + cmdBouncers.AddCommand(NewBouncersListCmd()) + cmdBouncers.AddCommand(NewBouncersAddCmd()) + cmdBouncers.AddCommand(NewBouncersDeleteCmd()) - var cmdBouncersAdd = &cobra.Command{ - Use: "add MyBouncerName [--length 16]", - Short: "add bouncer", - Long: `add bouncer`, - Example: fmt.Sprintf(`cscli bouncers add MyBouncerName -cscli bouncers add MyBouncerName -l 24 -cscli bouncers add MyBouncerName -k %s`, generatePassword(32)), - Args: cobra.ExactArgs(1), - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, arg []string) { - keyName := arg[0] - var apiKey string - var err error - if keyName == "" { - log.Fatalf("Please provide a name for the api key") - } - apiKey = key - if key == "" { - apiKey, err = middlewares.GenerateAPIKey(keyLength) - } - if err != nil { - log.Fatalf("unable to generate api key: %s", err) - } - _, err = dbClient.CreateBouncer(keyName, keyIP, middlewares.HashSHA512(apiKey), types.ApiKeyAuthType) - if err != nil { - log.Fatalf("unable to create bouncer: %s", err) - } - - if csConfig.Cscli.Output == "human" { - fmt.Printf("Api key for '%s':\n\n", keyName) - fmt.Printf(" %s\n\n", apiKey) - fmt.Print("Please keep this key since you will not be able to retrieve it!\n") - } else if csConfig.Cscli.Output == "raw" { - fmt.Printf("%s", apiKey) - } else if csConfig.Cscli.Output == "json" { - j, err := json.Marshal(apiKey) - if err != nil { - log.Fatalf("unable to marshal api key") - } - fmt.Printf("%s", string(j)) - } - }, - } - cmdBouncersAdd.Flags().IntVarP(&keyLength, "length", "l", 16, "length of the api key") - cmdBouncersAdd.Flags().StringVarP(&key, "key", "k", "", "api key for the bouncer") - cmdBouncers.AddCommand(cmdBouncersAdd) - - var cmdBouncersDelete = &cobra.Command{ - Use: "delete MyBouncerName", - Short: "delete bouncer", - Args: cobra.MinimumNArgs(1), - Aliases: []string{"remove"}, - DisableAutoGenTag: true, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var err error - dbClient, err = getDBClient() - if err != nil { - cobra.CompError("unable to create new database client: " + err.Error()) - return nil, cobra.ShellCompDirectiveNoFileComp - } - bouncers, err := dbClient.ListBouncers() - if err != nil { - cobra.CompError("unable to list bouncers " + err.Error()) - } - ret := make([]string, 0) - for _, bouncer := range bouncers { - if strings.Contains(bouncer.Name, toComplete) && !inSlice(bouncer.Name, args) { - ret = append(ret, bouncer.Name) - } - } - return ret, cobra.ShellCompDirectiveNoFileComp - }, - Run: func(cmd *cobra.Command, args []string) { - for _, bouncerID := range args { - err := dbClient.DeleteBouncer(bouncerID) - if err != nil { - log.Fatalf("unable to delete bouncer '%s': %s", bouncerID, err) - } - log.Infof("bouncer '%s' deleted successfully", bouncerID) - } - }, - } - cmdBouncers.AddCommand(cmdBouncersDelete) return cmdBouncers } diff --git a/cmd/crowdsec-cli/capi.go b/cmd/crowdsec-cli/capi.go index e7e2e3942..648d79cf4 100644 --- a/cmd/crowdsec-cli/capi.go +++ b/cmd/crowdsec-cli/capi.go @@ -22,6 +22,7 @@ import ( var CAPIURLPrefix string = "v2" var CAPIBaseURL string = "https://api.crowdsec.net/" var capiUserPrefix string +var outputFile string func NewCapiCmd() *cobra.Command { var cmdCapi = &cobra.Command{ diff --git a/cmd/crowdsec-cli/lapi.go b/cmd/crowdsec-cli/lapi.go index 8c9d71858..c588c50a4 100644 --- a/cmd/crowdsec-cli/lapi.go +++ b/cmd/crowdsec-cli/lapi.go @@ -21,7 +21,178 @@ import ( ) var LAPIURLPrefix string = "v1" -var lapiUser string + +func runLapiStatus (cmd *cobra.Command, args []string) error { + var err error + + password := strfmt.Password(csConfig.API.Client.Credentials.Password) + apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL) + login := csConfig.API.Client.Credentials.Login + if err != nil { + log.Fatalf("parsing api url ('%s'): %s", apiurl, err) + } + if err := csConfig.LoadHub(); err != nil { + log.Fatal(err) + } + + if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { + log.Info("Run 'sudo cscli hub update' to get the hub index") + log.Fatalf("Failed to load hub index : %s", err) + } + scenarios, err := cwhub.GetInstalledScenariosAsString() + if err != nil { + log.Fatalf("failed to get scenarios : %s", err) + } + + Client, err = apiclient.NewDefaultClient(apiurl, + LAPIURLPrefix, + fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), + nil) + if err != nil { + log.Fatalf("init default client: %s", err) + } + t := models.WatcherAuthRequest{ + MachineID: &login, + Password: &password, + Scenarios: scenarios, + } + log.Infof("Loaded credentials from %s", csConfig.API.Client.CredentialsFilePath) + log.Infof("Trying to authenticate with username %s on %s", login, apiurl) + _, err = Client.Auth.AuthenticateWatcher(context.Background(), t) + if err != nil { + log.Fatalf("Failed to authenticate to Local API (LAPI) : %s", err) + } else { + log.Infof("You can successfully interact with Local API (LAPI)") + } + + return nil +} + + +func runLapiRegister(cmd *cobra.Command, args []string) error { + var err error + + flags := cmd.Flags() + + apiURL, err := flags.GetString("url") + if err != nil { + return err + } + + outputFile, err := flags.GetString("file") + if err != nil { + return err + } + + lapiUser, err := flags.GetString("machine") + if err != nil { + return err + } + + if lapiUser == "" { + lapiUser, err = generateID("") + if err != nil { + log.Fatalf("unable to generate machine id: %s", err) + } + } + password := strfmt.Password(generatePassword(passwordLength)) + if apiURL == "" { + if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" { + apiURL = csConfig.API.Client.Credentials.URL + } else { + log.Fatalf("No Local API URL. Please provide it in your configuration or with the -u parameter") + } + } + /*URL needs to end with /, but user doesn't care*/ + if !strings.HasSuffix(apiURL, "/") { + apiURL += "/" + } + /*URL needs to start with http://, but user doesn't care*/ + if !strings.HasPrefix(apiURL, "http://") && !strings.HasPrefix(apiURL, "https://") { + apiURL = "http://" + apiURL + } + apiurl, err := url.Parse(apiURL) + if err != nil { + log.Fatalf("parsing api url: %s", err) + } + _, err = apiclient.RegisterClient(&apiclient.Config{ + MachineID: lapiUser, + Password: password, + UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), + URL: apiurl, + VersionPrefix: LAPIURLPrefix, + }, nil) + + if err != nil { + log.Fatalf("api client register: %s", err) + } + + log.Printf("Successfully registered to Local API (LAPI)") + + var dumpFile string + if outputFile != "" { + dumpFile = outputFile + } else if csConfig.API.Client.CredentialsFilePath != "" { + dumpFile = csConfig.API.Client.CredentialsFilePath + } else { + dumpFile = "" + } + apiCfg := csconfig.ApiCredentialsCfg{ + Login: lapiUser, + Password: password.String(), + URL: apiURL, + } + apiConfigDump, err := yaml.Marshal(apiCfg) + if err != nil { + log.Fatalf("unable to marshal api credentials: %s", err) + } + if dumpFile != "" { + err = os.WriteFile(dumpFile, apiConfigDump, 0644) + if err != nil { + log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) + } + log.Printf("Local API credentials dumped to '%s'", dumpFile) + } else { + fmt.Printf("%s\n", string(apiConfigDump)) + } + log.Warning(ReloadMessage()) + + return nil +} + + +func NewLapiStatusCmd() *cobra.Command { + cmdLapiStatus := &cobra.Command{ + Use: "status", + Short: "Check authentication to Local API (LAPI)", + Args: cobra.MinimumNArgs(0), + DisableAutoGenTag: true, + RunE: runLapiStatus, + } + + return cmdLapiStatus +} + + +func NewLapiRegisterCmd() *cobra.Command { + cmdLapiRegister := &cobra.Command{ + Use: "register", + Short: "Register a machine to Local API (LAPI)", + Long: `Register you machine to the Local API (LAPI). +Keep in mind the machine needs to be validated by an administrator on LAPI side to be effective.`, + Args: cobra.MinimumNArgs(0), + DisableAutoGenTag: true, + RunE: runLapiRegister, + } + + flags := cmdLapiRegister.Flags() + flags.StringP("url", "u", "", "URL of the API (ie. http://127.0.0.1)") + flags.StringP("file", "f", "", "output file destination") + flags.String("machine", "", "Name of the machine to register with") + + return cmdLapiRegister +} + func NewLapiCmd() *cobra.Command { var cmdLapi = &cobra.Command{ @@ -37,138 +208,8 @@ func NewLapiCmd() *cobra.Command { }, } - var cmdLapiRegister = &cobra.Command{ - Use: "register", - Short: "Register a machine to Local API (LAPI)", - Long: `Register you machine to the Local API (LAPI). -Keep in mind the machine needs to be validated by an administrator on LAPI side to be effective.`, - Args: cobra.MinimumNArgs(0), - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - var err error - if lapiUser == "" { - lapiUser, err = generateID("") - if err != nil { - log.Fatalf("unable to generate machine id: %s", err) - } - } - password := strfmt.Password(generatePassword(passwordLength)) - if apiURL == "" { - if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" { - apiURL = csConfig.API.Client.Credentials.URL - } else { - log.Fatalf("No Local API URL. Please provide it in your configuration or with the -u parameter") - } - } - /*URL needs to end with /, but user doesn't care*/ - if !strings.HasSuffix(apiURL, "/") { - apiURL += "/" - } - /*URL needs to start with http://, but user doesn't care*/ - if !strings.HasPrefix(apiURL, "http://") && !strings.HasPrefix(apiURL, "https://") { - apiURL = "http://" + apiURL - } - apiurl, err := url.Parse(apiURL) - if err != nil { - log.Fatalf("parsing api url: %s", err) - } - _, err = apiclient.RegisterClient(&apiclient.Config{ - MachineID: lapiUser, - Password: password, - UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), - URL: apiurl, - VersionPrefix: LAPIURLPrefix, - }, nil) + cmdLapi.AddCommand(NewLapiRegisterCmd()) + cmdLapi.AddCommand(NewLapiStatusCmd()) - if err != nil { - log.Fatalf("api client register: %s", err) - } - - log.Printf("Successfully registered to Local API (LAPI)") - - var dumpFile string - if outputFile != "" { - dumpFile = outputFile - } else if csConfig.API.Client.CredentialsFilePath != "" { - dumpFile = csConfig.API.Client.CredentialsFilePath - } else { - dumpFile = "" - } - apiCfg := csconfig.ApiCredentialsCfg{ - Login: lapiUser, - Password: password.String(), - URL: apiURL, - } - apiConfigDump, err := yaml.Marshal(apiCfg) - if err != nil { - log.Fatalf("unable to marshal api credentials: %s", err) - } - if dumpFile != "" { - err = os.WriteFile(dumpFile, apiConfigDump, 0644) - if err != nil { - log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) - } - log.Printf("Local API credentials dumped to '%s'", dumpFile) - } else { - fmt.Printf("%s\n", string(apiConfigDump)) - } - log.Warning(ReloadMessage()) - }, - } - cmdLapiRegister.Flags().StringVarP(&apiURL, "url", "u", "", "URL of the API (ie. http://127.0.0.1)") - cmdLapiRegister.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination") - cmdLapiRegister.Flags().StringVar(&lapiUser, "machine", "", "Name of the machine to register with") - cmdLapi.AddCommand(cmdLapiRegister) - - var cmdLapiStatus = &cobra.Command{ - Use: "status", - Short: "Check authentication to Local API (LAPI)", - Args: cobra.MinimumNArgs(0), - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - var err error - - password := strfmt.Password(csConfig.API.Client.Credentials.Password) - apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL) - login := csConfig.API.Client.Credentials.Login - if err != nil { - log.Fatalf("parsing api url ('%s'): %s", apiurl, err) - } - if err := csConfig.LoadHub(); err != nil { - log.Fatal(err) - } - - if err := cwhub.GetHubIdx(csConfig.Hub); err != nil { - log.Info("Run 'sudo cscli hub update' to get the hub index") - log.Fatalf("Failed to load hub index : %s", err) - } - scenarios, err := cwhub.GetInstalledScenariosAsString() - if err != nil { - log.Fatalf("failed to get scenarios : %s", err) - } - - Client, err = apiclient.NewDefaultClient(apiurl, - LAPIURLPrefix, - fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()), - nil) - if err != nil { - log.Fatalf("init default client: %s", err) - } - t := models.WatcherAuthRequest{ - MachineID: &login, - Password: &password, - Scenarios: scenarios, - } - log.Infof("Loaded credentials from %s", csConfig.API.Client.CredentialsFilePath) - log.Infof("Trying to authenticate with username %s on %s", login, apiurl) - _, err = Client.Auth.AuthenticateWatcher(context.Background(), t) - if err != nil { - log.Fatalf("Failed to authenticate to Local API (LAPI) : %s", err) - } else { - log.Infof("You can successfully interact with Local API (LAPI)") - } - }, - } - cmdLapi.AddCommand(cmdLapiStatus) return cmdLapi } diff --git a/cmd/crowdsec-cli/machines.go b/cmd/crowdsec-cli/machines.go index 444072bc0..2c9722af3 100644 --- a/cmd/crowdsec-cli/machines.go +++ b/cmd/crowdsec-cli/machines.go @@ -29,22 +29,15 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/types" ) -var machineID string -var machinePassword string -var interactive bool -var apiURL string -var outputFile string -var forceAdd bool -var autoAdd bool - var ( passwordLength = 64 - upper = "ABCDEFGHIJKLMNOPQRSTUVWXY" - lower = "abcdefghijklmnopqrstuvwxyz" - digits = "0123456789" ) func generatePassword(length int) string { + upper := "ABCDEFGHIJKLMNOPQRSTUVWXY" + lower := "abcdefghijklmnopqrstuvwxyz" + digits := "0123456789" + charset := upper + lower + digits charsetLength := len(charset) @@ -149,32 +142,8 @@ func getAgents(out io.Writer, dbClient *database.Client) error { return nil } -func NewMachinesCmd() *cobra.Command { - /* ---- DECISIONS COMMAND */ - var cmdMachines = &cobra.Command{ - Use: "machines [action]", - Short: "Manage local API machines [requires local API]", - Long: `To list/add/delete/validate machines. -Note: This command requires database direct access, so is intended to be run on the local API machine. -`, - Example: `cscli machines [action]`, - DisableAutoGenTag: true, - Aliases: []string{"machine"}, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI { - if err != nil { - log.Errorf("local api : %s", err) - } - log.Fatal("Local API is disabled, please run this command on the local API machine") - } - if err := csConfig.LoadDBConfig(); err != nil { - log.Errorf("This command requires direct database access (must be run on the local API machine)") - log.Fatal(err) - } - }, - } - - var cmdMachinesList = &cobra.Command{ +func NewMachinesListCmd() *cobra.Command { + cmdMachinesList := &cobra.Command{ Use: "list", Short: "List machines", Long: `List `, @@ -195,9 +164,12 @@ Note: This command requires database direct access, so is intended to be run on } }, } - cmdMachines.AddCommand(cmdMachinesList) - var cmdMachinesAdd = &cobra.Command{ + return cmdMachinesList +} + +func NewMachinesAddCmd() *cobra.Command { + cmdMachinesAdd := &cobra.Command{ Use: "add", Short: "add machine to the database.", DisableAutoGenTag: true, @@ -214,90 +186,132 @@ cscli machines add MyTestMachine --password MyPassword log.Fatalf("unable to create new database client: %s", err) } }, - Run: func(cmd *cobra.Command, args []string) { - var dumpFile string - var err error - - // create machineID if not specified by user - if len(args) == 0 { - if !autoAdd { - printHelp(cmd) - return - } - machineID, err = generateID("") - if err != nil { - log.Fatalf("unable to generate machine id : %s", err) - } - } else { - machineID = args[0] - } - - /*check if file already exists*/ - if outputFile != "" { - dumpFile = outputFile - } else if csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" { - dumpFile = csConfig.API.Client.CredentialsFilePath - } - - // create a password if it's not specified by user - if machinePassword == "" && !interactive { - if !autoAdd { - printHelp(cmd) - return - } - machinePassword = generatePassword(passwordLength) - } else if machinePassword == "" && interactive { - qs := &survey.Password{ - Message: "Please provide a password for the machine", - } - survey.AskOne(qs, &machinePassword) - } - password := strfmt.Password(machinePassword) - _, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType) - if err != nil { - log.Fatalf("unable to create machine: %s", err) - } - log.Infof("Machine '%s' successfully added to the local API", machineID) - - if apiURL == "" { - if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" { - apiURL = csConfig.API.Client.Credentials.URL - } else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" { - apiURL = "http://" + csConfig.API.Server.ListenURI - } else { - log.Fatalf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") - } - } - apiCfg := csconfig.ApiCredentialsCfg{ - Login: machineID, - Password: password.String(), - URL: apiURL, - } - apiConfigDump, err := yaml.Marshal(apiCfg) - if err != nil { - log.Fatalf("unable to marshal api credentials: %s", err) - } - if dumpFile != "" && dumpFile != "-" { - err = os.WriteFile(dumpFile, apiConfigDump, 0644) - if err != nil { - log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) - } - log.Printf("API credentials dumped to '%s'", dumpFile) - } else { - fmt.Printf("%s\n", string(apiConfigDump)) - } - }, + RunE: runMachinesAdd, } - cmdMachinesAdd.Flags().StringVarP(&machinePassword, "password", "p", "", "machine password to login to the API") - cmdMachinesAdd.Flags().StringVarP(&outputFile, "file", "f", "", - "output file destination (defaults to "+csconfig.DefaultConfigPath("local_api_credentials.yaml")) - cmdMachinesAdd.Flags().StringVarP(&apiURL, "url", "u", "", "URL of the local API") - cmdMachinesAdd.Flags().BoolVarP(&interactive, "interactive", "i", false, "interfactive mode to enter the password") - cmdMachinesAdd.Flags().BoolVarP(&autoAdd, "auto", "a", false, "automatically generate password (and username if not provided)") - cmdMachinesAdd.Flags().BoolVar(&forceAdd, "force", false, "will force add the machine if it already exist") - cmdMachines.AddCommand(cmdMachinesAdd) - var cmdMachinesDelete = &cobra.Command{ + flags := cmdMachinesAdd.Flags() + flags.StringP("password", "p", "", "machine password to login to the API") + flags.StringP("file", "f", "", "output file destination (defaults to "+csconfig.DefaultConfigPath("local_api_credentials.yaml")) + flags.StringP("url", "u", "", "URL of the local API") + flags.BoolP("interactive", "i", false, "interfactive mode to enter the password") + flags.BoolP("auto", "a", false, "automatically generate password (and username if not provided)") + flags.Bool("force", false, "will force add the machine if it already exist") + + return cmdMachinesAdd +} + +func runMachinesAdd(cmd *cobra.Command, args []string) error { + var dumpFile string + var err error + + flags := cmd.Flags() + + machinePassword, err := flags.GetString("password") + if err != nil { + return err + } + + outputFile, err := flags.GetString("file") + if err != nil { + return err + } + + apiURL, err := flags.GetString("url") + if err != nil { + return err + } + + interactive, err := flags.GetBool("interactive") + if err != nil { + return err + } + + autoAdd, err := flags.GetBool("auto") + if err != nil { + return err + } + + forceAdd, err := flags.GetBool("force") + if err != nil { + return err + } + + var machineID string + + // create machineID if not specified by user + if len(args) == 0 { + if !autoAdd { + printHelp(cmd) + return nil + } + machineID, err = generateID("") + if err != nil { + log.Fatalf("unable to generate machine id : %s", err) + } + } else { + machineID = args[0] + } + + /*check if file already exists*/ + if outputFile != "" { + dumpFile = outputFile + } else if csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" { + dumpFile = csConfig.API.Client.CredentialsFilePath + } + + // create a password if it's not specified by user + if machinePassword == "" && !interactive { + if !autoAdd { + printHelp(cmd) + return nil + } + machinePassword = generatePassword(passwordLength) + } else if machinePassword == "" && interactive { + qs := &survey.Password{ + Message: "Please provide a password for the machine", + } + survey.AskOne(qs, &machinePassword) + } + password := strfmt.Password(machinePassword) + _, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType) + if err != nil { + log.Fatalf("unable to create machine: %s", err) + } + log.Infof("Machine '%s' successfully added to the local API", machineID) + + if apiURL == "" { + if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" { + apiURL = csConfig.API.Client.Credentials.URL + } else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" { + apiURL = "http://" + csConfig.API.Server.ListenURI + } else { + log.Fatalf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") + } + } + apiCfg := csconfig.ApiCredentialsCfg{ + Login: machineID, + Password: password.String(), + URL: apiURL, + } + apiConfigDump, err := yaml.Marshal(apiCfg) + if err != nil { + log.Fatalf("unable to marshal api credentials: %s", err) + } + if dumpFile != "" && dumpFile != "-" { + err = os.WriteFile(dumpFile, apiConfigDump, 0644) + if err != nil { + log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) + } + log.Printf("API credentials dumped to '%s'", dumpFile) + } else { + fmt.Printf("%s\n", string(apiConfigDump)) + } + + return nil +} + +func NewMachinesDeleteCmd() *cobra.Command { + cmdMachinesDelete := &cobra.Command{ Use: "delete [machine_name]...", Short: "delete machines", Example: `cscli machines delete "machine1" "machine2"`, @@ -330,20 +344,27 @@ cscli machines add MyTestMachine --password MyPassword } return ret, cobra.ShellCompDirectiveNoFileComp }, - Run: func(cmd *cobra.Command, args []string) { - for _, machineID := range args { - err := dbClient.DeleteWatcher(machineID) - if err != nil { - log.Errorf("unable to delete machine '%s': %s", machineID, err) - return - } - log.Infof("machine '%s' deleted successfully", machineID) - } - }, + RunE: runMachinesDelete, } - cmdMachines.AddCommand(cmdMachinesDelete) - var cmdMachinesValidate = &cobra.Command{ + return cmdMachinesDelete +} + +func runMachinesDelete(cmd *cobra.Command, args []string) error { + for _, machineID := range args { + err := dbClient.DeleteWatcher(machineID) + if err != nil { + log.Errorf("unable to delete machine '%s': %s", machineID, err) + return nil + } + log.Infof("machine '%s' deleted successfully", machineID) + } + + return nil +} + +func NewMachinesValidateCmd() *cobra.Command { + cmdMachinesValidate := &cobra.Command{ Use: "validate", Short: "validate a machine to access the local API", Long: `validate a machine to access the local API.`, @@ -358,14 +379,45 @@ cscli machines add MyTestMachine --password MyPassword } }, Run: func(cmd *cobra.Command, args []string) { - machineID = args[0] + machineID := args[0] if err := dbClient.ValidateMachine(machineID); err != nil { log.Fatalf("unable to validate machine '%s': %s", machineID, err) } log.Infof("machine '%s' validated successfully", machineID) }, } - cmdMachines.AddCommand(cmdMachinesValidate) + + return cmdMachinesValidate +} + +func NewMachinesCmd() *cobra.Command { + var cmdMachines = &cobra.Command{ + Use: "machines [action]", + Short: "Manage local API machines [requires local API]", + Long: `To list/add/delete/validate machines. +Note: This command requires database direct access, so is intended to be run on the local API machine. +`, + Example: `cscli machines [action]`, + DisableAutoGenTag: true, + Aliases: []string{"machine"}, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI { + if err != nil { + log.Errorf("local api : %s", err) + } + log.Fatal("Local API is disabled, please run this command on the local API machine") + } + if err := csConfig.LoadDBConfig(); err != nil { + log.Errorf("This command requires direct database access (must be run on the local API machine)") + log.Fatal(err) + } + }, + } + + cmdMachines.AddCommand(NewMachinesListCmd()) + cmdMachines.AddCommand(NewMachinesAddCmd()) + cmdMachines.AddCommand(NewMachinesDeleteCmd()) + cmdMachines.AddCommand(NewMachinesValidateCmd()) return cmdMachines } diff --git a/cmd/crowdsec-cli/postoverflows.go b/cmd/crowdsec-cli/postoverflows.go index f51f8a42a..19cffccd2 100644 --- a/cmd/crowdsec-cli/postoverflows.go +++ b/cmd/crowdsec-cli/postoverflows.go @@ -10,8 +10,150 @@ import ( "github.com/crowdsecurity/crowdsec/pkg/cwhub" ) +func NewPostOverflowsInstallCmd() *cobra.Command { + var ignoreError bool + + cmdPostOverflowsInstall := &cobra.Command{ + Use: "install [config]", + Short: "Install given postoverflow(s)", + Long: `Fetch and install given postoverflow(s) from hub`, + Example: `cscli postoverflows install crowdsec/xxx crowdsec/xyz`, + Args: cobra.MinimumNArgs(1), + DisableAutoGenTag: true, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compAllItems(cwhub.PARSERS_OVFLW, args, toComplete) + }, + Run: func(cmd *cobra.Command, args []string) { + for _, name := range args { + t := cwhub.GetItem(cwhub.PARSERS_OVFLW, name) + if t == nil { + nearestItem, score := GetDistance(cwhub.PARSERS_OVFLW, name) + Suggest(cwhub.PARSERS_OVFLW, name, nearestItem.Name, score, ignoreError) + continue + } + if err := cwhub.InstallItem(csConfig, name, cwhub.PARSERS_OVFLW, forceAction, downloadOnly); err != nil { + if ignoreError { + log.Errorf("Error while installing '%s': %s", name, err) + } else { + log.Fatalf("Error while installing '%s': %s", name, err) + } + } + } + }, + } + + cmdPostOverflowsInstall.PersistentFlags().BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable") + cmdPostOverflowsInstall.PersistentFlags().BoolVar(&forceAction, "force", false, "Force install : Overwrite tainted and outdated files") + cmdPostOverflowsInstall.PersistentFlags().BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple postoverflows") + + return cmdPostOverflowsInstall +} + +func NewPostOverflowsRemoveCmd() *cobra.Command { + cmdPostOverflowsRemove := &cobra.Command{ + Use: "remove [config]", + Short: "Remove given postoverflow(s)", + Long: `remove given postoverflow(s)`, + Example: `cscli postoverflows remove crowdsec/xxx crowdsec/xyz`, + DisableAutoGenTag: true, + Aliases: []string{"delete"}, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) + }, + Run: func(cmd *cobra.Command, args []string) { + if all { + cwhub.RemoveMany(csConfig, cwhub.PARSERS_OVFLW, "", all, purge, forceAction) + return + } + + if len(args) == 0 { + log.Fatalf("Specify at least one postoverflow to remove or '--all' flag.") + } + + for _, name := range args { + cwhub.RemoveMany(csConfig, cwhub.PARSERS_OVFLW, name, all, purge, forceAction) + } + }, + } + + cmdPostOverflowsRemove.PersistentFlags().BoolVar(&purge, "purge", false, "Delete source file too") + cmdPostOverflowsRemove.PersistentFlags().BoolVar(&forceAction, "force", false, "Force remove : Remove tainted and outdated files") + cmdPostOverflowsRemove.PersistentFlags().BoolVar(&all, "all", false, "Delete all the postoverflows") + + return cmdPostOverflowsRemove +} + +func NewPostOverflowsUpgradeCmd() *cobra.Command { + cmdPostOverflowsUpgrade := &cobra.Command{ + Use: "upgrade [config]", + Short: "Upgrade given postoverflow(s)", + Long: `Fetch and Upgrade given postoverflow(s) from hub`, + Example: `cscli postoverflows upgrade crowdsec/xxx crowdsec/xyz`, + DisableAutoGenTag: true, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) + }, + Run: func(cmd *cobra.Command, args []string) { + if all { + cwhub.UpgradeConfig(csConfig, cwhub.PARSERS_OVFLW, "", forceAction) + } else { + if len(args) == 0 { + log.Fatalf("no target postoverflow to upgrade") + } + for _, name := range args { + cwhub.UpgradeConfig(csConfig, cwhub.PARSERS_OVFLW, name, forceAction) + } + } + }, + } + + cmdPostOverflowsUpgrade.PersistentFlags().BoolVarP(&all, "all", "a", false, "Upgrade all the postoverflows") + cmdPostOverflowsUpgrade.PersistentFlags().BoolVar(&forceAction, "force", false, "Force upgrade : Overwrite tainted and outdated files") + + return cmdPostOverflowsUpgrade +} + +func NewPostOverflowsInspectCmd() *cobra.Command { + cmdPostOverflowsInspect := &cobra.Command{ + Use: "inspect [config]", + Short: "Inspect given postoverflow", + Long: `Inspect given postoverflow`, + Example: `cscli postoverflows inspect crowdsec/xxx crowdsec/xyz`, + DisableAutoGenTag: true, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) + }, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + InspectItem(args[0], cwhub.PARSERS_OVFLW) + }, + } + + return cmdPostOverflowsInspect +} + +func NewPostOverflowsListCmd() *cobra.Command { + cmdPostOverflowsList := &cobra.Command{ + Use: "list [config]", + Short: "List all postoverflows or given one", + Long: `List all postoverflows or given one`, + Example: `cscli postoverflows list +cscli postoverflows list crowdsecurity/xxx`, + DisableAutoGenTag: true, + Run: func(cmd *cobra.Command, args []string) { + ListItems(color.Output, []string{cwhub.PARSERS_OVFLW}, args, false, true, all) + }, + } + + cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well") + + return cmdPostOverflowsList +} + + + func NewPostOverflowsCmd() *cobra.Command { - var cmdPostOverflows = &cobra.Command{ + cmdPostOverflows := &cobra.Command{ Use: "postoverflows [action] [config]", Short: "Install/Remove/Upgrade/Inspect postoverflow(s) from hub", Example: `cscli postoverflows install crowdsecurity/cdn-whitelist @@ -48,125 +190,11 @@ func NewPostOverflowsCmd() *cobra.Command { }, } - var ignoreError bool - var cmdPostOverflowsInstall = &cobra.Command{ - Use: "install [config]", - Short: "Install given postoverflow(s)", - Long: `Fetch and install given postoverflow(s) from hub`, - Example: `cscli postoverflows install crowdsec/xxx crowdsec/xyz`, - Args: cobra.MinimumNArgs(1), - DisableAutoGenTag: true, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compAllItems(cwhub.PARSERS_OVFLW, args, toComplete) - }, - Run: func(cmd *cobra.Command, args []string) { - for _, name := range args { - t := cwhub.GetItem(cwhub.PARSERS_OVFLW, name) - if t == nil { - nearestItem, score := GetDistance(cwhub.PARSERS_OVFLW, name) - Suggest(cwhub.PARSERS_OVFLW, name, nearestItem.Name, score, ignoreError) - continue - } - if err := cwhub.InstallItem(csConfig, name, cwhub.PARSERS_OVFLW, forceAction, downloadOnly); err != nil { - if ignoreError { - log.Errorf("Error while installing '%s': %s", name, err) - } else { - log.Fatalf("Error while installing '%s': %s", name, err) - } - } - } - }, - } - cmdPostOverflowsInstall.PersistentFlags().BoolVarP(&downloadOnly, "download-only", "d", false, "Only download packages, don't enable") - cmdPostOverflowsInstall.PersistentFlags().BoolVar(&forceAction, "force", false, "Force install : Overwrite tainted and outdated files") - cmdPostOverflowsInstall.PersistentFlags().BoolVar(&ignoreError, "ignore", false, "Ignore errors when installing multiple postoverflows") - cmdPostOverflows.AddCommand(cmdPostOverflowsInstall) - - var cmdPostOverflowsRemove = &cobra.Command{ - Use: "remove [config]", - Short: "Remove given postoverflow(s)", - Long: `remove given postoverflow(s)`, - Example: `cscli postoverflows remove crowdsec/xxx crowdsec/xyz`, - DisableAutoGenTag: true, - Aliases: []string{"delete"}, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) - }, - Run: func(cmd *cobra.Command, args []string) { - if all { - cwhub.RemoveMany(csConfig, cwhub.PARSERS_OVFLW, "", all, purge, forceAction) - return - } - - if len(args) == 0 { - log.Fatalf("Specify at least one postoverflow to remove or '--all' flag.") - } - - for _, name := range args { - cwhub.RemoveMany(csConfig, cwhub.PARSERS_OVFLW, name, all, purge, forceAction) - } - }, - } - cmdPostOverflowsRemove.PersistentFlags().BoolVar(&purge, "purge", false, "Delete source file too") - cmdPostOverflowsRemove.PersistentFlags().BoolVar(&forceAction, "force", false, "Force remove : Remove tainted and outdated files") - cmdPostOverflowsRemove.PersistentFlags().BoolVar(&all, "all", false, "Delete all the postoverflows") - cmdPostOverflows.AddCommand(cmdPostOverflowsRemove) - - var cmdPostOverflowsUpgrade = &cobra.Command{ - Use: "upgrade [config]", - Short: "Upgrade given postoverflow(s)", - Long: `Fetch and Upgrade given postoverflow(s) from hub`, - Example: `cscli postoverflows upgrade crowdsec/xxx crowdsec/xyz`, - DisableAutoGenTag: true, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) - }, - Run: func(cmd *cobra.Command, args []string) { - if all { - cwhub.UpgradeConfig(csConfig, cwhub.PARSERS_OVFLW, "", forceAction) - } else { - if len(args) == 0 { - log.Fatalf("no target postoverflow to upgrade") - } - for _, name := range args { - cwhub.UpgradeConfig(csConfig, cwhub.PARSERS_OVFLW, name, forceAction) - } - } - }, - } - cmdPostOverflowsUpgrade.PersistentFlags().BoolVarP(&all, "all", "a", false, "Upgrade all the postoverflows") - cmdPostOverflowsUpgrade.PersistentFlags().BoolVar(&forceAction, "force", false, "Force upgrade : Overwrite tainted and outdated files") - cmdPostOverflows.AddCommand(cmdPostOverflowsUpgrade) - - var cmdPostOverflowsInspect = &cobra.Command{ - Use: "inspect [config]", - Short: "Inspect given postoverflow", - Long: `Inspect given postoverflow`, - Example: `cscli postoverflows inspect crowdsec/xxx crowdsec/xyz`, - DisableAutoGenTag: true, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstalledItems(cwhub.PARSERS_OVFLW, args, toComplete) - }, - Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - InspectItem(args[0], cwhub.PARSERS_OVFLW) - }, - } - cmdPostOverflows.AddCommand(cmdPostOverflowsInspect) - - var cmdPostOverflowsList = &cobra.Command{ - Use: "list [config]", - Short: "List all postoverflows or given one", - Long: `List all postoverflows or given one`, - Example: `cscli postoverflows list -cscli postoverflows list crowdsecurity/xxx`, - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - ListItems(color.Output, []string{cwhub.PARSERS_OVFLW}, args, false, true, all) - }, - } - cmdPostOverflowsList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List disabled items as well") - cmdPostOverflows.AddCommand(cmdPostOverflowsList) + cmdPostOverflows.AddCommand(NewPostOverflowsInstallCmd()) + cmdPostOverflows.AddCommand(NewPostOverflowsRemoveCmd()) + cmdPostOverflows.AddCommand(NewPostOverflowsUpgradeCmd()) + cmdPostOverflows.AddCommand(NewPostOverflowsInspectCmd()) + cmdPostOverflows.AddCommand(NewPostOverflowsListCmd()) return cmdPostOverflows }