github.com/crowdsecurity/crowdsec@v1.6.1/pkg/alertcontext/alertcontext.go (about)

     1  package alertcontext
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"slices"
     7  	"strconv"
     8  
     9  	"github.com/antonmedv/expr"
    10  	"github.com/antonmedv/expr/vm"
    11  	log "github.com/sirupsen/logrus"
    12  
    13  	"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
    14  	"github.com/crowdsecurity/crowdsec/pkg/models"
    15  	"github.com/crowdsecurity/crowdsec/pkg/types"
    16  )
    17  
    18  const (
    19  	maxContextValueLen = 4000
    20  )
    21  
    22  var (
    23  	alertContext = Context{}
    24  )
    25  
    26  type Context struct {
    27  	ContextToSend         map[string][]string
    28  	ContextValueLen       int
    29  	ContextToSendCompiled map[string][]*vm.Program
    30  	Log                   *log.Logger
    31  }
    32  
    33  func ValidateContextExpr(key string, expressions []string) error {
    34  	for _, expression := range expressions {
    35  		_, err := expr.Compile(expression, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
    36  		if err != nil {
    37  			return fmt.Errorf("compilation of '%s' failed: %v", expression, err)
    38  		}
    39  	}
    40  	return nil
    41  }
    42  
    43  func NewAlertContext(contextToSend map[string][]string, valueLength int) error {
    44  	var clog = log.New()
    45  	if err := types.ConfigureLogger(clog); err != nil {
    46  		return fmt.Errorf("couldn't create logger for alert context: %s", err)
    47  	}
    48  
    49  	if valueLength == 0 {
    50  		clog.Debugf("No console context value length provided, using default: %d", maxContextValueLen)
    51  		valueLength = maxContextValueLen
    52  	}
    53  	if valueLength > maxContextValueLen {
    54  		clog.Debugf("Provided console context value length (%d) is higher than the maximum, using default: %d", valueLength, maxContextValueLen)
    55  		valueLength = maxContextValueLen
    56  	}
    57  
    58  	alertContext = Context{
    59  		ContextToSend:         contextToSend,
    60  		ContextValueLen:       valueLength,
    61  		Log:                   clog,
    62  		ContextToSendCompiled: make(map[string][]*vm.Program),
    63  	}
    64  
    65  	for key, values := range contextToSend {
    66  		if _, ok := alertContext.ContextToSend[key]; !ok {
    67  			alertContext.ContextToSend[key] = make([]string, 0)
    68  		}
    69  
    70  		if _, ok := alertContext.ContextToSendCompiled[key]; !ok {
    71  			alertContext.ContextToSendCompiled[key] = make([]*vm.Program, 0)
    72  		}
    73  
    74  		for _, value := range values {
    75  			valueCompiled, err := expr.Compile(value, exprhelpers.GetExprOptions(map[string]interface{}{"evt": &types.Event{}})...)
    76  			if err != nil {
    77  				return fmt.Errorf("compilation of '%s' context value failed: %v", value, err)
    78  			}
    79  			alertContext.ContextToSendCompiled[key] = append(alertContext.ContextToSendCompiled[key], valueCompiled)
    80  			alertContext.ContextToSend[key] = append(alertContext.ContextToSend[key], value)
    81  		}
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  func truncate(values []string, contextValueLen int) (string, error) {
    88  	var ret string
    89  	valueByte, err := json.Marshal(values)
    90  	if err != nil {
    91  		return "", fmt.Errorf("unable to dump metas: %s", err)
    92  	}
    93  	ret = string(valueByte)
    94  	for {
    95  		if len(ret) <= contextValueLen {
    96  			break
    97  		}
    98  		// if there is only 1 value left and that the size is too big, truncate it
    99  		if len(values) == 1 {
   100  			valueToTruncate := values[0]
   101  			half := len(valueToTruncate) / 2
   102  			lastValueTruncated := valueToTruncate[:half] + "..."
   103  			values = values[:len(values)-1]
   104  			values = append(values, lastValueTruncated)
   105  		} else {
   106  			// if there is multiple value inside, just remove the last one
   107  			values = values[:len(values)-1]
   108  		}
   109  		valueByte, err = json.Marshal(values)
   110  		if err != nil {
   111  			return "", fmt.Errorf("unable to dump metas: %s", err)
   112  		}
   113  		ret = string(valueByte)
   114  	}
   115  	return ret, nil
   116  }
   117  
   118  func EventToContext(events []types.Event) (models.Meta, []error) {
   119  	var errors []error
   120  
   121  	metas := make([]*models.MetaItems0, 0)
   122  	tmpContext := make(map[string][]string)
   123  	for _, evt := range events {
   124  		for key, values := range alertContext.ContextToSendCompiled {
   125  			if _, ok := tmpContext[key]; !ok {
   126  				tmpContext[key] = make([]string, 0)
   127  			}
   128  			for _, value := range values {
   129  				var val string
   130  				output, err := expr.Run(value, map[string]interface{}{"evt": evt})
   131  				if err != nil {
   132  					errors = append(errors, fmt.Errorf("failed to get value for %s : %v", key, err))
   133  					continue
   134  				}
   135  				switch out := output.(type) {
   136  				case string:
   137  					val = out
   138  				case int:
   139  					val = strconv.Itoa(out)
   140  				default:
   141  					errors = append(errors, fmt.Errorf("unexpected return type for %s : %T", key, output))
   142  					continue
   143  				}
   144  				if val != "" && !slices.Contains(tmpContext[key], val) {
   145  					tmpContext[key] = append(tmpContext[key], val)
   146  				}
   147  			}
   148  		}
   149  	}
   150  	for key, values := range tmpContext {
   151  		if len(values) == 0 {
   152  			continue
   153  		}
   154  		valueStr, err := truncate(values, alertContext.ContextValueLen)
   155  		if err != nil {
   156  			log.Warningf(err.Error())
   157  		}
   158  		meta := models.MetaItems0{
   159  			Key:   key,
   160  			Value: valueStr,
   161  		}
   162  		metas = append(metas, &meta)
   163  	}
   164  
   165  	ret := models.Meta(metas)
   166  	return ret, errors
   167  }