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