github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/helpers.go (about) 1 package exprhelpers 2 3 import ( 4 "bufio" 5 "encoding/base64" 6 "fmt" 7 "math" 8 "net" 9 "net/url" 10 "os" 11 "path/filepath" 12 "reflect" 13 "regexp" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/antonmedv/expr" 19 "github.com/bluele/gcache" 20 "github.com/c-robinson/iplib" 21 "github.com/cespare/xxhash/v2" 22 "github.com/davecgh/go-spew/spew" 23 "github.com/prometheus/client_golang/prometheus" 24 log "github.com/sirupsen/logrus" 25 "github.com/umahmood/haversine" 26 "github.com/wasilibs/go-re2" 27 28 "github.com/crowdsecurity/go-cs-lib/ptr" 29 30 "github.com/crowdsecurity/crowdsec/pkg/cache" 31 "github.com/crowdsecurity/crowdsec/pkg/database" 32 "github.com/crowdsecurity/crowdsec/pkg/fflag" 33 "github.com/crowdsecurity/crowdsec/pkg/types" 34 ) 35 36 var dataFile map[string][]string 37 var dataFileRegex map[string][]*regexp.Regexp 38 var dataFileRe2 map[string][]*re2.Regexp 39 40 // This is used to (optionally) cache regexp results for RegexpInFile operations 41 var dataFileRegexCache map[string]gcache.Cache = make(map[string]gcache.Cache) 42 43 /*prometheus*/ 44 var RegexpCacheMetrics = prometheus.NewGaugeVec( 45 prometheus.GaugeOpts{ 46 Name: "cs_regexp_cache_size", 47 Help: "Entries per regexp cache.", 48 }, 49 []string{"name"}, 50 ) 51 52 var dbClient *database.Client 53 54 var exprFunctionOptions []expr.Option 55 56 var keyValuePattern = regexp.MustCompile(`(?P<key>[^=\s]+)=(?:"(?P<quoted_value>[^"\\]*(?:\\.[^"\\]*)*)"|(?P<value>[^=\s]+)|\s*)`) 57 58 func GetExprOptions(ctx map[string]interface{}) []expr.Option { 59 if len(exprFunctionOptions) == 0 { 60 exprFunctionOptions = []expr.Option{} 61 for _, function := range exprFuncs { 62 exprFunctionOptions = append(exprFunctionOptions, 63 expr.Function(function.name, 64 function.function, 65 function.signature..., 66 )) 67 } 68 } 69 ret := []expr.Option{} 70 ret = append(ret, exprFunctionOptions...) 71 ret = append(ret, expr.Env(ctx)) 72 return ret 73 } 74 75 func Init(databaseClient *database.Client) error { 76 dataFile = make(map[string][]string) 77 dataFileRegex = make(map[string][]*regexp.Regexp) 78 dataFileRe2 = make(map[string][]*re2.Regexp) 79 dbClient = databaseClient 80 81 return nil 82 } 83 84 func RegexpCacheInit(filename string, CacheCfg types.DataSource) error { 85 86 //cache is explicitly disabled 87 if CacheCfg.Cache != nil && !*CacheCfg.Cache { 88 return nil 89 } 90 //cache is implicitly disabled if no cache config is provided 91 if CacheCfg.Strategy == nil && CacheCfg.TTL == nil && CacheCfg.Size == nil { 92 return nil 93 } 94 //cache is enabled 95 96 if CacheCfg.Size == nil { 97 CacheCfg.Size = ptr.Of(50) 98 } 99 100 gc := gcache.New(*CacheCfg.Size) 101 102 if CacheCfg.Strategy == nil { 103 CacheCfg.Strategy = ptr.Of("LRU") 104 } 105 switch *CacheCfg.Strategy { 106 case "LRU": 107 gc = gc.LRU() 108 case "LFU": 109 gc = gc.LFU() 110 case "ARC": 111 gc = gc.ARC() 112 default: 113 return fmt.Errorf("unknown cache strategy '%s'", *CacheCfg.Strategy) 114 } 115 116 if CacheCfg.TTL != nil { 117 gc.Expiration(*CacheCfg.TTL) 118 } 119 cache := gc.Build() 120 dataFileRegexCache[filename] = cache 121 return nil 122 } 123 124 // UpdateCacheMetrics is called directly by the prom handler 125 func UpdateRegexpCacheMetrics() { 126 RegexpCacheMetrics.Reset() 127 for name := range dataFileRegexCache { 128 RegexpCacheMetrics.With(prometheus.Labels{"name": name}).Set(float64(dataFileRegexCache[name].Len(true))) 129 } 130 } 131 132 func FileInit(fileFolder string, filename string, fileType string) error { 133 log.Debugf("init (folder:%s) (file:%s) (type:%s)", fileFolder, filename, fileType) 134 if fileType == "" { 135 log.Debugf("ignored file %s%s because no type specified", fileFolder, filename) 136 return nil 137 } 138 ok, err := existsInFileMaps(filename, fileType) 139 if ok { 140 log.Debugf("ignored file %s%s because already loaded", fileFolder, filename) 141 return nil 142 } 143 if err != nil { 144 return err 145 } 146 147 filepath := filepath.Join(fileFolder, filename) 148 file, err := os.Open(filepath) 149 if err != nil { 150 return err 151 } 152 defer file.Close() 153 154 scanner := bufio.NewScanner(file) 155 for scanner.Scan() { 156 if strings.HasPrefix(scanner.Text(), "#") { // allow comments 157 continue 158 } 159 if len(scanner.Text()) == 0 { //skip empty lines 160 continue 161 } 162 switch fileType { 163 case "regex", "regexp": 164 if fflag.Re2RegexpInfileSupport.IsEnabled() { 165 dataFileRe2[filename] = append(dataFileRe2[filename], re2.MustCompile(scanner.Text())) 166 continue 167 } 168 dataFileRegex[filename] = append(dataFileRegex[filename], regexp.MustCompile(scanner.Text())) 169 case "string": 170 dataFile[filename] = append(dataFile[filename], scanner.Text()) 171 } 172 } 173 174 if err := scanner.Err(); err != nil { 175 return err 176 } 177 return nil 178 } 179 180 // Expr helpers 181 182 func Distinct(params ...any) (any, error) { 183 184 if rt := reflect.TypeOf(params[0]).Kind(); rt != reflect.Slice && rt != reflect.Array { 185 return nil, nil 186 } 187 array := params[0].([]interface{}) 188 if array == nil { 189 return []interface{}{}, nil 190 } 191 192 var exists map[any]bool = make(map[any]bool) 193 var ret []interface{} = make([]interface{}, 0) 194 195 for _, val := range array { 196 if _, ok := exists[val]; !ok { 197 exists[val] = true 198 ret = append(ret, val) 199 } 200 } 201 return ret, nil 202 203 } 204 205 func FlattenDistinct(params ...any) (any, error) { 206 return Distinct(flatten(nil, reflect.ValueOf(params))) //nolint:asasalint 207 } 208 209 func Flatten(params ...any) (any, error) { 210 return flatten(nil, reflect.ValueOf(params)), nil 211 } 212 213 func flatten(args []interface{}, v reflect.Value) []interface{} { 214 if v.Kind() == reflect.Interface { 215 v = v.Elem() 216 } 217 218 if v.Kind() == reflect.Array || v.Kind() == reflect.Slice { 219 for i := 0; i < v.Len(); i++ { 220 args = flatten(args, v.Index(i)) 221 } 222 } else { 223 args = append(args, v.Interface()) 224 } 225 226 return args 227 } 228 func existsInFileMaps(filename string, ftype string) (bool, error) { 229 ok := false 230 var err error 231 switch ftype { 232 case "regex", "regexp": 233 if fflag.Re2RegexpInfileSupport.IsEnabled() { 234 _, ok = dataFileRe2[filename] 235 } else { 236 _, ok = dataFileRegex[filename] 237 } 238 case "string": 239 _, ok = dataFile[filename] 240 default: 241 err = fmt.Errorf("unknown data type '%s' for : '%s'", ftype, filename) 242 } 243 return ok, err 244 } 245 246 //Expr helpers 247 248 // func Get(arr []string, index int) string { 249 func Get(params ...any) (any, error) { 250 arr := params[0].([]string) 251 index := params[1].(int) 252 if index >= len(arr) { 253 return "", nil 254 } 255 return arr[index], nil 256 } 257 258 // func Atof(x string) float64 { 259 func Atof(params ...any) (any, error) { 260 x := params[0].(string) 261 log.Debugf("debug atof %s", x) 262 ret, err := strconv.ParseFloat(x, 64) 263 if err != nil { 264 log.Warningf("Atof : can't convert float '%s' : %v", x, err) 265 } 266 return ret, nil 267 } 268 269 // func Upper(s string) string { 270 func Upper(params ...any) (any, error) { 271 s := params[0].(string) 272 return strings.ToUpper(s), nil 273 } 274 275 // func Lower(s string) string { 276 func Lower(params ...any) (any, error) { 277 s := params[0].(string) 278 return strings.ToLower(s), nil 279 } 280 281 // func Distance(lat1 string, long1 string, lat2 string, long2 string) (float64, error) { 282 func Distance(params ...any) (any, error) { 283 lat1 := params[0].(string) 284 long1 := params[1].(string) 285 lat2 := params[2].(string) 286 long2 := params[3].(string) 287 lat1f, err := strconv.ParseFloat(lat1, 64) 288 if err != nil { 289 log.Warningf("lat1 is not a float : %v", err) 290 return 0.0, fmt.Errorf("lat1 is not a float : %v", err) 291 } 292 long1f, err := strconv.ParseFloat(long1, 64) 293 if err != nil { 294 log.Warningf("long1 is not a float : %v", err) 295 return 0.0, fmt.Errorf("long1 is not a float : %v", err) 296 } 297 lat2f, err := strconv.ParseFloat(lat2, 64) 298 if err != nil { 299 log.Warningf("lat2 is not a float : %v", err) 300 301 return 0.0, fmt.Errorf("lat2 is not a float : %v", err) 302 } 303 long2f, err := strconv.ParseFloat(long2, 64) 304 if err != nil { 305 log.Warningf("long2 is not a float : %v", err) 306 307 return 0.0, fmt.Errorf("long2 is not a float : %v", err) 308 } 309 310 //either set of coordinates is 0,0, return 0 to avoid FPs 311 if (lat1f == 0.0 && long1f == 0.0) || (lat2f == 0.0 && long2f == 0.0) { 312 log.Warningf("one of the coordinates is 0,0, returning 0") 313 return 0.0, nil 314 } 315 316 first := haversine.Coord{Lat: lat1f, Lon: long1f} 317 second := haversine.Coord{Lat: lat2f, Lon: long2f} 318 319 _, km := haversine.Distance(first, second) 320 return km, nil 321 } 322 323 // func QueryEscape(s string) string { 324 func QueryEscape(params ...any) (any, error) { 325 s := params[0].(string) 326 return url.QueryEscape(s), nil 327 } 328 329 // func PathEscape(s string) string { 330 func PathEscape(params ...any) (any, error) { 331 s := params[0].(string) 332 return url.PathEscape(s), nil 333 } 334 335 // func PathUnescape(s string) string { 336 func PathUnescape(params ...any) (any, error) { 337 s := params[0].(string) 338 ret, err := url.PathUnescape(s) 339 if err != nil { 340 log.Debugf("unable to PathUnescape '%s': %+v", s, err) 341 return s, nil 342 } 343 return ret, nil 344 } 345 346 // func QueryUnescape(s string) string { 347 func QueryUnescape(params ...any) (any, error) { 348 s := params[0].(string) 349 ret, err := url.QueryUnescape(s) 350 if err != nil { 351 log.Debugf("unable to QueryUnescape '%s': %+v", s, err) 352 return s, nil 353 } 354 return ret, nil 355 } 356 357 // func File(filename string) []string { 358 func File(params ...any) (any, error) { 359 filename := params[0].(string) 360 if _, ok := dataFile[filename]; ok { 361 return dataFile[filename], nil 362 } 363 log.Errorf("file '%s' (type:string) not found in expr library", filename) 364 log.Errorf("expr library : %s", spew.Sdump(dataFile)) 365 return []string{}, nil 366 } 367 368 // func RegexpInFile(data string, filename string) bool { 369 func RegexpInFile(params ...any) (any, error) { 370 data := params[0].(string) 371 filename := params[1].(string) 372 var hash uint64 373 hasCache := false 374 matched := false 375 376 if _, ok := dataFileRegexCache[filename]; ok { 377 hasCache = true 378 hash = xxhash.Sum64String(data) 379 if val, err := dataFileRegexCache[filename].Get(hash); err == nil { 380 return val.(bool), nil 381 } 382 } 383 384 switch fflag.Re2RegexpInfileSupport.IsEnabled() { 385 case true: 386 if _, ok := dataFileRe2[filename]; ok { 387 for _, re := range dataFileRe2[filename] { 388 if re.MatchString(data) { 389 matched = true 390 break 391 } 392 } 393 } else { 394 log.Errorf("file '%s' (type:regexp) not found in expr library", filename) 395 log.Errorf("expr library : %s", spew.Sdump(dataFileRe2)) 396 } 397 case false: 398 if _, ok := dataFileRegex[filename]; ok { 399 for _, re := range dataFileRegex[filename] { 400 if re.MatchString(data) { 401 matched = true 402 break 403 } 404 } 405 } else { 406 log.Errorf("file '%s' (type:regexp) not found in expr library", filename) 407 log.Errorf("expr library : %s", spew.Sdump(dataFileRegex)) 408 } 409 } 410 if hasCache { 411 dataFileRegexCache[filename].Set(hash, matched) 412 } 413 return matched, nil 414 } 415 416 // func IpInRange(ip string, ipRange string) bool { 417 func IpInRange(params ...any) (any, error) { 418 var err error 419 var ipParsed net.IP 420 var ipRangeParsed *net.IPNet 421 422 ip := params[0].(string) 423 ipRange := params[1].(string) 424 425 ipParsed = net.ParseIP(ip) 426 if ipParsed == nil { 427 log.Debugf("'%s' is not a valid IP", ip) 428 return false, nil 429 } 430 if _, ipRangeParsed, err = net.ParseCIDR(ipRange); err != nil { 431 log.Debugf("'%s' is not a valid IP Range", ipRange) 432 return false, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility 433 } 434 if ipRangeParsed.Contains(ipParsed) { 435 return true, nil 436 } 437 return false, nil 438 } 439 440 // func IsIPV6(ip string) bool { 441 func IsIPV6(params ...any) (any, error) { 442 ip := params[0].(string) 443 ipParsed := net.ParseIP(ip) 444 if ipParsed == nil { 445 log.Debugf("'%s' is not a valid IP", ip) 446 return false, nil 447 } 448 449 // If it's a valid IP and can't be converted to IPv4 then it is an IPv6 450 return ipParsed.To4() == nil, nil 451 } 452 453 // func IsIPV4(ip string) bool { 454 func IsIPV4(params ...any) (any, error) { 455 ip := params[0].(string) 456 ipParsed := net.ParseIP(ip) 457 if ipParsed == nil { 458 log.Debugf("'%s' is not a valid IP", ip) 459 return false, nil 460 } 461 return ipParsed.To4() != nil, nil 462 } 463 464 // func IsIP(ip string) bool { 465 func IsIP(params ...any) (any, error) { 466 ip := params[0].(string) 467 ipParsed := net.ParseIP(ip) 468 if ipParsed == nil { 469 log.Debugf("'%s' is not a valid IP", ip) 470 return false, nil 471 } 472 return true, nil 473 } 474 475 // func IpToRange(ip string, cidr string) string { 476 func IpToRange(params ...any) (any, error) { 477 ip := params[0].(string) 478 cidr := params[1].(string) 479 cidr = strings.TrimPrefix(cidr, "/") 480 mask, err := strconv.Atoi(cidr) 481 if err != nil { 482 log.Errorf("bad cidr '%s': %s", cidr, err) 483 return "", nil 484 } 485 486 ipAddr := net.ParseIP(ip) 487 if ipAddr == nil { 488 log.Errorf("can't parse IP address '%s'", ip) 489 return "", nil 490 } 491 ipRange := iplib.NewNet(ipAddr, mask) 492 if ipRange.IP() == nil { 493 log.Errorf("can't get cidr '%s' of '%s'", cidr, ip) 494 return "", nil 495 } 496 return ipRange.String(), nil 497 } 498 499 // func TimeNow() string { 500 func TimeNow(params ...any) (any, error) { 501 return time.Now().UTC().Format(time.RFC3339), nil 502 } 503 504 // func ParseUri(uri string) map[string][]string { 505 func ParseUri(params ...any) (any, error) { 506 uri := params[0].(string) 507 ret := make(map[string][]string) 508 u, err := url.Parse(uri) 509 if err != nil { 510 log.Errorf("Could not parse URI: %s", err) 511 return ret, nil 512 } 513 parsed, err := url.ParseQuery(u.RawQuery) 514 if err != nil { 515 log.Errorf("Could not parse query uri : %s", err) 516 return ret, nil 517 } 518 for k, v := range parsed { 519 ret[k] = v 520 } 521 return ret, nil 522 } 523 524 // func KeyExists(key string, dict map[string]interface{}) bool { 525 func KeyExists(params ...any) (any, error) { 526 key := params[0].(string) 527 dict := params[1].(map[string]interface{}) 528 _, ok := dict[key] 529 return ok, nil 530 } 531 532 // func GetDecisionsCount(value string) int { 533 func GetDecisionsCount(params ...any) (any, error) { 534 value := params[0].(string) 535 if dbClient == nil { 536 log.Error("No database config to call GetDecisionsCount()") 537 return 0, nil 538 539 } 540 count, err := dbClient.CountDecisionsByValue(value) 541 if err != nil { 542 log.Errorf("Failed to get decisions count from value '%s'", value) 543 return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility 544 } 545 return count, nil 546 } 547 548 // func GetDecisionsSinceCount(value string, since string) int { 549 func GetDecisionsSinceCount(params ...any) (any, error) { 550 value := params[0].(string) 551 since := params[1].(string) 552 if dbClient == nil { 553 log.Error("No database config to call GetDecisionsCount()") 554 return 0, nil 555 } 556 sinceDuration, err := time.ParseDuration(since) 557 if err != nil { 558 log.Errorf("Failed to parse since parameter '%s' : %s", since, err) 559 return 0, nil 560 } 561 sinceTime := time.Now().UTC().Add(-sinceDuration) 562 count, err := dbClient.CountDecisionsSinceByValue(value, sinceTime) 563 if err != nil { 564 log.Errorf("Failed to get decisions count from value '%s'", value) 565 return 0, nil //nolint:nilerr // This helper did not return an error before the move to expr.Function, we keep this behavior for backward compatibility 566 } 567 return count, nil 568 } 569 570 // func LookupHost(value string) []string { 571 func LookupHost(params ...any) (any, error) { 572 value := params[0].(string) 573 addresses, err := net.LookupHost(value) 574 if err != nil { 575 log.Errorf("Failed to lookup host '%s' : %s", value, err) 576 return []string{}, nil 577 } 578 return addresses, nil 579 } 580 581 // func ParseUnixTime(value string) (time.Time, error) { 582 func ParseUnixTime(params ...any) (any, error) { 583 value := params[0].(string) 584 //Splitting string here as some unix timestamp may have milliseconds and break ParseInt 585 i, err := strconv.ParseInt(strings.Split(value, ".")[0], 10, 64) 586 if err != nil || i <= 0 { 587 return time.Time{}, fmt.Errorf("unable to parse %s as unix timestamp", value) 588 } 589 return time.Unix(i, 0), nil 590 } 591 592 // func ParseUnix(value string) string { 593 func ParseUnix(params ...any) (any, error) { 594 value := params[0].(string) 595 t, err := ParseUnixTime(value) 596 if err != nil { 597 log.Error(err) 598 return "", nil 599 } 600 return t.(time.Time).Format(time.RFC3339), nil 601 } 602 603 // func ToString(value interface{}) string { 604 func ToString(params ...any) (any, error) { 605 value := params[0] 606 s, ok := value.(string) 607 if !ok { 608 return "", nil 609 } 610 return s, nil 611 } 612 613 // func GetFromStash(cacheName string, key string) (string, error) { 614 func GetFromStash(params ...any) (any, error) { 615 cacheName := params[0].(string) 616 key := params[1].(string) 617 return cache.GetKey(cacheName, key) 618 } 619 620 // func SetInStash(cacheName string, key string, value string, expiration *time.Duration) any { 621 func SetInStash(params ...any) (any, error) { 622 cacheName := params[0].(string) 623 key := params[1].(string) 624 value := params[2].(string) 625 expiration := params[3].(*time.Duration) 626 return cache.SetKey(cacheName, key, value, expiration), nil 627 } 628 629 func Sprintf(params ...any) (any, error) { 630 format := params[0].(string) 631 return fmt.Sprintf(format, params[1:]...), nil 632 } 633 634 // func Match(pattern, name string) bool { 635 func Match(params ...any) (any, error) { 636 var matched bool 637 638 pattern := params[0].(string) 639 name := params[1].(string) 640 641 if pattern == "" { 642 return name == "", nil 643 } 644 if name == "" { 645 if pattern == "*" || pattern == "" { 646 return true, nil 647 } 648 return false, nil 649 } 650 if pattern[0] == '*' { 651 for i := 0; i <= len(name); i++ { 652 matched, _ := Match(pattern[1:], name[i:]) 653 if matched.(bool) { 654 return matched, nil 655 } 656 } 657 return matched, nil 658 } 659 if pattern[0] == '?' || pattern[0] == name[0] { 660 return Match(pattern[1:], name[1:]) 661 } 662 return matched, nil 663 } 664 665 func FloatApproxEqual(params ...any) (any, error) { 666 float1 := params[0].(float64) 667 float2 := params[1].(float64) 668 669 if math.Abs(float1-float2) < 1e-6 { 670 return true, nil 671 } 672 return false, nil 673 } 674 675 func B64Decode(params ...any) (any, error) { 676 encoded := params[0].(string) 677 decoded, err := base64.StdEncoding.DecodeString(encoded) 678 if err != nil { 679 return "", err 680 } 681 return string(decoded), nil 682 } 683 684 func ParseKV(params ...any) (any, error) { 685 686 blob := params[0].(string) 687 target := params[1].(map[string]interface{}) 688 prefix := params[2].(string) 689 690 matches := keyValuePattern.FindAllStringSubmatch(blob, -1) 691 if matches == nil { 692 log.Errorf("could not find any key/value pair in line") 693 return nil, fmt.Errorf("invalid input format") 694 } 695 if _, ok := target[prefix]; !ok { 696 target[prefix] = make(map[string]string) 697 } else { 698 _, ok := target[prefix].(map[string]string) 699 if !ok { 700 log.Errorf("ParseKV: target is not a map[string]string") 701 return nil, fmt.Errorf("target is not a map[string]string") 702 } 703 } 704 for _, match := range matches { 705 key := "" 706 value := "" 707 for i, name := range keyValuePattern.SubexpNames() { 708 if name == "key" { 709 key = match[i] 710 } else if name == "quoted_value" && match[i] != "" { 711 value = match[i] 712 } else if name == "value" && match[i] != "" { 713 value = match[i] 714 } 715 } 716 target[prefix].(map[string]string)[key] = value 717 } 718 log.Tracef("unmarshaled KV: %+v", target[prefix]) 719 return nil, nil 720 } 721 722 func Hostname(params ...any) (any, error) { 723 hostname, err := os.Hostname() 724 if err != nil { 725 return "", err 726 } 727 return hostname, nil 728 }