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  }