浏览代码

Merge branch 'master' into postoverflow_reinject_meta

Manuel Sabban 1 年之前
父节点
当前提交
f7f484249f

+ 1 - 1
cmd/crowdsec-cli/capi.go

@@ -110,7 +110,7 @@ func NewCapiRegisterCmd() *cobra.Command {
 				if err != nil {
 					return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err)
 				}
-				log.Printf("Central API credentials dumped to '%s'", dumpFile)
+				log.Printf("Central API credentials written to '%s'", dumpFile)
 			} else {
 				fmt.Printf("%s\n", string(apiConfigDump))
 			}

+ 1 - 1
cmd/crowdsec-cli/config_restore.go

@@ -183,7 +183,7 @@ func restoreConfigFromDirectory(dirPath string, oldBackup bool) error {
 			if csConfig.API.Server.OnlineClient != nil && csConfig.API.Server.OnlineClient.CredentialsFilePath != "" {
 				apiConfigDumpFile = csConfig.API.Server.OnlineClient.CredentialsFilePath
 			}
-			err = os.WriteFile(apiConfigDumpFile, apiConfigDump, 0o644)
+			err = os.WriteFile(apiConfigDumpFile, apiConfigDump, 0o600)
 			if err != nil {
 				return fmt.Errorf("write api credentials in '%s' failed: %s", apiConfigDumpFile, err)
 			}

+ 1 - 1
cmd/crowdsec-cli/decisions_import.go

@@ -232,7 +232,7 @@ func NewDecisionsImportCmd() *cobra.Command {
 		Short: "Import decisions from a file or pipe",
 		Long: "expected format:\n" +
 			"csv  : any of duration,reason,scope,type,value, with a header line\n" +
-			`json : {"duration" : "24h", "reason" : "my_scenario", "scope" : "ip", "type" : "ban", "value" : "x.y.z.z"}`,
+			"json :" + "`{" + `"duration" : "24h", "reason" : "my_scenario", "scope" : "ip", "type" : "ban", "value" : "x.y.z.z"` + "}`",
 		DisableAutoGenTag: true,
 		Example: `decisions.csv:
 duration,scope,value

+ 65 - 30
cmd/crowdsec-cli/explain.go

@@ -21,9 +21,15 @@ func GetLineCountForFile(filepath string) (int, error) {
 	}
 	defer f.Close()
 	lc := 0
-	fs := bufio.NewScanner(f)
-	for fs.Scan() {
-		lc++
+	fs := bufio.NewReader(f)
+	for {
+		input, err := fs.ReadBytes('\n')
+		if len(input) > 1 {
+			lc++
+		}
+		if err != nil && err == io.EOF {
+			break
+		}
 	}
 	return lc, nil
 }
@@ -79,19 +85,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	fileInfo, _ := os.Stdin.Stat()
-
-	if logType == "" || (logLine == "" && logFile == "" && dsn == "") {
-		printHelp(cmd)
-		fmt.Println()
-		fmt.Printf("Please provide --type flag\n")
-		os.Exit(1)
-	}
-
-	if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) {
-		return fmt.Errorf("the option -f - is intended to work with pipes")
-	}
-
 	var f *os.File
 
 	// using empty string fallback to /tmp
@@ -99,6 +92,13 @@ func runExplain(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("couldn't create a temporary directory to store cscli explain result: %s", err)
 	}
+	defer func() {
+		if _, err := os.Stat(dir); !os.IsNotExist(err) {
+			if err := os.RemoveAll(dir); err != nil {
+				log.Errorf("unable to delete temporary directory '%s': %s", dir, err)
+			}
+		}
+	}()
 	tmpFile := ""
 	// we create a  temporary log file if a log line/stdin has been provided
 	if logLine != "" || logFile == "-" {
@@ -121,13 +121,15 @@ func runExplain(cmd *cobra.Command, args []string) error {
 				if err != nil && err == io.EOF {
 					break
 				}
-				_, err = f.Write(input)
-				if err != nil {
+				if len(input) > 1 {
+					_, err = f.Write(input)
+				}
+				if err != nil || len(input) <= 1 {
 					errCount++
 				}
 			}
 			if errCount > 0 {
-				log.Warnf("Failed to write %d lines to tmp file", errCount)
+				log.Warnf("Failed to write %d lines to %s", errCount, tmpFile)
 			}
 		}
 		f.Close()
@@ -145,8 +147,12 @@ func runExplain(cmd *cobra.Command, args []string) error {
 		if err != nil {
 			return err
 		}
+		log.Debugf("file %s has %d lines", absolutePath, lineCount)
+		if lineCount == 0 {
+			return fmt.Errorf("the log file is empty: %s", absolutePath)
+		}
 		if lineCount > 100 {
-			log.Warnf("The log file contains %d lines. This may take a lot of resources.", lineCount)
+			log.Warnf("%s contains %d lines. This may take a lot of resources.", absolutePath, lineCount)
 		}
 	}
 
@@ -166,12 +172,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("fail to run crowdsec for test: %v", err)
 	}
 
-	// rm the temporary log file if only a log line/stdin was provided
-	if tmpFile != "" {
-		if err := os.Remove(tmpFile); err != nil {
-			return fmt.Errorf("unable to remove tmp log file '%s': %+v", tmpFile, err)
-		}
-	}
 	parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
 	bucketStateDumpFile := filepath.Join(dir, hubtest.BucketPourResultFileName)
 
@@ -187,10 +187,6 @@ func runExplain(cmd *cobra.Command, args []string) error {
 
 	hubtest.DumpTree(*parserDump, *bucketStateDump, opts)
 
-	if err := os.RemoveAll(dir); err != nil {
-		return fmt.Errorf("unable to delete temporary directory '%s': %s", dir, err)
-	}
-
 	return nil
 }
 
@@ -210,6 +206,45 @@ tail -n 5 myfile.log | cscli explain --type nginx -f -
 		Args:              cobra.ExactArgs(0),
 		DisableAutoGenTag: true,
 		RunE:              runExplain,
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			flags := cmd.Flags()
+
+			logFile, err := flags.GetString("file")
+			if err != nil {
+				return err
+			}
+
+			dsn, err := flags.GetString("dsn")
+			if err != nil {
+				return err
+			}
+
+			logLine, err := flags.GetString("log")
+			if err != nil {
+				return err
+			}
+
+			logType, err := flags.GetString("type")
+			if err != nil {
+				return err
+			}
+
+			if logLine == "" && logFile == "" && dsn == "" {
+				printHelp(cmd)
+				fmt.Println()
+				return fmt.Errorf("please provide --log, --file or --dsn flag")
+			}
+			if logType == "" {
+				printHelp(cmd)
+				fmt.Println()
+				return fmt.Errorf("please provide --type flag")
+			}
+			fileInfo, _ := os.Stdin.Stat()
+			if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) {
+				return fmt.Errorf("the option -f - is intended to work with pipes")
+			}
+			return nil
+		},
 	}
 
 	flags := cmdExplain.Flags()

+ 2 - 2
cmd/crowdsec-cli/lapi.go

@@ -150,11 +150,11 @@ func runLapiRegister(cmd *cobra.Command, args []string) error {
 		log.Fatalf("unable to marshal api credentials: %s", err)
 	}
 	if dumpFile != "" {
-		err = os.WriteFile(dumpFile, apiConfigDump, 0644)
+		err = os.WriteFile(dumpFile, apiConfigDump, 0o600)
 		if err != nil {
 			log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err)
 		}
-		log.Printf("Local API credentials dumped to '%s'", dumpFile)
+		log.Printf("Local API credentials written to '%s'", dumpFile)
 	} else {
 		fmt.Printf("%s\n", string(apiConfigDump))
 	}

+ 23 - 14
cmd/crowdsec-cli/machines.go

@@ -30,9 +30,7 @@ import (
 	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 
-var (
-	passwordLength = 64
-)
+const passwordLength = 64
 
 func generatePassword(length int) string {
 	upper := "ABCDEFGHIJKLMNOPQRSTUVWXY"
@@ -43,6 +41,7 @@ func generatePassword(length int) string {
 	charsetLength := len(charset)
 
 	buf := make([]byte, length)
+
 	for i := 0; i < length; i++ {
 		rInt, err := saferand.Int(saferand.Reader, big.NewInt(int64(charsetLength)))
 		if err != nil {
@@ -190,7 +189,6 @@ cscli machines add MyTestMachine --password MyPassword
 }
 
 func runMachinesAdd(cmd *cobra.Command, args []string) error {
-	var dumpFile string
 	var err error
 
 	flags := cmd.Flags()
@@ -200,7 +198,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	outputFile, err := flags.GetString("file")
+	dumpFile, err := flags.GetString("file")
 	if err != nil {
 		return err
 	}
@@ -220,7 +218,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	forceAdd, err := flags.GetBool("force")
+	force, err := flags.GetBool("force")
 	if err != nil {
 		return err
 	}
@@ -242,17 +240,28 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 	}
 
 	/*check if file already exists*/
-	if outputFile != "" {
-		dumpFile = outputFile
-	} else if csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" {
-		dumpFile = csConfig.API.Client.CredentialsFilePath
+	if dumpFile == "" && csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" {
+		credFile := csConfig.API.Client.CredentialsFilePath
+		// use the default only if the file does not exist
+		_, err := os.Stat(credFile)
+		switch {
+		case os.IsNotExist(err) || force:
+			dumpFile = csConfig.API.Client.CredentialsFilePath
+		case err != nil:
+			return fmt.Errorf("unable to stat '%s': %s", credFile, err)
+		default:
+			return fmt.Errorf(`credentials file '%s' already exists: please remove it, use "--force" or specify a different file with "-f" ("-f -" for standard output)`, credFile)
+		}
+	}
+
+	if dumpFile == "" {
+		return fmt.Errorf(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`)
 	}
 
 	// create a password if it's not specified by user
 	if machinePassword == "" && !interactive {
 		if !autoAdd {
-			printHelp(cmd)
-			return nil
+			return fmt.Errorf("please specify a password with --password or use --auto")
 		}
 		machinePassword = generatePassword(passwordLength)
 	} else if machinePassword == "" && interactive {
@@ -262,7 +271,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 		survey.AskOne(qs, &machinePassword)
 	}
 	password := strfmt.Password(machinePassword)
