一、基础用法
前段时间学了点golang的基础语法,就来学习cel-go模块的使用,网上有用的教程倒是比较少,cel-go的github官方页面倒是给了点示例代码可以学习下。
1.变量使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package examples
import ( "fmt" "log"
"github.com/google/cel-go/cel" )
func ExampleSimple() { env, err := cel.NewEnv(cel.Variable("name", cel.StringType)) if err != nil { log.Fatalf("environment creation error: %v\n", err) } ast, iss := env.Compile(`"Hello world! I'm " + name + "."`) if iss.Err() != nil { log.Fatalln(iss.Err()) } prg, err := env.Program(ast) if err != nil { log.Fatalln(err) } out, _, err := prg.Eval(map[string]any{ "name": "CEL", }) if err != nil { log.Fatalln(err) } fmt.Println(out) }
|
注释也按照自己的理解在对应代码上方写出,其他示例代码不会对上述代码进行注释。
2.函数使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| package examples
import ( "fmt" "log"
"github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" )
func ExampleCustomInstanceFunction() { env, err := cel.NewEnv(cel.Lib(customLib{})) if err != nil { log.Fatalf("environment creation error: %v\n", err) } ast, iss := env.Compile(`i.greet(you)`) if iss.Err() != nil { log.Fatalln(iss.Err()) } prg, err := env.Program(ast) if err != nil { log.Fatalf("Program creation error: %v\n", err) }
out, _, err := prg.Eval(map[string]any{ "i": "CEL", "you": "world", }) if err != nil { log.Fatalf("Evaluation error: %v\n", err) }
fmt.Println(out) }
type customLib struct{}
func (customLib) CompileOptions() []cel.EnvOption { return []cel.EnvOption{ cel.Variable("i", cel.StringType), cel.Variable("you", cel.StringType), cel.Function("greet", cel.MemberOverload("string_greet_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, cel.BinaryBinding(func(lhs, rhs ref.Val) ref.Val { return types.String( fmt.Sprintf("Hello %s! Nice to meet you, I'm %s.\n", rhs, lhs)) }), ), ), } }
func (customLib) ProgramOptions() []cel.ProgramOption { return []cel.ProgramOption{} }
|
第三个示例代码就不展示了,因为和第二个也大差不差,总结下来主要是以下不同:
- 使用的重载方法不同:示例2的代码采用
MemberOverload
进行重载,调用方式为已声明的变量.funcName()
,而示例3的代码采用Overload
进行重载,调用方式可直接通过正常方式funcName()
即可完成调用;
- 另外还有一点是在编写
CEL
函数是,根据参数的需要来选择是用BinaryBinding
与UnaryBinding
,前者可以传入两个参数,后者只能传入一个参数;
二、动态Env
在实际的开发中,我们可能需要多种不同的程序环境,但如果为每一种类型去写一种Env
,那属实会造成代码冗余,不妨我们整一个动态Env
,并根据需要动态加载对应的变量和函数。
首先我们正常定义一个SimpleLib
结构体
1 2 3 4 5
| type SimpleLib struct { Variables map[string]any GlobalVariable []cel.EnvOption GlobalFunction []cel.EnvOption }
|
Variables
用于存储变量与对应的值,例如{“name”:”王刚”};
GlobalVariable
用于存储Env
环境创建所需要的变量类型;
GlobalFunction
用于存储Env
环境创建所需要的函数类型;
1.动态添加变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func (s *SimpleLib) AddVariable(varMap map[string]any) { var env []cel.EnvOption s.Variables = make(map[string]any) for k, v := range varMap { if reflect.TypeOf(v).String() == "int" { env = append(env, cel.Variable(k, types.IntType)) s.Variables[k] = v } else if reflect.TypeOf(v).String() == "string" { env = append(env, cel.Variable(k, types.StringType)) s.Variables[k] = v } else if reflect.TypeOf(v).Elem().String() == "otherType" { } else { fmt.Println(fmt.Sprintf("UnSupport Variable Type %s", reflect.TypeOf(v).String())) } } s.GlobalVariable = append(s.GlobalVariable, env...) }
|
通过遍历varMap
内值的类型去创建对应Env
环境创建所需要的EnvOption
类型,若有自定义可在条件判断后继续添加判断和对应操作。
2.动态添加函数
动态添加函数相对来说就比较简单,因为不需要像变量一样需要判断数据类型
1 2 3
| func (s *SimpleLib) AddGlobalFunction(funcArray []cel.EnvOption) { s.GlobalFunction = append(s.GlobalFunction, funcArray...) }
|
这边我采用函数的方式去获取对应的CEL
函数,可根据CEL
函数的不同类型和作用去创建对应的获取函数,如下代码所示为获取的Instance
类型的函数,只作用于变量。
1 2 3 4 5 6 7
| func GetAllInstanceFunction() []cel.EnvOption { return []cel.EnvOption{ cel.Function("contains", containsFunction), cel.Function("bContains", bContainsFunction), cel.Function("iContains", iContainsFunction), } }
|
而containsFunction
等也都是定义好的FunctionOpt
类型,如下为其中一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var containsFunction = cel.MemberOverload("contains_string", []*cel.Type{cel.StringType, cel.StringType}, cel.BoolType, cel.BinaryBinding(func(lhs ref.Val, rhs ref.Val) ref.Val { v1, ok := lhs.(types.String) if !ok { return types.ValOrErr(lhs, "unexpected type '%v' passed to contains_string", lhs.Type()) } v2, ok := rhs.(types.String) if !ok { return types.ValOrErr(rhs, "unexpected type '%v' passed to contains_string", rhs.Type()) } return types.Bool(strings.Contains(string(v1), string(v2))) }))
|
3.CompileOption
CompileOption
方法内是可以配置我们环境内的自定义变量和函数,故逻辑也是在该函数内实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func (s *SimpleLib) CompileOptions() []cel.EnvOption { env := []cel.EnvOption{ cel.Types( &http.UrlType{}, &http.Request{}, &http.Response{}, )} if s.GlobalVariable != nil { env = append(env, s.GlobalVariable...) } if s.GlobalFunction != nil { env = append(env, s.GlobalFunction...) } return env }
|
三、使用案例
以下是对上述代码的一个简单使用例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| var sayFunction = cel.MemberOverload( "saying", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(func(value ref.Val) ref.Val { val, ok := value.(types.String) if !ok { return types.ValOrErr(value, "unexpected type '%v'", value.Type()) } return types.String(fmt.Sprintf("我叫%s", string(val))) }))
func main() { var funcArray = []cel.EnvOption{ cel.Function("say", sayFunction), }
simple := SimpleLib{} simple.AddVariable(map[string]any{ "name": "王刚", }) simple.AddGlobalFunction(funcArray) out, err := simple.Execute("name.say()") if err != nil { panic(err) } fmt.Println(out) }
|
我自己也写好了一套通过yaml
文件去进行漏洞检测的功能,函数和功能都是参考xray
扫描器上的,后续是决定完全按照xray
上的解析规则实现还是怎样暂时还没想好,这个项目写好后也会发到github
上进行开源。以下是yaml
模板和最后执行的结果输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| set: test1: base64("hello") test2: md5("hello") rules: r1: path: / method: GET headers: Content-Type: application/json User-Agent: "{{test1}}" expression: response.body.bContains(bytes('baidu')) r2: path: / method: POST headers: Content-Type: application/json User-Agent: "{{test2}}" body: '{"admin": "admin"}' expression: response.status_code == 200
expression: r1() || r2()
|
下图是运行结果