environment.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package expressions
  2. import (
  3. "math/rand/v2"
  4. "strings"
  5. "github.com/TecharoHQ/anubis/internal/dns"
  6. "github.com/google/cel-go/cel"
  7. "github.com/google/cel-go/common/types"
  8. "github.com/google/cel-go/common/types/ref"
  9. "github.com/google/cel-go/common/types/traits"
  10. "github.com/google/cel-go/ext"
  11. )
  12. // BotEnvironment creates a new CEL environment, this is the set of
  13. // variables and functions that are passed into the CEL scope so that
  14. // Anubis can fail loudly and early when something is invalid instead
  15. // of blowing up at runtime.
  16. func BotEnvironment(dnsObj *dns.Dns) (*cel.Env, error) {
  17. return New(
  18. // Variables exposed to CEL programs:
  19. cel.Variable("remoteAddress", cel.StringType),
  20. cel.Variable("contentLength", cel.IntType),
  21. cel.Variable("host", cel.StringType),
  22. cel.Variable("method", cel.StringType),
  23. cel.Variable("userAgent", cel.StringType),
  24. cel.Variable("path", cel.StringType),
  25. cel.Variable("query", cel.MapType(cel.StringType, cel.StringType)),
  26. cel.Variable("headers", cel.MapType(cel.StringType, cel.StringType)),
  27. cel.Variable("load_1m", cel.DoubleType),
  28. cel.Variable("load_5m", cel.DoubleType),
  29. cel.Variable("load_15m", cel.DoubleType),
  30. // Bot-specific functions:
  31. cel.Function("missingHeader",
  32. cel.Overload("missingHeader_map_string_string_string",
  33. []*cel.Type{cel.MapType(cel.StringType, cel.StringType), cel.StringType},
  34. cel.BoolType,
  35. cel.BinaryBinding(func(headers, key ref.Val) ref.Val {
  36. // Convert headers to a trait that supports Find
  37. headersMap, ok := headers.(traits.Indexer)
  38. if !ok {
  39. return types.ValOrErr(headers, "headers is not a map, but is %T", headers)
  40. }
  41. keyStr, ok := key.(types.String)
  42. if !ok {
  43. return types.ValOrErr(key, "key is not a string, but is %T", key)
  44. }
  45. val := headersMap.Get(keyStr)
  46. // Check if the key is missing by testing for an error
  47. if types.IsError(val) {
  48. return types.Bool(true) // header is missing
  49. }
  50. return types.Bool(false) // header is present
  51. }),
  52. ),
  53. ),
  54. cel.Function("reverseDNS",
  55. cel.Overload("reverseDNS_string_list_string",
  56. []*cel.Type{cel.StringType},
  57. cel.ListType(cel.StringType),
  58. cel.UnaryBinding(func(addr ref.Val) ref.Val {
  59. addrStr, ok := addr.(types.String)
  60. if !ok {
  61. return types.ValOrErr(addr, "addr is not a string, but is %T", addr)
  62. }
  63. names, err := dnsObj.ReverseDNS(string(addrStr))
  64. if err != nil {
  65. return types.NewStringList(types.DefaultTypeAdapter, []string{})
  66. }
  67. return types.NewStringList(types.DefaultTypeAdapter, names)
  68. }),
  69. ),
  70. ),
  71. cel.Function("lookupHost",
  72. cel.Overload("lookupHost_string_list_string",
  73. []*cel.Type{cel.StringType},
  74. cel.ListType(cel.StringType),
  75. cel.UnaryBinding(func(host ref.Val) ref.Val {
  76. hostStr, ok := host.(types.String)
  77. if !ok {
  78. return types.ValOrErr(host, "host is not a string, but is %T", host)
  79. }
  80. addrs, err := dnsObj.LookupHost(string(hostStr))
  81. if err != nil {
  82. return types.NewStringList(types.DefaultTypeAdapter, []string{})
  83. }
  84. return types.NewStringList(types.DefaultTypeAdapter, addrs)
  85. }),
  86. ),
  87. ),
  88. cel.Function("verifyFCrDNS",
  89. cel.Overload("verifyFCrDNS_string_bool",
  90. []*cel.Type{cel.StringType},
  91. cel.BoolType,
  92. cel.UnaryBinding(func(addr ref.Val) ref.Val {
  93. addrStr, ok := addr.(types.String)
  94. if !ok {
  95. return types.ValOrErr(addr, "addr is not a string")
  96. }
  97. return types.Bool(dnsObj.VerifyFCrDNS(string(addrStr), nil))
  98. }),
  99. ),
  100. cel.Overload("verifyFCrDNS_string_string_bool",
  101. []*cel.Type{cel.StringType, cel.StringType},
  102. cel.BoolType,
  103. cel.BinaryBinding(func(addr, pattern ref.Val) ref.Val {
  104. addrStr, ok := addr.(types.String)
  105. if !ok {
  106. return types.ValOrErr(addr, "addr is not a string")
  107. }
  108. patternStr, ok := pattern.(types.String)
  109. if !ok {
  110. return types.ValOrErr(pattern, "pattern is not a string")
  111. }
  112. p := string(patternStr)
  113. return types.Bool(dnsObj.VerifyFCrDNS(string(addrStr), &p))
  114. }),
  115. ),
  116. ),
  117. // arpaReverseIP transforms ip into arpa reverse notation like this
  118. // 1.2.3.4 -> 4.3.2.1
  119. // 2001:db8::1 -> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2
  120. cel.Function("arpaReverseIP",
  121. cel.Overload("arpaReverseIP_string_string",
  122. []*cel.Type{cel.StringType},
  123. cel.StringType,
  124. cel.UnaryBinding(func(addr ref.Val) ref.Val {
  125. s, ok := addr.(types.String)
  126. if !ok {
  127. return types.ValOrErr(addr, "addr is not a string")
  128. }
  129. reversedIp, err := dnsObj.ArpaReverseIP(string(s))
  130. if err != nil {
  131. return types.ValOrErr(addr, "%s", err.Error())
  132. }
  133. return types.String(reversedIp)
  134. }),
  135. ),
  136. ),
  137. // regexSafe escapes a string for insertion into a regular expression
  138. cel.Function("regexSafe",
  139. cel.Overload("regexSafe_string_string",
  140. []*cel.Type{cel.StringType},
  141. cel.StringType,
  142. cel.UnaryBinding(func(str ref.Val) ref.Val {
  143. s, ok := str.(types.String)
  144. if !ok {
  145. return types.ValOrErr(str, "addr is not a string")
  146. }
  147. escapes := []string{"\\", ".", ":", "*", "?", "-", "[", "]", "(", ")", "+", "{", "}", "|", "^", "$"}
  148. r := string(s)
  149. for _, escape := range escapes {
  150. r = strings.ReplaceAll(r, escape, "\\"+escape)
  151. }
  152. return types.String(r)
  153. }),
  154. ),
  155. ),
  156. cel.Function("segments",
  157. cel.Overload("segments_string_list_string",
  158. []*cel.Type{cel.StringType},
  159. cel.ListType(cel.StringType),
  160. cel.UnaryBinding(func(path ref.Val) ref.Val {
  161. pathStrType, ok := path.(types.String)
  162. if !ok {
  163. return types.ValOrErr(path, "path is not a string, but is %T", path)
  164. }
  165. pathStr := string(pathStrType)
  166. if !strings.HasPrefix(pathStr, "/") {
  167. return types.ValOrErr(path, "path does not start with /")
  168. }
  169. pathList := strings.Split(string(pathStr), "/")[1:]
  170. return types.NewStringList(types.DefaultTypeAdapter, pathList)
  171. }),
  172. ),
  173. ),
  174. )
  175. }
  176. // NewThreshold creates a new CEL environment for threshold checking.
  177. func ThresholdEnvironment() (*cel.Env, error) {
  178. return New(
  179. cel.Variable("weight", cel.IntType),
  180. )
  181. }
  182. func New(opts ...cel.EnvOption) (*cel.Env, error) {
  183. args := []cel.EnvOption{
  184. ext.Strings(
  185. ext.StringsLocale("en_US"),
  186. ext.StringsValidateFormatCalls(true),
  187. ),
  188. // default all timestamps to UTC
  189. cel.DefaultUTCTimeZone(true),
  190. // Functions exposed to all CEL programs:
  191. cel.Function("randInt",
  192. cel.Overload("randInt_int",
  193. []*cel.Type{cel.IntType},
  194. cel.IntType,
  195. cel.UnaryBinding(func(val ref.Val) ref.Val {
  196. n, ok := val.(types.Int)
  197. if !ok {
  198. return types.ValOrErr(val, "value is not an integer, but is %T", val)
  199. }
  200. return types.Int(rand.IntN(int(n)))
  201. }),
  202. ),
  203. ),
  204. }
  205. args = append(args, opts...)
  206. return cel.NewEnv(args...)
  207. }
  208. // Compile takes CEL environment and syntax tree then emits an optimized
  209. // Program for execution.
  210. func Compile(env *cel.Env, src string) (cel.Program, error) {
  211. intermediate, iss := env.Compile(src)
  212. if iss != nil {
  213. return nil, iss.Err()
  214. }
  215. ast, iss := env.Check(intermediate)
  216. if iss != nil {
  217. return nil, iss.Err()
  218. }
  219. return env.Program(
  220. ast,
  221. cel.EvalOptions(
  222. // optimize regular expressions right now instead of on the fly
  223. cel.OptOptimize,
  224. ),
  225. )
  226. }