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  }