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 }