-	_, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType)
+	_, err = dbClient.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType)
 	if err != nil {
 		return fmt.Errorf("unable to create machine: %s", err)
 	}
@@ -291,7 +300,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 		if err != nil {
 			return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err)
 		}
-		log.Printf("API credentials dumped to '%s'", dumpFile)
+		log.Printf("API credentials written to '%s'", dumpFile)
 	} else {
 		fmt.Printf("%s\n", string(apiConfigDump))
 	}

+ 1 - 2
cmd/notification-http/main.go

@@ -58,13 +58,12 @@ func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notific
 	if err != nil {
 		return nil, err
 	}
-
 	for headerName, headerValue := range cfg.Headers {
 		logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
 		request.Header.Add(headerName, headerValue)
 	}
 	logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
-	resp, err := client.Do(request)
+	resp, err := client.Do(request.WithContext(ctx))
 	if err != nil {
 		logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
 		return nil, err

+ 1 - 1
cmd/notification-sentinel/main.go

@@ -90,7 +90,7 @@ func (s *SentinelPlugin) Notify(ctx context.Context, notification *protobufs.Not
 	req.Header.Set("x-ms-date", now)
 
 	client := &http.Client{}
-	resp, err := client.Do(req)
+	resp, err := client.Do(req.WithContext(ctx))
 	if err != nil {
 		logger.Error("failed to send request", "error", err)
 		return &protobufs.Empty{}, err

+ 1 - 2
cmd/notification-slack/main.go

@@ -38,10 +38,9 @@ func (n *Notify) Notify(ctx context.Context, notification *protobufs.Notificatio
 	if cfg.LogLevel != nil && *cfg.LogLevel != "" {
 		logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
 	}
-
 	logger.Info(fmt.Sprintf("found notify signal for %s config", notification.Name))
 	logger.Debug(fmt.Sprintf("posting to %s webhook, message %s", cfg.Webhook, notification.Text))
-	err := slack.PostWebhook(n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{
+	err := slack.PostWebhookContext(ctx, n.ConfigByName[notification.Name].Webhook, &slack.WebhookMessage{
 		Text: notification.Text,
 	})
 	if err != nil {

+ 1 - 1
cmd/notification-splunk/main.go

@@ -65,7 +65,7 @@ func (s *Splunk) Notify(ctx context.Context, notification *protobufs.Notificatio
 
 	req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", cfg.Token))
 	logger.Debug(fmt.Sprintf("posting event %s to %s", string(data), req.URL))
-	resp, err := s.Client.Do(req)
+	resp, err := s.Client.Do(req.WithContext(ctx))
 	if err != nil {
 		return &protobufs.Empty{}, err
 	}

+ 6 - 6
pkg/apiserver/apiserver.go

@@ -106,13 +106,13 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 	var flushScheduler *gocron.Scheduler
 	dbClient, err := database.NewClient(config.DbConfig)
 	if err != nil {
-		return &APIServer{}, fmt.Errorf("unable to init database client: %w", err)
+		return nil, fmt.Errorf("unable to init database client: %w", err)
 	}
 
 	if config.DbConfig.Flush != nil {
 		flushScheduler, err = dbClient.StartFlushScheduler(config.DbConfig.Flush)
 		if err != nil {
-			return &APIServer{}, err
+			return nil, err
 		}
 	}
 
@@ -129,7 +129,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 
 	if config.TrustedProxies != nil && config.UseForwardedForHeaders {
 		if err := router.SetTrustedProxies(*config.TrustedProxies); err != nil {
-			return &APIServer{}, fmt.Errorf("while setting trusted_proxies: %w", err)
+			return nil, fmt.Errorf("while setting trusted_proxies: %w", err)
 		}
 		router.ForwardedByClientIP = true
 	} else {
@@ -215,7 +215,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 		log.Printf("Loading CAPI manager")
 		apiClient, err = NewAPIC(config.OnlineClient, dbClient, config.ConsoleConfig, config.CapiWhitelists)
 		if err != nil {
-			return &APIServer{}, err
+			return nil, err
 		}
 		log.Infof("CAPI manager configured successfully")
 		isMachineEnrolled = isEnrolled(apiClient.apiClient)
@@ -225,7 +225,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 				log.Infof("Machine is enrolled in the console, Loading PAPI Client")
 				papiClient, err = NewPAPI(apiClient, dbClient, config.ConsoleConfig, *config.PapiLogLevel)
 				if err != nil {
-					return &APIServer{}, err
+					return nil, err
 				}
 				controller.DecisionDeleteChan = papiClient.Channels.DeleteDecisionChannel
 			} else {
@@ -241,7 +241,7 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 	if trustedIPs, err := config.GetTrustedIPs(); err == nil {
 		controller.TrustedIPs = trustedIPs
 	} else {
-		return &APIServer{}, err
+		return nil, err
 	}
 
 	return &APIServer{

+ 5 - 4
pkg/apiserver/apiserver_test.go

@@ -11,6 +11,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/crowdsecurity/go-cs-lib/cstest"
 	"github.com/crowdsecurity/go-cs-lib/version"
 
 	middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
@@ -295,8 +296,8 @@ func TestWithWrongDBConfig(t *testing.T) {
 	config.API.Server.DbConfig.Type = "test"
 	apiServer, err := NewServer(config.API.Server)
 
-	assert.Equal(t, apiServer, &APIServer{})
-	assert.Equal(t, "unable to init database client: unknown database type 'test'", err.Error())
+	cstest.RequireErrorContains(t, err, "unable to init database client: unknown database type 'test'")
+	assert.Nil(t, apiServer)
 }
 
 func TestWithWrongFlushConfig(t *testing.T) {
@@ -305,8 +306,8 @@ func TestWithWrongFlushConfig(t *testing.T) {
 	config.API.Server.DbConfig.Flush.MaxItems = &maxItems
 	apiServer, err := NewServer(config.API.Server)
 
-	assert.Equal(t, apiServer, &APIServer{})
-	assert.Equal(t, "max_items can't be zero or negative number", err.Error())
+	cstest.RequireErrorContains(t, err, "max_items can't be zero or negative number")
+	assert.Nil(t, apiServer)
 }
 
 func TestUnknownPath(t *testing.T) {

+ 90 - 123
pkg/apiserver/middlewares/v1/api_key.go

@@ -55,132 +55,110 @@ func HashSHA512(str string) string {
 	return hashStr
 }
 
+func (a *APIKey) authTLS(c *gin.Context, logger *log.Entry) *ent.Bouncer {
+	if a.TlsAuth == nil {
+		logger.Error("TLS Auth is not configured but client presented a certificate")
+		return nil
+	}
+
+	validCert, extractedCN, err := a.TlsAuth.ValidateCert(c)
+	if !validCert {
+		logger.Errorf("invalid client certificate: %s", err)
+		return nil
+	}
+	if err != nil {
+		logger.Error(err)
+		return nil
+	}
+
+	logger = logger.WithFields(log.Fields{
+		"cn": extractedCN,
+	})
+
+	bouncerName := fmt.Sprintf("%s@%s", extractedCN, c.ClientIP())
+	bouncer, err := a.DbClient.SelectBouncerByName(bouncerName)
+
+	//This is likely not the proper way, but isNotFound does not seem to work
+	if err != nil && strings.Contains(err.Error(), "bouncer not found") {
+		//Because we have a valid cert, automatically create the bouncer in the database if it does not exist
+		//Set a random API key, but it will never be used
+		apiKey, err := GenerateAPIKey(dummyAPIKeySize)
+		if err != nil {
+			logger.Errorf("error generating mock api key: %s", err)
+			return nil
+		}
+		logger.Infof("Creating bouncer %s", bouncerName)
+		bouncer, err = a.DbClient.CreateBouncer(bouncerName, c.ClientIP(), HashSHA512(apiKey), types.TlsAuthType)
+		if err != nil {
+			logger.Errorf("while creating bouncer db entry: %s", err)
+			return nil
+		}
+	} else if err != nil {
+		//error while selecting bouncer
+		logger.Errorf("while selecting bouncers: %s", err)
+		return nil
+	} else if bouncer.AuthType != types.TlsAuthType {
+		//bouncer was found in DB
+		logger.Errorf("bouncer isn't allowed to auth by TLS")
+		return nil
+	}
+	return bouncer
+}
+
+func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer {
+	val, ok := c.Request.Header[APIKeyHeader]
+	if !ok {
+		logger.Errorf("API key not found")
+		return nil
+	}
+	hashStr := HashSHA512(val[0])
+
+	bouncer, err := a.DbClient.SelectBouncer(hashStr)
+	if err != nil {
+		logger.Errorf("while fetching bouncer info: %s", err)
+		return nil
+	}
+
+	if bouncer.AuthType != types.ApiKeyAuthType {
+		logger.Errorf("bouncer %s attempted to login using an API key but it is configured to auth with %s", bouncer.Name, bouncer.AuthType)
+		return nil
+	}
+
+	return bouncer
+}
+
 func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		var bouncer *ent.Bouncer
-		var err error
+
+		logger := log.WithFields(log.Fields{
+			"ip": c.ClientIP(),
+		})
 
 		if c.Request.TLS != nil && len(c.Request.TLS.PeerCertificates) > 0 {
-			if a.TlsAuth == nil {
-				log.WithField("ip", c.ClientIP()).Error("TLS Auth is not configured but client presented a certificate")
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
-			validCert, extractedCN, err := a.TlsAuth.ValidateCert(c)
-			if !validCert {
-				log.WithField("ip", c.ClientIP()).Errorf("invalid client certificate: %s", err)
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
-			if err != nil {
-				log.WithField("ip", c.ClientIP()).Error(err)
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
-			bouncerName := fmt.Sprintf("%s@%s", extractedCN, c.ClientIP())
-			bouncer, err = a.DbClient.SelectBouncerByName(bouncerName)
-			//This is likely not the proper way, but isNotFound does not seem to work
-			if err != nil && strings.Contains(err.Error(), "bouncer not found") {
-				//Because we have a valid cert, automatically create the bouncer in the database if it does not exist
-				//Set a random API key, but it will never be used
-				apiKey, err := GenerateAPIKey(dummyAPIKeySize)
-				if err != nil {
-					log.WithFields(log.Fields{
-						"ip": c.ClientIP(),
-						"cn": extractedCN,
-					}).Errorf("error generating mock api key: %s", err)
-					c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-					c.Abort()
-					return
-				}
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-					"cn": extractedCN,
-				}).Infof("Creating bouncer %s", bouncerName)
-				bouncer, err = a.DbClient.CreateBouncer(bouncerName, c.ClientIP(), HashSHA512(apiKey), types.TlsAuthType)
-				if err != nil {
-					log.WithFields(log.Fields{
-						"ip": c.ClientIP(),
-						"cn": extractedCN,
-					}).Errorf("creating bouncer db entry : %s", err)
-					c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-					c.Abort()
-					return
-				}
-			} else if err != nil {
-				//error while selecting bouncer
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-					"cn": extractedCN,
-				}).Errorf("while selecting bouncers: %s", err)
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			} else if bouncer.AuthType != types.TlsAuthType {
-				//bouncer was found in DB
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-					"cn": extractedCN,
-				}).Errorf("bouncer isn't allowed to auth by TLS")
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
+			bouncer = a.authTLS(c, logger)
 		} else {
-			//API Key Authentication
-			val, ok := c.Request.Header[APIKeyHeader]
-			if !ok {
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-				}).Errorf("API key not found")
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
-			hashStr := HashSHA512(val[0])
-			bouncer, err = a.DbClient.SelectBouncer(hashStr)
-			if err != nil {
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-				}).Errorf("while fetching bouncer info: %s", err)
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
-			if bouncer.AuthType != types.ApiKeyAuthType {
-				log.WithFields(log.Fields{
-					"ip": c.ClientIP(),
-				}).Errorf("bouncer %s attempted to login using an API key but it is configured to auth with %s", bouncer.Name, bouncer.AuthType)
-				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
-				c.Abort()
-				return
-			}
+			bouncer = a.authPlain(c, logger)
 		}
 
 		if bouncer == nil {
-			log.WithFields(log.Fields{
-				"ip": c.ClientIP(),
-			}).Errorf("bouncer not found")
 			c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
 			c.Abort()
 			return
 		}
 
-		//maybe we want to store the whole bouncer object in the context instead, this would avoid another db query
-		//in StreamDecision
+		logger = logger.WithFields(log.Fields{
+			"name": bouncer.Name,
+		})
+
+		// maybe we want to store the whole bouncer object in the context instead, this would avoid another db query
+		// in StreamDecision
 		c.Set("BOUNCER_NAME", bouncer.Name)
 		c.Set("BOUNCER_HASHED_KEY", bouncer.APIKey)
 
 		if bouncer.IPAddress == "" {
-			err = a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID)
-			if err != nil {
-				log.WithFields(log.Fields{
-					"ip":   c.ClientIP(),
-					"name": bouncer.Name,
-				}).Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
+			if err := a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID); err != nil {
+				logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
 				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
 				c.Abort()
 				return
@@ -189,12 +167,8 @@ func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
 
 		if bouncer.IPAddress != c.ClientIP() && bouncer.IPAddress != "" {
 			log.Warningf("new IP address detected for bouncer '%s': %s (old: %s)", bouncer.Name, c.ClientIP(), bouncer.IPAddress)
-			err = a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID)
-			if err != nil {
-				log.WithFields(log.Fields{
-					"ip":   c.ClientIP(),
-					"name": bouncer.Name,
-				}).Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
+			if err := a.DbClient.UpdateBouncerIP(c.ClientIP(), bouncer.ID); err != nil {
+				logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err)
 				c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"})
 				c.Abort()
 				return
@@ -202,21 +176,14 @@ func (a *APIKey) MiddlewareFunc() gin.HandlerFunc {
 		}
 
 		useragent := strings.Split(c.Request.UserAgent(), "/")
-
 		if len(useragent) != 2 {
-			log.WithFields(log.Fields{
-				"ip":   c.ClientIP(),
-				"name": bouncer.Name,
-			}).Warningf("bad user agent '%s'", c.Request.UserAgent())
+			logger.Warningf("bad user agent '%s'", c.Request.UserAgent())
 			useragent = []string{c.Request.UserAgent(), "N/A"}
 		}
 
 		if bouncer.Version != useragent[1] || bouncer.Type != useragent[0] {
 			if err := a.DbClient.UpdateBouncerTypeAndVersion(useragent[0], useragent[1], bouncer.ID); err != nil {
-				log.WithFields(log.Fields{
-					"ip":   c.ClientIP(),
-					"name": bouncer.Name,
-				}).Errorf("failed to update bouncer version and type: %s", err)
+				logger.Errorf("failed to update bouncer version and type: %s", err)
 				c.JSON(http.StatusForbidden, gin.H{"message": "bad user agent"})
 				c.Abort()
 				return

+ 1 - 1
pkg/apiserver/middlewares/v1/middlewares.go

@@ -14,7 +14,7 @@ func NewMiddlewares(dbClient *database.Client) (*Middlewares, error) {
 
 	ret.JWT, err = NewJWT(dbClient)
 	if err != nil {
-		return &Middlewares{}, err
+		return nil, err
 	}
 
 	ret.APIKey = NewAPIKey(dbClient)

+ 1 - 1
pkg/csplugin/notifier.go

@@ -26,7 +26,7 @@ func (m *GRPCClient) Notify(ctx context.Context, notification *protobufs.Notific
 	done := make(chan error)
 	go func() {
 		_, err := m.client.Notify(
-			context.Background(), &protobufs.Notification{Text: notification.Text, Name: notification.Name},
+			ctx, &protobufs.Notification{Text: notification.Text, Name: notification.Name},
 		)
 		done <- err
 	}()

+ 3 - 3
pkg/database/bouncers.go

@@ -13,7 +13,7 @@ import (
 func (c *Client) SelectBouncer(apiKeyHash string) (*ent.Bouncer, error) {
 	result, err := c.Ent.Bouncer.Query().Where(bouncer.APIKeyEQ(apiKeyHash)).First(c.CTX)
 	if err != nil {
-		return &ent.Bouncer{}, errors.Wrapf(QueryFail, "select bouncer: %s", err)
+		return nil, err
 	}
 
 	return result, nil
@@ -22,7 +22,7 @@ func (c *Client) SelectBouncer(apiKeyHash string) (*ent.Bouncer, error) {
 func (c *Client) SelectBouncerByName(bouncerName string) (*ent.Bouncer, error) {
 	result, err := c.Ent.Bouncer.Query().Where(bouncer.NameEQ(bouncerName)).First(c.CTX)
 	if err != nil {
-		return &ent.Bouncer{}, errors.Wrapf(QueryFail, "select bouncer: %s", err)
+		return nil, err
 	}
 
 	return result, nil
@@ -31,7 +31,7 @@ func (c *Client) SelectBouncerByName(bouncerName string) (*ent.Bouncer, error) {
 func (c *Client) ListBouncers() ([]*ent.Bouncer, error) {
 	result, err := c.Ent.Bouncer.Query().All(c.CTX)
 	if err != nil {
-		return []*ent.Bouncer{}, errors.Wrapf(QueryFail, "listing bouncer: %s", err)
+		return nil, errors.Wrapf(QueryFail, "listing bouncers: %s", err)
 	}
 	return result, nil
 }

+ 6 - 5
pkg/database/machines.go

@@ -19,8 +19,8 @@ const CapiListsMachineID = types.ListOrigin
 func (c *Client) CreateMachine(machineID *string, password *strfmt.Password, ipAddress string, isValidated bool, force bool, authType string) (*ent.Machine, error) {
 	hashPassword, err := bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
 	if err != nil {
-		c.Log.Warningf("CreateMachine : %s", err)
-		return nil, errors.Wrap(HashError, "")
+		c.Log.Warningf("CreateMachine: %s", err)
+		return nil, HashError
 	}
 
 	machineExist, err := c.Ent.Machine.
@@ -78,7 +78,7 @@ func (c *Client) QueryMachineByID(machineID string) (*ent.Machine, error) {
 func (c *Client) ListMachines() ([]*ent.Machine, error) {
 	machines, err := c.Ent.Machine.Query().All(c.CTX)
 	if err != nil {
-		return []*ent.Machine{}, errors.Wrapf(QueryFail, "listing machines: %s", err)
+		return nil, errors.Wrapf(QueryFail, "listing machines: %s", err)
 	}
 	return machines, nil
 }
@@ -101,7 +101,7 @@ func (c *Client) QueryPendingMachine() ([]*ent.Machine, error) {
 	machines, err = c.Ent.Machine.Query().Where(machine.IsValidatedEQ(false)).All(c.CTX)
 	if err != nil {
 		c.Log.Warningf("QueryPendingMachine : %s", err)
-		return []*ent.Machine{}, errors.Wrapf(QueryFail, "querying pending machines: %s", err)
+		return nil, errors.Wrapf(QueryFail, "querying pending machines: %s", err)
 	}
 	return machines, nil
 }
@@ -190,12 +190,13 @@ func (c *Client) IsMachineRegistered(machineID string) (bool, error) {
 		return true, nil
 	}
 	if len(exist) > 1 {
-		return false, fmt.Errorf("More than one item with the same machineID in database")
+		return false, fmt.Errorf("more than one item with the same machineID in database")
 	}
 
 	return false, nil
 
 }
+
 func (c *Client) QueryLastValidatedHeartbeatLT(t time.Time) ([]*ent.Machine, error) {
 	return c.Ent.Machine.Query().Where(machine.LastHeartbeatLT(t), machine.IsValidatedEQ(true)).All(c.CTX)
 }

+ 13 - 0
test/bats/01_crowdsec.bats

@@ -227,3 +227,16 @@ teardown() {
     assert_stderr --partial "crowdsec init: while loading acquisition config: no datasource enabled"
 }
 
+@test "crowdsec -t (error in acquisition file)" {
+    # we can verify the acquisition configuration without running crowdsec
+    ACQUIS_YAML=$(config_get '.crowdsec_service.acquisition_path')
+    config_set "$ACQUIS_YAML" 'del(.filenames)'
+
+    rune -1 wait-for "${CROWDSEC}"
+    assert_stderr --partial "failed to configure datasource file: no filename or filenames configuration provided"
+
+    config_set "$ACQUIS_YAML" '.filenames=["file.log"]'
+    config_set "$ACQUIS_YAML" '.meh=3'
+    rune -1 wait-for "${CROWDSEC}"
+    assert_stderr --partial "field meh not found in type fileacquisition.FileConfiguration"
+}

+ 10 - 6
test/bats/30_machines.bats

@@ -23,20 +23,24 @@ teardown() {
 
 #----------
 
-@test "can list machines as regular user" {
-    rune -0 cscli machines list
-}
-
 @test "we have exactly one machine" {
     rune -0 cscli machines list -o json
     rune -0 jq -c '[. | length, .[0].machineId[0:32], .[0].isValidated]' <(output)
     assert_output '[1,"githubciXXXXXXXXXXXXXXXXXXXXXXXX",true]'
 }
 
+@test "don't overwrite local credentials by default" {
+    rune -1 cscli machines add local -a -o json
+    rune -0 jq -r '.msg' <(stderr)
+    assert_output --partial 'already exists: please remove it, use "--force" or specify a different file with "-f"'
+    rune -0 cscli machines add local -a --force
+    assert_stderr --partial "Machine 'local' successfully added to the local API"
+}
+
 @test "add a new machine and delete it" {
     rune -0 cscli machines add -a -f /dev/null CiTestMachine -o human
     assert_stderr --partial "Machine 'CiTestMachine' successfully added to the local API"
-    assert_stderr --partial "API credentials dumped to '/dev/null'"
+    assert_stderr --partial "API credentials written to '/dev/null'"
 
     # we now have two machines
     rune -0 cscli machines list -o json
@@ -56,7 +60,7 @@ teardown() {
 @test "register, validate and then remove a machine" {
     rune -0 cscli lapi register --machine CiTestMachineRegister -f /dev/null -o human
     assert_stderr --partial "Successfully registered to Local API (LAPI)"
-    assert_stderr --partial "Local API credentials dumped to '/dev/null'"
+    assert_stderr --partial "Local API credentials written to '/dev/null'"
 
     # the machine is not validated yet
     rune -0 cscli machines list -o json

+ 3 - 3
test/lib/config/config-global

@@ -61,12 +61,12 @@ make_init_data() {
     ./instance-db config-yaml
     ./instance-db setup
 
+    ./bin/preload-hub-items
+
     # when installed packages are always using sqlite, so no need to regenerate
     # local credz for sqlite
 
-    ./bin/preload-hub-items
-
-    [[ "${DB_BACKEND}" == "sqlite" ]] || ${CSCLI} machines add --auto
+    [[ "${DB_BACKEND}" == "sqlite" ]] || ${CSCLI} machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto --force
 
     mkdir -p "$LOCAL_INIT_DIR"
 

+ 2 - 1
test/lib/config/config-local

@@ -115,11 +115,12 @@ make_init_data() {
     ./instance-db config-yaml
     ./instance-db setup
 
-    "$CSCLI" --warning machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto
     "$CSCLI" --warning hub update
 
     ./bin/preload-hub-items
 
+    "$CSCLI" --warning machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto --force
+
     mkdir -p "$LOCAL_INIT_DIR"
 
     ./instance-db dump "${LOCAL_INIT_DIR}/database"