From cef7be1fdc521b49b9b925445dfbe9064d144dba Mon Sep 17 00:00:00 2001 From: Netanel Kadosh Date: Thu, 14 Nov 2024 10:10:25 +0200 Subject: [PATCH] feat: Add Manifest Index to ImageRegistry context (#9883) * feat: Add Manifest Index to ImageRegistry context Signed-off-by: Netanel Kadosh * test: adding manifest list tests Signed-off-by: Netanel Kadosh --------- Signed-off-by: Netanel Kadosh Co-authored-by: shuting --- pkg/engine/adapters/rclient.go | 11 ++++++++ pkg/engine/api/client.go | 1 + pkg/engine/context/loaders/imagedata.go | 8 ++++++ test/cli/registry/image-example.yaml | 37 +++++++++++++++++++++++++ test/cli/registry/kyverno-test.yaml | 13 +++++++++ test/cli/registry/resources.yaml | 19 ++++++++++++- test/cli/test/globalcontext/values.yaml | 14 +++++----- 7 files changed, 95 insertions(+), 8 deletions(-) diff --git a/pkg/engine/adapters/rclient.go b/pkg/engine/adapters/rclient.go index 94ff74733a..24c8228757 100644 --- a/pkg/engine/adapters/rclient.go +++ b/pkg/engine/adapters/rclient.go @@ -34,6 +34,8 @@ func (a *rclientAdapter) ForRef(ctx context.Context, ref string) (*engineapi.Ima if err != nil { return nil, fmt.Errorf("failed to resolve image reference: %s, error: %v", ref, err) } + // we ignore image index errors as it might be unavailable + manifestList, _ := desc.ImageIndex() // We need to use the raw config and manifest to avoid dropping unknown keys // which are not defined in GGCR structs. rawManifest, err := image.RawManifest() @@ -44,12 +46,21 @@ func (a *rclientAdapter) ForRef(ctx context.Context, ref string) (*engineapi.Ima if err != nil { return nil, fmt.Errorf("failed to fetch config for image reference: %s, error: %v", ref, err) } + var rawManifestList []byte + if manifestList != nil { + rawManifestList, err = manifestList.RawManifest() + if err != nil { + return nil, fmt.Errorf("failed to fetch image index for image reference: %s, error: %v", ref, err) + } + } + data := engineapi.ImageData{ Image: ref, ResolvedImage: fmt.Sprintf("%s@%s", parsedRef.Context().Name(), desc.Digest.String()), Registry: parsedRef.Context().RegistryStr(), Repository: parsedRef.Context().RepositoryStr(), Identifier: parsedRef.Identifier(), + ManifestList: rawManifestList, Manifest: rawManifest, Config: rawConfig, } diff --git a/pkg/engine/api/client.go b/pkg/engine/api/client.go index 6f5dd8f440..9e4bfa2069 100644 --- a/pkg/engine/api/client.go +++ b/pkg/engine/api/client.go @@ -48,6 +48,7 @@ type ImageData struct { Registry string Repository string Identifier string + ManifestList []byte Manifest []byte Config []byte } diff --git a/pkg/engine/context/loaders/imagedata.go b/pkg/engine/context/loaders/imagedata.go index 83873f1065..38652f3868 100644 --- a/pkg/engine/context/loaders/imagedata.go +++ b/pkg/engine/context/loaders/imagedata.go @@ -123,12 +123,20 @@ func (idl *imageDataLoader) fetchImageDataMap(client engineapi.ImageDataClient, return nil, fmt.Errorf("failed to decode config for image reference: %s, error: %v", ref, err) } + var manifestList interface{} + if desc.ManifestList != nil { + if err := json.Unmarshal(desc.ManifestList, &manifestList); err != nil { + return nil, fmt.Errorf("failed to decode image index for image reference: %s, error: %v", ref, err) + } + } + data := map[string]interface{}{ "image": desc.Image, "resolvedImage": desc.ResolvedImage, "registry": desc.Registry, "repository": desc.Repository, "identifier": desc.Identifier, + "manifestList": manifestList, "manifest": manifest, "configData": configData, } diff --git a/test/cli/registry/image-example.yaml b/test/cli/registry/image-example.yaml index 1cb3a04a5b..863cc57b5c 100644 --- a/test/cli/registry/image-example.yaml +++ b/test/cli/registry/image-example.yaml @@ -77,3 +77,40 @@ spec: list: request.object.spec.containers message: Images must specify a source/base image from which they are built to be valid. +--- +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: check-manifest-list +spec: + rules: + - match: + any: + - resources: + kinds: + - Pod + name: check-manifest-list-rule + preconditions: + all: + - key: '{{request.operation}}' + operator: NotEquals + value: DELETE + validate: + foreach: + - context: + - imageRegistry: + reference: '{{ element.image }}' + name: imageData + - name: manifests + variable: + default: 0 + jmesPath: 'imageData.manifestList.manifests | length(@)' + deny: + conditions: + all: + - key: '{{ manifests }}' + operator: Equals + value: 0 + list: request.object.spec.containers + message: Images must specify a manifest list to be valid. + validationFailureAction: Enforce diff --git a/test/cli/registry/kyverno-test.yaml b/test/cli/registry/kyverno-test.yaml index 5cc8ad9fb0..1408fac4b5 100644 --- a/test/cli/registry/kyverno-test.yaml +++ b/test/cli/registry/kyverno-test.yaml @@ -13,6 +13,19 @@ results: - test-pod-with-trusted-registry result: pass rule: check-image-base-rule +- kind: Pod + policy: check-manifest-list + resources: + - test-pod-with-single-arch-no-index + result: fail + rule: check-manifest-list-rule +- kind: Pod + policy: check-manifest-list + resources: + - test-pod-with-trusted-registry + - test-pod-with-single-arch-index + result: pass + rule: check-manifest-list-rule - kind: Pod policy: images resources: diff --git a/test/cli/registry/resources.yaml b/test/cli/registry/resources.yaml index f63de3bed2..548d64f69a 100644 --- a/test/cli/registry/resources.yaml +++ b/test/cli/registry/resources.yaml @@ -16,4 +16,21 @@ spec: containers: - name: kyverno image: ghcr.io/kyverno/kyverno:v1.7.3 - +--- +apiVersion: v1 +kind: Pod +metadata: + name: test-pod-with-single-arch-index +spec: + containers: + - name: solr-single-arch + image: solr:6 +--- +apiVersion: v1 +kind: Pod +metadata: + name: test-pod-with-single-arch-no-index +spec: + containers: + - name: no-index + image: ghcr.io/kyverno/test-verify-image:signed diff --git a/test/cli/test/globalcontext/values.yaml b/test/cli/test/globalcontext/values.yaml index 2a906e39aa..4cd97f1de3 100644 --- a/test/cli/test/globalcontext/values.yaml +++ b/test/cli/test/globalcontext/values.yaml @@ -1,12 +1,12 @@ apiVersion: cli.kyverno.io/v1alpha1 +globalValues: + request.operation: CREATE kind: Value metadata: name: values -globalValues: - request.operation: CREATE policies: - - name: gctx - rules: - - name: main-deployment-exists - values: - deploymentCount: 1 +- name: gctx + rules: + - name: main-deployment-exists + values: + deploymentCount: 1