mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-20 23:02:36 +00:00
feat: add globalcontext CEL lib (#12425)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
be974e7d93
commit
e785ee4882
23 changed files with 309 additions and 212 deletions
|
@ -7,7 +7,7 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/kyverno/kyverno/api/kyverno"
|
||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
resourcelib "github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/cel/matching"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
eval "github.com/kyverno/kyverno/pkg/imageverification/evaluator"
|
||||
|
@ -169,7 +169,7 @@ func (e *ivengine) matchPolicy(policy CompiledImageVerificationPolicy, attr admi
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (e *ivengine) handleMutation(ctx context.Context, policies []CompiledImageVerificationPolicy, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context contextlib.ContextInterface) ([]eval.ImageVerifyPolicyResponse, []jsonpatch.JsonPatchOperation, error) {
|
||||
func (e *ivengine) handleMutation(ctx context.Context, policies []CompiledImageVerificationPolicy, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context resourcelib.ContextInterface) ([]eval.ImageVerifyPolicyResponse, []jsonpatch.JsonPatchOperation, error) {
|
||||
results := make(map[string]eval.ImageVerifyPolicyResponse, len(policies))
|
||||
filteredPolicies := make([]CompiledImageVerificationPolicy, 0)
|
||||
if e.matcher != nil {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
resourcelib "github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/cel/matching"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
eval "github.com/kyverno/kyverno/pkg/imageverification/evaluator"
|
||||
|
@ -158,7 +158,7 @@ func Test_ImageVerifyEngine(t *testing.T) {
|
|||
},
|
||||
RequestResource: &metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
|
||||
},
|
||||
context: &contextlib.MockCtx{},
|
||||
context: &resourcelib.MockCtx{},
|
||||
}
|
||||
|
||||
resp, patches, err := engine.HandleMutating(context.Background(), engineRequest)
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
|
||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
"github.com/kyverno/kyverno/pkg/cel/matching"
|
||||
"github.com/kyverno/kyverno/pkg/cel/policy"
|
||||
celpolicy "github.com/kyverno/kyverno/pkg/cel/policy"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/engine/handlers"
|
||||
|
@ -29,17 +29,17 @@ import (
|
|||
type EngineRequest struct {
|
||||
jsonPayload *unstructured.Unstructured
|
||||
request admissionv1.AdmissionRequest
|
||||
context contextlib.ContextInterface
|
||||
context policy.ContextInterface
|
||||
}
|
||||
|
||||
func RequestFromAdmission(context contextlib.ContextInterface, request admissionv1.AdmissionRequest) EngineRequest {
|
||||
func RequestFromAdmission(context policy.ContextInterface, request admissionv1.AdmissionRequest) EngineRequest {
|
||||
return EngineRequest{
|
||||
request: request,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
func RequestFromJSON(context contextlib.ContextInterface, jsonPayload *unstructured.Unstructured) EngineRequest {
|
||||
func RequestFromJSON(context policy.ContextInterface, jsonPayload *unstructured.Unstructured) EngineRequest {
|
||||
return EngineRequest{
|
||||
jsonPayload: jsonPayload,
|
||||
context: context,
|
||||
|
@ -47,7 +47,7 @@ func RequestFromJSON(context contextlib.ContextInterface, jsonPayload *unstructu
|
|||
}
|
||||
|
||||
func Request(
|
||||
context contextlib.ContextInterface,
|
||||
context policy.ContextInterface,
|
||||
gvk schema.GroupVersionKind,
|
||||
gvr schema.GroupVersionResource,
|
||||
subResource string,
|
||||
|
@ -206,7 +206,7 @@ func (e *engine) matchPolicy(policy CompiledValidatingPolicy, attr admission.Att
|
|||
return false, -1, nil
|
||||
}
|
||||
|
||||
func (e *engine) handlePolicy(ctx context.Context, policy CompiledValidatingPolicy, jsonPayload interface{}, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context contextlib.ContextInterface) ValidatingPolicyResponse {
|
||||
func (e *engine) handlePolicy(ctx context.Context, policy CompiledValidatingPolicy, jsonPayload interface{}, attr admission.Attributes, request *admissionv1.AdmissionRequest, namespace runtime.Object, context policy.ContextInterface) ValidatingPolicyResponse {
|
||||
response := ValidatingPolicyResponse{
|
||||
Actions: policy.Actions,
|
||||
Policy: policy.Policy,
|
||||
|
|
31
pkg/cel/libs/globalcontext/impl.go
Normal file
31
pkg/cel/libs/globalcontext/impl.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/kyverno/kyverno/pkg/cel/utils"
|
||||
)
|
||||
|
||||
type impl struct {
|
||||
types.Adapter
|
||||
}
|
||||
|
||||
func (c *impl) get_string_string(args ...ref.Val) ref.Val {
|
||||
if len(args) != 3 {
|
||||
return types.NewErr("expected 3 arguments, got %d", len(args))
|
||||
}
|
||||
if self, err := utils.ConvertToNative[Context](args[0]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else if name, err := utils.ConvertToNative[string](args[1]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else if projection, err := utils.ConvertToNative[string](args[2]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else {
|
||||
globalRef, err := self.GetGlobalReference(name, projection)
|
||||
if err != nil {
|
||||
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
|
||||
return types.NewErr("failed to get global reference: %v", err)
|
||||
}
|
||||
return c.NativeToValue(globalRef)
|
||||
}
|
||||
}
|
102
pkg/cel/libs/globalcontext/impl_test.go
Normal file
102
pkg/cel/libs/globalcontext/impl_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/globalcontext/store"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func Test_impl_get_globalreference_string_string(t *testing.T) {
|
||||
opts := Lib()
|
||||
base, err := cel.NewEnv(opts)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("globalcontext", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`globalcontext.Get("foo", "bar")`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, prog)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gctxStoreData map[string]store.Entry
|
||||
expectedValue any
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "global context entry not found",
|
||||
gctxStoreData: map[string]store.Entry{},
|
||||
expectedError: "global context entry not found",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns error",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &resource.MockEntry{Err: errors.New("get entry error")},
|
||||
},
|
||||
expectedError: "get entry error",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns string",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &resource.MockEntry{Data: "stringValue"},
|
||||
},
|
||||
expectedValue: "stringValue",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns map",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &resource.MockEntry{Data: map[string]interface{}{"key": "value"}},
|
||||
},
|
||||
expectedValue: map[string]interface{}{"key": "value"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockStore := &resource.MockGctxStore{Data: tt.gctxStoreData}
|
||||
data := map[string]any{
|
||||
"globalcontext": Context{&resource.MockCtx{
|
||||
GetGlobalReferenceFunc: func(name string, path string) (any, error) {
|
||||
ent, ok := mockStore.Get(name)
|
||||
if !ok {
|
||||
return nil, errors.New("global context entry not found")
|
||||
}
|
||||
return ent.Get(path)
|
||||
},
|
||||
}},
|
||||
}
|
||||
out, _, err := prog.Eval(data)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tt.expectedValue == nil {
|
||||
assert.Nil(t, out.Value())
|
||||
} else {
|
||||
assert.NotNil(t, out)
|
||||
if expectedUnstructured, ok := tt.expectedValue.(unstructured.Unstructured); ok {
|
||||
actualUnstructured, ok := out.Value().(unstructured.Unstructured)
|
||||
assert.True(t, ok, "Expected unstructured.Unstructured, got %T", out.Value())
|
||||
assert.Equal(t, expectedUnstructured, actualUnstructured)
|
||||
} else {
|
||||
assert.Equal(t, tt.expectedValue, out.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
58
pkg/cel/libs/globalcontext/lib.go
Normal file
58
pkg/cel/libs/globalcontext/lib.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/ext"
|
||||
)
|
||||
|
||||
const libraryName = "kyverno.globalcontext"
|
||||
|
||||
type lib struct{}
|
||||
|
||||
func Lib() cel.EnvOption {
|
||||
// create the cel lib env option
|
||||
return cel.Lib(&lib{})
|
||||
}
|
||||
|
||||
func (*lib) LibraryName() string {
|
||||
return libraryName
|
||||
}
|
||||
|
||||
func (c *lib) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
ext.NativeTypes(reflect.TypeFor[Context]()),
|
||||
c.extendEnv,
|
||||
}
|
||||
}
|
||||
|
||||
func (*lib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func (c *lib) extendEnv(env *cel.Env) (*cel.Env, error) {
|
||||
// create implementation, recording the envoy types aware adapter
|
||||
impl := impl{
|
||||
Adapter: env.CELTypeAdapter(),
|
||||
}
|
||||
// build our function overloads
|
||||
libraryDecls := map[string][]cel.FunctionOpt{
|
||||
"Get": {
|
||||
cel.MemberOverload(
|
||||
"globalcontext_get_string_string",
|
||||
[]*cel.Type{ContextType, types.StringType, types.StringType},
|
||||
types.DynType,
|
||||
cel.FunctionBinding(impl.get_string_string),
|
||||
),
|
||||
},
|
||||
}
|
||||
// create env options corresponding to our function overloads
|
||||
options := []cel.EnvOption{}
|
||||
for name, overloads := range libraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
// extend environment with our function overloads
|
||||
return env.Extend(options...)
|
||||
}
|
15
pkg/cel/libs/globalcontext/types.go
Normal file
15
pkg/cel/libs/globalcontext/types.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package globalcontext
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
)
|
||||
|
||||
var ContextType = types.NewOpaqueType("globalcontext.Context")
|
||||
|
||||
type ContextInterface interface {
|
||||
GetGlobalReference(string, string) (any, error)
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
ContextInterface
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
@ -27,26 +27,6 @@ func (c *impl) get_configmap_string_string(args ...ref.Val) ref.Val {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *impl) get_globalreference_string_string(args ...ref.Val) ref.Val {
|
||||
if len(args) != 3 {
|
||||
return types.NewErr("expected 3 arguments, got %d", len(args))
|
||||
}
|
||||
if self, err := utils.ConvertToNative[Context](args[0]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else if name, err := utils.ConvertToNative[string](args[1]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else if projection, err := utils.ConvertToNative[string](args[2]); err != nil {
|
||||
return types.WrapErr(err)
|
||||
} else {
|
||||
globalRef, err := self.GetGlobalReference(name, projection)
|
||||
if err != nil {
|
||||
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
|
||||
return types.NewErr("failed to get global reference: %v", err)
|
||||
}
|
||||
return c.NativeToValue(globalRef)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *impl) get_imagedata_string(ctx ref.Val, image ref.Val) ref.Val {
|
||||
if self, err := utils.ConvertToNative[Context](ctx); err != nil {
|
||||
return types.WrapErr(err)
|
|
@ -1,14 +1,12 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/kyverno/kyverno/pkg/globalcontext/store"
|
||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -20,12 +18,12 @@ func Test_impl_get_configmap_string_string(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("context", ContextType),
|
||||
cel.Variable("resource", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`context.GetConfigMap("foo","bar")`)
|
||||
ast, issues := env.Compile(`resource.GetConfigMap("foo","bar")`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
|
@ -33,7 +31,7 @@ func Test_impl_get_configmap_string_string(t *testing.T) {
|
|||
assert.NotNil(t, prog)
|
||||
called := false
|
||||
data := map[string]any{
|
||||
"context": Context{&MockCtx{
|
||||
"resource": Context{&MockCtx{
|
||||
GetConfigMapFunc: func(string, string) (*unstructured.Unstructured, error) {
|
||||
called = true
|
||||
return &unstructured.Unstructured{}, nil
|
||||
|
@ -46,115 +44,25 @@ func Test_impl_get_configmap_string_string(t *testing.T) {
|
|||
assert.True(t, called)
|
||||
}
|
||||
|
||||
func Test_impl_get_globalreference_string_string(t *testing.T) {
|
||||
opts := Lib()
|
||||
base, err := cel.NewEnv(opts)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("context", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`context.GetGlobalReference("foo", "bar")`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, prog)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gctxStoreData map[string]store.Entry
|
||||
expectedValue any
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "global context entry not found",
|
||||
gctxStoreData: map[string]store.Entry{},
|
||||
expectedError: "global context entry not found",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns error",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &mockEntry{err: errors.New("get entry error")},
|
||||
},
|
||||
expectedError: "get entry error",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns string",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &mockEntry{data: "stringValue"},
|
||||
},
|
||||
expectedValue: "stringValue",
|
||||
},
|
||||
{
|
||||
name: "global context entry returns map",
|
||||
gctxStoreData: map[string]store.Entry{
|
||||
"foo": &mockEntry{data: map[string]interface{}{"key": "value"}},
|
||||
},
|
||||
expectedValue: map[string]interface{}{"key": "value"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockStore := &mockGctxStore{data: tt.gctxStoreData}
|
||||
data := map[string]any{
|
||||
"context": Context{&MockCtx{
|
||||
GetGlobalReferenceFunc: func(name string, path string) (any, error) {
|
||||
ent, ok := mockStore.Get(name)
|
||||
if !ok {
|
||||
return nil, errors.New("global context entry not found")
|
||||
}
|
||||
return ent.Get(path)
|
||||
},
|
||||
}},
|
||||
}
|
||||
out, _, err := prog.Eval(data)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tt.expectedValue == nil {
|
||||
assert.Nil(t, out.Value())
|
||||
} else {
|
||||
assert.NotNil(t, out)
|
||||
if expectedUnstructured, ok := tt.expectedValue.(unstructured.Unstructured); ok {
|
||||
actualUnstructured, ok := out.Value().(unstructured.Unstructured)
|
||||
assert.True(t, ok, "Expected unstructured.Unstructured, got %T", out.Value())
|
||||
assert.Equal(t, expectedUnstructured, actualUnstructured)
|
||||
} else {
|
||||
assert.Equal(t, tt.expectedValue, out.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_impl_get_imagedata_string(t *testing.T) {
|
||||
opts := Lib()
|
||||
base, err := cel.NewEnv(opts)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("context", ContextType),
|
||||
cel.Variable("resource", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`context.GetImageData("ghcr.io/kyverno/kyverno:latest").resolvedImage`)
|
||||
ast, issues := env.Compile(`resource.GetImageData("ghcr.io/kyverno/kyverno:latest").resolvedImage`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, prog)
|
||||
data := map[string]any{
|
||||
"context": Context{&MockCtx{
|
||||
"resource": Context{&MockCtx{
|
||||
GetImageDataFunc: func(image string) (map[string]interface{}, error) {
|
||||
idl, err := imagedataloader.New(nil)
|
||||
assert.NoError(t, err)
|
||||
|
@ -187,19 +95,19 @@ func Test_impl_get_resource_string_string_string_string(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("context", ContextType),
|
||||
cel.Variable("resource", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`context.GetResource("apps/v1", "deployments", "default", "nginx")`)
|
||||
ast, issues := env.Compile(`resource.Get("apps/v1", "deployments", "default", "nginx")`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, prog)
|
||||
data := map[string]any{
|
||||
"context": Context{&MockCtx{
|
||||
"resource": Context{&MockCtx{
|
||||
GetResourceFunc: func(apiVersion, resource, namespace, name string) (*unstructured.Unstructured, error) {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]any{
|
||||
|
@ -228,19 +136,19 @@ func Test_impl_list_resources_string_string_string(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, base)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable("context", ContextType),
|
||||
cel.Variable("resource", ContextType),
|
||||
}
|
||||
env, err := base.Extend(options...)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, env)
|
||||
ast, issues := env.Compile(`context.ListResources("apps/v1", "deployments", "default")`)
|
||||
ast, issues := env.Compile(`resource.List("apps/v1", "deployments", "default")`)
|
||||
assert.Nil(t, issues)
|
||||
assert.NotNil(t, ast)
|
||||
prog, err := env.Program(ast)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, prog)
|
||||
data := map[string]any{
|
||||
"context": Context{&MockCtx{
|
||||
"resource": Context{&MockCtx{
|
||||
ListResourcesFunc: func(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
|
||||
return &unstructured.UnstructuredList{
|
||||
Items: []unstructured.Unstructured{
|
|
@ -1,4 +1,4 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
@ -9,7 +9,7 @@ import (
|
|||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
const libraryName = "kyverno.context"
|
||||
const libraryName = "kyverno.resource"
|
||||
|
||||
type lib struct{}
|
||||
|
||||
|
@ -49,46 +49,38 @@ func (c *lib) extendEnv(env *cel.Env) (*cel.Env, error) {
|
|||
libraryDecls := map[string][]cel.FunctionOpt{
|
||||
"GetConfigMap": {
|
||||
cel.MemberOverload(
|
||||
"get_configmap_string_string",
|
||||
"resource_getconfigmap_string_string",
|
||||
[]*cel.Type{ContextType, types.StringType, types.StringType},
|
||||
configMapType.CelType(),
|
||||
cel.FunctionBinding(impl.get_configmap_string_string),
|
||||
),
|
||||
},
|
||||
"GetGlobalReference": {
|
||||
cel.MemberOverload(
|
||||
"get_globalreference_string_string",
|
||||
[]*cel.Type{ContextType, types.StringType, types.StringType},
|
||||
types.DynType,
|
||||
cel.FunctionBinding(impl.get_globalreference_string_string),
|
||||
),
|
||||
},
|
||||
"GetImageData": {
|
||||
cel.MemberOverload(
|
||||
"get_imagedata_string",
|
||||
[]*cel.Type{ContextType, types.StringType},
|
||||
types.DynType,
|
||||
cel.BinaryBinding(impl.get_imagedata_string),
|
||||
),
|
||||
},
|
||||
"ListResources": {
|
||||
"List": {
|
||||
// TODO: should not use DynType in return
|
||||
cel.MemberOverload(
|
||||
"list_resources_string_string_string",
|
||||
"resource_list_string_string_string",
|
||||
[]*cel.Type{ContextType, types.StringType, types.StringType, types.StringType},
|
||||
types.DynType,
|
||||
cel.FunctionBinding(impl.list_resources_string_string_string),
|
||||
),
|
||||
},
|
||||
"GetResource": {
|
||||
"Get": {
|
||||
// TODO: should not use DynType in return
|
||||
cel.MemberOverload(
|
||||
"get_resource_string_string_string_string",
|
||||
"resource_get_string_string_string_string",
|
||||
[]*cel.Type{ContextType, types.StringType, types.StringType, types.StringType, types.StringType},
|
||||
types.DynType,
|
||||
cel.FunctionBinding(impl.get_resource_string_string_string_string),
|
||||
),
|
||||
},
|
||||
"GetImageData": {
|
||||
cel.MemberOverload(
|
||||
"resource_getimagedata_string",
|
||||
[]*cel.Type{ContextType, types.StringType},
|
||||
types.DynType,
|
||||
cel.BinaryBinding(impl.get_imagedata_string),
|
||||
),
|
||||
},
|
||||
}
|
||||
// create env options corresponding to our function overloads
|
||||
options := []cel.EnvOption{}
|
|
@ -1,4 +1,4 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -1,4 +1,4 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/kyverno/kyverno/pkg/globalcontext/store"
|
||||
|
@ -34,29 +34,29 @@ func (mock *MockCtx) GetResource(apiVersion, resource, namespace, name string) (
|
|||
return mock.GetResourceFunc(apiVersion, resource, namespace, name)
|
||||
}
|
||||
|
||||
type mockGctxStore struct {
|
||||
data map[string]store.Entry
|
||||
type MockGctxStore struct {
|
||||
Data map[string]store.Entry
|
||||
}
|
||||
|
||||
func (m *mockGctxStore) Get(name string) (store.Entry, bool) {
|
||||
entry, ok := m.data[name]
|
||||
func (m *MockGctxStore) Get(name string) (store.Entry, bool) {
|
||||
entry, ok := m.Data[name]
|
||||
return entry, ok
|
||||
}
|
||||
|
||||
func (m *mockGctxStore) Set(name string, data store.Entry) {
|
||||
if m.data == nil {
|
||||
m.data = make(map[string]store.Entry)
|
||||
func (m *MockGctxStore) Set(name string, data store.Entry) {
|
||||
if m.Data == nil {
|
||||
m.Data = make(map[string]store.Entry)
|
||||
}
|
||||
m.data[name] = data
|
||||
m.Data[name] = data
|
||||
}
|
||||
|
||||
type mockEntry struct {
|
||||
data any
|
||||
err error
|
||||
type MockEntry struct {
|
||||
Data any
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m *mockEntry) Get(_ string) (any, error) {
|
||||
return m.data, m.err
|
||||
func (m *MockEntry) Get(_ string) (any, error) {
|
||||
return m.Data, m.Err
|
||||
}
|
||||
|
||||
func (m *mockEntry) Stop() {}
|
||||
func (m *MockEntry) Stop() {}
|
|
@ -1,4 +1,4 @@
|
|||
package context
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
@ -7,14 +7,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ContextType = types.NewOpaqueType("context.Context")
|
||||
ContextType = types.NewOpaqueType("resource.Context")
|
||||
configMapType = BuildConfigMapType()
|
||||
imageDataType = BuildImageDataType()
|
||||
)
|
||||
|
||||
type ContextInterface interface {
|
||||
GetConfigMap(string, string) (*unstructured.Unstructured, error)
|
||||
GetGlobalReference(string, string) (any, error)
|
||||
GetImageData(string) (map[string]interface{}, error)
|
||||
ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error)
|
||||
GetResource(apiVersion, resource, namespace, name string) (*unstructured.Unstructured, error)
|
|
@ -8,8 +8,9 @@ import (
|
|||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
engine "github.com/kyverno/kyverno/pkg/cel"
|
||||
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/globalcontext"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/http"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/user"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
@ -17,12 +18,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
ContextKey = "context"
|
||||
GlobalContextKey = "globalcontext"
|
||||
HttpKey = "http"
|
||||
NamespaceObjectKey = "namespaceObject"
|
||||
ObjectKey = "object"
|
||||
OldObjectKey = "oldObject"
|
||||
RequestKey = "request"
|
||||
ResourceKey = "resource"
|
||||
VariablesKey = "variables"
|
||||
)
|
||||
|
||||
|
@ -54,7 +56,7 @@ func (c *compiler) compileForJSON(policy *policiesv1alpha1.ValidatingPolicy, exc
|
|||
}
|
||||
|
||||
options = append(options, declOptions...)
|
||||
options = append(options, context.Lib(), http.Lib())
|
||||
options = append(options, resource.Lib(), http.Lib())
|
||||
env, err := base.Extend(options...)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.InternalError(nil, err))
|
||||
|
@ -108,14 +110,15 @@ func (c *compiler) compileForKubernetes(policy *policiesv1alpha1.ValidatingPolic
|
|||
}
|
||||
var declTypes []*apiservercel.DeclType
|
||||
declTypes = append(declTypes, NamespaceType, RequestType)
|
||||
declTypes = append(declTypes, context.Types()...)
|
||||
declTypes = append(declTypes, resource.Types()...)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable(ContextKey, context.ContextType),
|
||||
cel.Variable(GlobalContextKey, globalcontext.ContextType),
|
||||
cel.Variable(HttpKey, http.HTTPType),
|
||||
cel.Variable(NamespaceObjectKey, NamespaceType.CelType()),
|
||||
cel.Variable(ObjectKey, cel.DynType),
|
||||
cel.Variable(OldObjectKey, cel.DynType),
|
||||
cel.Variable(RequestKey, RequestType.CelType()),
|
||||
cel.Variable(ResourceKey, resource.ContextType),
|
||||
cel.Variable(VariablesKey, VariablesType),
|
||||
}
|
||||
for _, declType := range declTypes {
|
||||
|
@ -129,7 +132,7 @@ func (c *compiler) compileForKubernetes(policy *policiesv1alpha1.ValidatingPolic
|
|||
panic(err)
|
||||
}
|
||||
options = append(options, declOptions...)
|
||||
options = append(options, context.Lib(), http.Lib(), user.Lib())
|
||||
options = append(options, resource.Lib(), http.Lib(), user.Lib())
|
||||
// TODO: params, authorizer, authorizer.requestResource ?
|
||||
env, err := base.Extend(options...)
|
||||
if err != nil {
|
||||
|
|
|
@ -81,7 +81,7 @@ func Test_compiler_Compile(t *testing.T) {
|
|||
},
|
||||
Variables: []admissionregistrationv1.Variable{{
|
||||
Name: "cm",
|
||||
Expression: "context.GetConfigMap('foo', 'bar')",
|
||||
Expression: "resource.GetConfigMap('foo', 'bar')",
|
||||
}},
|
||||
Validations: []admissionregistrationv1.Validation{{
|
||||
Expression: "variables.cm != null",
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
||||
"github.com/kyverno/kyverno/pkg/config"
|
||||
gctxstore "github.com/kyverno/kyverno/pkg/globalcontext/store"
|
||||
|
@ -19,7 +18,7 @@ import (
|
|||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type Context = contextlib.ContextInterface
|
||||
type Context = ContextInterface
|
||||
|
||||
type contextProvider struct {
|
||||
client kubernetes.Interface
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
contextlib "github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
globalcontextlib "github.com/kyverno/kyverno/pkg/cel/libs/globalcontext"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/http"
|
||||
resourcelib "github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/cel/utils"
|
||||
"go.uber.org/multierr"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
|
@ -31,8 +32,13 @@ type EvaluationResult struct {
|
|||
PatchedResource unstructured.Unstructured
|
||||
}
|
||||
|
||||
type ContextInterface interface {
|
||||
resourcelib.ContextInterface
|
||||
globalcontextlib.ContextInterface
|
||||
}
|
||||
|
||||
type CompiledPolicy interface {
|
||||
Evaluate(context.Context, interface{}, admission.Attributes, *admissionv1.AdmissionRequest, runtime.Object, contextlib.ContextInterface, int) (*EvaluationResult, error)
|
||||
Evaluate(context.Context, interface{}, admission.Attributes, *admissionv1.AdmissionRequest, runtime.Object, ContextInterface, int) (*EvaluationResult, error)
|
||||
}
|
||||
|
||||
type CompiledValidation struct {
|
||||
|
@ -69,7 +75,7 @@ type evaluationData struct {
|
|||
Object interface{}
|
||||
OldObject interface{}
|
||||
Request interface{}
|
||||
Context contextlib.ContextInterface
|
||||
Context ContextInterface
|
||||
Variables *lazy.MapValue
|
||||
}
|
||||
|
||||
|
@ -79,7 +85,7 @@ func (p *compiledPolicy) Evaluate(
|
|||
attr admission.Attributes,
|
||||
request *admissionv1.AdmissionRequest,
|
||||
namespace runtime.Object,
|
||||
context contextlib.ContextInterface,
|
||||
context ContextInterface,
|
||||
autogenIndex int,
|
||||
) (*EvaluationResult, error) {
|
||||
switch p.mode {
|
||||
|
@ -106,7 +112,7 @@ func (p *compiledPolicy) evaluateKubernetes(
|
|||
attr admission.Attributes,
|
||||
request *admissionv1.AdmissionRequest,
|
||||
namespace runtime.Object,
|
||||
context contextlib.ContextInterface,
|
||||
context ContextInterface,
|
||||
autogenIndex int,
|
||||
) (*EvaluationResult, error) {
|
||||
data, err := p.prepareK8sData(attr, request, namespace, context)
|
||||
|
@ -162,7 +168,8 @@ func (p *compiledPolicy) evaluateWithData(
|
|||
|
||||
vars := lazy.NewMapValue(VariablesType)
|
||||
dataNew := map[string]any{
|
||||
ContextKey: contextlib.Context{ContextInterface: data.Context},
|
||||
ResourceKey: resourcelib.Context{ContextInterface: data.Context},
|
||||
GlobalContextKey: globalcontextlib.Context{ContextInterface: data.Context},
|
||||
HttpKey: http.NewHTTP(),
|
||||
NamespaceObjectKey: data.Namespace,
|
||||
ObjectKey: data.Object,
|
||||
|
@ -235,7 +242,7 @@ func (p *compiledPolicy) prepareK8sData(
|
|||
attr admission.Attributes,
|
||||
request *admissionv1.AdmissionRequest,
|
||||
namespace runtime.Object,
|
||||
context contextlib.ContextInterface,
|
||||
context ContextInterface,
|
||||
) (evaluationData, error) {
|
||||
namespaceVal, err := objectToResolveVal(namespace)
|
||||
if err != nil {
|
||||
|
@ -264,10 +271,10 @@ func (p *compiledPolicy) prepareK8sData(
|
|||
|
||||
func (p *compiledPolicy) match(
|
||||
ctx context.Context,
|
||||
namespaceVal interface{},
|
||||
objectVal interface{},
|
||||
oldObjectVal interface{},
|
||||
requestVal interface{},
|
||||
namespaceVal any,
|
||||
objectVal any,
|
||||
oldObjectVal any,
|
||||
requestVal any,
|
||||
matchConditions []cel.Program,
|
||||
) (bool, error) {
|
||||
data := map[string]any{
|
||||
|
|
|
@ -4,8 +4,9 @@ import (
|
|||
"github.com/google/cel-go/cel"
|
||||
policiesv1alpha1 "github.com/kyverno/kyverno/api/policies.kyverno.io/v1alpha1"
|
||||
engine "github.com/kyverno/kyverno/pkg/cel"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/context"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/globalcontext"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/http"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/resource"
|
||||
"github.com/kyverno/kyverno/pkg/cel/libs/user"
|
||||
"github.com/kyverno/kyverno/pkg/cel/policy"
|
||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
||||
|
@ -19,15 +20,16 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
RequestKey = "request"
|
||||
AttestationKey = "attestations"
|
||||
AttestorKey = "attestors"
|
||||
GlobalContextKey = "globalcontext"
|
||||
HttpKey = "http"
|
||||
ImagesKey = "images"
|
||||
NamespaceObjectKey = "namespaceObject"
|
||||
ObjectKey = "object"
|
||||
OldObjectKey = "oldObject"
|
||||
ImagesKey = "images"
|
||||
AttestorKey = "attestors"
|
||||
AttestationKey = "attestations"
|
||||
ContextKey = "context"
|
||||
HttpKey = "http"
|
||||
RequestKey = "request"
|
||||
ResourceKey = "resource"
|
||||
)
|
||||
|
||||
type Compiler interface {
|
||||
|
@ -57,7 +59,8 @@ func (c *compiler) Compile(ivpolicy *policiesv1alpha1.ImageVerificationPolicy) (
|
|||
var declTypes []*apiservercel.DeclType
|
||||
declTypes = append(declTypes, imageverifierfunctions.Types()...)
|
||||
options := []cel.EnvOption{
|
||||
cel.Variable(ContextKey, context.ContextType),
|
||||
cel.Variable(ResourceKey, resource.ContextType),
|
||||
cel.Variable(GlobalContextKey, globalcontext.ContextType),
|
||||
cel.Variable(HttpKey, http.HTTPType),
|
||||
cel.Variable(ImagesKey, cel.MapType(cel.StringType, cel.ListType(cel.StringType))),
|
||||
cel.Variable(AttestorKey, cel.MapType(cel.StringType, cel.StringType)),
|
||||
|
@ -76,7 +79,7 @@ func (c *compiler) Compile(ivpolicy *policiesv1alpha1.ImageVerificationPolicy) (
|
|||
for _, declType := range declTypes {
|
||||
options = append(options, cel.Types(declType.CelType()))
|
||||
}
|
||||
options = append(options, imageverifierfunctions.Lib(c.ictx, ivpolicy, c.lister), context.Lib(), http.Lib(), user.Lib())
|
||||
options = append(options, imageverifierfunctions.Lib(c.ictx, ivpolicy, c.lister), resource.Lib(), http.Lib(), user.Lib())
|
||||
env, err := base.Extend(options...)
|
||||
if err != nil {
|
||||
return nil, append(allErrs, field.InternalError(nil, err))
|
||||
|
|
|
@ -12,7 +12,7 @@ spec:
|
|||
variables:
|
||||
- name: cm
|
||||
expression: >-
|
||||
context.GetConfigMap(object.metadata.namespace, "policy-cm")
|
||||
resource.GetConfigMap(object.metadata.namespace, "policy-cm")
|
||||
validations:
|
||||
- expression: >-
|
||||
object.metadata.name == variables.cm.data.name
|
||||
|
|
|
@ -12,7 +12,7 @@ spec:
|
|||
variables:
|
||||
- name: cm
|
||||
expression: >-
|
||||
context.GetConfigMap(object.metadata.namespace, "policy-cm")
|
||||
resource.GetConfigMap(object.metadata.namespace, "policy-cm")
|
||||
- name: environment
|
||||
expression: >-
|
||||
has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == variables.cm.data.env
|
||||
|
|
|
@ -12,7 +12,7 @@ spec:
|
|||
variables:
|
||||
- name: dcount
|
||||
expression: >-
|
||||
context.GetGlobalReference("gctxentry-apicall-correct", "")
|
||||
globalcontext.Get("gctxentry-apicall-correct", "")
|
||||
validations:
|
||||
- expression: >-
|
||||
variables.dcount != 0
|
||||
|
|
|
@ -12,7 +12,7 @@ spec:
|
|||
variables:
|
||||
- name: image
|
||||
expression: >-
|
||||
context.GetImageData("ghcr.io/kyverno/kyverno:latest")
|
||||
resource.GetImageData("ghcr.io/kyverno/kyverno:latest")
|
||||
- name: accept
|
||||
expression: >-
|
||||
variables.image != null
|
||||
|
|
|
@ -12,7 +12,7 @@ spec:
|
|||
variables:
|
||||
- name: pod
|
||||
expression: >-
|
||||
context.GetResource("v1", "pods", object.metadata.namespace, "policy-pod")
|
||||
resource.Get("v1", "pods", object.metadata.namespace, "policy-pod")
|
||||
- name: environment
|
||||
expression: >-
|
||||
has(object.metadata.labels) && 'env' in object.metadata.labels && object.metadata.labels['env'] == variables.pod.metadata.labels.env
|
||||
|
|
Loading…
Add table
Reference in a new issue