helpers.go 17 KB

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