2020-08-05 09:39:54 +00:00
package leakybucket
import (
"fmt"
"net"
2021-10-04 15:14:52 +00:00
"sort"
2020-08-05 09:39:54 +00:00
"strconv"
2023-06-23 12:04:58 +00:00
"github.com/antonmedv/expr"
2020-11-30 09:37:17 +00:00
"github.com/davecgh/go-spew/spew"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
2023-06-23 12:04:58 +00:00
"github.com/crowdsecurity/crowdsec/pkg/alertcontext"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/crowdsec/pkg/types"
2020-08-05 09:39:54 +00:00
)
2023-01-04 15:50:02 +00:00
// SourceFromEvent extracts and formats a valid models.Source object from an Event
2020-11-30 09:37:17 +00:00
func SourceFromEvent ( evt types . Event , leaky * Leaky ) ( map [ string ] models . Source , error ) {
srcs := make ( map [ string ] models . Source )
2020-12-02 16:15:48 +00:00
/ * if it ' s already an overflow , we have properly formatted sources .
we can just twitch them to reflect the requested scope * /
2020-11-30 09:37:17 +00:00
if evt . Type == types . OVFLW {
2020-12-02 16:15:48 +00:00
for k , v := range evt . Overflow . Sources {
/*the scopes are already similar, nothing to do*/
if leaky . scopeType . Scope == * v . Scope {
srcs [ k ] = v
continue
}
/*The bucket requires a decision on scope Range */
if leaky . scopeType . Scope == types . Range {
/*the original bucket was target IPs, check that we do have range*/
if * v . Scope == types . Ip {
2022-02-14 15:50:52 +00:00
src := models . Source { }
src . AsName = v . AsName
src . AsNumber = v . AsNumber
src . Cn = v . Cn
src . Latitude = v . Latitude
src . Longitude = v . Longitude
src . Range = v . Range
src . Value = new ( string )
src . Scope = new ( string )
* src . Scope = leaky . scopeType . Scope
* src . Value = ""
2020-12-02 16:15:48 +00:00
if v . Range != "" {
* src . Value = v . Range
2022-02-14 15:50:52 +00:00
}
if leaky . scopeType . RunTimeFilter != nil {
2023-03-28 08:49:01 +00:00
retValue , err := expr . Run ( leaky . scopeType . RunTimeFilter , map [ string ] interface { } { "evt" : & evt } )
2022-02-14 15:50:52 +00:00
if err != nil {
2023-06-23 12:04:58 +00:00
return srcs , fmt . Errorf ( "while running scope filter: %w" , err )
2022-02-14 15:50:52 +00:00
}
value , ok := retValue . ( string )
if ! ok {
value = ""
}
src . Value = & value
}
if * src . Value != "" {
2020-12-02 16:15:48 +00:00
srcs [ * src . Value ] = src
2020-12-03 16:34:57 +00:00
} else {
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 )
2020-12-02 16:15:48 +00:00
}
} else {
log . Warningf ( "bucket %s requires scope Range, but can't extrapolate from %s (%s)" ,
leaky . Name , * v . Scope , * v . Value )
}
}
}
return srcs , nil
2020-08-05 09:39:54 +00:00
}
2020-12-02 16:15:48 +00:00
src := models . Source { }
2020-11-30 09:37:17 +00:00
switch leaky . scopeType . Scope {
case types . Range , types . Ip :
2022-02-01 21:08:06 +00:00
v , ok := evt . Meta [ "source_ip" ]
if ! ok {
2020-11-30 09:37:17 +00:00
return srcs , fmt . Errorf ( "scope is %s but Meta[source_ip] doesn't exist" , leaky . scopeType . Scope )
}
2022-02-01 21:08:06 +00:00
if net . ParseIP ( v ) == nil {
return srcs , fmt . Errorf ( "scope is %s but '%s' isn't a valid ip" , leaky . scopeType . Scope , v )
}
src . IP = v
2020-11-30 09:37:17 +00:00
src . Scope = & leaky . scopeType . Scope
if v , ok := evt . Enriched [ "ASNumber" ] ; ok {
src . AsNumber = v
2021-11-15 13:16:18 +00:00
} else if v , ok := evt . Enriched [ "ASNNumber" ] ; ok {
src . AsNumber = v
2020-11-30 09:37:17 +00:00
}
if v , ok := evt . Enriched [ "IsoCode" ] ; ok {
src . Cn = v
}
if v , ok := evt . Enriched [ "ASNOrg" ] ; ok {
src . AsName = v
}
if v , ok := evt . Enriched [ "Latitude" ] ; ok {
l , err := strconv . ParseFloat ( v , 32 )
if err != nil {
log . Warningf ( "bad latitude %s : %s" , v , err )
2020-08-05 09:39:54 +00:00
}
2020-11-30 09:37:17 +00:00
src . Latitude = float32 ( l )
}
if v , ok := evt . Enriched [ "Longitude" ] ; ok {
l , err := strconv . ParseFloat ( v , 32 )
if err != nil {
log . Warningf ( "bad longitude %s : %s" , v , err )
2020-08-05 09:39:54 +00:00
}
2020-11-30 09:37:17 +00:00
src . Longitude = float32 ( l )
}
if v , ok := evt . Meta [ "SourceRange" ] ; ok && v != "" {
_ , ipNet , err := net . ParseCIDR ( v )
if err != nil {
return srcs , fmt . Errorf ( "Declared range %s of %s can't be parsed" , v , src . IP )
2022-02-01 21:08:06 +00:00
}
if ipNet != nil {
2020-11-30 09:37:17 +00:00
src . Range = ipNet . String ( )
leaky . logger . Tracef ( "Valid range from %s : %s" , src . IP , src . Range )
}
}
if leaky . scopeType . Scope == types . Ip {
src . Value = & src . IP
} else if leaky . scopeType . Scope == types . Range {
src . Value = & src . Range
2022-02-14 15:50:52 +00:00
if leaky . scopeType . RunTimeFilter != nil {
2023-03-28 08:49:01 +00:00
retValue , err := expr . Run ( leaky . scopeType . RunTimeFilter , map [ string ] interface { } { "evt" : & evt } )
2022-02-14 15:50:52 +00:00
if err != nil {
2023-06-23 12:04:58 +00:00
return srcs , fmt . Errorf ( "while running scope filter: %w" , err )
2022-02-14 15:50:52 +00:00
}
value , ok := retValue . ( string )
if ! ok {
value = ""
}
src . Value = & value
}
2020-11-30 09:37:17 +00:00
}
2020-12-02 16:15:48 +00:00
srcs [ * src . Value ] = src
2020-11-30 09:37:17 +00:00
default :
2022-02-01 21:08:06 +00:00
if leaky . scopeType . RunTimeFilter == nil {
2020-11-30 09:37:17 +00:00
return srcs , fmt . Errorf ( "empty scope information" )
2020-08-05 09:39:54 +00:00
}
2023-03-28 08:49:01 +00:00
retValue , err := expr . Run ( leaky . scopeType . RunTimeFilter , map [ string ] interface { } { "evt" : & evt } )
2022-02-01 21:08:06 +00:00
if err != nil {
2023-06-23 12:04:58 +00:00
return srcs , fmt . Errorf ( "while running scope filter: %w" , err )
2022-02-01 21:08:06 +00:00
}
value , ok := retValue . ( string )
if ! ok {
value = ""
}
src . Value = & value
src . Scope = new ( string )
* src . Scope = leaky . scopeType . Scope
srcs [ * src . Value ] = src
2020-11-30 09:37:17 +00:00
}
return srcs , nil
}
2020-08-05 09:39:54 +00:00
2023-01-04 15:50:02 +00:00
// EventsFromQueue iterates the queue to collect & prepare meta-datas from alert
2020-11-30 09:37:17 +00:00
func EventsFromQueue ( queue * Queue ) [ ] * models . Event {
events := [ ] * models . Event { }
for _ , evt := range queue . Queue {
if evt . Meta == nil {
2020-08-05 09:39:54 +00:00
continue
}
2020-11-30 09:37:17 +00:00
meta := models . Meta { }
2021-10-04 15:14:52 +00:00
//we want consistence
skeys := make ( [ ] string , 0 , len ( evt . Meta ) )
for k := range evt . Meta {
skeys = append ( skeys , k )
}
sort . Strings ( skeys )
for _ , k := range skeys {
v := evt . Meta [ k ]
2020-11-30 09:37:17 +00:00
subMeta := models . MetaItems0 { Key : k , Value : v }
meta = append ( meta , & subMeta )
}
/*check which date to use*/
ovflwEvent := models . Event {
Meta : meta ,
}
//either MarshaledTime is present and is extracted from log
if evt . MarshaledTime != "" {
2022-06-30 15:35:42 +00:00
tmpTimeStamp := evt . MarshaledTime
ovflwEvent . Timestamp = & tmpTimeStamp
2022-01-19 13:56:05 +00:00
} else if ! evt . Time . IsZero ( ) { //or .Time has been set during parse as time.Now().UTC()
2020-11-30 09:37:17 +00:00
ovflwEvent . Timestamp = new ( string )
raw , err := evt . Time . MarshalText ( )
if err != nil {
log . Warningf ( "while marshaling time '%s' : %s" , evt . Time . String ( ) , err )
} else {
* ovflwEvent . Timestamp = string ( raw )
}
2020-08-05 09:39:54 +00:00
} else {
2022-06-22 07:38:23 +00:00
log . Warning ( "Event has no parsed time, no runtime timestamp" )
2020-11-30 09:37:17 +00:00
}
events = append ( events , & ovflwEvent )
}
return events
}
2023-01-04 15:50:02 +00:00
// alertFormatSource iterates over the queue to collect sources
2020-11-30 09:37:17 +00:00
func alertFormatSource ( leaky * Leaky , queue * Queue ) ( map [ string ] models . Source , string , error ) {
2023-03-09 10:56:02 +00:00
var sources = make ( map [ string ] models . Source )
2020-11-30 09:37:17 +00:00
var source_type string
log . Debugf ( "Formatting (%s) - scope Info : scope_type:%s / scope_filter:%s" , leaky . Name , leaky . scopeType . Scope , leaky . scopeType . Filter )
for _ , evt := range queue . Queue {
srcs , err := SourceFromEvent ( evt , leaky )
if err != nil {
2023-06-23 12:04:58 +00:00
return nil , "" , fmt . Errorf ( "while extracting scope from bucket %s: %w" , leaky . Name , err )
2020-11-30 09:37:17 +00:00
}
for key , src := range srcs {
if source_type == types . Undefined {
source_type = * src . Scope
}
if * src . Scope != source_type {
return nil , "" ,
fmt . Errorf ( "event has multiple source types : %s != %s" , * src . Scope , source_type )
}
sources [ key ] = src
2020-08-05 09:39:54 +00:00
}
}
2020-11-30 09:37:17 +00:00
return sources , source_type , nil
}
2020-08-05 09:39:54 +00:00
2023-01-04 15:50:02 +00:00
// NewAlert will generate a RuntimeAlert and its APIAlert(s) from a bucket that overflowed
2020-11-30 09:37:17 +00:00
func NewAlert ( leaky * Leaky , queue * Queue ) ( types . RuntimeAlert , error ) {
var runtimeAlert types . RuntimeAlert
leaky . logger . Tracef ( "Overflow (start: %s, end: %s)" , leaky . First_ts , leaky . Ovflw_ts )
/ *
Craft the models . Alert that is going to be duplicated for each source
* /
start_at , err := leaky . First_ts . MarshalText ( )
if err != nil {
log . Warningf ( "failed to marshal start ts %s : %s" , leaky . First_ts . String ( ) , err )
}
stop_at , err := leaky . Ovflw_ts . MarshalText ( )
if err != nil {
log . Warningf ( "failed to marshal ovflw ts %s : %s" , leaky . First_ts . String ( ) , err )
}
capacity := int32 ( leaky . Capacity )
EventsCount := int32 ( leaky . Total_count )
leakSpeed := leaky . Leakspeed . String ( )
startAt := string ( start_at )
stopAt := string ( stop_at )
apiAlert := models . Alert {
Scenario : & leaky . Name ,
ScenarioHash : & leaky . hash ,
ScenarioVersion : & leaky . scenarioVersion ,
Capacity : & capacity ,
EventsCount : & EventsCount ,
Leakspeed : & leakSpeed ,
Message : new ( string ) ,
StartAt : & startAt ,
StopAt : & stopAt ,
Simulated : & leaky . Simulated ,
}
if leaky . BucketConfig == nil {
return runtimeAlert , fmt . Errorf ( "leaky.BucketConfig is nil" )
}
//give information about the bucket
runtimeAlert . Mapkey = leaky . Mapkey
//Get the sources from Leaky/Queue
sources , source_scope , err := alertFormatSource ( leaky , queue )
if err != nil {
2023-06-23 12:04:58 +00:00
return runtimeAlert , fmt . Errorf ( "unable to collect sources from bucket: %w" , err )
2020-11-30 09:37:17 +00:00
}
runtimeAlert . Sources = sources
//Include source info in format string
2022-03-09 15:15:18 +00:00
sourceStr := "UNKNOWN"
2020-11-30 09:37:17 +00:00
if len ( sources ) > 1 {
2020-12-02 16:15:48 +00:00
sourceStr = fmt . Sprintf ( "%d sources" , len ( sources ) )
2020-11-30 09:37:17 +00:00
} else if len ( sources ) == 1 {
2022-03-09 15:15:18 +00:00
for k := range sources {
2020-11-30 09:37:17 +00:00
sourceStr = k
break
2020-08-05 09:39:54 +00:00
}
}
2022-03-09 15:15:18 +00:00
2020-11-30 09:37:17 +00:00
* 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 )
//Get the events from Leaky/Queue
apiAlert . Events = EventsFromQueue ( queue )
2023-01-04 15:50:02 +00:00
var warnings [ ] error
apiAlert . Meta , warnings = alertcontext . EventToContext ( leaky . Queue . GetQueue ( ) )
for _ , w := range warnings {
log . Warningf ( "while extracting context from bucket %s : %s" , leaky . Name , w )
}
2020-11-30 09:37:17 +00:00
//Loop over the Sources and generate appropriate number of ApiAlerts
for _ , srcValue := range sources {
newApiAlert := apiAlert
srcCopy := srcValue
newApiAlert . Source = & srcCopy
2023-07-24 13:19:28 +00:00
if v , ok := leaky . BucketConfig . Labels [ "remediation" ] ; ok && v == true {
2020-11-30 09:37:17 +00:00
newApiAlert . Remediation = true
}
2020-08-05 09:39:54 +00:00
2020-11-30 09:37:17 +00:00
if err := newApiAlert . Validate ( strfmt . Default ) ; err != nil {
log . Errorf ( "Generated alerts isn't valid" )
log . Errorf ( "->%s" , spew . Sdump ( newApiAlert ) )
log . Fatalf ( "error : %s" , err )
}
runtimeAlert . APIAlerts = append ( runtimeAlert . APIAlerts , newApiAlert )
}
2020-12-03 16:34:57 +00:00
if len ( runtimeAlert . APIAlerts ) > 0 {
runtimeAlert . Alert = & runtimeAlert . APIAlerts [ 0 ]
}
2020-11-30 09:37:17 +00:00
if leaky . Reprocess {
runtimeAlert . Reprocess = true
}
return runtimeAlert , nil
2020-08-05 09:39:54 +00:00
}