github.com/crowdsecurity/crowdsec@v1.6.1/pkg/leakybucket/overflows.go (about) 1 package leakybucket 2 3 import ( 4 "fmt" 5 "net" 6 "sort" 7 "strconv" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/go-openapi/strfmt" 11 log "github.com/sirupsen/logrus" 12 13 "github.com/crowdsecurity/crowdsec/pkg/alertcontext" 14 "github.com/crowdsecurity/crowdsec/pkg/exprhelpers" 15 "github.com/crowdsecurity/crowdsec/pkg/models" 16 "github.com/crowdsecurity/crowdsec/pkg/types" 17 ) 18 19 // SourceFromEvent extracts and formats a valid models.Source object from an Event 20 func SourceFromEvent(evt types.Event, leaky *Leaky) (map[string]models.Source, error) { 21 srcs := make(map[string]models.Source) 22 /*if it's already an overflow, we have properly formatted sources. 23 we can just twitch them to reflect the requested scope*/ 24 if evt.Type == types.OVFLW { 25 26 for k, v := range evt.Overflow.Sources { 27 28 /*the scopes are already similar, nothing to do*/ 29 if leaky.scopeType.Scope == *v.Scope { 30 srcs[k] = v 31 continue 32 } 33 34 /*The bucket requires a decision on scope Range */ 35 if leaky.scopeType.Scope == types.Range { 36 /*the original bucket was target IPs, check that we do have range*/ 37 if *v.Scope == types.Ip { 38 src := models.Source{} 39 src.AsName = v.AsName 40 src.AsNumber = v.AsNumber 41 src.Cn = v.Cn 42 src.Latitude = v.Latitude 43 src.Longitude = v.Longitude 44 src.Range = v.Range 45 src.Value = new(string) 46 src.Scope = new(string) 47 *src.Scope = leaky.scopeType.Scope 48 *src.Value = "" 49 if v.Range != "" { 50 *src.Value = v.Range 51 } 52 if leaky.scopeType.RunTimeFilter != nil { 53 retValue, err := exprhelpers.Run(leaky.scopeType.RunTimeFilter, map[string]interface{}{"evt": &evt}, leaky.logger, leaky.BucketConfig.Debug) 54 if err != nil { 55 return srcs, fmt.Errorf("while running scope filter: %w", err) 56 } 57 value, ok := retValue.(string) 58 if !ok { 59 value = "" 60 } 61 src.Value = &value 62 } 63 if *src.Value != "" { 64 srcs[*src.Value] = src 65 } else { 66 log.Warningf("bucket %s requires scope Range, but none was provided. It seems that the %s wasn't enriched to include its range.", leaky.Name, *v.Value) 67 } 68 } else { 69 log.Warningf("bucket %s requires scope Range, but can't extrapolate from %s (%s)", 70 leaky.Name, *v.Scope, *v.Value) 71 } 72 } 73 } 74 return srcs, nil 75 } 76 src := models.Source{} 77 switch leaky.scopeType.Scope { 78 case types.Range, types.Ip: 79 v, ok := evt.Meta["source_ip"] 80 if !ok { 81 return srcs, fmt.Errorf("scope is %s but Meta[source_ip] doesn't exist", leaky.scopeType.Scope) 82 } 83 if net.ParseIP(v) == nil { 84 return srcs, fmt.Errorf("scope is %s but '%s' isn't a valid ip", leaky.scopeType.Scope, v) 85 } 86 src.IP = v 87 src.Scope = &leaky.scopeType.Scope 88 if v, ok := evt.Enriched["ASNumber"]; ok { 89 src.AsNumber = v 90 } else if v, ok := evt.Enriched["ASNNumber"]; ok { 91 src.AsNumber = v 92 } 93 if v, ok := evt.Enriched["IsoCode"]; ok { 94 src.Cn = v 95 } 96 if v, ok := evt.Enriched["ASNOrg"]; ok { 97 src.AsName = v 98 } 99 if v, ok := evt.Enriched["Latitude"]; ok { 100 l, err := strconv.ParseFloat(v, 32) 101 if err != nil { 102 log.Warningf("bad latitude %s : %s", v, err) 103 } 104 src.Latitude = float32(l) 105 } 106 if v, ok := evt.Enriched["Longitude"]; ok { 107 l, err := strconv.ParseFloat(v, 32) 108 if err != nil { 109 log.Warningf("bad longitude %s : %s", v, err) 110 } 111 src.Longitude = float32(l) 112 } 113 if v, ok := evt.Meta["SourceRange"]; ok && v != "" { 114 _, ipNet, err := net.ParseCIDR(v) 115 if err != nil { 116 return srcs, fmt.Errorf("Declared range %s of %s can't be parsed", v, src.IP) 117 } 118 if ipNet != nil { 119 src.Range = ipNet.String() 120 leaky.logger.Tracef("Valid range from %s : %s", src.IP, src.Range) 121 } 122 } 123 if leaky.scopeType.Scope == types.Ip { 124 src.Value = &src.IP 125 } else if leaky.scopeType.Scope == types.Range { 126 src.Value = &src.Range 127 if leaky.scopeType.RunTimeFilter != nil { 128 retValue, err := exprhelpers.Run(leaky.scopeType.RunTimeFilter, map[string]interface{}{"evt": &evt}, leaky.logger, leaky.BucketConfig.Debug) 129 if err != nil { 130 return srcs, fmt.Errorf("while running scope filter: %w", err) 131 } 132 133 value, ok := retValue.(string) 134 if !ok { 135 value = "" 136 } 137 src.Value = &value 138 } 139 } 140 srcs[*src.Value] = src 141 default: 142 if leaky.scopeType.RunTimeFilter == nil { 143 return srcs, fmt.Errorf("empty scope information") 144 } 145 retValue, err := exprhelpers.Run(leaky.scopeType.RunTimeFilter, map[string]interface{}{"evt": &evt}, leaky.logger, leaky.BucketConfig.Debug) 146 if err != nil { 147 return srcs, fmt.Errorf("while running scope filter: %w", err) 148 } 149 150 value, ok := retValue.(string) 151 if !ok { 152 value = "" 153 } 154 src.Value = &value 155 src.Scope = new(string) 156 *src.Scope = leaky.scopeType.Scope 157 srcs[*src.Value] = src 158 } 159 return srcs, nil 160 } 161 162 // EventsFromQueue iterates the queue to collect & prepare meta-datas from alert 163 func EventsFromQueue(queue *types.Queue) []*models.Event { 164 165 events := []*models.Event{} 166 167 for _, evt := range queue.Queue { 168 if evt.Meta == nil { 169 continue 170 } 171 meta := models.Meta{} 172 //we want consistence 173 skeys := make([]string, 0, len(evt.Meta)) 174 for k := range evt.Meta { 175 skeys = append(skeys, k) 176 } 177 sort.Strings(skeys) 178 for _, k := range skeys { 179 v := evt.Meta[k] 180 subMeta := models.MetaItems0{Key: k, Value: v} 181 meta = append(meta, &subMeta) 182 } 183 184 /*check which date to use*/ 185 ovflwEvent := models.Event{ 186 Meta: meta, 187 } 188 //either MarshaledTime is present and is extracted from log 189 if evt.MarshaledTime != "" { 190 tmpTimeStamp := evt.MarshaledTime 191 ovflwEvent.Timestamp = &tmpTimeStamp 192 } else if !evt.Time.IsZero() { //or .Time has been set during parse as time.Now().UTC() 193 ovflwEvent.Timestamp = new(string) 194 raw, err := evt.Time.MarshalText() 195 if err != nil { 196 log.Warningf("while marshaling time '%s' : %s", evt.Time.String(), err) 197 } else { 198 *ovflwEvent.Timestamp = string(raw) 199 } 200 } else { 201 log.Warning("Event has no parsed time, no runtime timestamp") 202 } 203 204 events = append(events, &ovflwEvent) 205 } 206 return events 207 } 208 209 // alertFormatSource iterates over the queue to collect sources 210 func alertFormatSource(leaky *Leaky, queue *types.Queue) (map[string]models.Source, string, error) { 211 var sources = make(map[string]models.Source) 212 var source_type string 213 214 log.Debugf("Formatting (%s) - scope Info : scope_type:%s / scope_filter:%s", leaky.Name, leaky.scopeType.Scope, leaky.scopeType.Filter) 215 216 for _, evt := range queue.Queue { 217 srcs, err := SourceFromEvent(evt, leaky) 218 if err != nil { 219 return nil, "", fmt.Errorf("while extracting scope from bucket %s: %w", leaky.Name, err) 220 } 221 for key, src := range srcs { 222 if source_type == types.Undefined { 223 source_type = *src.Scope 224 } 225 if *src.Scope != source_type { 226 return nil, "", 227 fmt.Errorf("event has multiple source types : %s != %s", *src.Scope, source_type) 228 } 229 sources[key] = src 230 } 231 } 232 return sources, source_type, nil 233 } 234 235 // NewAlert will generate a RuntimeAlert and its APIAlert(s) from a bucket that overflowed 236 func NewAlert(leaky *Leaky, queue *types.Queue) (types.RuntimeAlert, error) { 237 var runtimeAlert types.RuntimeAlert 238 239 leaky.logger.Tracef("Overflow (start: %s, end: %s)", leaky.First_ts, leaky.Ovflw_ts) 240 /* 241 Craft the models.Alert that is going to be duplicated for each source 242 */ 243 start_at, err := leaky.First_ts.MarshalText() 244 if err != nil { 245 log.Warningf("failed to marshal start ts %s : %s", leaky.First_ts.String(), err) 246 } 247 stop_at, err := leaky.Ovflw_ts.MarshalText() 248 if err != nil { 249 log.Warningf("failed to marshal ovflw ts %s : %s", leaky.First_ts.String(), err) 250 } 251 capacity := int32(leaky.Capacity) 252 EventsCount := int32(leaky.Total_count) 253 leakSpeed := leaky.Leakspeed.String() 254 startAt := string(start_at) 255 stopAt := string(stop_at) 256 apiAlert := models.Alert{ 257 Scenario: &leaky.Name, 258 ScenarioHash: &leaky.hash, 259 ScenarioVersion: &leaky.scenarioVersion, 260 Capacity: &capacity, 261 EventsCount: &EventsCount, 262 Leakspeed: &leakSpeed, 263 Message: new(string), 264 StartAt: &startAt, 265 StopAt: &stopAt, 266 Simulated: &leaky.Simulated, 267 } 268 if leaky.BucketConfig == nil { 269 return runtimeAlert, fmt.Errorf("leaky.BucketConfig is nil") 270 } 271 272 //give information about the bucket 273 runtimeAlert.Mapkey = leaky.Mapkey 274 275 //Get the sources from Leaky/Queue 276 sources, source_scope, err := alertFormatSource(leaky, queue) 277 if err != nil { 278 return runtimeAlert, fmt.Errorf("unable to collect sources from bucket: %w", err) 279 } 280 runtimeAlert.Sources = sources 281 //Include source info in format string 282 sourceStr := "UNKNOWN" 283 if len(sources) > 1 { 284 sourceStr = fmt.Sprintf("%d sources", len(sources)) 285 } else if len(sources) == 1 { 286 for k := range sources { 287 sourceStr = k 288 break 289 } 290 } 291 292 *apiAlert.Message = fmt.Sprintf("%s %s performed '%s' (%d events over %s) at %s", source_scope, sourceStr, leaky.Name, leaky.Total_count, leaky.Ovflw_ts.Sub(leaky.First_ts), leaky.Last_ts) 293 //Get the events from Leaky/Queue 294 apiAlert.Events = EventsFromQueue(queue) 295 var warnings []error 296 apiAlert.Meta, warnings = alertcontext.EventToContext(leaky.Queue.GetQueue()) 297 for _, w := range warnings { 298 log.Warningf("while extracting context from bucket %s : %s", leaky.Name, w) 299 } 300 301 //Loop over the Sources and generate appropriate number of ApiAlerts 302 for _, srcValue := range sources { 303 newApiAlert := apiAlert 304 srcCopy := srcValue 305 newApiAlert.Source = &srcCopy 306 if v, ok := leaky.BucketConfig.Labels["remediation"]; ok && v == true { 307 newApiAlert.Remediation = true 308 } 309 310 if err := newApiAlert.Validate(strfmt.Default); err != nil { 311 log.Errorf("Generated alerts isn't valid") 312 log.Errorf("->%s", spew.Sdump(newApiAlert)) 313 log.Fatalf("error : %s", err) 314 } 315 runtimeAlert.APIAlerts = append(runtimeAlert.APIAlerts, newApiAlert) 316 } 317 318 if len(runtimeAlert.APIAlerts) > 0 { 319 runtimeAlert.Alert = &runtimeAlert.APIAlerts[0] 320 } 321 322 if leaky.Reprocess { 323 runtimeAlert.Reprocess = true 324 } 325 return runtimeAlert, nil 326 }