github.com/crowdsecurity/crowdsec@v1.6.1/pkg/csprofiles/csprofiles.go (about) 1 package csprofiles 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/antonmedv/expr" 8 "github.com/antonmedv/expr/vm" 9 log "github.com/sirupsen/logrus" 10 11 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 12 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 13 "github.com/crowdsecurity/crowdsec/pkg/models" 14 "github.com/crowdsecurity/crowdsec/pkg/types" 15 ) 16 17 type Runtime struct { 18 RuntimeFilters []*vm.Program `json:"-" yaml:"-"` 19 RuntimeDurationExpr *vm.Program `json:"-" yaml:"-"` 20 Cfg *csconfig.ProfileCfg `json:"-" yaml:"-"` 21 Logger *log.Entry `json:"-" yaml:"-"` 22 } 23 24 const defaultDuration = "4h" 25 26 func NewProfile(profilesCfg []*csconfig.ProfileCfg) ([]*Runtime, error) { 27 var err error 28 29 profilesRuntime := make([]*Runtime, 0) 30 31 for _, profile := range profilesCfg { 32 var runtimeFilter, runtimeDurationExpr *vm.Program 33 34 runtime := &Runtime{} 35 36 xlog := log.New() 37 if err := types.ConfigureLogger(xlog); err != nil { 38 log.Fatalf("While creating profiles-specific logger : %s", err) 39 } 40 41 xlog.SetLevel(log.InfoLevel) 42 runtime.Logger = xlog.WithFields(log.Fields{ 43 "type": "profile", 44 "name": profile.Name, 45 }) 46 47 runtime.RuntimeFilters = make([]*vm.Program, len(profile.Filters)) 48 runtime.Cfg = profile 49 50 if runtime.Cfg.OnSuccess != "" && runtime.Cfg.OnSuccess != "continue" && runtime.Cfg.OnSuccess != "break" { 51 return nil, fmt.Errorf("invalid 'on_success' for '%s': %s", profile.Name, runtime.Cfg.OnSuccess) 52 } 53 54 if runtime.Cfg.OnFailure != "" && runtime.Cfg.OnFailure != "continue" && runtime.Cfg.OnFailure != "break" && runtime.Cfg.OnFailure != "apply" { 55 return nil, fmt.Errorf("invalid 'on_failure' for '%s' : %s", profile.Name, runtime.Cfg.OnFailure) 56 } 57 58 for fIdx, filter := range profile.Filters { 59 if runtimeFilter, err = expr.Compile(filter, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil { 60 return nil, fmt.Errorf("error compiling filter of '%s': %w", profile.Name, err) 61 } 62 63 runtime.RuntimeFilters[fIdx] = runtimeFilter 64 if profile.Debug != nil && *profile.Debug { 65 runtime.Logger.Logger.SetLevel(log.DebugLevel) 66 } 67 } 68 69 if profile.DurationExpr != "" { 70 if runtimeDurationExpr, err = expr.Compile(profile.DurationExpr, exprhelpers.GetExprOptions(map[string]interface{}{"Alert": &models.Alert{}})...); err != nil { 71 return nil, fmt.Errorf("error compiling duration_expr of %s: %w", profile.Name, err) 72 } 73 74 runtime.RuntimeDurationExpr = runtimeDurationExpr 75 } 76 77 for _, decision := range profile.Decisions { 78 if runtime.RuntimeDurationExpr == nil { 79 var duration string 80 if decision.Duration != nil { 81 duration = *decision.Duration 82 } else { 83 runtime.Logger.Warningf("No duration specified for %s, using default duration %s", profile.Name, defaultDuration) 84 duration = defaultDuration 85 } 86 87 if _, err := time.ParseDuration(duration); err != nil { 88 return nil, fmt.Errorf("error parsing duration '%s' of %s: %w", duration, profile.Name, err) 89 } 90 } 91 } 92 93 profilesRuntime = append(profilesRuntime, runtime) 94 } 95 96 return profilesRuntime, nil 97 } 98 99 func (Profile *Runtime) GenerateDecisionFromProfile(Alert *models.Alert) ([]*models.Decision, error) { 100 var decisions []*models.Decision 101 102 for _, refDecision := range Profile.Cfg.Decisions { 103 decision := models.Decision{} 104 /*the reference decision from profile is in simulated mode */ 105 if refDecision.Simulated != nil && *refDecision.Simulated { 106 decision.Simulated = new(bool) 107 *decision.Simulated = true 108 /*the event is already in simulation mode */ 109 } else if Alert.Simulated != nil && *Alert.Simulated { 110 decision.Simulated = new(bool) 111 *decision.Simulated = true 112 } 113 /*If the profile specifies a scope, this will prevail. 114 If not, we're going to get the scope from the source itself*/ 115 decision.Scope = new(string) 116 if refDecision.Scope != nil && *refDecision.Scope != "" { 117 *decision.Scope = *refDecision.Scope 118 } else { 119 *decision.Scope = *Alert.Source.Scope 120 } 121 /*some fields are populated from the reference object : duration, scope, type*/ 122 123 decision.Duration = new(string) 124 if refDecision.Duration != nil { 125 *decision.Duration = *refDecision.Duration 126 } 127 128 if Profile.Cfg.DurationExpr != "" && Profile.RuntimeDurationExpr != nil { 129 profileDebug := false 130 if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug { 131 profileDebug = true 132 } 133 134 duration, err := exprhelpers.Run(Profile.RuntimeDurationExpr, map[string]interface{}{"Alert": Alert}, Profile.Logger, profileDebug) 135 if err != nil { 136 Profile.Logger.Warningf("Failed to run duration_expr : %v", err) 137 } else { 138 durationStr := fmt.Sprint(duration) 139 if _, err := time.ParseDuration(durationStr); err != nil { 140 Profile.Logger.Warningf("Failed to parse expr duration result '%s'", duration) 141 } else { 142 *decision.Duration = durationStr 143 } 144 } 145 } 146 147 decision.Type = new(string) 148 *decision.Type = *refDecision.Type 149 150 /*for the others, let's populate it from the alert and its source*/ 151 decision.Value = new(string) 152 *decision.Value = *Alert.Source.Value 153 decision.Origin = new(string) 154 *decision.Origin = types.CrowdSecOrigin 155 156 if refDecision.Origin != nil { 157 *decision.Origin = fmt.Sprintf("%s/%s", *decision.Origin, *refDecision.Origin) 158 } 159 160 decision.Scenario = new(string) 161 *decision.Scenario = *Alert.Scenario 162 decisions = append(decisions, &decision) 163 } 164 165 return decisions, nil 166 } 167 168 // EvaluateProfile is going to evaluate an Alert against a profile to generate Decisions 169 func (Profile *Runtime) EvaluateProfile(Alert *models.Alert) ([]*models.Decision, bool, error) { 170 var decisions []*models.Decision 171 172 matched := false 173 174 for eIdx, expression := range Profile.RuntimeFilters { 175 debugProfile := false 176 if Profile.Cfg.Debug != nil && *Profile.Cfg.Debug { 177 debugProfile = true 178 } 179 180 output, err := exprhelpers.Run(expression, map[string]interface{}{"Alert": Alert}, Profile.Logger, debugProfile) 181 if err != nil { 182 Profile.Logger.Warningf("failed to run profile expr for %s: %v", Profile.Cfg.Name, err) 183 return nil, matched, fmt.Errorf("while running expression %s: %w", Profile.Cfg.Filters[eIdx], err) 184 } 185 186 switch out := output.(type) { 187 case bool: 188 if out { 189 matched = true 190 /*the expression matched, create the associated decision*/ 191 subdecisions, err := Profile.GenerateDecisionFromProfile(Alert) 192 if err != nil { 193 return nil, matched, fmt.Errorf("while generating decision from profile %s: %w", Profile.Cfg.Name, err) 194 } 195 196 decisions = append(decisions, subdecisions...) 197 } else { 198 Profile.Logger.Debugf("Profile %s filter is unsuccessful", Profile.Cfg.Name) 199 if Profile.Cfg.OnFailure == "break" { 200 break 201 } 202 } 203 204 default: 205 return nil, matched, fmt.Errorf("unexpected type %t (%v) while running '%s'", output, output, Profile.Cfg.Filters[eIdx]) 206 } 207 } 208 209 return decisions, matched, nil 210 }