helpers.go 15 KB

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