瀏覽代碼

Merge branch 'master' into http_plugin_unix_socket

Laurence Jones 1 年之前
父節點
當前提交
87d07c70eb

+ 0 - 9
.golangci.yml

@@ -295,10 +295,6 @@ issues:
         - nosprintfhostport
       text: "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
 
-    - linters:
-        - wastedassign
-      text: "assigned to .*, but reassigned without using the value"
-
     # https://github.com/timakin/bodyclose
     - linters:
         - bodyclose
@@ -310,11 +306,6 @@ issues:
         - nonamedreturns
       text: "named return .* with type .* found"
 
-    # https://github.com/alexkohler/nakedret#purpose
-    - linters:
-        - nakedret
-      text: "naked return in func .* with .* lines of code"
-
     #
     # Will fix,  might be trickier
     #

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

@@ -71,8 +71,7 @@ func SourceFromAlert(alert *models.Alert) string {
 
 	//try to compose a human friendly version
 	if *alert.Source.Value != "" && *alert.Source.Scope != "" {
-		scope := ""
-		scope = fmt.Sprintf("%s:%s", *alert.Source.Scope, *alert.Source.Value)
+		scope := fmt.Sprintf("%s:%s", *alert.Source.Scope, *alert.Source.Value)
 		extra := ""
 		if alert.Source.Cn != "" {
 			extra = alert.Source.Cn

+ 5 - 5
cmd/crowdsec-cli/copyfile.go

@@ -41,7 +41,7 @@ func copyFileContents(src, dst string) (err error) {
 }
 
 /*copy the file, ioutile doesn't offer the feature*/
-func CopyFile(sourceSymLink, destinationFile string) (err error) {
+func CopyFile(sourceSymLink, destinationFile string) error {
 	sourceFile, err := filepath.EvalSymlinks(sourceSymLink)
 	if err != nil {
 		log.Infof("Not a symlink : %s", err)
@@ -51,7 +51,7 @@ func CopyFile(sourceSymLink, destinationFile string) (err error) {
 
 	sourceFileStat, err := os.Stat(sourceFile)
 	if err != nil {
-		return
+		return err
 	}
 
 	if !sourceFileStat.Mode().IsRegular() {
@@ -63,14 +63,14 @@ func CopyFile(sourceSymLink, destinationFile string) (err error) {
 	destinationFileStat, err := os.Stat(destinationFile)
 	if err != nil {
 		if !os.IsNotExist(err) {
-			return
+			return err
 		}
 	} else {
 		if !(destinationFileStat.Mode().IsRegular()) {
 			return fmt.Errorf("copyFile: non-regular destination file %s (%q)", destinationFileStat.Name(), destinationFileStat.Mode().String())
 		}
 		if os.SameFile(sourceFileStat, destinationFileStat) {
-			return
+			return err
 		}
 	}
 
@@ -78,6 +78,6 @@ func CopyFile(sourceSymLink, destinationFile string) (err error) {
 		err = copyFileContents(sourceFile, destinationFile)
 	}
 
-	return
+	return err
 }
 

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

@@ -243,6 +243,7 @@ func (cli cliDecisions) NewImportCmd() *cobra.Command {
 		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"` + "}`",
+		Args:	 cobra.NoArgs,
 		DisableAutoGenTag: true,
 		Example: `decisions.csv:
 duration,scope,value

+ 1 - 2
cmd/crowdsec-cli/explain.go

@@ -188,10 +188,9 @@ func (cli cliExplain) run(cmd *cobra.Command, args []string) error {
 			}
 		}
 	}()
-	tmpFile := ""
 	// we create a  temporary log file if a log line/stdin has been provided
 	if logLine != "" || logFile == "-" {
-		tmpFile = filepath.Join(dir, "cscli_test_tmp.log")
+		tmpFile := filepath.Join(dir, "cscli_test_tmp.log")
 		f, err = os.Create(tmpFile)
 		if err != nil {
 			return err

+ 4 - 0
cmd/crowdsec-cli/require/branch.go

@@ -56,3 +56,7 @@ func HubBranch(cfg *csconfig.Config) string {
 
 	return branch
 }
+
+func HubURLTemplate(cfg *csconfig.Config) string {
+	return cfg.Cscli.HubURLTemplate
+}

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

@@ -66,10 +66,10 @@ func Notifications(c *csconfig.Config) error {
 func RemoteHub(c *csconfig.Config) *cwhub.RemoteHubCfg {
 	// set branch in config, and log if necessary
 	branch := HubBranch(c)
+	urlTemplate := HubURLTemplate(c)
 	remote := &cwhub.RemoteHubCfg{
 		Branch:      branch,
-		URLTemplate: "https://hub-cdn.crowdsec.net/%s/%s",
-		// URLTemplate: "http://localhost:8000/crowdsecurity/%s/hub/%s",
+		URLTemplate: urlTemplate,
 		IndexPath: ".index.json",
 	}
 

+ 3 - 2
cmd/crowdsec/win_service.go

@@ -23,7 +23,7 @@ type crowdsec_winservice struct {
 	config *csconfig.Config
 }
 
-func (m *crowdsec_winservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
+func (m *crowdsec_winservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
 	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
 	changes <- svc.Status{State: svc.StartPending}
 	tick := time.Tick(500 * time.Millisecond)
@@ -59,7 +59,8 @@ func (m *crowdsec_winservice) Execute(args []string, r <-chan svc.ChangeRequest,
 	if err != nil {
 		log.Fatal(err)
 	}
-	return
+
+	return false, 0
 }
 
 func runService(name string) error {

+ 2 - 3
docker/docker_start.sh

@@ -3,7 +3,7 @@
 # shellcheck disable=SC2292      # allow [ test ] syntax
 # shellcheck disable=SC2310      # allow "if function..." syntax with -e
 
-set -e
+# set -e
 shopt -s inherit_errexit
 
 # match true, TRUE, True, tRuE, etc.
@@ -302,9 +302,8 @@ conf_set_if "$PLUGIN_DIR" '.config_paths.plugin_dir = strenv(PLUGIN_DIR)'
 
 ## Install hub items
 cscli hub update
+cscli hub upgrade
 
-cscli_if_clean collections upgrade crowdsecurity/linux
-cscli_if_clean parsers upgrade crowdsecurity/whitelists
 cscli_if_clean parsers install crowdsecurity/docker-logs
 cscli_if_clean parsers install crowdsecurity/cri-logs
 

+ 1 - 0
docker/test/tests/test_flavors.py

@@ -22,6 +22,7 @@ def test_cscli_lapi(crowdsec, flavor):
         assert "You can successfully interact with Local API (LAPI)" in stdout
 
 
+@pytest.mark.skip(reason="currently broken by hub upgrade")
 def test_flavor_content(crowdsec, flavor):
     """Test flavor contents"""
     with crowdsec(flavor=flavor) as cs:

+ 4 - 6
pkg/acquisition/modules/appsec/utils.go

@@ -179,11 +179,9 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *types.Event, req *appsec.ParsedR
 
 	req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool {
 		for _, variable := range col.FindAll() {
-			key := ""
-			if variable.Key() == "" {
-				key = variable.Variable().Name()
-			} else {
-				key = variable.Variable().Name() + "." + variable.Key()
+			key := variable.Variable().Name()
+			if variable.Key() != "" {
+				key += "." + variable.Key()
 			}
 			if variable.Value() == "" {
 				continue
@@ -214,7 +212,7 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *types.Event, req *appsec.ParsedR
 			evt.Appsec.HasOutBandMatches = true
 		}
 
-		name := ""
+		var name string
 		version := ""
 		hash := ""
 		ruleNameProm := fmt.Sprintf("%d", rule.Rule().ID())

+ 2 - 2
pkg/apiserver/apiserver.go

@@ -243,9 +243,9 @@ func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
 		controller.AlertsAddChan = apiClient.AlertsAddChan
 
 		if apiClient.apiClient.IsEnrolled() {
-			log.Infof("Machine is enrolled in the console, Loading PAPI Client")
-
 			if config.ConsoleConfig.IsPAPIEnabled() {
+				log.Info("Machine is enrolled in the console, Loading PAPI Client")
+
 				papiClient, err = NewPAPI(apiClient, dbClient, config.ConsoleConfig, *config.PapiLogLevel)
 				if err != nil {
 					return nil, err

+ 45 - 19
pkg/appsec/appsec.go

@@ -130,9 +130,9 @@ type AppsecConfig struct {
 }
 
 func (w *AppsecRuntimeConfig) ClearResponse() {
-	log.Debugf("#-> %p", w)
+	w.Logger.Debugf("#-> %p", w)
 	w.Response = AppsecTempResponse{}
-	log.Debugf("-> %p", w.Config)
+	w.Logger.Debugf("-> %p", w.Config)
 	w.Response.Action = w.Config.DefaultPassAction
 	w.Response.HTTPResponseCode = w.Config.PassedHTTPCode
 	w.Response.SendEvent = true
@@ -290,20 +290,26 @@ func (w *AppsecRuntimeConfig) ProcessOnLoadRules() error {
 			switch t := output.(type) {
 			case bool:
 				if !t {
-					log.Debugf("filter didnt match")
+					w.Logger.Debugf("filter didnt match")
 					continue
 				}
 			default:
-				log.Errorf("Filter must return a boolean, can't filter")
+				w.Logger.Errorf("Filter must return a boolean, can't filter")
 				continue
 			}
 		}
 		for _, applyExpr := range rule.ApplyExpr {
-			_, err := exprhelpers.Run(applyExpr, GetOnLoadEnv(w), w.Logger, w.Logger.Level >= log.DebugLevel)
+			o, err := exprhelpers.Run(applyExpr, GetOnLoadEnv(w), w.Logger, w.Logger.Level >= log.DebugLevel)
 			if err != nil {
-				log.Errorf("unable to apply appsec on_load expr: %s", err)
+				w.Logger.Errorf("unable to apply appsec on_load expr: %s", err)
 				continue
 			}
+			switch t := o.(type) {
+			case error:
+				w.Logger.Errorf("unable to apply appsec on_load expr: %s", t)
+				continue
+			default:
+			}
 		}
 	}
 	return nil
@@ -320,27 +326,33 @@ func (w *AppsecRuntimeConfig) ProcessOnMatchRules(request *ParsedRequest, evt ty
 			switch t := output.(type) {
 			case bool:
 				if !t {
-					log.Debugf("filter didnt match")
+					w.Logger.Debugf("filter didnt match")
 					continue
 				}
 			default:
-				log.Errorf("Filter must return a boolean, can't filter")
+				w.Logger.Errorf("Filter must return a boolean, can't filter")
 				continue
 			}
 		}
 		for _, applyExpr := range rule.ApplyExpr {
-			_, err := exprhelpers.Run(applyExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel)
+			o, err := exprhelpers.Run(applyExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel)
 			if err != nil {
-				log.Errorf("unable to apply appsec on_match expr: %s", err)
+				w.Logger.Errorf("unable to apply appsec on_match expr: %s", err)
 				continue
 			}
+			switch t := o.(type) {
+			case error:
+				w.Logger.Errorf("unable to apply appsec on_match expr: %s", t)
+				continue
+			default:
+			}
 		}
 	}
 	return nil
 }
 
 func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error {
-	log.Debugf("processing %d pre_eval rules", len(w.CompiledPreEval))
+	w.Logger.Debugf("processing %d pre_eval rules", len(w.CompiledPreEval))
 	for _, rule := range w.CompiledPreEval {
 		if rule.FilterExpr != nil {
 			output, err := exprhelpers.Run(rule.FilterExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
@@ -350,20 +362,26 @@ func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error
 			switch t := output.(type) {
 			case bool:
 				if !t {
-					log.Debugf("filter didnt match")
+					w.Logger.Debugf("filter didnt match")
 					continue
 				}
 			default:
-				log.Errorf("Filter must return a boolean, can't filter")
+				w.Logger.Errorf("Filter must return a boolean, can't filter")
 				continue
 			}
 		}
 		// here means there is no filter or the filter matched
 		for _, applyExpr := range rule.ApplyExpr {
-			_, err := exprhelpers.Run(applyExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
+			o, err := exprhelpers.Run(applyExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
 			if err != nil {
-				log.Errorf("unable to apply appsec pre_eval expr: %s", err)
+				w.Logger.Errorf("unable to apply appsec pre_eval expr: %s", err)
+				continue
+			}
+			switch t := o.(type) {
+			case error:
+				w.Logger.Errorf("unable to apply appsec pre_eval expr: %s", t)
 				continue
+			default:
 			}
 		}
 	}
@@ -381,21 +399,29 @@ func (w *AppsecRuntimeConfig) ProcessPostEvalRules(request *ParsedRequest) error
 			switch t := output.(type) {
 			case bool:
 				if !t {
-					log.Debugf("filter didnt match")
+					w.Logger.Debugf("filter didnt match")
 					continue
 				}
 			default:
-				log.Errorf("Filter must return a boolean, can't filter")
+				w.Logger.Errorf("Filter must return a boolean, can't filter")
 				continue
 			}
 		}
 		// here means there is no filter or the filter matched
 		for _, applyExpr := range rule.ApplyExpr {
-			_, err := exprhelpers.Run(applyExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
+			o, err := exprhelpers.Run(applyExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel)
+
 			if err != nil {
-				log.Errorf("unable to apply appsec post_eval expr: %s", err)
+				w.Logger.Errorf("unable to apply appsec post_eval expr: %s", err)
 				continue
 			}
+
+			switch t := o.(type) {
+			case error:
+				w.Logger.Errorf("unable to apply appsec post_eval expr: %s", t)
+				continue
+			default:
+			}
 		}
 	}
 

+ 10 - 5
pkg/appsec/request.go

@@ -38,7 +38,7 @@ type ParsedRequest struct {
 	Body                 []byte                  `json:"body,omitempty"`
 	TransferEncoding     []string                `json:"transfer_encoding,omitempty"`
 	UUID                 string                  `json:"uuid,omitempty"`
-	Tx                   ExtendedTransaction     `json:"transaction,omitempty"`
+	Tx                   ExtendedTransaction     `json:"-"`
 	ResponseChannel      chan AppsecTempResponse `json:"-"`
 	IsInBand             bool                    `json:"-"`
 	IsOutBand            bool                    `json:"-"`
@@ -260,12 +260,17 @@ func (r *ReqDumpFilter) ToJSON() error {
 
 	req := r.GetFilteredRequest()
 
-	log.Warningf("dumping : %+v", req)
+	log.Tracef("dumping : %+v", req)
 
 	if err := enc.Encode(req); err != nil {
+		//Don't clobber the temp directory with empty files
+		err2 := os.Remove(fd.Name())
+		if err2 != nil {
+			log.Errorf("while removing temp file %s: %s", fd.Name(), err)
+		}
 		return fmt.Errorf("while encoding request: %w", err)
 	}
-	log.Warningf("request dumped to %s", fd.Name())
+	log.Infof("request dumped to %s", fd.Name())
 	return nil
 }
 
@@ -324,7 +329,7 @@ func NewParsedRequestFromRequest(r *http.Request, logger *logrus.Entry) (ParsedR
 		return ParsedRequest{}, fmt.Errorf("unable to parse url '%s': %s", clientURI, err)
 	}
 
-	remoteAddrNormalized := ""
+	var remoteAddrNormalized string
 	host, _, err := net.SplitHostPort(r.RemoteAddr)
 	if err != nil {
 		log.Errorf("Invalid appsec remote IP source %v: %s", r.RemoteAddr, err.Error())
@@ -332,7 +337,7 @@ func NewParsedRequestFromRequest(r *http.Request, logger *logrus.Entry) (ParsedR
 	} else {
 		ip := net.ParseIP(host)
 		if ip == nil {
-			log.Errorf("Invalid appsec remote IP address source %v: %s", r.RemoteAddr, err.Error())
+			log.Errorf("Invalid appsec remote IP address source %v", r.RemoteAddr)
 			remoteAddrNormalized = r.RemoteAddr
 		} else {
 			remoteAddrNormalized = ip.String()

+ 7 - 0
pkg/csconfig/cscli.go

@@ -9,6 +9,7 @@ type CscliCfg struct {
 	Output             string            `yaml:"output,omitempty"`
 	Color              string            `yaml:"color,omitempty"`
 	HubBranch          string            `yaml:"hub_branch"`
+	HubURLTemplate     string            `yaml:"__hub_url_template__,omitempty"`
 	SimulationConfig   *SimulationConfig `yaml:"-"`
 	DbConfig           *DatabaseCfg      `yaml:"-"`
 
@@ -16,6 +17,8 @@ type CscliCfg struct {
 	PrometheusUrl      string            `yaml:"prometheus_uri"`
 }
 
+const defaultHubURLTemplate = "https://hub-cdn.crowdsec.net/%s/%s"
+
 func (c *Config) loadCSCLI() error {
 	if c.Cscli == nil {
 		c.Cscli = &CscliCfg{}
@@ -25,5 +28,9 @@ func (c *Config) loadCSCLI() error {
 		c.Cscli.PrometheusUrl = fmt.Sprintf("http://%s:%d/metrics", c.Prometheus.ListenAddr, c.Prometheus.ListenPort)
 	}
 
+	if c.Cscli.HubURLTemplate == "" {
+		c.Cscli.HubURLTemplate = defaultHubURLTemplate
+	}
+
 	return nil
 }

+ 2 - 1
pkg/csconfig/cscli_test.go

@@ -32,7 +32,8 @@ func TestLoadCSCLI(t *testing.T) {
 				},
 			},
 			expected: &CscliCfg{
-				PrometheusUrl: "http://127.0.0.1:6060/metrics",
+				PrometheusUrl:  "http://127.0.0.1:6060/metrics",
+				HubURLTemplate: defaultHubURLTemplate,
 			},
 		},
 	}

+ 37 - 18
pkg/database/alerts.go

@@ -77,14 +77,11 @@ func formatAlertSource(alert *models.Alert) string {
 func formatAlertAsString(machineID string, alert *models.Alert) []string {
 	src := formatAlertSource(alert)
 
-	/**/
-	msg := ""
+	msg := "empty scenario"
 	if alert.Scenario != nil && *alert.Scenario != "" {
 		msg = *alert.Scenario
 	} else if alert.Message != nil && *alert.Message != "" {
 		msg = *alert.Message
-	} else {
-		msg = "empty scenario"
 	}
 
 	reason := fmt.Sprintf("%s by %s", msg, src)
@@ -116,7 +113,7 @@ func formatAlertAsString(machineID string, alert *models.Alert) []string {
 			reason = fmt.Sprintf("%s for %d/%d decisions", msg, i+1, len(alert.Decisions))
 		}
 
-		machineIDOrigin := ""
+		var machineIDOrigin string
 		if machineID == "" {
 			machineIDOrigin = *decisionItem.Origin
 		} else {
@@ -209,9 +206,9 @@ func (c *Client) CreateOrUpdateAlert(machineID string, alertItem *models.Alert)
 	//add missing decisions
 	log.Debugf("Adding %d missing decisions to alert %s", len(missingDecisions), foundAlert.UUID)
 
-	decisionBuilders := make([]*ent.DecisionCreate, len(missingDecisions))
+	decisionBuilders := []*ent.DecisionCreate{}
 
-	for i, decisionItem := range missingDecisions {
+	for _, decisionItem := range missingDecisions {
 		var start_ip, start_sfx, end_ip, end_sfx int64
 		var sz int
 
@@ -219,7 +216,8 @@ func (c *Client) CreateOrUpdateAlert(machineID string, alertItem *models.Alert)
 		if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
 			sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value)
 			if err != nil {
-				return "", errors.Wrapf(InvalidIPOrRange, "invalid addr/range %s : %s", *decisionItem.Value, err)
+				log.Errorf("invalid addr/range '%s': %s", *decisionItem.Value, err)
+				continue
 			}
 		}
 
@@ -254,7 +252,7 @@ func (c *Client) CreateOrUpdateAlert(machineID string, alertItem *models.Alert)
 			SetSimulated(*alertItem.Simulated).
 			SetUUID(decisionItem.UUID)
 
-		decisionBuilders[i] = decisionBuilder
+		decisionBuilders = append(decisionBuilders, decisionBuilder)
 	}
 
 	decisions := []*ent.Decision{}
@@ -486,9 +484,9 @@ func (c *Client) UpdateCommunityBlocklist(alertItem *models.Alert) (int, int, in
 }
 
 func (c *Client) createDecisionChunk(simulated bool, stopAtTime time.Time, decisions []*models.Decision) ([]*ent.Decision, error) {
-	decisionCreate := make([]*ent.DecisionCreate, len(decisions))
+	decisionCreate := []*ent.DecisionCreate{}
 
-	for i, decisionItem := range decisions {
+	for _, decisionItem := range decisions {
 		var start_ip, start_sfx, end_ip, end_sfx int64
 		var sz int
 
@@ -501,7 +499,8 @@ func (c *Client) createDecisionChunk(simulated bool, stopAtTime time.Time, decis
 		if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" {
 			sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value)
 			if err != nil {
-				return nil, fmt.Errorf("%s: %w", *decisionItem.Value, InvalidIPOrRange)
+				log.Errorf("invalid addr/range '%s': %s", *decisionItem.Value, err)
+				continue
 			}
 		}
 
@@ -520,7 +519,11 @@ func (c *Client) createDecisionChunk(simulated bool, stopAtTime time.Time, decis
 			SetSimulated(simulated).
 			SetUUID(decisionItem.UUID)
 
-		decisionCreate[i] = newDecision
+		decisionCreate = append(decisionCreate, newDecision)
+	}
+
+	if len(decisionCreate) == 0 {
+		return nil, nil
 	}
 
 	ret, err := c.Ent.Decision.CreateBulk(decisionCreate...).Save(c.CTX)
@@ -532,10 +535,10 @@ func (c *Client) createDecisionChunk(simulated bool, stopAtTime time.Time, decis
 }
 
 func (c *Client) createAlertChunk(machineID string, owner *ent.Machine, alerts []*models.Alert) ([]string, error) {
-	alertBuilders := make([]*ent.AlertCreate, len(alerts))
-	alertDecisions := make([][]*ent.Decision, len(alerts))
+	alertBuilders := []*ent.AlertCreate{}
+	alertDecisions := [][]*ent.Decision{}
 
-	for i, alertItem := range alerts {
+	for _, alertItem := range alerts {
 		var metas []*ent.Meta
 		var events []*ent.Event
 
@@ -656,6 +659,17 @@ func (c *Client) createAlertChunk(machineID string, owner *ent.Machine, alerts [
 			decisions = append(decisions, decisionRet...)
 		}
 
+		discarded := len(alertItem.Decisions) - len(decisions)
+		if discarded > 0 {
+			c.Log.Warningf("discarded %d decisions for %s", discarded, alertItem.UUID)
+		}
+
+		// if all decisions were discarded, discard the alert too
+		if discarded > 0 && len(decisions) == 0 {
+			c.Log.Warningf("dropping alert %s with invalid decisions", alertItem.UUID)
+			continue
+		}
+
 		alertBuilder := c.Ent.Alert.
 			Create().
 			SetScenario(*alertItem.Scenario).
@@ -685,8 +699,13 @@ func (c *Client) createAlertChunk(machineID string, owner *ent.Machine, alerts [
 			alertBuilder.SetOwner(owner)
 		}
 
-		alertBuilders[i] = alertBuilder
-		alertDecisions[i] = decisions
+		alertBuilders = append(alertBuilders, alertBuilder)
+		alertDecisions = append(alertDecisions, decisions)
+	}
+
+	if len(alertBuilders) == 0 {
+		log.Warningf("no alerts to create, discarded?")
+		return nil, nil
 	}
 
 	alertsCreateBulk, err := c.Ent.Alert.CreateBulk(alertBuilders...).Save(c.CTX)

+ 1 - 1
pkg/hubtest/parser_assert.go

@@ -107,7 +107,7 @@ func (p *ParserAssert) AssertFile(testFile string) error {
 			}
 
 			match := variableRE.FindStringSubmatch(scanner.Text())
-			variable := ""
+			var variable string
 
 			if len(match) == 0 {
 				log.Infof("Couldn't get variable of line '%s'", scanner.Text())

+ 1 - 1
pkg/parser/runtime.go

@@ -319,7 +319,7 @@ func Parse(ctx UnixParserCtx, xp types.Event, nodes []Node) (types.Event, error)
 			}
 			clog.Tracef("node (%s) ret : %v", node.rn, ret)
 			if ParseDump {
-				parserIdxInStage := 0
+				var parserIdxInStage int
 				StageParseMutex.Lock()
 				if len(StageParseCache[stage][node.Name]) == 0 {
 					StageParseCache[stage][node.Name] = make([]dumps.ParserResult, 0)

+ 7 - 4
test/bats/01_cscli.bats

@@ -115,15 +115,18 @@ teardown() {
     assert_output "&false"
 
     # complex type
-    rune -0 cscli config show --key Config.PluginConfig
+    rune -0 cscli config show --key Config.Prometheus
     assert_output - <<-EOT
-	&csconfig.PluginCfg{
-	  User: "nobody",
-	  Group: "nogroup",
+	&csconfig.PrometheusCfg{
+	  Enabled: true,
+	  Level: "full",
+	  ListenAddr: "127.0.0.1",
+	  ListenPort: 6060,
 	}
 	EOT
 }
 
+
 @test "cscli - required configuration paths" {
     config=$(cat "${CONFIG_YAML}")
     configdir=$(config_get '.config_paths.config_dir')

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

@@ -43,7 +43,7 @@ setup() {
 
     config_set 'del(.api.server.online_client)'
     rune -1 cscli capi status
-    assert_stderr --partial "no configuration for Central API (CAPI) in '$CONFIG_YAML'"
+    assert_stderr --regexp "no configuration for Central API \(CAPI\) in '$(echo $CONFIG_YAML|sed s#//#/#g)'"
 }
 
 @test "cscli capi status" {

+ 34 - 2
test/bats/90_decisions.bats

@@ -16,7 +16,10 @@ teardown_file() {
 
 setup() {
     load "../lib/setup.sh"
+    load "../lib/bats-file/load.bash"
     ./instance-data load
+    LOGFILE=$(config_get '.common.log_dir')/crowdsec.log
+    export LOGFILE
     ./instance-crowdsec start
 }
 
@@ -151,6 +154,7 @@ teardown() {
     assert_stderr --partial 'Parsing values'
     assert_stderr --partial 'Imported 3 decisions'
 
+    # leading or trailing spaces are ignored
     rune -0 cscli decisions import -i - --format values <<-EOT
 	  10.2.3.4  
 	10.2.3.5   
@@ -159,11 +163,39 @@ teardown() {
     assert_stderr --partial 'Parsing values'
     assert_stderr --partial 'Imported 3 decisions'
 
-    rune -1 cscli decisions import -i - --format values <<-EOT
+    # silently discarding (but logging) invalid decisions
+
+    rune -0 cscli alerts delete --all
+    truncate -s 0 "${LOGFILE}"
+
+    rune -0 cscli decisions import -i - --format values <<-EOT
 	whatever
 	EOT
     assert_stderr --partial 'Parsing values'
-    assert_stderr --partial 'creating alert decisions: whatever: invalid ip address / range'
+    assert_stderr --partial 'Imported 1 decisions'
+    assert_file_contains "$LOGFILE" "invalid addr/range 'whatever': invalid address"
+
+    rune -0 cscli decisions list -a -o json
+    assert_json '[]'
+
+    # disarding only some invalid decisions
+
+
+    rune -0 cscli alerts delete --all
+    truncate -s 0 "${LOGFILE}"
+
+    rune -0 cscli decisions import -i - --format values <<-EOT
+        1.2.3.4
+	bad-apple
+        1.2.3.5
+	EOT
+    assert_stderr --partial 'Parsing values'
+    assert_stderr --partial 'Imported 3 decisions'
+    assert_file_contains "$LOGFILE" "invalid addr/range 'bad-apple': invalid address"
+
+    rune -0 cscli decisions list -a -o json
+    rune -0 jq -r '.[0].decisions | length' <(output)
+    assert_output 2
 
     #----------
     # Batch