diff --git a/pkg/csprofiles/csprofiles.go b/pkg/csprofiles/csprofiles.go index c71291cdb..95fbb356f 100644 --- a/pkg/csprofiles/csprofiles.go +++ b/pkg/csprofiles/csprofiles.go @@ -6,7 +6,6 @@ import ( "github.com/antonmedv/expr" "github.com/antonmedv/expr/vm" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/crowdsecurity/crowdsec/pkg/csconfig" @@ -22,19 +21,23 @@ type Runtime struct { Logger *log.Entry `json:"-" yaml:"-"` } -var defaultDuration = "4h" +const defaultDuration = "4h" func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) { var err error + profilesRuntime := make([]*Runtime, 0) for _, profile := range profilesCfg { var runtimeFilter, runtimeDurationExpr *vm.Program + runtime := &Runtime{} + xlog := log.New() if err := types.ConfigureLogger(xlog); err != nil { log.Fatalf("While creating profiles-specific logger : %s", err) } + xlog.SetLevel(log.InfoLevel) runtime.Logger = xlog.WithFields(log.Fields{ "type": "profile", @@ -43,17 +46,20 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) { runtime.RuntimeFilters = make([]*vm.Program, len(profile.Filters)) runtime.Cfg = profile - if runtime.Cfg.OnSuccess != "" && runtime.Cfg.OnSuccess != "continue" && runtime.Cfg.OnSuccess != "break" { - return []*Runtime{}, fmt.Errorf("invalid 'on_success' for '%s': %s", profile.Name, runtime.Cfg.OnSuccess) - } - if runtime.Cfg.OnFailure != "" && runtime.Cfg.OnFailure != "continue" && runtime.Cfg.OnFailure != "break" && runtime.Cfg.OnFailure != "apply" { - return []*Runtime{}, fmt.Errorf("invalid 'on_failure' for '%s' : %s", profile.Name, runtime.Cfg.OnFailure) - } - for fIdx, filter := range profile.Filters { + if runtime.Cfg.OnSuccess != "" && runtime.Cfg.OnSuccess != "continue" && runtime.Cfg.OnSuccess != "break" { + return nil, fmt.Errorf("invalid 'on_success' for '%s': %s", profile.Name, runtime.Cfg.OnSuccess) + } + + if runtime.Cfg.OnFailure != "" && runtime.Cfg.OnFailure != "continue" && runtime.Cfg.OnFailure != "break" && runtime.Cfg.OnFailure != "apply" { + return nil, fmt.Errorf("invalid 'on_failure' for '%s' : %s", profile.Name, runtime.Cfg.OnFailure) + } + + for fIdx, filter := range profile.Filters { if runtimeFilter, err = expr.Compile(filter, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil { - return []*Runtime{}, errors.Wrapf(err, "error compiling filter of '%s'", profile.Name) + return nil, fmt.Errorf("error compiling filter of '%s': %w", profile.Name, err) } + runtime.RuntimeFilters[fIdx] = runtimeFilter if profile.Debug != nil && *profile.Debug { runtime.Logger.Logger.SetLevel(log.DebugLevel) @@ -62,8 +68,9 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) { if profile.DurationExpr != "" { if runtimeDurationExpr, err = expr.Compile(profile.DurationExpr, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil { - return []*Runtime{}, errors.Wrapf(err, "error compiling duration_expr of %s", profile.Name) + return nil, fmt.Errorf("error compiling duration_expr of %s: %w", profile.Name, err) } + runtime.RuntimeDurationExpr = runtimeDurationExpr } @@ -76,14 +83,16 @@ func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) { runtime.Logger.Warningf("No duration specified for %s, using default duration %s", profile.Name, defaultDuration) duration = defaultDuration } + if _, err := time.ParseDuration(duration); err != nil { - return []*Runtime{}, errors.Wrapf(err, "error parsing duration '%s' of %s", duration, profile.Name) + return nil, fmt.Errorf("error parsing duration '%s' of %s: %w", duration, profile.Name, err) } } } profilesRuntime = append(profilesRuntime, runtime) } + return profilesRuntime, nil } @@ -110,30 +119,29 @@ func (Profile *Runtime) GenerateDecisionFromProfile(Alert *models.Alert) ([]*mod *decision.Scope = *Alert.Source.Scope } /*some fields are populated from the reference object : duration, scope, type*/ + decision.Duration = new(string) + if refDecision.Duration != nil { + *decision.Duration = *refDecision.Duration + } + if Profile.Cfg.DurationExpr != "" && Profile.RuntimeDurationExpr != nil { profileDebug := false if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug { profileDebug = true } + duration, err := exprhelpers.Run(Profile.RuntimeDurationExpr, map[string]interface{}{"Alert": Alert}, Profile.Logger, profileDebug) if err != nil { Profile.Logger.Warningf("Failed to run duration_expr : %v", err) - *decision.Duration = *refDecision.Duration } else { durationStr := fmt.Sprint(duration) if _, err := time.ParseDuration(durationStr); err != nil { Profile.Logger.Warningf("Failed to parse expr duration result '%s'", duration) - *decision.Duration = *refDecision.Duration } else { *decision.Duration = durationStr } } - } else { - if refDecision.Duration == nil { - *decision.Duration = defaultDuration - } - *decision.Duration = *refDecision.Duration } decision.Type = new(string) @@ -144,13 +152,16 @@ func (Profile *Runtime) GenerateDecisionFromProfile(Alert *models.Alert) ([]*mod *decision.Value = *Alert.Source.Value decision.Origin = new(string) *decision.Origin = types.CrowdSecOrigin + if refDecision.Origin != nil { *decision.Origin = fmt.Sprintf("%s/%s", *decision.Origin, *refDecision.Origin) } + decision.Scenario = new(string) *decision.Scenario = *Alert.Scenario decisions = append(decisions, &decision) } + return decisions, nil } @@ -159,16 +170,19 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision var decisions []*models.Decision matched := false + for eIdx, expression := range Profile.RuntimeFilters { debugProfile := false if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug { debugProfile = true } + output, err := exprhelpers.Run(expression, map[string]interface{}{"Alert": Alert}, Profile.Logger, debugProfile) if err != nil { - Profile.Logger.Warningf("failed to run profile expr for %s : %v", Profile.Cfg.Name, err) - return nil, matched, errors.Wrapf(err, "while running expression %s", Profile.Cfg.Filters[eIdx]) + Profile.Logger.Warningf("failed to run profile expr for %s: %v", Profile.Cfg.Name, err) + return nil, matched, fmt.Errorf("while running expression %s: %w", Profile.Cfg.Filters[eIdx], err) } + switch out := output.(type) { case bool: if out { @@ -176,7 +190,7 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision /*the expression matched, create the associated decision*/ subdecisions, err := Profile.GenerateDecisionFromProfile(Alert) if err != nil { - return nil, matched, errors.Wrapf(err, "while generating decision from profile %s", Profile.Cfg.Name) + return nil, matched, fmt.Errorf("while generating decision from profile %s: %w", Profile.Cfg.Name, err) } decisions = append(decisions, subdecisions...) @@ -189,9 +203,7 @@ func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision default: return nil, matched, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, Profile.Cfg.Filters[eIdx]) - } - } return decisions, matched, nil