Переглянути джерело

Check cscli preconditions with crowdsec-cli/require package (#2388)

mmetc 1 рік тому
батько
коміт
5cb7013575

+ 4 - 2
cmd/crowdsec-cli/alerts.go

@@ -25,6 +25,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 func DecisionsFromAlert(alert *models.Alert) string {
 func DecisionsFromAlert(alert *models.Alert) string {
@@ -525,8 +527,8 @@ func NewAlertsFlushCmd() *cobra.Command {
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		RunE: func(cmd *cobra.Command, args []string) error {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				return fmt.Errorf("local API is disabled, please run this command on the local API machine")
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			if err != nil {

+ 5 - 2
cmd/crowdsec-cli/bouncers.go

@@ -16,6 +16,8 @@ import (
 	middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
 	middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 func getBouncers(out io.Writer, dbClient *database.Client) error {
 func getBouncers(out io.Writer, dbClient *database.Client) error {
@@ -200,9 +202,10 @@ Note: This command requires database direct access, so is intended to be run on
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				return fmt.Errorf("local API is disabled, please run this command on the local API machine")
+			if err = require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
+
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			if err != nil {
 				return fmt.Errorf("unable to create new database client: %s", err)
 				return fmt.Errorf("unable to create new database client: %s", err)

+ 7 - 11
cmd/crowdsec-cli/capi.go

@@ -19,6 +19,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/fflag"
 	"github.com/crowdsecurity/crowdsec/pkg/fflag"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 const CAPIBaseURL string = "https://api.crowdsec.net/"
 const CAPIBaseURL string = "https://api.crowdsec.net/"
@@ -31,14 +33,12 @@ func NewCapiCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadAPIServer(); err != nil {
-				return fmt.Errorf("local API is disabled, please run this command on the local API machine: %w", err)
-			}
-			if csConfig.DisableAPI {
-				return nil
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.API.Server.OnlineClient == nil {
-				log.Fatalf("no configuration for Central API in '%s'", *csConfig.FilePath)
+
+			if err := require.CAPI(csConfig); err != nil {
+				return err
 			}
 			}
 
 
 			return nil
 			return nil
@@ -134,10 +134,6 @@ func NewCapiStatusCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(0),
 		Args:              cobra.MinimumNArgs(0),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		Run: func(cmd *cobra.Command, args []string) {
 		Run: func(cmd *cobra.Command, args []string) {
-			var err error
-			if csConfig.API.Server == nil {
-				log.Fatal("There is no configuration on 'api.server:'")
-			}
 			if csConfig.API.Server.OnlineClient == nil {
 			if csConfig.API.Server.OnlineClient == nil {
 				log.Fatalf("Please provide credentials for the Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath)
 				log.Fatalf("Please provide credentials for the Central API (CAPI) in '%s'", csConfig.API.Server.OnlineClient.CredentialsFilePath)
 			}
 			}

+ 11 - 3
cmd/crowdsec-cli/config_show.go

@@ -71,7 +71,7 @@ var configShowTemplate = `Global:
 {{- end }}
 {{- end }}
 
 
 {{- if .Crowdsec }}
 {{- if .Crowdsec }}
-Crowdsec:
+Crowdsec{{if and .Crowdsec.Enable (not (ValueBool .Crowdsec.Enable))}} (disabled){{end}}:
   - Acquisition File        : {{.Crowdsec.AcquisitionFilePath}}
   - Acquisition File        : {{.Crowdsec.AcquisitionFilePath}}
   - Parsers routines        : {{.Crowdsec.ParserRoutinesCount}}
   - Parsers routines        : {{.Crowdsec.ParserRoutinesCount}}
 {{- if .Crowdsec.AcquisitionDirPath }}
 {{- if .Crowdsec.AcquisitionDirPath }}
@@ -97,7 +97,7 @@ API Client:
 {{- end }}
 {{- end }}
 
 
 {{- if .API.Server }}
 {{- if .API.Server }}
-Local API Server:
+Local API Server{{if and .API.Server.Enable (not (ValueBool .API.Server.Enable))}} (disabled){{end}}:
   - Listen URL              : {{.API.Server.ListenURI}}
   - Listen URL              : {{.API.Server.ListenURI}}
   - Profile File            : {{.API.Server.ProfilesPath}}
   - Profile File            : {{.API.Server.ProfilesPath}}
 
 
@@ -194,7 +194,15 @@ func runConfigShow(cmd *cobra.Command, args []string) error {
 
 
 	switch csConfig.Cscli.Output {
 	switch csConfig.Cscli.Output {
 	case "human":
 	case "human":
-		tmp, err := template.New("config").Parse(configShowTemplate)
+		// The tests on .Enable look funny because the option has a true default which has
+		// not been set yet (we don't really load the LAPI) and go templates don't dereference
+		// pointers in boolean tests. Prefix notation is the cherry on top.
+		funcs := template.FuncMap{
+			// can't use generics here
+			"ValueBool": func(b *bool) bool { return b!=nil && *b },
+		}
+
+		tmp, err := template.New("config").Funcs(funcs).Parse(configShowTemplate)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}

+ 8 - 18
cmd/crowdsec-cli/console.go

@@ -4,9 +4,7 @@ import (
 	"context"
 	"context"
 	"encoding/csv"
 	"encoding/csv"
 	"encoding/json"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"fmt"
-	"io/fs"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 
 
@@ -24,6 +22,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 	"github.com/crowdsecurity/crowdsec/pkg/fflag"
 	"github.com/crowdsecurity/crowdsec/pkg/fflag"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 func NewConsoleCmd() *cobra.Command {
 func NewConsoleCmd() *cobra.Command {
@@ -33,24 +33,14 @@ func NewConsoleCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				var fdErr *fs.PathError
-				if errors.As(err, &fdErr) {
-					log.Fatalf("Unable to load Local API : %s", fdErr)
-				}
-				if err != nil {
-					log.Fatalf("Unable to load required Local API Configuration : %s", err)
-				}
-				log.Fatal("Local API is disabled, please run this command on the local API machine")
-			}
-			if csConfig.DisableAPI {
-				log.Fatal("Local API is disabled, please run this command on the local API machine")
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.API.Server.OnlineClient == nil {
-				log.Fatalf("No configuration for Central API (CAPI) in '%s'", *csConfig.FilePath)
+			if err := require.CAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.API.Server.OnlineClient.Credentials == nil {
-				log.Fatal("You must configure Central API (CAPI) with `cscli capi register` before accessing console features.")
+			if err := require.Enrolled(csConfig); err != nil {
+				return err
 			}
 			}
 			return nil
 			return nil
 		},
 		},

+ 12 - 9
cmd/crowdsec-cli/dashboard.go

@@ -17,6 +17,8 @@ import (
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
 	"github.com/crowdsecurity/crowdsec/pkg/metabase"
 	"github.com/crowdsecurity/crowdsec/pkg/metabase"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 var (
 var (
@@ -54,23 +56,23 @@ cscli dashboard start
 cscli dashboard stop
 cscli dashboard stop
 cscli dashboard remove
 cscli dashboard remove
 `,
 `,
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			if err := metabase.TestAvailability(); err != nil {
-				log.Fatalf("%s", err)
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
 
 
-			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 := metabase.TestAvailability(); err != nil {
+				return err
 			}
 			}
 
 
 			metabaseConfigFolderPath := filepath.Join(csConfig.ConfigPaths.ConfigDir, metabaseConfigFolder)
 			metabaseConfigFolderPath := filepath.Join(csConfig.ConfigPaths.ConfigDir, metabaseConfigFolder)
 			metabaseConfigPath = filepath.Join(metabaseConfigFolderPath, metabaseConfigFile)
 			metabaseConfigPath = filepath.Join(metabaseConfigFolderPath, metabaseConfigFile)
 			if err := os.MkdirAll(metabaseConfigFolderPath, os.ModePerm); err != nil {
 			if err := os.MkdirAll(metabaseConfigFolderPath, os.ModePerm); err != nil {
-				log.Fatal(err)
+				return err
 			}
 			}
-			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)
+
+			if err := require.DB(csConfig); err != nil {
+				return err
 			}
 			}
 
 
 			/*
 			/*
@@ -84,6 +86,7 @@ cscli dashboard remove
 					metabaseContainerID = oldContainerID
 					metabaseContainerID = oldContainerID
 				}
 				}
 			}
 			}
+			return nil
 		},
 		},
 	}
 	}
 
 

+ 4 - 5
cmd/crowdsec-cli/machines.go

@@ -26,6 +26,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent"
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 var (
 var (
@@ -411,11 +413,8 @@ Note: This command requires database direct access, so is intended to be run on
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		Aliases:           []string{"machine"},
 		Aliases:           []string{"machine"},
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				if err != nil {
-					log.Errorf("local api : %s", err)
-				}
-				return fmt.Errorf("local API is disabled, please run this command on the local API machine")
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
 
 
 			return nil
 			return nil

+ 12 - 8
cmd/crowdsec-cli/notifications.go

@@ -25,6 +25,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
 	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
 	"github.com/crowdsecurity/crowdsec/pkg/csplugin"
 	"github.com/crowdsecurity/crowdsec/pkg/csplugin"
 	"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
 	"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 type NotificationsCfg struct {
 type NotificationsCfg struct {
@@ -41,16 +43,18 @@ func NewNotificationsCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		Aliases:           []string{"notifications", "notification"},
 		Aliases:           []string{"notifications", "notification"},
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
-			var (
-				err error
-			)
-			if err = csConfig.API.Server.LoadProfiles(); err != nil {
-				log.Fatal(err)
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.ConfigPaths.NotificationDir == "" {
-				log.Fatalf("config_paths.notification_dir is not set in crowdsec config")
+			if err := require.Profiles(csConfig); err != nil {
+				return err
 			}
 			}
+			if err := require.Notifications(csConfig); err != nil {
+				return err
+			}
+
+			return nil
 		},
 		},
 	}
 	}
 
 

+ 8 - 7
cmd/crowdsec-cli/papi.go

@@ -1,7 +1,6 @@
 package main
 package main
 
 
 import (
 import (
-	"fmt"
 	"time"
 	"time"
 
 
 	log "github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
@@ -12,6 +11,8 @@ import (
 
 
 	"github.com/crowdsecurity/crowdsec/pkg/apiserver"
 	"github.com/crowdsecurity/crowdsec/pkg/apiserver"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
+
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 )
 
 
 func NewPapiCmd() *cobra.Command {
 func NewPapiCmd() *cobra.Command {
@@ -21,14 +22,14 @@ func NewPapiCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
-			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
-				return fmt.Errorf("Local API is disabled, please run this command on the local API machine: %w", err)
+			if err := require.LAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.API.Server.OnlineClient == nil {
-				log.Fatalf("no configuration for Central API in '%s'", *csConfig.FilePath)
+			if err := require.CAPI(csConfig); err != nil {
+				return err
 			}
 			}
-			if csConfig.API.Server.OnlineClient.Credentials.PapiURL == "" {
-				log.Fatalf("no PAPI URL in configuration")
+			if err := require.PAPI(csConfig); err != nil {
+				return err
 			}
 			}
 			return nil
 			return nil
 		},
 		},

+ 85 - 0
cmd/crowdsec-cli/require/require.go

@@ -0,0 +1,85 @@
+package require
+
+import (
+	"fmt"
+
+	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
+)
+
+func LAPI(c *csconfig.Config) error {
+	if err := c.LoadAPIServer(); err != nil {
+		return fmt.Errorf("failed to load Local API: %w", err)
+	}
+
+	if c.DisableAPI {
+		return fmt.Errorf("local API is disabled -- this command must be run on the local API machine")
+	}
+
+	return nil
+}
+
+func CAPI(c *csconfig.Config) error {
+	if c.API.Server.OnlineClient == nil {
+		return fmt.Errorf("no configuration for Central API (CAPI) in '%s'", *c.FilePath)
+	}
+	return nil
+}
+
+func PAPI(c *csconfig.Config) error {
+	if err := LAPI(c); err != nil {
+		return err
+	}
+
+	if err := CAPI(c); err != nil {
+		return err
+	}
+
+	if c.API.Server.OnlineClient.Credentials.PapiURL == "" {
+		return fmt.Errorf("no PAPI URL in configuration")
+	}
+	return nil
+}
+
+func Enrolled(c *csconfig.Config) error {
+	if err := CAPI(c); err != nil {
+		return err
+	}
+
+	if c.API.Server.OnlineClient.Credentials == nil {
+		return fmt.Errorf("the Central API (CAPI) must be configured with 'cscli capi register'")
+	}
+
+	return nil
+}
+
+func DB(c *csconfig.Config) error {
+	if err := c.LoadDBConfig(); err != nil {
+		return fmt.Errorf("this command requires direct database access (must be run on the local API machine): %w", err)
+	}
+	return nil
+}
+
+func Profiles(c *csconfig.Config) error {
+	if err := LAPI(c); err != nil {
+		return err
+	}
+
+	if err := c.API.Server.LoadProfiles(); err != nil {
+		return fmt.Errorf("while loading profiles: %w", err)
+	}
+
+	return nil
+}
+
+func Notifications(c *csconfig.Config) error {
+	if err := LAPI(c); err != nil {
+		return err
+	}
+
+	if c.ConfigPaths.NotificationDir == "" {
+		return fmt.Errorf("config_paths.notification_dir is not set in crowdsec config")
+	}
+
+	return nil
+}
+

+ 3 - 3
cmd/crowdsec/main.go

@@ -249,13 +249,13 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if !flags.DisableAgent {
+	if !cConfig.DisableAgent {
 		if err := cConfig.LoadCrowdsec(); err != nil {
 		if err := cConfig.LoadCrowdsec(); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
 
 
-	if !flags.DisableAPI {
+	if !cConfig.DisableAPI {
 		if err := cConfig.LoadAPIServer(); err != nil {
 		if err := cConfig.LoadAPIServer(); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -290,7 +290,7 @@ func LoadConfig(configFile string, disableAgent bool, disableAPI bool, quiet boo
 			cConfig.API.Server.OnlineClient = nil
 			cConfig.API.Server.OnlineClient = nil
 		}
 		}
 		/*if the api is disabled as well, just read file and exit, don't daemonize*/
 		/*if the api is disabled as well, just read file and exit, don't daemonize*/
-		if flags.DisableAPI {
+		if cConfig.DisableAPI {
 			cConfig.Common.Daemonize = false
 			cConfig.Common.Daemonize = false
 		}
 		}
 		log.Infof("single file mode : log_media=%s daemonize=%t", cConfig.Common.LogMedia, cConfig.Common.Daemonize)
 		log.Infof("single file mode : log_media=%s daemonize=%t", cConfig.Common.LogMedia, cConfig.Common.Daemonize)

+ 10 - 9
docker/docker_start.sh

@@ -243,7 +243,7 @@ if istrue "$DISABLE_ONLINE_API"; then
 fi
 fi
 
 
 # registration to online API for signal push
 # registration to online API for signal push
-if isfalse "$DISABLE_ONLINE_API" ; then
+if isfalse "$DISABLE_LOCAL_API" && isfalse "$DISABLE_ONLINE_API" ; then
     CONFIG_DIR=$(conf_get '.config_paths.config_dir')
     CONFIG_DIR=$(conf_get '.config_paths.config_dir')
     export CONFIG_DIR
     export CONFIG_DIR
     config_exists=$(conf_get '.api.server.online_client | has("credentials_path")')
     config_exists=$(conf_get '.api.server.online_client | has("credentials_path")')
@@ -255,7 +255,7 @@ if isfalse "$DISABLE_ONLINE_API" ; then
 fi
 fi
 
 
 # Enroll instance if enroll key is provided
 # Enroll instance if enroll key is provided
-if isfalse "$DISABLE_ONLINE_API" && [ "$ENROLL_KEY" != "" ]; then
+if isfalse "$DISABLE_LOCAL_API" && isfalse "$DISABLE_ONLINE_API" && [ "$ENROLL_KEY" != "" ]; then
     enroll_args=""
     enroll_args=""
     if [ "$ENROLL_INSTANCE_NAME" != "" ]; then
     if [ "$ENROLL_INSTANCE_NAME" != "" ]; then
         enroll_args="--name $ENROLL_INSTANCE_NAME"
         enroll_args="--name $ENROLL_INSTANCE_NAME"
@@ -278,8 +278,7 @@ if [ "$GID" != "" ]; then
     fi
     fi
 fi
 fi
 
 
-# XXX only with LAPI
-if istrue "$USE_TLS"; then
+if isfalse "$DISABLE_LOCAL_API" && istrue "$USE_TLS"; then
     agents_allowed_yaml=$(csv2yaml "$AGENTS_ALLOWED_OU")
     agents_allowed_yaml=$(csv2yaml "$AGENTS_ALLOWED_OU")
     export agents_allowed_yaml
     export agents_allowed_yaml
     bouncers_allowed_yaml=$(csv2yaml "$BOUNCERS_ALLOWED_OU")
     bouncers_allowed_yaml=$(csv2yaml "$BOUNCERS_ALLOWED_OU")
@@ -358,7 +357,7 @@ shopt -s nullglob extglob
 for BOUNCER in /run/secrets/@(bouncer_key|BOUNCER_KEY)* ; do
 for BOUNCER in /run/secrets/@(bouncer_key|BOUNCER_KEY)* ; do
     KEY=$(cat "${BOUNCER}")
     KEY=$(cat "${BOUNCER}")
     NAME=$(echo "${BOUNCER}" | awk -F "/" '{printf $NF}' | cut -d_  -f2-)
     NAME=$(echo "${BOUNCER}" | awk -F "/" '{printf $NF}' | cut -d_  -f2-)
-    if [[ -n $KEY ]] && [[ -n $NAME ]]; then    
+    if [[ -n $KEY ]] && [[ -n $NAME ]]; then
         register_bouncer "$NAME" "$KEY"
         register_bouncer "$NAME" "$KEY"
     fi
     fi
 done
 done
@@ -369,6 +368,12 @@ shopt -u nullglob extglob
 conf_set_if "$CAPI_WHITELISTS_PATH" '.api.server.capi_whitelists_path = strenv(CAPI_WHITELISTS_PATH)'
 conf_set_if "$CAPI_WHITELISTS_PATH" '.api.server.capi_whitelists_path = strenv(CAPI_WHITELISTS_PATH)'
 conf_set_if "$METRICS_PORT" '.prometheus.listen_port=env(METRICS_PORT)'
 conf_set_if "$METRICS_PORT" '.prometheus.listen_port=env(METRICS_PORT)'
 
 
+if istrue "$DISABLE_LOCAL_API"; then
+    conf_set '.api.server.enable=false'
+else
+    conf_set '.api.server.enable=true'
+fi
+
 ARGS=""
 ARGS=""
 if [ "$CONFIG_FILE" != "" ]; then
 if [ "$CONFIG_FILE" != "" ]; then
     ARGS="-c $CONFIG_FILE"
     ARGS="-c $CONFIG_FILE"
@@ -390,10 +395,6 @@ if istrue "$DISABLE_AGENT"; then
     ARGS="$ARGS -no-cs"
     ARGS="$ARGS -no-cs"
 fi
 fi
 
 
-if istrue "$DISABLE_LOCAL_API"; then
-    ARGS="$ARGS -no-api"
-fi
-
 if istrue "$LEVEL_TRACE"; then
 if istrue "$LEVEL_TRACE"; then
     ARGS="$ARGS -trace"
     ARGS="$ARGS -trace"
 fi
 fi

+ 15 - 15
pkg/csconfig/api.go

@@ -234,6 +234,21 @@ func (c *Config) LoadAPIServer() error {
 		return nil
 		return nil
 	}
 	}
 
 
+	if c.API.Server.Enable == nil {
+		// if the option is not present, it is enabled by default
+		c.API.Server.Enable = ptr.Of(true)
+	}
+
+	if !*c.API.Server.Enable {
+		log.Warning("crowdsec local API is disabled because 'enable' is set to false")
+		c.DisableAPI = true
+		return nil
+	}
+
+	if c.DisableAPI {
+		return nil
+	}
+
 	//inherit log level from common, then api->server
 	//inherit log level from common, then api->server
 	var logLevel log.Level
 	var logLevel log.Level
 	if c.API.Server.LogLevel != nil {
 	if c.API.Server.LogLevel != nil {
@@ -268,21 +283,6 @@ func (c *Config) LoadAPIServer() error {
 		log.Infof("loaded capi whitelist from %s: %d IPs, %d CIDRs", c.API.Server.CapiWhitelistsPath, len(c.API.Server.CapiWhitelists.Ips), len(c.API.Server.CapiWhitelists.Cidrs))
 		log.Infof("loaded capi whitelist from %s: %d IPs, %d CIDRs", c.API.Server.CapiWhitelistsPath, len(c.API.Server.CapiWhitelists.Ips), len(c.API.Server.CapiWhitelists.Cidrs))
 	}
 	}
 
 
-	if c.API.Server.Enable == nil {
-		// if the option is not present, it is enabled by default
-		c.API.Server.Enable = ptr.Of(true)
-	}
-
-	if !*c.API.Server.Enable {
-		log.Warning("crowdsec local API is disabled because 'enable' is set to false")
-		c.DisableAPI = true
-		return nil
-	}
-
-	if c.DisableAPI {
-		return nil
-	}
-
 	if err := c.LoadCommon(); err != nil {
 	if err := c.LoadCommon(); err != nil {
 		return fmt.Errorf("loading common configuration: %s", err)
 		return fmt.Errorf("loading common configuration: %s", err)
 	}
 	}

+ 1 - 0
pkg/csconfig/api_test.go

@@ -234,6 +234,7 @@ func TestLoadAPIServer(t *testing.T) {
 				DisableAPI: false,
 				DisableAPI: false,
 			},
 			},
 			expected: &LocalApiServerCfg{
 			expected: &LocalApiServerCfg{
+				Enable:    ptr.Of(true),
 				PapiLogLevel: &logLevel,
 				PapiLogLevel: &logLevel,
 			},
 			},
 			expectedErr: "no database configuration provided",
 			expectedErr: "no database configuration provided",

+ 13 - 6
test/bats/02_nolapi.bats

@@ -45,16 +45,23 @@ teardown() {
     config_disable_lapi
     config_disable_lapi
     rune -1 cscli capi status
     rune -1 cscli capi status
     assert_stderr --partial "crowdsec local API is disabled"
     assert_stderr --partial "crowdsec local API is disabled"
-    assert_stderr --partial "There is no configuration on 'api.server:'"
+    assert_stderr --partial "local API is disabled -- this command must be run on the local API machine"
 }
 }
 
 
-@test "cscli config show -o human" {
-    config_disable_lapi
+@test "no lapi: cscli config show -o human" {
+    config_set '.api.server.enable=false'
+    rune -0 cscli config show -o human
+    assert_output --partial "Global:"
+    assert_output --partial "Crowdsec:"
+    assert_output --partial "cscli:"
+    assert_output --partial "Local API Server (disabled):"
+
+    config_set 'del(.api.server)'
     rune -0 cscli config show -o human
     rune -0 cscli config show -o human
     assert_output --partial "Global:"
     assert_output --partial "Global:"
     assert_output --partial "Crowdsec:"
     assert_output --partial "Crowdsec:"
     assert_output --partial "cscli:"
     assert_output --partial "cscli:"
-    refute_output --partial "Local API Server:"
+    refute_output --partial "Local API Server"
 }
 }
 
 
 @test "cscli config backup" {
 @test "cscli config backup" {
@@ -73,7 +80,7 @@ teardown() {
     config_disable_lapi
     config_disable_lapi
     ./instance-crowdsec start || true
     ./instance-crowdsec start || true
     rune -1 cscli machines list
     rune -1 cscli machines list
-    assert_stderr --partial "local API is disabled, please run this command on the local API machine"
+    assert_stderr --partial "local API is disabled -- this command must be run on the local API machine"
 }
 }
 
 
 @test "cscli metrics" {
 @test "cscli metrics" {
@@ -85,5 +92,5 @@ teardown() {
     assert_output --partial "/v1/watchers/login"
     assert_output --partial "/v1/watchers/login"
 
 
     assert_stderr --partial "crowdsec local API is disabled"
     assert_stderr --partial "crowdsec local API is disabled"
-    assert_stderr --partial "local API is disabled, please run this command on the local API machine"
+    assert_stderr --partial "local API is disabled -- this command must be run on the local API machine"
 }
 }

+ 8 - 2
test/bats/03_noagent.bats

@@ -40,13 +40,19 @@ teardown() {
 }
 }
 
 
 @test "no agent: cscli config show" {
 @test "no agent: cscli config show" {
-    config_disable_agent
+    config_set '.crowdsec_service.enable=false'
     rune -0 cscli config show -o human
     rune -0 cscli config show -o human
     assert_output --partial "Global:"
     assert_output --partial "Global:"
     assert_output --partial "cscli:"
     assert_output --partial "cscli:"
     assert_output --partial "Local API Server:"
     assert_output --partial "Local API Server:"
+    assert_output --partial "Crowdsec (disabled):"
 
 
-    refute_output --partial "Crowdsec:"
+    config_set 'del(.crowdsec_service)'
+    rune -0 cscli config show -o human
+    assert_output --partial "Global:"
+    assert_output --partial "cscli:"
+    assert_output --partial "Local API Server:"
+    refute_output --partial "Crowdsec"
 }
 }
 
 
 @test "no agent: cscli config backup" {
 @test "no agent: cscli config backup" {

+ 7 - 1
test/bats/04_capi.bats

@@ -60,5 +60,11 @@ setup() {
     ONLINE_API_CREDENTIALS_YAML="$(config_get '.api.server.online_client.credentials_path')"
     ONLINE_API_CREDENTIALS_YAML="$(config_get '.api.server.online_client.credentials_path')"
     rm "${ONLINE_API_CREDENTIALS_YAML}"
     rm "${ONLINE_API_CREDENTIALS_YAML}"
     rune -1 cscli capi status
     rune -1 cscli capi status
-    assert_stderr --partial "local API is disabled, please run this command on the local API machine: loading online client credentials: failed to read api server credentials configuration file '${ONLINE_API_CREDENTIALS_YAML}': open ${ONLINE_API_CREDENTIALS_YAML}: no such file or directory"
+    assert_stderr --partial "failed to load Local API: loading online client credentials: failed to read api server credentials configuration file '${ONLINE_API_CREDENTIALS_YAML}': open ${ONLINE_API_CREDENTIALS_YAML}: no such file or directory"
+}
+
+@test "capi register must be run from lapi" {
+    config_disable_lapi
+    rune -1 cscli capi register --schmilblick githubciXXXXXXXXXXXXXXXXXXXXXXXX
+    assert_stderr --partial "local API is disabled -- this command must be run on the local API machine"
 }
 }

+ 1 - 1
test/bats/04_nocapi.bats

@@ -41,7 +41,7 @@ teardown() {
     config_disable_capi
     config_disable_capi
     ./instance-crowdsec start
     ./instance-crowdsec start
     rune -1 cscli capi status
     rune -1 cscli capi status
-    assert_stderr --partial "no configuration for Central API in "
+    assert_stderr --partial "no configuration for Central API (CAPI) in "
 }
 }
 
 
 @test "no capi: cscli config show" {
 @test "no capi: cscli config show" {

+ 1 - 1
test/bats/07_setup.bats

@@ -311,7 +311,7 @@ update-notifier-motd.timer              enabled enabled
 @test "cscli setup detect (process)" {
 @test "cscli setup detect (process)" {
     # This is harder to mock, because gopsutil requires proc/ to be a mount
     # This is harder to mock, because gopsutil requires proc/ to be a mount
     # point. So we pick a process that exists for sure.
     # point. So we pick a process that exists for sure.
-    expected_process=$(basename "$SHELL")
+    expected_process=cscli
 
 
     cat <<-EOT >"${DETECT_YAML}"
     cat <<-EOT >"${DETECT_YAML}"
 	version: 1.0
 	version: 1.0

+ 39 - 0
test/bats/12_notifications.bats

@@ -0,0 +1,39 @@
+#!/usr/bin/env bats
+# vim: ft=bats:list:ts=8:sts=4:sw=4:et:ai:si:
+
+set -u
+
+setup_file() {
+    load "../lib/setup_file.sh"
+}
+
+teardown_file() {
+    load "../lib/teardown_file.sh"
+}
+
+setup() {
+    load "../lib/setup.sh"
+    load "../lib/bats-file/load.bash"
+    ./instance-data load
+    ./instance-crowdsec start
+}
+
+teardown() {
+    cd "$TEST_DIR" || exit 1
+    ./instance-crowdsec stop
+}
+
+#----------
+
+@test "cscli notifications list" {
+    rune -0 cscli notifications list
+    assert_output --partial "Name"
+    assert_output --partial "Type"
+    assert_output --partial "Profile name"
+}
+
+@test "cscli notifications must be run from lapi" {
+    config_disable_lapi
+    rune -1 cscli notifications list
+    assert_stderr --partial "local API is disabled -- this command must be run on the local API machine"
+}

+ 6 - 2
test/lib/setup_file.sh

@@ -67,7 +67,9 @@ config_set() {
 export -f config_set
 export -f config_set
 
 
 config_disable_agent() {
 config_disable_agent() {
-    config_set 'del(.crowdsec_service)'
+    config_set '.crowdsec_service.enable=false'
+    # this should be equivalent to:
+    # config_set 'del(.crowdsec_service)'
 }
 }
 export -f config_disable_agent
 export -f config_disable_agent
 
 
@@ -77,7 +79,9 @@ config_log_stderr() {
 export -f config_log_stderr
 export -f config_log_stderr
 
 
 config_disable_lapi() {
 config_disable_lapi() {
-    config_set 'del(.api.server)'
+    config_set '.api.server.enable=false'
+    # this should be equivalent to:
+    # config_set 'del(.api.server)'
 }
 }
 export -f config_disable_lapi
 export -f config_disable_lapi