From f44efea8873b9e89550366012481c4d3f21c2ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Tue, 6 May 2025 10:32:56 +0000 Subject: [PATCH] feat: support image extraction from old object (#13026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- pkg/cel/compiler/images.go | 24 +-- pkg/cel/compiler/images_test.go | 226 ++++++++++++-------- pkg/imageverification/evaluator/compiler.go | 2 +- 3 files changed, 145 insertions(+), 107 deletions(-) diff --git a/pkg/cel/compiler/images.go b/pkg/cel/compiler/images.go index c29be2f0284a..c1a968fad664 100644 --- a/pkg/cel/compiler/images.go +++ b/pkg/cel/compiler/images.go @@ -11,33 +11,33 @@ import ( var ( podImageExtractors = []v1alpha1.ImageExtractor{{ Name: "containers", - Expression: "has(object.spec.containers) ? object.spec.containers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.?containers.orValue([]).map(e, e.image)", }, { Name: "initContainers", - Expression: "has(object.spec.initContainers) ? object.spec.initContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.?initContainers.orValue([]).map(e, e.image)", }, { Name: "ephemeralContainers", - Expression: "has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.?ephemeralContainers.orValue([]).map(e, e.image)", }} podControllerImageExtractors = []v1alpha1.ImageExtractor{{ Name: "containers", - Expression: "has(object.spec.template.spec.containers) ? object.spec.template.spec.containers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.template.spec.?containers.orValue([]).map(e, e.image)", }, { Name: "initContainers", - Expression: "has(object.spec.template.spec.initContainers) ? object.spec.template.spec.initContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.template.spec.?initContainers.orValue([]).map(e, e.image)", }, { Name: "ephemeralContainers", - Expression: "has(object.spec.template.spec.ephemeralContainers) ? object.spec.template.spec.ephemeralContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.template.spec.?ephemeralContainers.orValue([]).map(e, e.image)", }} cronJobImageExtractors = []v1alpha1.ImageExtractor{{ Name: "containers", - Expression: "has(object.spec.jobTemplate.spec.template.spec.containers) ? object.spec.jobTemplate.spec.template.spec.containers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.jobTemplate.spec.template.spec.?containers.orValue([]).map(e, e.image)", }, { Name: "initContainers", - Expression: "has(object.spec.jobTemplate.spec.template.spec.initContainers) ? object.spec.jobTemplate.spec.template.spec.initContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.jobTemplate.spec.template.spec.?initContainers.orValue([]).map(e, e.image)", }, { Name: "ephemeralContainers", - Expression: "has(object.spec.jobTemplate.spec.template.spec.ephemeralContainers) ? object.spec.jobTemplate.spec.template.spec.ephemeralContainers.map(e, e.image) : []", + Expression: "(object != null ? object : oldObject).spec.jobTemplate.spec.template.spec.?ephemeralContainers.orValue([]).map(e, e.image)", }} ) @@ -79,7 +79,7 @@ func (c *ImageExtractor) GetImages(data map[string]any) ([]string, error) { return result, nil } -func CompileImageExtractors(path *field.Path, envOpts []cel.EnvOption, gvr *metav1.GroupVersionResource, imageExtractors ...v1alpha1.ImageExtractor) (map[string]ImageExtractor, field.ErrorList) { +func CompileImageExtractors(path *field.Path, env *cel.Env, gvr *metav1.GroupVersionResource, imageExtractors ...v1alpha1.ImageExtractor) (map[string]ImageExtractor, field.ErrorList) { var extractors []v1alpha1.ImageExtractor if gvr != nil { extractors = append(extractors, getImageExtractorsFromGVR(*gvr)...) @@ -90,10 +90,6 @@ func CompileImageExtractors(path *field.Path, envOpts []cel.EnvOption, gvr *meta } var allErrs field.ErrorList compiled := make(map[string]ImageExtractor, len(extractors)) - env, err := cel.NewEnv(envOpts...) - if err != nil { - return nil, append(allErrs, field.InternalError(path, err)) - } for i, m := range extractors { path := path.Index(i).Child("expression") ast, iss := env.Compile(m.Expression) diff --git a/pkg/cel/compiler/images_test.go b/pkg/cel/compiler/images_test.go index f57982b85ac9..3a51490d9b6b 100644 --- a/pkg/cel/compiler/images_test.go +++ b/pkg/cel/compiler/images_test.go @@ -21,123 +21,165 @@ func Test_Match(t *testing.T) { gvr *metav1.GroupVersionResource wantResult map[string][]string wantErr bool - }{ - { - name: "standard", - imageExtractor: []v1alpha1.ImageExtractor{ - { - Name: "one", - Expression: "request.images", + }{{ + name: "standard", + imageExtractor: []v1alpha1.ImageExtractor{{ + Name: "one", + Expression: "request.images", + }}, + request: map[string]any{ + "request": map[string][]string{ + "images": { + "nginx:latest", + "alpine:latest", }, }, - request: map[string]any{ - "request": map[string][]string{ - "images": { - "nginx:latest", - "alpine:latest", - }, - }, + }, + wantResult: map[string][]string{ + "one": { + "nginx:latest", + "alpine:latest", }, - wantResult: map[string][]string{ - "one": { + }, + gvr: nil, + wantErr: false, + }, { + name: "pod image extraction", + imageExtractor: []v1alpha1.ImageExtractor{{ + Name: "one", + Expression: "request.images", + }}, + request: map[string]any{ + "request": map[string]any{ + "images": []string{ "nginx:latest", "alpine:latest", }, }, - gvr: nil, - wantErr: false, - }, - { - name: "pod image extraction", - imageExtractor: []v1alpha1.ImageExtractor{ - { - Name: "one", - Expression: "request.images", + "object": map[string]any{ + "spec": map[string]any{ + "containers": []map[string]string{{ + "image": "kyverno/image-one", + }, { + "image": "kyverno/image-two", + }}, + "initContainers": []map[string]string{{ + "image": "kyverno/init-image-one", + }, { + "image": "kyverno/init-image-two", + }}, + "ephemeralContainers": []map[string]string{{ + "image": "kyverno/ephr-image-one", + }, { + "image": "kyverno/ephr-image-two", + }}, }, }, - request: map[string]any{ - "request": map[string]any{ - "images": []string{ - "nginx:latest", - "alpine:latest", - }, - }, - "object": map[string]any{ - "spec": map[string]any{ - "containers": []map[string]string{ - { - "image": "kyverno/image-one", - }, - { - "image": "kyverno/image-two", - }, - }, - "initContainers": []map[string]string{ - { - "image": "kyverno/init-image-one", - }, - { - "image": "kyverno/init-image-two", - }, - }, - "ephemeralContainers": []map[string]string{ - { - "image": "kyverno/ephr-image-one", - }, - { - "image": "kyverno/ephr-image-two", - }, - }, - }, - }, + }, + wantResult: map[string][]string{ + "one": { + "nginx:latest", + "alpine:latest", + }, + "containers": { + "kyverno/image-one", + "kyverno/image-two", + }, + "initContainers": { + "kyverno/init-image-one", + "kyverno/init-image-two", + }, + "ephemeralContainers": { + "kyverno/ephr-image-one", + "kyverno/ephr-image-two", }, - wantResult: map[string][]string{ - "one": { + }, + gvr: &metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}, + wantErr: false, + }, { + name: "pod image extraction (old object)", + imageExtractor: []v1alpha1.ImageExtractor{{ + Name: "one", + Expression: "request.images", + }}, + request: map[string]any{ + "request": map[string]any{ + "images": []string{ "nginx:latest", "alpine:latest", }, - "containers": { - "kyverno/image-one", - "kyverno/image-two", - }, - "initContainers": { - "kyverno/init-image-one", - "kyverno/init-image-two", - }, - "ephemeralContainers": { - "kyverno/ephr-image-one", - "kyverno/ephr-image-two", + }, + "object": nil, + "oldObject": map[string]any{ + "spec": map[string]any{ + "containers": []map[string]string{{ + "image": "kyverno/image-one", + }, { + "image": "kyverno/image-two", + }}, + "initContainers": []map[string]string{{ + "image": "kyverno/init-image-one", + }, { + "image": "kyverno/init-image-two", + }}, + "ephemeralContainers": []map[string]string{{ + "image": "kyverno/ephr-image-one", + }, { + "image": "kyverno/ephr-image-two", + }}, }, }, - gvr: &metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}, - wantErr: false, }, - { - name: "standard fail", - imageExtractor: []v1alpha1.ImageExtractor{ - { - Name: "one", - Expression: "request.images", - }, + wantResult: map[string][]string{ + "one": { + "nginx:latest", + "alpine:latest", }, - request: map[string]any{ - "request": map[string][]int{ - "images": {0, 1}, - }, + "containers": { + "kyverno/image-one", + "kyverno/image-two", + }, + "initContainers": { + "kyverno/init-image-one", + "kyverno/init-image-two", + }, + "ephemeralContainers": { + "kyverno/ephr-image-one", + "kyverno/ephr-image-two", }, - gvr: nil, - wantErr: true, }, - } + gvr: &metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}, + wantErr: false, + }, { + name: "standard fail", + imageExtractor: []v1alpha1.ImageExtractor{{ + Name: "one", + Expression: "request.images", + }}, + request: map[string]any{ + "request": map[string][]int{ + "images": {0, 1}, + }, + }, + gvr: nil, + wantErr: true, + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c, errList := CompileImageExtractors( + base, err := NewBaseEnv() + assert.NoError(t, err) + env, err := base.Extend( + cel.Variable(RequestKey, types.DynType), + cel.Variable(ObjectKey, types.DynType), + cel.Variable(OldObjectKey, types.DynType), + ) + c, errs := CompileImageExtractors( field.NewPath("spec", "images"), - []cel.EnvOption{cel.Variable(RequestKey, types.DynType), cel.Variable(ObjectKey, types.DynType)}, + env, tt.gvr, tt.imageExtractor..., ) - assert.Nil(t, errList) + assert.Nil(t, errs) images, err := ExtractImages(tt.request, c) if tt.wantErr { assert.Error(t, err) diff --git a/pkg/imageverification/evaluator/compiler.go b/pkg/imageverification/evaluator/compiler.go index 36f749e84dd6..57e76103b34a 100644 --- a/pkg/imageverification/evaluator/compiler.go +++ b/pkg/imageverification/evaluator/compiler.go @@ -101,7 +101,7 @@ func (c *compiler) Compile(ivpolicy *policiesv1alpha1.ImageValidatingPolicy, exc return nil, append(allErrs, errs...) } - imageExtractors, errs := engine.CompileImageExtractors(path.Child("images"), options, c.reqGVR, ivpolicy.Spec.ImageExtractors...) + imageExtractors, errs := engine.CompileImageExtractors(path.Child("images"), env, c.reqGVR, ivpolicy.Spec.ImageExtractors...) if errs != nil { return nil, append(allErrs, errs...) }