1
0
Fork 0
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:
Charles-Edouard Brétéché 2025-03-17 16:53:32 +01:00 committed by GitHub
parent be974e7d93
commit e785ee4882
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 309 additions and 212 deletions

View file

@ -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 {

View file

@ -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)

View file

@ -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,

View 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)
}
}

View 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())
}
}
}
})
}
}

View 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...)
}

View 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
}

View file

@ -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)

View file

@ -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{

View file

@ -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{}

View file

@ -1,4 +1,4 @@
package context
package resource
import (
"testing"

View file

@ -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() {}

View file

@ -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)

View file

@ -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 {

View file

@ -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",

View file

@ -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

View file

@ -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{

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -12,7 +12,7 @@ spec:
variables:
- name: dcount
expression: >-
context.GetGlobalReference("gctxentry-apicall-correct", "")
globalcontext.Get("gctxentry-apicall-correct", "")
validations:
- expression: >-
variables.dcount != 0

View file

@ -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

View file

@ -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