Bladeren bron

separate cscli cobra constructors: lapi, machines, bouncers, postoverflows (#1945)

mmetc 2 jaren geleden
bovenliggende
commit
59f6610721
5 gewijzigde bestanden met toevoegingen van 548 en 390 verwijderingen
  1. 113 77
      cmd/crowdsec-cli/bouncers.go
  2. 1 0
      cmd/crowdsec-cli/capi.go
  3. 173 132
      cmd/crowdsec-cli/lapi.go
  4. 185 133
      cmd/crowdsec-cli/machines.go
  5. 76 48
      cmd/crowdsec-cli/postoverflows.go

+ 113 - 77
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,33 +55,8 @@ func getBouncers(out io.Writer, dbClient *database.Client) error {
 	return nil
 }
 
-func NewBouncersCmd() *cobra.Command {
-	/* ---- DECISIONS COMMAND */
-	var cmdBouncers = &cobra.Command{
-		Use:   "bouncers [action]",
-		Short: "Manage bouncers [requires local API]",
-		Long: `To list/add/delete bouncers.
-Note: This command requires database direct access, so is intended to be run on Local API/master.
-`,
-		Args:              cobra.MinimumNArgs(1),
-		Aliases:           []string{"bouncer"},
-		DisableAutoGenTag: true,
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			var err error
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				log.Fatal("Local API is disabled, please run this command on the local API machine")
-			}
-			if err := csConfig.LoadDBConfig(); err != nil {
-				log.Fatal(err)
-			}
-			dbClient, err = database.NewClient(csConfig.DbConfig)
-			if err != nil {
-				log.Fatalf("unable to create new database client: %s", err)
-			}
-		},
-	}
-
-	var cmdBouncersList = &cobra.Command{
+func NewBouncersListCmd() *cobra.Command {
+	cmdBouncersList := &cobra.Command{
 		Use:               "list",
 		Short:             "List bouncers",
 		Long:              `List bouncers`,
@@ -99,9 +70,61 @@ Note: This command requires database direct access, so is intended to be run on
 			}
 		},
 	}
-	cmdBouncers.AddCommand(cmdBouncersList)
 
-	var cmdBouncersAdd = &cobra.Command{
+	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`,
@@ -110,45 +133,33 @@ 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)
-			}
+		RunE: runBouncersAdd,
+	}
 
-			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))
-			}
-		},
+	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)
 	}
-	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{
+	return nil
+}
+
+
+func NewBouncersDeleteCmd() *cobra.Command {
+	cmdBouncersDelete := &cobra.Command{
 		Use:               "delete MyBouncerName",
 		Short:             "delete bouncer",
 		Args:              cobra.MinimumNArgs(1),
@@ -173,16 +184,41 @@ cscli bouncers add MyBouncerName -k %s`, generatePassword(32)),
 			}
 			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)
+		RunE: runBouncersDelete,
+	}
+
+	return cmdBouncersDelete
+}
+
+func NewBouncersCmd() *cobra.Command {
+	/* ---- DECISIONS COMMAND */
+	var cmdBouncers = &cobra.Command{
+		Use:   "bouncers [action]",
+		Short: "Manage bouncers [requires local API]",
+		Long: `To list/add/delete bouncers.
+Note: This command requires database direct access, so is intended to be run on Local API/master.
+`,
+		Args:              cobra.MinimumNArgs(1),
+		Aliases:           []string{"bouncer"},
+		DisableAutoGenTag: true,
+		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+			var err error
+			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
+				log.Fatal("Local API is disabled, please run this command on the local API machine")
+			}
+			if err := csConfig.LoadDBConfig(); err != nil {
+				log.Fatal(err)
+			}
+			dbClient, err = database.NewClient(csConfig.DbConfig)
+			if err != nil {
+				log.Fatalf("unable to create new database client: %s", err)
 			}
 		},
 	}
-	cmdBouncers.AddCommand(cmdBouncersDelete)
+
+	cmdBouncers.AddCommand(NewBouncersListCmd())
+	cmdBouncers.AddCommand(NewBouncersAddCmd())
+	cmdBouncers.AddCommand(NewBouncersDeleteCmd())
+
 	return cmdBouncers
 }

+ 1 - 0
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{

+ 173 - 132
cmd/crowdsec-cli/lapi.go

@@ -21,154 +21,195 @@ import (
 )
 
 var LAPIURLPrefix string = "v1"
-var lapiUser string
 
-func NewLapiCmd() *cobra.Command {
-	var cmdLapi = &cobra.Command{
-		Use:               "lapi [action]",
-		Short:             "Manage interaction with Local API (LAPI)",
-		Args:              cobra.MinimumNArgs(1),
+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,
-		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadAPIClient(); err != nil {
-				return errors.Wrap(err, "loading api client")
-			}
-			return nil
-		},
+		RunE:              runLapiStatus,
 	}
 
-	var cmdLapiRegister = &cobra.Command{
+	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,
-		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)
-
-			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())
-		},
+		RunE: runLapiRegister,
 	}
-	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)
-			}
+	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")
 
-			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)
-			}
+	return cmdLapiRegister
+}
 
-			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)")
+
+func NewLapiCmd() *cobra.Command {
+	var cmdLapi = &cobra.Command{
+		Use:               "lapi [action]",
+		Short:             "Manage interaction with Local API (LAPI)",
+		Args:              cobra.MinimumNArgs(1),
+		DisableAutoGenTag: true,
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := csConfig.LoadAPIClient(); err != nil {
+				return errors.Wrap(err, "loading api client")
 			}
+			return nil
 		},
 	}
