diff --git a/go.mod b/go.mod index e00118e..1e74c2a 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/google/cel-policy-templates-go -go 1.12 +go 1.13 require ( - github.com/google/cel-go v0.7.2 + github.com/google/cel-go v0.7.3 github.com/kr/pretty v0.1.0 // indirect google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 google.golang.org/protobuf v1.25.0 diff --git a/go.sum b/go.sum index 8f7acf2..1e6a813 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/cel-go v0.7.2 h1:FoLWxW4h8SV1UEOwth7xOU0tpeY7l58ycOs00xs6eu8= github.com/google/cel-go v0.7.2/go.mod h1:4EtyFAHT5xNr0Msu0MJjyGxPUgdr9DlcaPyzLt/kkt8= +github.com/google/cel-go v0.7.3 h1:8v9BSN0avuGwrHFKNCjfiQ/CE6+D6sW+BDyOVoEeP6o= +github.com/google/cel-go v0.7.3/go.mod h1:4EtyFAHT5xNr0Msu0MJjyGxPUgdr9DlcaPyzLt/kkt8= github.com/google/cel-spec v0.5.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= diff --git a/policy/compiler/compiler.go b/policy/compiler/compiler.go index 8f213a0..43b66af 100644 --- a/policy/compiler/compiler.go +++ b/policy/compiler/compiler.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package compiler contains a suite of tools for convering parsed representations of CEL Policy +// Package compiler contains a suite of tools for covering parsed representations of CEL Policy // Template sources into type-checked and validated in-memory representations. package compiler @@ -42,20 +42,20 @@ import ( ) // NewCompiler creates a new Compiler instance with the given Registry and CEL evaluation options. -func NewCompiler(reg *model.Registry, l *limits.Limits, evalOpts ...cel.ProgramOption) *Compiler { +func NewCompiler(reg *model.Registry, l *limits.Limits, rtOpts ...runtime.TemplateOption) *Compiler { return &Compiler{ - evalOpts: evalOpts, - reg: reg, - limits: l, + rtOpts: rtOpts, + reg: reg, + limits: l, } } // Compiler type-checks and compiles a raw model.ParsedValue into a strongly typed in-memory // representation of a template or policy instance. type Compiler struct { - evalOpts []cel.ProgramOption - reg *model.Registry - limits *limits.Limits + rtOpts []runtime.TemplateOption + reg *model.Registry + limits *limits.Limits } // CompileEnv type-checks and builds model.Env instance from a parsed representation. @@ -149,7 +149,7 @@ func (c *Compiler) newInstanceCompiler(src *model.Source, parsedInst *model.Pars dyn: dyn, rt: tmpl.RuleTypes, tmpl: tmpl, - evalOpts: c.evalOpts, + rtOpts: c.rtOpts, }, nil } @@ -305,10 +305,10 @@ func (ec *envCompiler) collectTypes(env *model.Env, typ *model.DeclType) { type instanceCompiler struct { *dynCompiler - dyn *model.DynValue - rt *model.RuleTypes - tmpl *model.Template - evalOpts []cel.ProgramOption + dyn *model.DynValue + rt *model.RuleTypes + tmpl *model.Template + rtOpts []runtime.TemplateOption } func (ic *instanceCompiler) compile() (*model.Instance, *cel.Issues) { @@ -349,11 +349,11 @@ func (ic *instanceCompiler) compile() (*model.Instance, *cel.Issues) { "rule limit set to %d, but %d found", ic.limits.RuleLimit, len(cinst.Rules)) } + rtOpts := append([]runtime.TemplateOption{runtime.Limits(ic.limits)}, ic.rtOpts...) exec, err := runtime.NewTemplate( ic.reg, ic.tmpl, - runtime.Limits(ic.limits), - runtime.ExprOptions(ic.evalOpts...)) + rtOpts...) if err != nil { // report the error ic.reportError(err.Error()) diff --git a/policy/compiler/compiler_test.go b/policy/compiler/compiler_test.go index 47ab0df..cf2a02b 100644 --- a/policy/compiler/compiler_test.go +++ b/policy/compiler/compiler_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/cel-policy-templates-go/policy/limits" "github.com/google/cel-policy-templates-go/policy/model" "github.com/google/cel-policy-templates-go/policy/parser" + "github.com/google/cel-policy-templates-go/policy/runtime" "github.com/google/cel-policy-templates-go/test" "github.com/google/cel-go/cel" @@ -47,7 +48,8 @@ func TestCompiler(t *testing.T) { limits.ValidatorProductionLimit = 15 limits.RuleLimit = 4 limits.EvaluatorExprCostLimit = 100 - comp := NewCompiler(reg, limits) + rtOpts := []runtime.TemplateOption{runtime.Functions(test.Funcs...)} + comp := NewCompiler(reg, limits, rtOpts...) for _, tc := range tests { tst := tc t.Run(tst.ID, func(tt *testing.T) { diff --git a/policy/engine.go b/policy/engine.go index c64b5a2..ce98555 100644 --- a/policy/engine.go +++ b/policy/engine.go @@ -33,7 +33,6 @@ import ( type Engine struct { *model.Registry rwMux sync.RWMutex - evalOpts []cel.ProgramOption rtOpts []runtime.TemplateOption selectors []Selector limits *limits.Limits @@ -51,7 +50,6 @@ func NewEngine(opts ...EngineOption) (*Engine, error) { // TODO: Make the base environment more easily configurable. e := &Engine{ Registry: model.NewRegistry(stdEnv), - evalOpts: []cel.ProgramOption{}, rtOpts: []runtime.TemplateOption{}, selectors: []Selector{}, limits: limits.NewLimits(), @@ -115,7 +113,6 @@ func (e *Engine) SetTemplate(name string, tmpl *model.Template) error { } rtOpts := []runtime.TemplateOption{ runtime.Limits(e.limits), - runtime.ExprOptions(e.evalOpts...), } rtOpts = append(rtOpts, e.rtOpts...) rtTmpl, err := runtime.NewTemplate(e.Registry, tmpl, rtOpts...) @@ -132,7 +129,7 @@ func (e *Engine) CompileEnv(src *model.Source) (*model.Env, *Issues) { if iss.Err() != nil { return nil, iss } - c := compiler.NewCompiler(e.Registry, e.limits, e.evalOpts...) + c := compiler.NewCompiler(e.Registry, e.limits, e.rtOpts...) return c.CompileEnv(src, ast) } @@ -144,7 +141,7 @@ func (e *Engine) CompileInstance(src *model.Source) (*model.Instance, *Issues) { if iss.Err() != nil { return nil, iss } - c := compiler.NewCompiler(e.Registry, e.limits, e.evalOpts...) + c := compiler.NewCompiler(e.Registry, e.limits, e.rtOpts...) return c.CompileInstance(src, ast) } @@ -154,7 +151,7 @@ func (e *Engine) CompileTemplate(src *model.Source) (*model.Template, *Issues) { if iss.Err() != nil { return nil, iss } - c := compiler.NewCompiler(e.Registry, e.limits, e.evalOpts...) + c := compiler.NewCompiler(e.Registry, e.limits, e.rtOpts...) return c.CompileTemplate(src, ast) } diff --git a/policy/engine_test.go b/policy/engine_test.go index 57e58e2..92e3e83 100644 --- a/policy/engine_test.go +++ b/policy/engine_test.go @@ -64,6 +64,21 @@ var ( outputs []interface{} } }{ + { + name: "validator_with_custom_function_test", + policy: "validator_with_custom_function", + input: map[string]interface{}{ + "port": "22", + }, + opts: []EngineOption{ + ValidatorProductionLimit(8), + ValidatorTermLimit(15), + EvaluatorTermLimit(15), + }, + outputs: []interface{}{ + "22-60", + }, + }, // Binauthz { name: "binauthz_package_violations", diff --git a/test/env.go b/test/env.go index bf4f230..2b4f7cd 100644 --- a/test/env.go +++ b/test/env.go @@ -16,6 +16,9 @@ package test import ( + "strconv" + "strings" + "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" @@ -41,6 +44,18 @@ var ( decls.String, ), ), + decls.NewFunction("rangeLow", + decls.NewOverload("range_low", + []*exprpb.Type{decls.String}, + decls.Int, + ), + ), + decls.NewFunction("rangeHigh", + decls.NewOverload("range_high", + []*exprpb.Type{decls.String}, + decls.Int, + ), + ), ) // Funcs are the custom function implementations used within templates. @@ -59,5 +74,49 @@ var ( } }, }, + { + Operator: "range_low", + Unary: func(arg ref.Val) ref.Val { + r := arg.(types.String).Value().(string) + r = strings.TrimSpace(r) + if r == "" { + return types.Int(0) + } + if strings.Contains(r, "-") { + rs := strings.Split(r, "-") + if len(rs) != 2 { + return types.ValOrErr(arg, "invalid port") + } + r = rs[0] + } + v, err := strconv.Atoi(r) + if err != nil { + return types.ValOrErr(arg, "invalid port") + } + return types.Int(v) + }, + }, + { + Operator: "range_high", + Unary: func(arg ref.Val) ref.Val { + r := arg.(types.String).Value().(string) + r = strings.TrimSpace(r) + if r == "" { + return types.Int(65535) + } + if strings.Contains(r, "-") { + rs := strings.Split(r, "-") + if len(rs) != 2 { + return types.ValOrErr(arg, "invalid port") + } + r = rs[1] + } + v, err := strconv.Atoi(r) + if err != nil { + return types.ValOrErr(arg, "invalid port") + } + return types.Int(v) + }, + }, } ) diff --git a/test/testdata/validator_with_custom_function/instance.compile.out b/test/testdata/validator_with_custom_function/instance.compile.out new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/validator_with_custom_function/instance.invalid_port.compile.err b/test/testdata/validator_with_custom_function/instance.invalid_port.compile.err new file mode 100644 index 0000000..da6cfd8 --- /dev/null +++ b/test/testdata/validator_with_custom_function/instance.invalid_port.compile.err @@ -0,0 +1 @@ +ERROR: ../../test/testdata/validator_with_custom_function/instance.invalid_port.yaml:-1:0: invalid port diff --git a/test/testdata/validator_with_custom_function/instance.invalid_port.yaml b/test/testdata/validator_with_custom_function/instance.invalid_port.yaml new file mode 100644 index 0000000..ebc9f7e --- /dev/null +++ b/test/testdata/validator_with_custom_function/instance.invalid_port.yaml @@ -0,0 +1,21 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: policy.acme.co/v1 +kind: validator_with_custom_function +metadata: + name: instance_with_port + namespace: acme +rule: + port: "-1" diff --git a/test/testdata/validator_with_custom_function/instance.yaml b/test/testdata/validator_with_custom_function/instance.yaml new file mode 100644 index 0000000..64d8b37 --- /dev/null +++ b/test/testdata/validator_with_custom_function/instance.yaml @@ -0,0 +1,21 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: policy.acme.co/v1 +kind: validator_with_custom_function +metadata: + name: instance_with_port + namespace: acme +rule: + port: "22-60" diff --git a/test/testdata/validator_with_custom_function/template.compile.out b/test/testdata/validator_with_custom_function/template.compile.out new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/validator_with_custom_function/template.invalid_function.compile.err b/test/testdata/validator_with_custom_function/template.invalid_function.compile.err new file mode 100644 index 0000000..55c770f --- /dev/null +++ b/test/testdata/validator_with_custom_function/template.invalid_function.compile.err @@ -0,0 +1,6 @@ +ERROR: ../../test/testdata/validator_with_custom_function/template.invalid_function.yaml:30:23: undeclared reference to 'invalidFunc' (in container '') + | - match: invalidFunc(rule.port) < 0 + | ......................^ +ERROR: ../../test/testdata/validator_with_custom_function/template.invalid_function.yaml:31:15: undeclared reference to 'invalidFunc' (in container '') + | message: "invalidFunc" + | ..............^ \ No newline at end of file diff --git a/test/testdata/validator_with_custom_function/template.invalid_function.yaml b/test/testdata/validator_with_custom_function/template.invalid_function.yaml new file mode 100644 index 0000000..1983fff --- /dev/null +++ b/test/testdata/validator_with_custom_function/template.invalid_function.yaml @@ -0,0 +1,37 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: policy.acme.co/v1 +kind: PolicyTemplate +metadata: + name: validator_with_custom_function +schema: + type: object + description: Policy with validator referencing to custom function. + properties: + port: + description: > + port contains a string which could be a range ex : "22-44" or "22". + It has to be a positive number and less than 65535 + type: string +validator: + productions: + - match: invalidFunc(rule.port) < 0 + message: "invalidFunc" + +evaluator: + productions: + - match: rule.port != '' + decision: policy.report + output: rule.port diff --git a/test/testdata/validator_with_custom_function/template.yaml b/test/testdata/validator_with_custom_function/template.yaml new file mode 100644 index 0000000..45fa63c --- /dev/null +++ b/test/testdata/validator_with_custom_function/template.yaml @@ -0,0 +1,41 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: policy.acme.co/v1 +kind: PolicyTemplate +metadata: + name: validator_with_custom_function +schema: + type: object + description: Policy with validator referencing to custom function. + properties: + port: + description: > + port contains a string which could be a range ex : "22-44" or "22". + It has to be a positive number and less than 65535 + type: string +validator: + productions: + - match: > + has(rule.port) && ( + rangeLow(rule.port) < 0 + || rangeHigh(rule.port) > 65535 + || rangeLow(rule.port) > rangeHigh(rule.port)) + message: "invalid port. allowed port range is [0, 65535]" + +evaluator: + productions: + - match: rule.port != '' + decision: policy.report + output: rule.port