helpers.go 14 KB

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