-	cmdLapi.AddCommand(cmdLapiStatus)
+
+	cmdLapi.AddCommand(NewLapiRegisterCmd())
+	cmdLapi.AddCommand(NewLapiStatusCmd())
+
 	return cmdLapi
 }

+ 185 - 133
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
+		RunE: runMachinesAdd,
+	}
 
-			// 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]
-			}
+	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")
 
-			/*check if file already exists*/
-			if outputFile != "" {
-				dumpFile = outputFile
-			} else if csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" {
-				dumpFile = csConfig.API.Client.CredentialsFilePath
-			}
+	return cmdMachinesAdd
+}
 
-			// 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))
-			}
-		},
+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,
 	}
-	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{
+	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
 }

+ 76 - 48
cmd/crowdsec-cli/postoverflows.go

@@ -10,46 +10,10 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewPostOverflowsCmd() *cobra.Command {
-	var cmdPostOverflows = &cobra.Command{
-		Use:   "postoverflows [action] [config]",
-		Short: "Install/Remove/Upgrade/Inspect postoverflow(s) from hub",
-		Example: `cscli postoverflows install crowdsecurity/cdn-whitelist
-		cscli postoverflows inspect crowdsecurity/cdn-whitelist
-		cscli postoverflows upgrade crowdsecurity/cdn-whitelist
-		cscli postoverflows list
-		cscli postoverflows remove crowdsecurity/cdn-whitelist`,
-		Args:              cobra.MinimumNArgs(1),
-		Aliases:           []string{"postoverflow"},
-		DisableAutoGenTag: true,
-		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadHub(); err != nil {
-				log.Fatal(err)
-			}
-			if csConfig.Hub == nil {
-				return fmt.Errorf("you must configure cli before interacting with hub")
-			}
-
-			if err := cwhub.SetHubBranch(); err != nil {
-				return fmt.Errorf("error while setting hub branch: %s", 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 get Hub index : %v", err)
-			}
-			return nil
-		},
-		PersistentPostRun: func(cmd *cobra.Command, args []string) {
-			if cmd.Name() == "inspect" || cmd.Name() == "list" {
-				return
-			}
-			log.Infof(ReloadMessage())
-		},
-	}
-
+func NewPostOverflowsInstallCmd() *cobra.Command {
 	var ignoreError bool
-	var cmdPostOverflowsInstall = &cobra.Command{
+
+	cmdPostOverflowsInstall := &cobra.Command{
 		Use:               "install [config]",
 		Short:             "Install given postoverflow(s)",
 		Long:              `Fetch and install given postoverflow(s) from hub`,
@@ -77,12 +41,16 @@ func NewPostOverflowsCmd() *cobra.Command {
 			}
 		},
 	}
+
 	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{
+	return cmdPostOverflowsInstall
+}
+
+func NewPostOverflowsRemoveCmd() *cobra.Command {
+	cmdPostOverflowsRemove := &cobra.Command{
 		Use:               "remove [config]",
 		Short:             "Remove given postoverflow(s)",
 		Long:              `remove given postoverflow(s)`,
@@ -107,12 +75,16 @@ func NewPostOverflowsCmd() *cobra.Command {
 			}
 		},
 	}
+
 	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{
+	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`,
@@ -134,11 +106,15 @@ func NewPostOverflowsCmd() *cobra.Command {
 			}
 		},
 	}
+
 	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{
+	return cmdPostOverflowsUpgrade
+}
+
+func NewPostOverflowsInspectCmd() *cobra.Command {
+	cmdPostOverflowsInspect := &cobra.Command{
 		Use:               "inspect [config]",
 		Short:             "Inspect given postoverflow",
 		Long:              `Inspect given postoverflow`,
@@ -152,9 +128,12 @@ func NewPostOverflowsCmd() *cobra.Command {
 			InspectItem(args[0], cwhub.PARSERS_OVFLW)
 		},
 	}
-	cmdPostOverflows.AddCommand(cmdPostOverflowsInspect)
 
-	var cmdPostOverflowsList = &cobra.Command{
+	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`,
@@ -165,8 +144,57 @@ cscli postoverflows list crowdsecurity/xxx`,
 			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)
+
+	return cmdPostOverflowsList
+}
+
+
+
+func NewPostOverflowsCmd() *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
+		cscli postoverflows inspect crowdsecurity/cdn-whitelist
+		cscli postoverflows upgrade crowdsecurity/cdn-whitelist
+		cscli postoverflows list
+		cscli postoverflows remove crowdsecurity/cdn-whitelist`,
+		Args:              cobra.MinimumNArgs(1),
+		Aliases:           []string{"postoverflow"},
+		DisableAutoGenTag: true,
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := csConfig.LoadHub(); err != nil {
+				log.Fatal(err)
+			}
+			if csConfig.Hub == nil {
+				return fmt.Errorf("you must configure cli before interacting with hub")
+			}
+
+			if err := cwhub.SetHubBranch(); err != nil {
+				return fmt.Errorf("error while setting hub branch: %s", 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 get Hub index : %v", err)
+			}
+			return nil
+		},
+		PersistentPostRun: func(cmd *cobra.Command, args []string) {
+			if cmd.Name() == "inspect" || cmd.Name() == "list" {
+				return
+			}
+			log.Infof(ReloadMessage())
+		},
+	}
+
+	cmdPostOverflows.AddCommand(NewPostOverflowsInstallCmd())
+	cmdPostOverflows.AddCommand(NewPostOverflowsRemoveCmd())
+	cmdPostOverflows.AddCommand(NewPostOverflowsUpgradeCmd())
+	cmdPostOverflows.AddCommand(NewPostOverflowsInspectCmd())
+	cmdPostOverflows.AddCommand(NewPostOverflowsListCmd())
 
 	return cmdPostOverflows
 }