diff --git a/pkg/cel/engine/imageverifyengine.go b/pkg/cel/engine/imageverifyengine.go index 5763b772d3..aea8a470e7 100644 --- a/pkg/cel/engine/imageverifyengine.go +++ b/pkg/cel/engine/imageverifyengine.go @@ -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 { diff --git a/pkg/cel/engine/imageverifyengine_test.go b/pkg/cel/engine/imageverifyengine_test.go index e631f9813c..03b46aab44 100644 --- a/pkg/cel/engine/imageverifyengine_test.go +++ b/pkg/cel/engine/imageverifyengine_test.go @@ -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) diff --git a/pkg/cel/engine/vpolengine.go b/pkg/cel/engine/vpolengine.go index 42f8aa5121..824cbd4519 100644 --- a/pkg/cel/engine/vpolengine.go +++ b/pkg/cel/engine/vpolengine.go @@ -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, diff --git a/pkg/cel/libs/globalcontext/impl.go b/pkg/cel/libs/globalcontext/impl.go new file mode 100644 index 0000000000..6df6540b43 --- /dev/null +++ b/pkg/cel/libs/globalcontext/impl.go @@ -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) + } +} diff --git a/pkg/cel/libs/globalcontext/impl_test.go b/pkg/cel/libs/globalcontext/impl_test.go new file mode 100644 index 0000000000..0575ee3137 --- /dev/null +++ b/pkg/cel/libs/globalcontext/impl_test.go @@ -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()) + } + } + } + }) + } +} diff --git a/pkg/cel/libs/globalcontext/lib.go b/pkg/cel/libs/globalcontext/lib.go new file mode 100644 index 0000000000..32d80a09f5 --- /dev/null +++ b/pkg/cel/libs/globalcontext/lib.go @@ -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...) +} diff --git a/pkg/cel/libs/globalcontext/types.go b/pkg/cel/libs/globalcontext/types.go new file mode 100644 index 0000000000..cee509f78c --- /dev/null +++ b/pkg/cel/libs/globalcontext/types.go @@ -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 +} diff --git a/pkg/cel/libs/context/impl.go b/pkg/cel/libs/resource/impl.go similarity index 79% rename from pkg/cel/libs/context/impl.go rename to pkg/cel/libs/resource/impl.go index c3fbece134..cf576ce0ee 100644 --- a/pkg/cel/libs/context/impl.go +++ b/pkg/cel/libs/resource/impl.go @@ -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) diff --git a/pkg/cel/libs/context/impl_test.go b/pkg/cel/libs/resource/impl_test.go similarity index 56% rename from pkg/cel/libs/context/impl_test.go rename to pkg/cel/libs/resource/impl_test.go index da0ecc5889..7731fbb531 100644 --- a/pkg/cel/libs/context/impl_test.go +++ b/pkg/cel/libs/resource/impl_test.go @@ -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{ diff --git a/pkg/cel/libs/context/lib.go b/pkg/cel/libs/resource/lib.go similarity index 80% rename from pkg/cel/libs/context/lib.go rename to pkg/cel/libs/resource/lib.go index 7a0041c7d2..8a8d304304 100644 --- a/pkg/cel/libs/context/lib.go +++ b/pkg/cel/libs/resource/lib.go @@ -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{} diff --git a/pkg/cel/libs/context/lib_test.go b/pkg/cel/libs/resource/lib_test.go similarity index 94% rename from pkg/cel/libs/context/lib_test.go rename to pkg/cel/libs/resource/lib_test.go index f60c6c0c51..ceed2f9f4c 100644 --- a/pkg/cel/libs/context/lib_test.go +++ b/pkg/cel/libs/resource/lib_test.go @@ -1,4 +1,4 @@ -package context +package resource import ( "testing" diff --git a/pkg/cel/libs/context/mock.go b/pkg/cel/libs/resource/mock.go similarity index 74% rename from pkg/cel/libs/context/mock.go rename to pkg/cel/libs/resource/mock.go index c2f6fa6d11..5647404185 100644 --- a/pkg/cel/libs/context/mock.go +++ b/pkg/cel/libs/resource/mock.go @@ -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() {} diff --git a/pkg/cel/libs/context/types.go b/pkg/cel/libs/resource/types.go similarity index 96% rename from pkg/cel/libs/context/types.go rename to pkg/cel/libs/resource/types.go index b948206fda..3ba9328650 100644 --- a/pkg/cel/libs/context/types.go +++ b/pkg/cel/libs/resource/types.go @@ -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) diff --git a/pkg/cel/policy/compiler.go b/pkg/cel/policy/compiler.go index 6d5f80f371..88f9ee54d0 100644 --- a/pkg/cel/policy/compiler.go +++ b/pkg/cel/policy/compiler.go @@ -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 { diff --git a/pkg/cel/policy/compiler_test.go b/pkg/cel/policy/compiler_test.go index aa38e7c7bf..19fa67f400 100644 --- a/pkg/cel/policy/compiler_test.go +++ b/pkg/cel/policy/compiler_test.go @@ -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", diff --git a/pkg/cel/policy/context.go b/pkg/cel/policy/context.go index 1906495997..87e8f9ef38 100644 --- a/pkg/cel/policy/context.go +++ b/pkg/cel/policy/context.go @@ -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 diff --git a/pkg/cel/policy/policy.go b/pkg/cel/policy/policy.go index ba616a8efc..b6907454db 100644 --- a/pkg/cel/policy/policy.go +++ b/pkg/cel/policy/policy.go @@ -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{ diff --git a/pkg/imageverification/evaluator/compiler.go b/pkg/imageverification/evaluator/compiler.go index ed0412ba78..ccfdcae3e3 100644 --- a/pkg/imageverification/evaluator/compiler.go +++ b/pkg/imageverification/evaluator/compiler.go @@ -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)) diff --git a/test/cli/test-validating-policy/policy-with-cm/policy.yaml b/test/cli/test-validating-policy/policy-with-cm/policy.yaml index 14bbfb1a5f..2ffd229a35 100644 --- a/test/cli/test-validating-policy/policy-with-cm/policy.yaml +++ b/test/cli/test-validating-policy/policy-with-cm/policy.yaml @@ -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 diff --git a/test/conformance/chainsaw/validating-policies/context/configmap/policy.yaml b/test/conformance/chainsaw/validating-policies/context/configmap/policy.yaml index db46af78c5..65e708b87f 100644 --- a/test/conformance/chainsaw/validating-policies/context/configmap/policy.yaml +++ b/test/conformance/chainsaw/validating-policies/context/configmap/policy.yaml @@ -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 diff --git a/test/conformance/chainsaw/validating-policies/context/globalreference/policy.yaml b/test/conformance/chainsaw/validating-policies/context/globalreference/policy.yaml index 62c580ec84..134543843d 100755 --- a/test/conformance/chainsaw/validating-policies/context/globalreference/policy.yaml +++ b/test/conformance/chainsaw/validating-policies/context/globalreference/policy.yaml @@ -12,7 +12,7 @@ spec: variables: - name: dcount expression: >- - context.GetGlobalReference("gctxentry-apicall-correct", "") + globalcontext.Get("gctxentry-apicall-correct", "") validations: - expression: >- variables.dcount != 0 diff --git a/test/conformance/chainsaw/validating-policies/context/image-data/policy.yaml b/test/conformance/chainsaw/validating-policies/context/image-data/policy.yaml index 96cba3d71a..2fc812b57c 100644 --- a/test/conformance/chainsaw/validating-policies/context/image-data/policy.yaml +++ b/test/conformance/chainsaw/validating-policies/context/image-data/policy.yaml @@ -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 diff --git a/test/conformance/chainsaw/validating-policies/context/resource/policy.yaml b/test/conformance/chainsaw/validating-policies/context/resource/policy.yaml index 67a279ad6b..98824b551f 100644 --- a/test/conformance/chainsaw/validating-policies/context/resource/policy.yaml +++ b/test/conformance/chainsaw/validating-policies/context/resource/policy.yaml @@ -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